Python的後端筆記(五)

GaryLin
Gary的程式學習紀錄簿
15 min readJan 16, 2023

來實作資料庫migration吧

原本想直接把登入+資料庫migration一次解決,但好像會變太長,所以先記錄一下migration的部分

說真的這個migration真的很厲害…如果完全是新手的話完全不用會SQL就可以進行後端開發,但是但是但是,如果真的要走後端還請一定要去學SQL,查詢語法優化和設定,不要聽某些人說不用會SQL,SQL很重要

  1. 建立一個新的app

Django的app區分大致上一個app對應到一個Table的操作,不過當然還是有例外,有些Table沒有獨立的CRUD,而是跟隨著主要Table的操作進行變更,這時候就會有2個Table設定在一個app裡

p.s. 記得要把app加到setting.py

2. 在models.py裡新增Table

class Layer(models.Model):

class Meta:
db_table = 'layer'

TYPE_FLAG = (
('A', '地上'),
('B', '地下')
)

name = models.CharField(max_length = 200) # 名稱
type = models.CharField(default = 'A', max_length = 1, choices = TYPE_FLAG) # 樓層種類
floor = models.PositiveIntegerField() # 樓層
remark = models.CharField(max_length = 1000) # 備註
created_user = models.PositiveIntegerField() # 建立者
created_time = models.DateTimeField(auto_now_add = True) # 建立時間
updated_user = models.PositiveIntegerField() # 最後編輯者
updated_time = models.DateTimeField(auto_now = True) # 最後編輯時間

def __str__(self):
return self.name

這邊定義了Table名稱,欄位格式,pk如果沒定義的話會自己建立一個seq當成pk

詳細定義內容請參考官方文件

https://docs.djangoproject.com/en/4.1/topics/db/models/

p.s. 先用預設的sqlite,之後再示範改到postgresSql

3. 進行makemigrations,會在app下自動生成檔案

 python3 manage.py makemigrations

然後進行migrate

python3 manage.py migrate

第一次進行migration時會連同Django預設的資料一起進行migration

是不是很簡單,SQL什麼都不用會就可以建立好了

要修改Table也是修改model然後重新進行migration操作就可以,但要注意的是如果不是對框架很了解,不要隨意手動刪除或調整migrations的內容

另外就是如果app變得很大時,各app裡的model產生關聯性時最好新增/調整一段落就執行一次makemigrations,因為之前遇過一次改太多,建立順序好像產生bug導致後續執行migrate順序出問題需要手動調整裡面

4. 建立Serializer

這是一個強大的序列化器,可以做到資料檢核及CRUD的動作,先來一個簡單的serializers.py

from rest_framework import serializers

from .models import Layer

class LayerSerializer(serializers.ModelSerializer):

class Meta:
model = Layer
fields = (
'id',
'name',
'type',
'floor',
'remark',
'created_user',
'updated_user',
)

def create(self, data):
# 這邊可以加入在create前後要做的事
instance = super().create(data)
return instance

def update(self, instance, data):
# 這邊可以加入在update前後要做的事
role = super().update(instance, data)
return role

def delete(self, instance, data):
# 這邊可以加入在update前後要做的事
role = super().update(instance, data)
return role

def validate(self, data):
# 這邊可以加入檢核,
return data

5. 建立CRUD的views

先在admin_api建一個共用的response.py元件,讓未來的API都可以使用

from django.http import JsonResponse


class CustomJsonResponse(JsonResponse):
"""CustomJsonResponse

An HTTP response class that inherits JsonResponse
:param res: A named_tuple imported from ci_admin.results.code_n_msg
and passed to pay_load.
:param data: A dictionary of kwargs passed to pay_load.
:param pagination: The response of ci_admin.pagination.CustomPagination

"""
def __init__(self, return_code = '0000', return_message = '', result_data = {}, pagination = {}, **kwargs):

self.data = self.set_payload(return_code, return_message, result_data, pagination)

super(CustomJsonResponse, self).__init__(
data=self.data,
**kwargs
)


def set_payload(self, return_code = '0000', return_message = '', result_data = {}, pagination = {}):
payload = {
'return_code': return_code,
'return_message': return_message,
'result_data': result_data
}

if pagination:
payload['pagination'] = pagination
return payload

然後來修改views.py,增加查詢List和新增資料的簡單API

from django.shortcuts import render
from django.conf import settings
from django.db import transaction

from rest_framework import status
from rest_framework.views import APIView
from rest_framework.pagination import PageNumberPagination

# swagger
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi

from admin_api.response import CustomJsonResponse

