Python的後端筆記(五)
來實作資料庫migration吧
原本想直接把登入+資料庫migration一次解決,但好像會變太長,所以先記錄一下migration的部分
說真的這個migration真的很厲害…如果完全是新手的話完全不用會SQL就可以進行後端開發,但是但是但是,如果真的要走後端還請一定要去學SQL,查詢語法優化和設定,不要聽某些人說不用會SQL,SQL很重要
- 建立一個新的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的