---
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 自帶了一個功能強大的管理後台,讓開發者可以輕鬆管理應用程式的數據,而不需要額外編寫任何代碼。

### MVT 設計模式
- Django 的 MVT 設計模式與傳統的 MVC 設計模式類似,但更加清晰和簡單,使得開發者更容易理解和開發應用程式。

* Model: 負責與資料有關的直接處理,定義物件關聯對映(ORM)。有對資料庫直接存取的權力。
* View: 負責處理 Model 和 Template 之間的邏輯,並做出回應。
* Template: 負責資料的顯示。
---

### 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`

<br>
<br>
<br>
## Admin
Django 的 admin 是一個非常強大的工具,它可以快速地建立、修改、刪除和查詢數據,並且可以根據需要進行擴展和定制。以下是一些常見的 Django admin 的延伸應用
#### 最原始畫面

### 自訂義 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 都是一樣的
#### 結果

### 自訂在新增/修改時多加欄位
一開始在新增的時候只有基本欄位,如下圖

在 `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)
```
結果如下圖

### 或是自訂顯示,可以進行排序、篩選、新增 method
一開始沒任何設定的顯示,如下圖

在 `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)
```
結果如下圖

尚未加上自訂義 action

只要補上
```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

## 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 注意事項
- 