Django – build core
Contents
1. Django basic
Cài đặt python
https://www.freecodecamp.org/news/how-to-install-python-in-windows-operating-system/
// tạo environment python -m venv venv // active venv\Scripts\activate.bat // install django pip install django // tạo project tên mysite django-admin startproject mysite
// Tạo app todo python manage.py startapp todo
// start server python manage.py runserver // run server with host python manage.py runserver localhost:8000
// create user admin account python manage.py createsuperuser // check django version python -m django --version // tạo file package sử dụng trong project pip freeze > installed_packages.txt
// check connect database python manage.py check --database default // chạy lệnh dưới cmd như tinker của laravel python manage.py shell
Try catch:
class TodoController(View): def get(self, request): try: dataTodo = TodoRepository.all() return ApiService.jsonData(dataTodo, _('messages.ok')) except Exception as e: LogService.error(str(e)) return ApiService.jsonError(_('errors.default_error'))
2. Thư viện MarshMallow – Validate
Install
pip install marshmallow
from marshmallow import Schema, fields, validate, validates, ValidationError class TodoRequest(Schema): name = fields.Str(validate=validate.OneOf(['Todo'])) email = fields.Email(error_messages={ 'invalid': 'Custom message', }) created_at = fields.DateTime() age = fields.Integer(required=True, error_messages={ 'required': 'custom message', }) @validates('age') def validate_age(self, value): if value < 0: raise ValidationError('Age must be greater than 0') elif value > 30: raise ValidationError('Age must not be greater than 30') # class Meta: # strict=True
3. Serializer
Convert data từ python sang json.
Cần cài rest_framework
pip install djangorestframework
Tạo file TodoSerializer.py
from rest_framework import serializers from app.http.models.Todo import Todo class TodoSerializer(serializers.ModelSerializer): title = serializers.CharField(max_length=100) status = serializers.BooleanField() date = serializers.DateTimeField() created_at = serializers.DateTimeField() updated_at = serializers.DateTimeField() class Meta: model = Todo # fields = ( 'title', 'status', 'date', 'created_at', 'updated_at') exclude = () read_only_fields = ()
Sử dụng:
from app.http.models.Todo import Todo from app.http.serializers.TodoSerializer import TodoSerializer from app.services.LogService import LogService class TodoRepository: ## # get all data # Author HaoDT ## @staticmethod def all(): try: dataTodo = Todo.objects.all() serializer = TodoSerializer(dataTodo, many=True) return serializer.data except Exception as e: LogService.error(str(e)) return {}
4. Middleware
Định nghĩa middleware
from app.services.ApiService import ApiService from http import HTTPStatus from django.utils.translation import gettext_lazy as _ class UserMiddleware: def __init__(self, get_response): self.get_response = get_response # One-time configuration and initialization. def __call__(self, request): response = self.get_response(request) if request.path.startswith('/admin/'): # Code to be executed for each request before # the view (and later middleware) are called. print("This is demo middleware in Django") # Code to be executed for each request/response after # the view is called. if False: return ApiService.jsonMessage(_('errors.unauthorized'), HTTPStatus.UNAUTHORIZED) return response def process_exception(self, request, exception): pass def process_template_response(self, request, response): pass
add vào file settings.py
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'core_django.middlewares.UserMiddleware', ]
5. Locale
-
Download gettext and install
# add to setting.py from posixpath import abspath, dirname DJANGO_ROOT = dirname(dirname(abspath(__file__))) SITE_ROOT = dirname(DJANGO_ROOT) USE_I18N = True LOCALE_PATHS = ( SITE_ROOT + '/locale', ) ugettext = lambda s: s LANGUAGES = ( ('en', ugettext('English')), ('ja', ugettext('Japan')), ) # hoặc #__app #__core_django #_____settings.py PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) LOCALE_PATHS = ( '../app/locale', # folder app ) ugettext = lambda s: s LANGUAGES = ( ('en', ugettext('English')), ('ja', ugettext('Japan')), )
// change language LANGUAGE_CODE = 'ja'
// lệnh tạo language => thay ja bằng code lang tương ứng. (ở đây là ja => japan) python manage.py makemessages -l ja
Có tạo file .po trong folder locale
Cấu trúc file .po
# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-21 13:45+0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" ############ ERRORS ############ msgid "errors.default_error" msgstr "エラーが発生しました。該当のデータが存在しません。" msgid "errors.unauthorized" msgstr "unauthorized" ############ MESSAGES ############ msgid "messages.created_failed" msgstr "更新中にエラーが発生しました。" msgid "messages.add_success" msgstr "追加完了しました。" msgid "messages.updated_success" msgstr "情報の更新が完了しました。" msgid "messages.updated_failed" msgstr "更新中にエラーが発生しました。" msgid "messages.deleted_success" msgstr "削除完了しました。" msgid "messages.deleted_failed" msgstr "削除できません。" msgid "messages.ok" msgstr "ok"
Gồm 2 thành phần
+ msgid => key
+ msgstr => text translate
Sau đó chạy lệnh để build:
python manage.py compilemessages // restart lại server python manage.py runserver
Sử dụng:
_(key) from django.utils.translation import gettext_lazy as _ return ApiService.jsonError(_('errors.default_error'))
6. Migration
Tạo models
from django.db import models from django.utils import timezone class Todo(models.Model): title = models.CharField(max_length=100) status = models.BooleanField() date = models.DateTimeField(default=timezone.now) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.title
Chạy build
// build model sang migrate python manage.py makemigrations
Chạy migrate
// migrate to db python manage.py migrate
Rollback to zero:
# thay app thành tên ứng dụng của bạn python manage.py migrate app zero python manage.py migrate user 0003_alter_user_created_at_alter_user_deleted_at_and_more
7. Route
File urls.py
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('app.routes.index')), ] urlpatterns = [ path('admin/', admin.site.urls), path('be/', include([ path('', include('apps.user.urls')), path('', include('apps.facility.urls')), path('', include('apps.common.urls')), ])), path('oauth2/', include('django_auth_adfs.urls')), ]
# File app.routes.index from django.urls import path from .api_v1 import apiV1 urlpatterns = [] + apiV1
# File app.routes.api_v1 from django.urls import path from app import views prefix = 'api/v1/' apiV1 = [ path(prefix + 'todo/', views.TodoController.as_view(), name = 'todo'), path(prefix + 'todo/test-validate/', views.TodoController.testValidate, name = 'todoTestValidate'), ]
8. Log
add vào setting.py
LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "verbose": { "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}", "style": "{", }, "simple": { "format": "{levelname} {asctime} {module} {funcName} {lineno} {module} {process:d} {thread:d} {message}", "style": "{", }, }, "filters": { # "special": { # "()": "project.logging.SpecialFilter", # "foo": "bar", # }, "require_debug_true": { "()": "django.utils.log.RequireDebugTrue", }, }, "handlers": { 'null': { 'level': 'DEBUG', 'class': 'logging.NullHandler', }, "console": { "level": "DEBUG", "filters": ["require_debug_true"], "class": "logging.StreamHandler", "formatter": "verbose", }, "info": { "level": "INFO", 'class': 'logging.handlers.RotatingFileHandler', 'filename': './app/storages/logs/app-' + datetime.datetime.now().strftime ("%Y-%m-%d") + '.log', 'maxBytes': 300 * 1024 * 1024, # 300M Size 'backupCount': 10, "formatter": "verbose", "encoding": "utf-8", }, "error": { "level": "ERROR", 'class': 'logging.handlers.RotatingFileHandler', 'filename': './app/storages/logs/app-' + datetime.datetime.now().strftime ("%Y-%m-%d") + '.log', 'maxBytes': 300 * 1024 * 1024, # 300M Size 'backupCount': 10, "formatter": "verbose", "encoding": "utf-8", }, }, "loggers": { "django": { "handlers": ['null'], 'level': 'DEBUG', 'propagate': False, }, "log": { "handlers": ["console", 'info', 'error'], "propagate": True, 'level': 'INFO', }, # "django.request": { # "handlers": ["console"], # "level": "ERROR", # "propagate": False, # }, # "myproject.custom": { # "handlers": ["console", "mail_admins"], # "level": "INFO", # "filters": ["special"], # }, }, }
Sử dụng, tạo LogService
import logging class LogService: ## # log info # Author HaoDT ## @staticmethod def info(message = ''): logger = logging.getLogger('log') logger.info(message) ## # log error # Author HaoDT ## @staticmethod def error(message = ''): logger = logging.getLogger('log') logger.error(message)
# Sử dụng from app.services.LogService import LogService LogService.error(str(e))
9. env
Sử dụng thư viện python-dotenv
// cài đặt pip install python-dotenv
Tạo file .env cùng cấp với file manage.py
Ví dụ tôi định nghĩa 2 biến: DEBUG và DB_CONNECTION
DEBUG=True DB_CONNECTION=django.db.backends.mysql
// sử dụng import os from dotenv import load_dotenv load_dotenv() os.getenv('DEBUG') os.getenv('DB_CONNECTION')
11. Queue/Jobs
Django không có job/queue như Laravel (à mà hình như có Django-q). Nhưng nó có multi thread, có thể thực hiện tách 2 tác vụ như queue.
Định nghĩa thread class
import threading from app.services.MailService import MailService class SendMailJob(threading.Thread): dataSendmail = { 'subject': '', 'message': '', 'listEmail': [] } def __init__(self, dataSendmail): self.dataSendmail = dataSendmail threading.Thread.__init__(self) def run(self): MailService.send( self.dataSendmail['subject'], self.dataSendmail['message'], self.dataSendmail['listEmail'] )
Sử dụng: Truyền param và gọi hàm start()
... from app.jobs.SendMailJob import SendMailJob class TodoController(View): ## # get # Author HaoDT ## def get(self, request): try: SendMailJob({ 'subject': 'Subject', 'message': 'Body', 'listEmail': ['tronghaomaico@gmail.com'] }).start() dataTodo = TodoRepository.all() return ApiService.jsonData(dataTodo, _('messages.ok')) except Exception as e: LogService.error(str(e)) return ApiService.jsonError(_('errors.default_error'))
10. Models
# get all def all(): try: dataTodo = Todo.objects.all() serializer = TodoSerializer(dataTodo, many=True) return serializer.data except Exception as e: LogService.error(str(e)) return {}
# Create def create(data): try: item = Todo() item.title = data['title'] item.status = 0 item.date = Helper.getTimeNow() item.save() return True except Exception as e: LogService.error(str(e)) return False
# Update def update(data): try: item = Todo.objects.get(id = data.get('id')) if item: if data.get('title'): item.title = data.get('title') if data.get('status') or data.get('status') == 0: item.status = data.get('status') item.save() return True except Exception as e: LogService.error(str(e)) return False
# delete def delete(data): try: item = Todo.objects.get(id = data.get('id')) if item: item.delete() return True except Exception as e: LogService.error(str(e)) return False
11. Template
{% load static %} {% if error %} <div>{{ error }}</div> {% endif %} <a href="{% url 'django_auth_adfs:login' %}"><button type="button">Login by AD</button></a> <form method="post" action="{% url 'user.login' %}"> {% csrf_token %} Username: <input type="type" name="username" value="" /> Password: <input type="password" name="password" value="" /> <input type="checkbox" name="remember_me" />Remember me <button type="submit">Login</button> <a href="{% url 'django_auth_adfs:login' %}"><button type="button">Login by AD</button></a> </form>
// layout {% block title %}{% endblock title %} {% extends '../layout/index.html' %} {% block title %} INSPINIA | Dashboard v.2{% endblock %}
// import css, js {% load static %} <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet"> <link href="{% static 'font-awesome/css/font-awesome.css' %}" rel="stylesheet"> <script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
{% include '../__partials/side-menu.html' %}
// xài biến {{ SITE_TITLE }}
12. Kinh nghiệm khác
12.1 Login Microsoft Entry
Tạo app và secret:
Làm theo tài liệu này: https://django-auth-adfs.readthedocs.io/en/latest/azure_ad_config_guide.html
Tạo app
Nhập các thông tin. Lưu ý nhập redirect URI: http://localhost:8000/oauth2/callback
Sau khi tạo bạn sẽ có thông tin. Lưu lại 2 thông tin để dùng cho ứng dụng:
+ Application (client) ID
+ Directory (tenant) ID
Tiếp theo tạo secret.
Copy value trên để sử dụng cho ứng dụng.
================================================================
Xử lý login microsoft bằng Django
Làm theo tài liệu này: https://django-auth-adfs.readthedocs.io/en/latest/install.html
Install django-auth-adfs
pip install django-auth-adfs
add setting.py
AUTHENTICATION_BACKENDS = ( 'django_auth_adfs.backend.AdfsAuthCodeBackend', ) LOGIN_URL = "django_auth_adfs:login" LOGIN_REDIRECT_URL = config('LOGIN_REDIRECT_URL') # Client secret is not public information. Should store it as an environment variable. client_id = config('CLIENT_ID') client_secret = config('CLIENT_SECRET') tenant_id = config('TENANT_ID') AUTH_ADFS = { 'AUDIENCE': client_id, 'CLIENT_ID': client_id, 'CLIENT_SECRET': client_secret, 'CLAIM_MAPPING': {'first_name': 'given_name', 'last_name': 'family_name', 'email': 'upn'}, 'GROUPS_CLAIM': 'roles', 'MIRROR_GROUPS': True, 'USERNAME_CLAIM': 'upn', 'TENANT_ID': tenant_id, 'RELYING_PARTY_ID': client_id, }
add env
CLIENT_ID=xxx CLIENT_SECRET=yyy TENANT_ID=zzz LOGIN_REDIRECT_URL=/be/login/microsoft
- CLIENT_ID: là Application (client) ID trong App registrations
- TENANT_ID: là Directory (tenant) ID trong App registrations
- CLIENT_SECRET: là secret trong certificate & secret
- LOGIN_REDIRECT_URL: là url sau khi login success sẽ redirect đến có dữ liệu user
add route
urlpatterns = [ ... path('oauth2/', include('django_auth_adfs.urls')), ]
- Đường dẫn vào trang login microsoft sẽ là: oauth2/login
- Đường dẫn callback là: oatuh2/callback
- Đường dẫn callback sẽ đăng ký trên App registrations: http://localhost:8000/oauth2/callback
add install app
INSTALLED_APPS = [ ... 'django_auth_adfs', ]
add middleware
MIDDLEWARE = [ ... 'django_auth_adfs.middleware.LoginRequiredMiddleware', ]
Tạo url để redirect khi success
urlpatterns = [ path("be/login/microsoft", views.MicrosoftEntryView.handleAfterLogin, name="microsoftEntry.afterLogin"), ]
Hàm xử lý
def handleAfterLogin(request): try: if (request.user): userMicrosoftEntry = { 'id': request.user.id, 'username': request.user.username, 'lastName': request.user.last_name, 'firstName': request.user.first_name, } print(userMicrosoftEntry) except Exception as e: # handle log