from .serializers import LayerSerializer

from .models import Layer
# Create your views here.

class LayerView(APIView, PageNumberPagination):
model = Layer
queryset = model.objects.all()
serializer_class = LayerSerializer

@swagger_auto_schema(
operation_summary = '查詢樓層清單',
manual_parameters = [
openapi.Parameter(
'name',
in_ = openapi.IN_QUERY,
description = 'name',
type = openapi.TYPE_STRING,
),
openapi.Parameter(
'floor',
in_ = openapi.IN_QUERY,
description = 'floor',
type = openapi.TYPE_NUMBER,
),
openapi.Parameter(
'type',
in_ = openapi.IN_QUERY,
description = '樓層種類 A:地上 B:地下',
type = openapi.TYPE_STRING,
enum = ['A', 'B'],
),
openapi.Parameter(
'order',
in_ = openapi.IN_QUERY,
description = 'order',
type = openapi.TYPE_STRING,
required=True,
enum = [
'created_time',
'-created_time',
]
),
openapi.Parameter(
'page',
in_ = openapi.IN_QUERY,
description = '頁碼',
type = openapi.TYPE_STRING
),
openapi.Parameter(
'page_size',
in_ = openapi.IN_QUERY,
description = '單頁筆數',
type = openapi.TYPE_STRING
),
],
)
def get(self, request):
self.page_size = int(request.GET.get('page_size', settings.DEFAULT_PAGE_SIZE))
name = request.GET.get('name')
floor = request.GET.get('floor')
type = request.GET.get('type')
order = request.GET.get('order')

layer = self.queryset

if name:
layer = layer.filter(name__icontains = name)

if floor:
layer = layer.filter(floor = floor)

if type:
layer = layer.filter(type = type)

layer = layer.order_by(order)

page = self.paginate_queryset(layer, request)

serializer = self.serializer_class(page if page is not None else layer, many = True)
data = serializer.data
page_info = {}

return CustomJsonResponse(result_data = data, pagination = page_info, status = status.HTTP_200_OK)

@swagger_auto_schema(
operation_summary = '新增Layer資料',
request_body = openapi.Schema(
type = openapi.TYPE_OBJECT,
properties = {
'type': openapi.Schema(
type = openapi.TYPE_STRING,
description = '樓層種類 A:地上 B:地下',
example = 'A',
),
'floor': openapi.Schema(
type = openapi.TYPE_NUMBER,
description = '樓層',
example = 1,
),
'remark': openapi.Schema(
type = openapi.TYPE_STRING,
description = '備註',
example = 'test',
),
},
required = ['type', 'floor']
),
)
@transaction.atomic
def post(self, request):
type = request.data.get('type')
floor = request.data.get('floor')
name = type + '%02d' % floor + 'F';

data = {
'name': name,
'type': type,
'floor': floor,
'remark': request.data.get('remark'),
'created_user': 1,
'updated_user': 1,
}

serializer = self.serializer_class(data = data)
serializer.is_valid(raise_exception=True)
serializer.save()

return CustomJsonResponse(result_data = serializer.data, status = status.HTTP_200_OK)

然後再把urls.py設定好

from django.urls import path

from .views import (
LayerView,
)

urlpatterns = [
path('data', LayerView.as_view(), name = 'Layer'),
]

再修改admin_api裡的settings.py

INSTALLED_APPS = [
...
'layer',
]

修改admin_api裡的urls.py

urlpatterns += [
...
path('api/layer/', include('layer.urls')),
]

admin_api裡的settings.py和urls.py以後每次新增app時就要修改,之後應該會直接略過這步

6. 啟動Server測試

如果是一路做到現在都沒有關Server,重新整理網頁就可以看到改變了,因為只要改變程式碼儲存,Server就會自動重載,這個在開發時很好用

如果要禁用的話要在啟動Server時加上noreload

python manage.py runserver --noreload

看到有這2個api代表有新增成功了,如果整個死掉的話,看一下是不是Server啟動失敗了

先來測試一下新增

response成功就可以了,一般來說Rest新增時是回傳201,這邊調整一下status.HTTP_200_OK成HTTP_201_CREATED就好

然後測試一下查詢

看來成功地查詢了

上面那些都只是可以動而已,如果要實際工作用的基本上會更複雜,例如Response那邊可以看得出來有分頁資訊,不過為了簡化程式碼所以我先把他拔掉了,也要做更多的資料檢核確保資料的正確性,Model那邊也有關聯的設定法以及Serializer的function,但到目前來看程式碼幾乎可以沿用3.2的

--

--