--- title: Django 介紹 tags: Tech description: Django. --- <style> .custom-div{background-image:url('https://drive.google.com/uc?export=view&id=11QcdhtRYVxklVsY6NX3MRNLUb77k4Xy7'); height:150px; width:180px; opacity:0; background-size:cover; background-position: center center; transition:all 0.2s; filter:blur(4px); } .custom-div:hover{ opacity:1; height:300px; width:360px; } </style> # Django 連我朋友 :monkey_face: 都會的 Django <div class='custom-div'> </div> ## 簡介 - Django 是一個使用 Python 語言編寫的 Web 應用程式框架,由 Lawrence Journal-World (一個新聞網站的開發) 於 2003 年開發。以 MVT(Model-View-Template)設計模式為基礎,可以幫助開發者快速且有效地開發出高品質的 Web 應用程式。 <br> <br> <br> ## 特點 ### 自帶管理後台 - Django 自帶了一個功能強大的管理後台,讓開發者可以輕鬆管理應用程式的數據,而不需要額外編寫任何代碼。 ![](https://i.imgur.com/MLR1LEJ.png) ### MVT 設計模式 - Django 的 MVT 設計模式與傳統的 MVC 設計模式類似,但更加清晰和簡單,使得開發者更容易理解和開發應用程式。 ![](https://i.imgur.com/LVWJSyZ.png =100%x) * Model: 負責與資料有關的直接處理,定義物件關聯對映(ORM)。有對資料庫直接存取的權力。 * View: 負責處理 Model 和 Template 之間的邏輯,並做出回應。 * Template: 負責資料的顯示。 --- ![](https://i.imgur.com/wrQSroA.jpg) ### ORM(對象關係映射) - Django 的 ORM 可以幫助開發者輕鬆地操作數據庫,而不需要寫 SQL 語句,大大提高了開發效率。 ### 安全性 - Django 內置了許多安全功能,如防止 CSRF(跨站請求偽造)攻擊、XSS(跨站腳本攻擊)攻擊等,讓開發者可以更加輕鬆地開發出安全性較高的應用程式。 <br> <br> <br> ## 組成 ### 模型 Model - 管理和查詢數據。 模型定義存儲數據的結構,包括字段類型以及字段可能的最大值,默認值,選擇列表選項,文檔幫助文本,表單的標籤文本等。 ### 視圖 View - Django 使用 MTV(Model-Template-View)設計模式,將邏輯分離,使得開發過程更加高效和簡單。但在 MVC 中 controllers 的邏輯不全然包含於 Django views;Django 通常習慣把部分邏輯放在 models,並且提供了一些額外的工具,來簡化views 需要承擔的責任 ### 模板 Template - 允許開發者將 Python 嵌入到 HTML 文件中,以便更好地處理網頁佈局和數據渲染等問題。 ### 路由 URL ( URL Routing ) - 用於處理網頁 URL 和視圖之間的映射的 Python 代碼。Django 的 URL 路由系統非常靈活,允許開發者定義自己的路由規則,以便更好地管理網頁的訪問和網址結構。 <br> <br> <br> ## Django REST Framework - Django REST Framework(DRF)是一個基於 Django 框架的強大且靈活的工具套件,它可以讓您快速構建RESTful API。DRF 具有許多功能,包括序列化,視圖,測試和驗證等等。 ### 主要功能 #### 1. 序列化 Serializer - 可以將模型數據轉換為Python數據類型,並且可以將其轉換回JSON或其他格式。 #### 2. 基於 Class 的 View - 提供了許多不同類別的 View,像是 Controllers,當 request 進到後端時會先通過這裡。 - 另外可依照使用情境選擇撰寫。例如: GenericViewSet 、 ModelViewSet #### 3. 驗證 - DRF支持多種身份驗證方式,例如基本身份驗證、令牌身份驗證和 OAuth 2.0 身份驗證等,使得API更加安全。 <br> <br> <br> ## 開始使用 ### 安裝 Django ```bash pip install django ``` ### 建立 Django 專案 ```bash django-admin startproject myproject ``` 建立一個 myproject 的資料夾,裡面就是 Django 的初始檔案 ``` mynewproject ├── myproject │ ├── settings.py │ ├── __init__.py │ ├── urls.py │ └── wsgi.py │ └── manage.py ``` ### 設定資料庫 Django 預設使用 <font color=white>SQLite</font> 作為資料庫,但也可以使用其他的資料庫,例如 MySQL 、 PostgreSQL 、 MongoDB。只需要在 `settings.py` 中設定資料庫的連線資訊: ```python!= DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mydatabase', 'USER': 'mydatabaseuser', 'PASSWORD': 'mypassword', 'HOST': '127.0.0.1', 'PORT': '3306', } } ``` ### 建立 App 檔案結果類似 NestJS 的 Module ```bash! python manage.py startapp blogs ``` 建立一個名為 blogs 的 app ``` mynewproject ├── myproject │ ├── settings.py │ ├── __init__.py │ ├── urls.py │ └── wsgi.py ├── blogs │ ├── migrations │ │ └── __init__.py │ ├── __init__.py │ ├── admin.py │ ├── models.py │ ├── tests.py │ └── views.py └── manage.py ``` ### 1. 建立 Model 目錄 ./blogs/models.py ,在 `models.py` 裡建立 Model ```python!= from datetime import date from django.db import models class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=200) email = models.EmailField() def __str__(self): return self.name class Entry(models.Model): blog = models.ForeignKey(Blog, on_delete=models.CASCADE) headline = models.CharField(max_length=255) body_text = models.TextField() pub_date = models.DateField() # mod_date = models.DateField(default=date.today) mod_date = models.DateField(auto_now_add=True) authors = models.ManyToManyField(Author) number_of_comments = models.IntegerField(default=0) number_of_pingbacks = models.IntegerField(default=0) rating = models.IntegerField(default=5) def __str__(self): return self.headline ``` ### 2. 建立 Serializer 可以有很多方式,例如可以單純使用 serializer.Serializer 來定義像是 DTO 的方式 在 ./blogs/ 目錄底下新建一個檔案叫做 `Serializers.py` ```python!= from rest_framework import serializers from blogs.models import Blog, Entry class BlogSerializer(serializers.ModelSerializer): """ 一般的 Blog Serializer """ class Meta: model = Blog fields = '__all__' class EntrySerializer(serializer.ModelSerializer): """ 一般的 Entry Serializer """ class Meta: model = Entry fields = '__all__' ``` 依照使用情況可以另外定義 Serializer - 例如想要在 GET 時,回傳的資料和資料庫中的資料不一樣,就可以自訂部分欄位回傳格式 ```python!= BAD_GUYS = ['王玄哥', '糸彖哥', '金安哥'] class GetEntrySerializer(serializer.ModelSerializer): """ 讀取 Entry Serializer """ class Meta: model = Entry fields = '__all__' # exclude = ['mod_date', 'pub_date'] author = serializers.SlugRelatedField(slug_field='name', read_only = True) pub_date = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S') # 這個欄位在 Model 中不存在 # 所以可以使用 SerializerMethodField 定義 Function 來回傳值 author_is_bad_guy = serializers.SerializerMethodField() def get_author_is_bad_gay(self, obj): """ 檢查作者是不是壞蛋,回傳值由 author_is_bad_guy """ if obj.author in BAD_GUYS: return True return False ``` ### 3. 建立 View 這裡也有很多方式,依照使用情境選擇 ```python=! from django.shortcuts import render from rest_framework import status from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet,GenericViewSet from rest_framework.views import APIView from rest_framework.mixins import ListModelMixin,RetrieveModelMixin from rest_framework.decorators import action,api_view from django.shortcuts import get_object_or_404 from blogs.models import Entry,Blog from blogs.serializers import GetEntrySerializer,BlogSerializer,EntrySerializer,AuthorSerializer class BlogAPIView(APIView): """ 使用 APIView 寫 Blog 的 API """ def get_object(self, pk): blog = get_object_or_404(Blog, pk=pk) serializer = BlogSerializer(data=blog) serializer.is_valid(raise_exception=True) return Response(serializer.data,status = status.HTTP_200_OK) def get(self, request, pk): blog = self.get_object(pk) serializer = BlogSerializer(blog) return Response(serializer.data) def put(self, request, pk): product = self.get_object(pk) serializer = BlogSerializer(product, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def delete(self, request, pk): product = self.get_object(pk) product.delete() return Response(status=status.HTTP_204_NO_CONTENT) @api_view(["GET", "PUT", "DELETE"]) def blog_api_view(request, pk): blog = Blog.objects.get(pk=pk) if request.method == "GET": serializer = BlogSerializer(blog) return Response(serializer.data) elif request.method == "PUT": serializer = BlogSerializer(blog, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == "DELETE": blog.delete() return Response(status=status.HTTP_204_NO_CONTENT) class EntryModelViewSet(ModelViewSet): """ 入口的 ModelViewSet """ queryset = Entry.objects.all() serializer_class = EntrySerializer def get_serializer_class(self): if self.action == 'list' or self.action == 'retrieve': return GetEntrySerializer return super().get_serializer_class() @action(methods=['GET'],detail=False,url_path='author-info') def get_authors_info(self,request): id = request.query_params.get('id') if not id: return Response(status=status.HTTP_400_BAD_REQUEST) entry = Entry.objects.get(id = id) authors = entry.authors.all() serializer = AuthorSerializer(data=authors, many = True) serializer.is_valid(raise_exception=True) return Response(serializer.data,status=status.HTTP_200_OK) class EntryGenericViewSet(GenericViewSet,ListModelMixin,RetrieveModelMixin): """ 入口的 GenericViewset """ queryset = Entry.objects.all() serializer_class = GetEntrySerializer ``` ### 4. 建立 URL 因為 View 有很多方式,所以這裡也有很多方式 ```python!= from django.urls import path, include from blogs.views import EntryModelViewSet, EntryModelViewSet, BlogAPIView, blog_api_view from rest_framework import routers router = routers.DefaultRouter() router.register(r'entrys',EntryModelViewSet or EntryModelViewSet) urlpatterns = [ path('', include(router.urls)), # ViewSet 使用方式 path('blog-view/', BlogAPIView.as_view()), # APIView 使用方式 path('blog-view/', blog_api_view), # api_view 使用方式 ] ``` ### 5. 設定專案 1. 在 myproject 目錄下的 `settings.py` 的 INSTALLED_APPS 加上 blogs ```python!= INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django_filters', 'rest_framework', 'blogs', # 加在這裡 ] ``` 2. 在同個目錄下的 `urls.py` 加上 blogs 的 urls ```python!= urlpatterns = [ path('admin/', admin.site.urls), # 這是預設的 path('',include('blogs.urls')), # 加在這裡 ] ``` 3. 執行 `python manage.py runserver` ![](https://i.imgur.com/2nFcvCe.png =100%x) <br> <br> <br> ## Admin Django 的 admin 是一個非常強大的工具,它可以快速地建立、修改、刪除和查詢數據,並且可以根據需要進行擴展和定制。以下是一些常見的 Django admin 的延伸應用 #### 最原始畫面 ![](https://i.imgur.com/KzHGwII.png) ### 自訂義 Admin 頁面 ```python!= from django.contrib import admin from blogs.models import Author,Blog,Entry admin.site.register(Author) admin.site.register(Blog) admin.site.register(Entry) ``` 因為 admin 為 Singleton Pattern 所以在每個 Apps 裡面的 admin 都是一樣的 #### 結果 ![](https://i.imgur.com/32GI3ws.png) ### 自訂在新增/修改時多加欄位 一開始在新增的時候只有基本欄位,如下圖 ![](https://i.imgur.com/AWfnoi0.png) 在 `admin.py` 裡面加上 ```python!= from django.contrib import admin from blogs.models import Author,Blog,Entry from django import forms class AuthorForm(forms.ModelForm): is_bad_guy = forms.BooleanField() def save(self, commit=True): # 做一些判斷,例如修改其他 Model 的資料 return super().save(commit) class AuthorAdmin(admin.ModelAdmin): form=AuthorForm admin.site.register(Author, AuthorAdmin) ``` 結果如下圖 ![](https://i.imgur.com/NrkycLi.png) ### 或是自訂顯示,可以進行排序、篩選、新增 method 一開始沒任何設定的顯示,如下圖 ![](https://i.imgur.com/TP7g5Eq.png) 在 `admin.py` 裡面加上 ```python!= from django.contrib import admin from blogs.models import Author,Blog,Entry from django import forms class EntryAdmin(admin.ModelAdmin): date_hierarchy = 'pub_date' list_display=('headline','body_text','pub_date') list_filter=['headline'] admin.site.register(Entry,EntryAdmin) ``` 結果如下圖 ![](https://i.imgur.com/K6RPw0k.png) 尚未加上自訂義 action ![](https://i.imgur.com/1fTfuQ8.png) 只要補上 ```python=! @admin.action(description='hellow i am new action') def test_action(modeladmin,request,queryset): pass class EntryAdmin(admin.ModelAdmin): date_hierarchy = 'pub_date' list_display=('headline','body_text','pub_date') list_filter=['headline'] actions = [test_action] ``` 就可以看到自己新增的 action ![](https://i.imgur.com/vSmazi9.png) ## Filterset Django FilterSet 是 Django 框架中一個方便的擴展套件,能夠輕鬆的創建自定義過濾器,讓你更快速、更簡單地搜尋和過濾資料。 - 如果一開始沒有用 FilterSet ,會在 View 裡面寫比較多的搜尋 `方法一` ```python=! # 這是在 View class EventStaffViewSet(ModelViewSet): queryset = Event.objects.filter(is_deleted=False) serializer_class = EventSerializers def get_queryset(self): event_type = self.request.query_params.get('type') title = self.request.query_params.get('title') month = self.request.query_params.get('month') query={} if event_type: query['type']=event_type if title: query['title__icontains']=title if month: query['publish_date__month']=month if self.action == "list" and query: return Event.objects.filter(**query) return super().get_queryset() ``` `方法二` ```python=! # 這是在 serializers.py class CommonQuerySerializer(serializers.Serializer): """ 共用搜尋 Serializer """ month = MonthField(required=False) type = serializers.IntegerField(required=False) keyword = serializers.CharField(required=False) # 定義 Serializer 顯示的內建 Function def to_representation(self, instance): query_dict = { 'title__icontains': instance.get('keyword', None), 'type': instance.get('type', None), 'publish_date__month': instance.get('month', None), } query = {key: value for key, value in query_dict.items() if value is not None} return query # 這是在 views.py class EventStaffViewSet(ModelViewSet): queryset = Event.objects.filter(is_deleted=DeleteState.NOTDELETED) serializer_class = EventSerializers def get_queryset(self): if self.action == "list" and len(self.request.query_params) > 0: query_serializer = CommonQuerySerializer(data=self.request.query_params) query_serializer.is_valid(raise_exception=True) return Event.objects.get_filtered_list(query=query_serializer.data) return super().get_queryset() ``` #### 使用 Filterset 後 先在 App 目錄底下建立檔案 `filters.py` ```python=! # 這是在 filters.py from django_filters import rest_framework as filters from events.models import Event class EventAdminFilter(filters.FilterSet): """ 活動 Filter """ # 活動類型 type = filters.NumberFilter(field_name='type', lookup_expr='exact') # 活動標題 (篩選包含字串) title = filters.CharFilter(lookup_expr='icontains') # 刊登日期範圍 date = filters.DateTimeFromToRangeFilter(field_name='publish_date') class Meta: model = Event fields = ['type', 'date', 'title'] # 這是在 views.py from rest_framework import filters from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter from events.filters import EventFilter class EventStaffViewSet(ModelViewSet): queryset = Event.objects.filter(is_deleted=DeleteState.NOTDELETED) serializer_class = EventSerializers filter_backends = [DjangoFilterBackend, OrderingFilter, filters.SearchFilter] filterset_class = EventFilter ordering_fields = ['publish_date'] # 這是另一個可以調整排序的方式 search_fields=['id', 'title'] ``` 使用 filterset_class 指定剛才寫的 Filterset 後 API 會如下 `localhost:8080/events/?title=123&month=2&type=1` or `localhost:8080/events/?month=3` 使用 ordering_fields 使用者可以在 API 自訂排序 API 會如下 `localhost:8080/events/?ordering=publish_date` 由舊到新 or `localhost:8080/events/?ordering=-publish_date` 由新到舊 使用 search_fields 使用者可以自訂搜尋會包含的欄位 範例中使用id和title API 會如下 `localhost:8080/events/?search=12` 會把 id 包含12 例如12、120、212 或 title 包含 12 找出來 ## Signal - 註冊 signal 注意事項 - ![image](https://hackmd.io/_uploads/BkdkH_YsA.png)