Thumbnail
Category: Lập trình

Django - build core

Date: February 22, 2024
60 views


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

  1. 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: DEBUGDB_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



Copyright © 2025 All Right Reserved