---
# System prepended metadata

title: Django 介紹
tags: [learning]

---

---
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)
