Django ORM restframework學習記錄

0X-1 踩坑記錄

如何統一處理DRF的異常?使用custom_exception_handler:
https://stackoverflow.com/questions/28197199/how-to-change-validation-error-responses-in-drf

 

對於可以爲None的值,前臺不設置該key即可,可以通過序列化器的數據校驗

嵌套序列化傳參:

data = {
    'up_credit.username': 'root',
    'up_credit.password': 'qwe',
    'name': 'z10',
    'hosts': '192.168.2.111;',
    "exclude_hosts": "",
    "alive_test": 1,
    "port_list": 1,
    "comment": "",
    'ssh_port': ''
}

參數傳遞過去後,會被序列化器封裝成一個字典,字典包含了被嵌套序列化對象的所有參數。

對於嵌套序列化的數據校驗,如果允許None的話,那麼up_credit都不傳值即可,但如果up_credit.username傳值了,password卻沒傳值,那麼是會引發報錯的。

當POST和PUT方法提交的參數不同的時候,可以使用兩個不同的URL和VIEW來提供API:

    path('remote-app/create/', views.RemoteAppCreateView.as_view(), name='remote-app-create'),
    path('remote-app/<uuid:pk>/update/', views.RemoteAppUpdateView.as_view(), name='remote-app-update'),

 

serializer實例化傳入data,這時data是ReturnDict類型,不可變對象,不能改變其的值。

https://stackoverflow.com/questions/37111118/update-append-serializer-data-in-django-rest-framework

https://www.django-rest-framework.org/api-guide/pagination/#pagenumberpagination  # 分頁

關於serializer驗證數據的機制,在進行serializer.save()之前,必須運行serializer.is_valid()方法,這個方法首先會執行默認在models或者serializer裏面定義的字段約束,如果不滿足該約束直接返回異常。如果通過了字段約束,會進入validate()方法,該方法默認直接返回原始值,用戶可以對該方法重寫,加入自己想要進行的數據驗證。整個驗證結束後,過濾後的值會被賦值給serializer.data

 

序列化器會將前臺POST的數據轉換成models中定義的數據類型,所以對於int型,不論前臺傳的是int還是str,都會被序列化成int類型。

 

可選字段

默認情況下,“一起唯一”驗證會強制執行所有字段 required=True。在某些情況下,您可能希望顯式應用於 required=False其中一個字段,在這種情況下,驗證的所需行爲是不明確的。

在這種情況下,您通常需要從序列化程序類中排除驗證程序,而是在.validate()方法中或在視圖中顯式寫入任何驗證邏輯。

例如:

class BillingRecordSerializer(serializers.ModelSerializer):
    def validate(self, data):
        # Apply custom validation either here, or in the view.

    class Meta:
        fields = ('client', 'date', 'amount')
        extra_kwargs = {'client': {'required': False}}
        validators = []  # Remove a default "unique together" constraint.

serializers.Serializer並不支持在class meta中定義model,fields等,需要顯式定義序列化字段。而ModelSerializer是支持的

關於depth參數(針對ModelSerializer):

使用該參數可以輕鬆獲得外鍵或多對多關係的具體數據,而不是單單顯示一個key,缺點是使用了該參數後無法使用create和update方法,存在外鍵關係的字段會被過濾掉。

解決方法:重寫create和update方法,將使用的serializer指向另一個serializer,一個除了沒有指定depth參數,其餘皆和原serializer相同的序列化器。

0X-2 反向查詢

方法1:
class A(models.Model):
    name = models.CharField(max_length=255, unique=True)

class B(models.Model):
    a = models.ForeignKey(A, on_delete=models.PROTECT)
    name = models.CharField(max_length=255, unique=True)

通過A的實例反向查詢所有引用過A的B對象:
    A.objects.filter(id=1).values_list('blist')

輸出QuerySet [None]  or  [(1,), (2,)]

方法2:

ret = PortList.objects.filter(id=1, targetlist__uniq_id__isnull=False)

返回QuerySet [] or [obj,obj],len(ret)即是id爲1的PortList被引用的次數。


0X-3 碰到的小知識點

獲取實例的類名:
obj.__class__.__name__
  • ForeignKey字段可以設置爲null=True,那麼允許前端不選擇關聯對象,而是傳遞一個null。經過序列化的數據該key對應的value爲None
    

使用ViewSet的情況下,如果再settings中設置了分頁,那麼默認會對數據進行分頁處理。該處理繼承於GenericAPIView,在APIView中是沒有這樣處理的,所以如果使用了APIView,需要手動進行分頁,代碼如下:

paginate_queryset,可以傳入queryset對象或者list等,該方法計算出相關數值,get_paginated_response會將數據進行分頁然後返回一個django restframework中的Response對象,不需要額外使用Response對象對其進行封裝。

def get(self, request):
    data = list()
    op_return = open_vas.exec_cmd('get_tasks')
    for i in op_return['get_tasks_response']['task']:
        data.append({'uniq_id': i['@id'],'status': i['status']})
    page = LimitOffsetPagination()
    page_roles = page.paginate_queryset(data, self.request, view=self)
    result = page.get_paginated_response(page_roles)
    return result
在中間件中獲取request數據出現的問題:
django request.POST / request.body
    當request.POST沒有值 需要考慮下面兩個要求
        1.如果請求頭中的: Content-Type: application/x-www-form-urlencoded   request.POST中纔會有值(纔會去request.body中解析數據)
        2.若1有,也不一定有值 必須有數據格式要求: name=alex&age=18&gender=男

如果已經讀取過request.body的數據,那麼不能二次讀取

multipart/form-data:既可以上傳文件等二進制數據,也可以上傳表單鍵值對,只是最後會轉化爲一條信息;

x-www-form-urlencoded:只能上傳鍵值對,並且鍵值對都是間隔分開的。

0X-4 使用filter來進行查詢、排序等操作

相關文檔:

https://www.django-rest-framework.org/api-guide/filtering/#searchfilter

https://django-filter.readthedocs.io/en/latest/index.html

https://django-filter.readthedocs.io/en/latest/guide/rest_framework.html

DEMO:

STEP 1:下載及加載模塊

pip install django-filter

INSTALLED_APPS = [
    ...
    'rest_framework',
    'django_filters',
]

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
}

STEP 2:編寫fiter

from django_filters import rest_framework as filters
from .models import ActionLog


class ActionLogFilter(filters.FilterSet):
    comment = filters.CharFilter(lookup_expr='icontains')

    class Meta:
        model = ActionLog
        fields = ['user', 'status', 'dtime', 'comment']  # 若不指定具體查找內容將自動使用精確查找

STEP 3:將filter與ViewSet綁定

class ActionLogViewSet(ModelViewSet):
    permission_classes = (permissions.IsAdminUser,)
    queryset = ActionLog.objects.all()
    serializer_class = ActionLogSerializer
    filter_backends = (filters.DjangoFilterBackend,)  # !!!
    filterset_class = ActionLogFilter  # !!!

STEP 4:測試

http://192.168.2.178:8000/logs/action/?user=AnonymousUser

支持使用fields字段來簡化代碼:

import django_filters

class ProductFilter(django_filters.FilterSet):
    class Meta:
        model = Product
        fields = {
            'price': ['lt', 'gt'],
            'release_date': ['exact', 'year__gt'],
        }

# 以上將生成'price__lt','price__gt','release_date'和'release_date__year__gt'過濾器

使用時間過濾器:

class ActionLogFilter(filters.FilterSet):
    comment = filters.CharFilter(lookup_expr='icontains')
    dtime = filters.DateFilter(lookup_expr='exact')
    dtime_range = filters.DateFromToRangeFilter(field_name='dtime')

    class Meta:
        model = ActionLog
        fields = ['user', 'status', 'dtime', 'comment']  # 若不指定具體查找內容將自動使用精確查找


http://192.168.2.178:8000/logs/action/?dtime_range_after=2019-06-18&dtime_range_before=2019-06-17

0X-5 在APIView中使用分頁器

在settings.py中針對DRF設置了默認分頁器的話,那麼ModelViewSet視圖集會自動進行分頁:

REST_FRAMEWORK = {
    #  只有使用rest_framework的API纔會受到JWT的保護
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        #  使用rest_framework的界面需要下列認證模塊
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
    #  以下代碼會禁用掉REST自帶的WEB API界面,只返回JSON
    # 'DEFAULT_RENDERER_CLASSES': (
    #     'rest_framework.renderers.JSONRenderer',
    # ),
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 50,
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
    ),
}

但是APIView不會,需要手動分頁,手動分頁方法如下:

class TaskStatusGetView(APIView):
    permission_classes = (UserReadAdminWrite,)
    def get(self, request):
        data = list()
        op_return = open_vas.exec_cmd('get_tasks')
        for i in op_return['get_tasks_response']['task']:
            #  如果status爲Done,progress爲-1
            data.append({'uniq_id': i['@id'],'status': i['status'],'progress': i['progress']})
        page = LimitOffsetPagination()
        page_roles = page.paginate_queryset(data, self.request, view=self)
        result = page.get_paginated_response(page_roles)
        return result

0X-6 返回一個文件

class BackupLogView(APIView):
    permission_classes = (permissions.IsAdminUser,)
    def get(self, request):
        with open(config['BACKUP']['LINE_PATH'], 'r') as f:
            line_num = f.readline()
        query_set = ActionLog.objects.filter(id__gt=line_num)
        if len(query_set) > 0:
            example = 'ID:%s,用戶:%s,操作詳情:%s,操作狀態:%s,時間:%s'
            with open(config['BACKUP']['LOG_PATH'], 'a') as syslog:
                for i in query_set:
                    syslog.write(example % (i.id, i.user, i.comment, i.status, i.dtime))
                    syslog.write('\n')
            with open(config['BACKUP']['LINE_PATH'], 'w') as fr:
                fr.write(str(query_set[len(query_set)-1].id))
        result = {'result': ''}
        with open(config['BACKUP']['LOG_PATH'], 'r') as file:
            for line in file:
                result['result'] = result['result'] + line + '\r\n'
        response = HttpResponse(result['result'], content_type='text/plain; charset=UTF-8')
        # response['Content-Type'] = 'application/octet-stream'
        response['Content-Disposition'] = 'attachment;filename="sys_op_log.txt"'
        return response

0X-7 上傳一個文件

支持通過binary file或表單來上傳文件。如果文件小於2.5MB,那麼Django會將數據全部保存到內存中,如果需要對文件進行操作,操作全部在內存執行,非常快。如果文件大於2.5MB,Django會把文件存儲到系統目錄,然後需要使用的時候將臨時文件按塊讀取。傳送門:https://docs.djangoproject.com/en/2.2/topics/http/file-uploads/

from rest_framework.parsers import MultiPartParser


class UpdateNvtView(APIView):
    permission_classes = (permissions.IsAdminUser,)
    parser_classes = [MultiPartParser]

    def put(self, request):
        print(request.data)
        zip_file = request.data['file']
        with open('/home/rule.zip', 'wb') as f:
            for chunk in zip_file.chunks():
               f.write(chunk)
        return Response(status=status.HTTP_200_OK)

 

0X00 什麼是ORM

  • MVC框架中包括一個重要的部分,就是ORM,它實現了數據模型與數據庫的解耦,即數據模型的設計不需要依賴於特定的數據庫,通過簡單的配置就可以輕鬆更換數據庫
  • ORM是“對象-關係-映射”的簡稱,主要任務是:
    • 根據對象的類型生成表結構
    • 將對象、列表的操作,轉換爲sql語句
    • 將sql查詢到的結果轉換爲對象、列表
  • 這極大的減輕了開發人員的工作量,不需要面對因數據庫變更而導致的無效勞動
  • Django中的模型包含存儲數據的字段和約束,對應着數據庫中唯一的表

0X01 ORM中的屬性及關係

  • 定義屬性時,需要字段類型
  • 字段類型被定義在django.db.models.fields目錄下,爲了方便使用,被導入到django.db.models中
  • 使用方式
    1. 導入from django.db import models
    2. 通過models.Field創建字段類型的對象,賦值給屬性
  • 對於重要數據都做邏輯刪除,不做物理刪除,實現方法是定義isDelete屬性,類型爲BooleanField,默認值爲False

字段類型

獲取choice字段的描述:

如果設置了choice類型,那麼非設置的值不允許傳入,傳入會報錯:

{
    "credit_type": [
        "“3” 不是合法選項。"
    ]
}
choice = (
        (1, 'up'),  # username + password
        (2, 'snmp')  # snmp community
    )
credit_type = models.IntegerField(choices=choice)
#  前臺傳參需要傳遞int類型
  • AutoField:一個根據實際ID自動增長的IntegerField,通常不指定
    • 如果不指定,一個主鍵字段將自動添加到模型中
  • BooleanField:true/false 字段,此字段的默認表單控制是CheckboxInput
  • NullBooleanField:支持null、true、false三種值
  • CharField(max_length=字符長度):字符串,默認的表單樣式是 TextInput
  • TextField:大文本字段,一般超過4000使用,默認的表單控件是Textarea
  • IntegerField:整數
  • DecimalField(max_digits=None, decimal_places=None):使用python的Decimal實例表示的十進制浮點數
    • DecimalField.max_digits:位數總數
    • DecimalField.decimal_places:小數點後的數字位數
  • FloatField:用Python的float實例來表示的浮點數
  • DateField[auto_now=False, auto_now_add=False]):使用Python的datetime.date實例表示的日期
    • 參數DateField.auto_now:每次保存對象時,自動設置該字段爲當前時間,用於"最後一次修改"的時間戳,它總是使用當前日期,默認爲false
    • 參數DateField.auto_now_add:當對象第一次被創建時自動設置當前時間,用於創建的時間戳,它總是使用當前日期,默認爲false
    • 該字段默認對應的表單控件是一個TextInput. 在管理員站點添加了一個JavaScript寫的日曆控件,和一個“Today"的快捷按鈕,包含了一個額外的invalid_date錯誤消息鍵
    • auto_now_add, auto_now, and default 這些設置是相互排斥的,他們之間的任何組合將會發生錯誤的結果
  • TimeField:使用Python的datetime.time實例表示的時間,參數同DateField
  • DateTimeField:使用Python的datetime.datetime實例表示的日期和時間,參數同DateField
  • FileField:一個上傳文件的字段
  • ImageField:繼承了FileField的所有屬性和方法,但對上傳的對象進行校驗,確保它是個有效的image

字段選項

  • 通過字段選項,可以實現對字段的約束
  • 在字段對象時通過關鍵字參數指定
  • null:如果爲True,Django 將空值以NULL 存儲到數據庫中,默認值是 False
  • blank:如果爲True,則該字段允許爲空白,默認值是 False
  • 對比:null是數據庫範疇的概念,blank是表單驗證證範疇的
  • db_column:字段的名稱,如果未指定,則使用屬性的名稱
  • db_index:若值爲 True, 則在表中會爲此字段創建索引
  • default:默認值
  • primary_key:若爲 True, 則該字段會成爲模型的主鍵字段
  • unique:如果爲 True, 這個字段在表中必須有唯一值

關係

  • 關係的類型包括
    • ForeignKey:一對多,將字段定義在多的端中,如果一個model中有多個外鍵指向同一個model,需要設置related_name,避免反向訪問時衝突(可以通過related_name來進行反向查詢!)
    • ManyToManyField:多對多,將字段定義在兩端中
    • OneToOneField:一對一,將字段定義在任意一端中
  • 可以維護遞歸的關聯關係,使用'self'指定,詳見“自關聯”
  • 用一訪問多:對象.模型類小寫_set

使用ORM創建了表後切忌手動進入數據庫刪除表信息,如果刪除了將導致無法再migrate提交修改到數據庫。解決方法:

刪除django_migrations表中的提交記錄,注意,只需刪除自己APP最近的一條提交記錄。

 

on_delete指的是通過ForeignKey連接起來的對象被刪除後,當前字段怎麼變化。

常見的選項有:

  models.CASCADE,對就對象刪除後,包含ForeignKey的字段也會被刪除

  models.PROTECT,刪除時會引起ProtectedError

  models.SET_NULL,注意只有噹噹前字段設置null設置爲True纔有效,此情況會將ForeignKey字段設置爲null

  models.SET_DEFAULT ,同樣,當前字段設置了default纔有效,此情況會將ForeignKey 字段設置爲default 值

  moels.SET,此時需要指定set的值

  models.DO_NOTHING ,什麼也不做

 

模型中Meta配置:
  對於一些模型級別的配置。我們可以在模型中定義一個類,叫做Meta。然後在這個類中添加一些類屬性來控制模型的作用。比如我們想要在數據庫映射的時候使用自己指定的表名,而不是使用模型的名稱。那麼我們可以在Meta類中添加一個db_table的屬性。示例代碼如下:

  class Book(models.Model):
    name = models.CharField(max_length=20,null=False)
    desc = models.CharField(max_length=100,null=True,blank=True)

    class Meta:
      db_table = 'book_model'

  以下將對Meta類中的一些常用配置進行解釋。

  1、db_table:這個模型映射到數據庫中的表名。如果沒有指定這個參數,那麼在映射的時候將會使用模型所在app的名稱加上模型名的小寫來作爲默認的表名。

  2、ordering:設置在提取數據的排序方式,因爲可以按照多個字段以優先關係進行排序,所以需要傳遞一個字段的列表,在我們提取數據時,可以根據列表中字段從前到後(優先級從高到低)的方式排序,排序默認爲正序,如果你需要哪個字段按倒序排列,就可以在這個字段前面加上"-"。後面章節會講到如何查找數據。比如我想在查找數據的時候根據添加的時間排序,那麼示例代碼如下:

class Book(models.Model):
    name = models.CharField(max_length=20,null=False)
    desc = models.CharField(max_length=100,name='description',db_column="description1")
    pub_date = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'book_model'
        ordering = ['pub_date',]

       3、get_latest_by

由於Django的管理方法中有個lastest()方法,就是得到最近一行記錄。如果你的數據模型中有 DateField 或 DateTimeField 類型的字段,你可以通過這個選項來指定lastest()是按照哪個字段進行選取的。

一個 DateField 或 DateTimeField 字段的名字. 若提供該選項, 該模塊將擁有一個 get_latest() 函數以得到 "最新的" 對象(依據那個字段):

get_latest_by = "order_date"

       4、app_label

app_label這個選項只在一種情況下使用,就是你的模型類不在默認的應用程序包下的models.py文件中,這時候你需要指定你這個模型類是那個應用程序的。比如你在其他地方寫了一個模型類,而這個模型類是屬於myapp的,那麼你這是需要指定爲:

app_label='myapp'

     5、unique_together

unique_together這個選項用於:當你需要通過兩個字段保持唯一性時使用。這會在 Django admin 層和數據庫層同時做出限制(也就是相關的 UNIQUE 語句會被包括在 CREATE TABLE 語句中)。比如:一個Person的FirstName和LastName兩者的組合必須是唯一的,那麼需要這樣設置:

unique_together = (("first_name", "last_name"),)

使用model層的validators:

https://docs.djangoproject.com/en/2.2/ref/validators/

from django.db import models
from django.core import validators

class Interface(models.Model):
    name = models.CharField(max_length=20)
    ip = models.GenericIPAddressField(protocol='IPv4')
    mask = models.IntegerField(validators=[
        validators.MinValueValidator(1),
        validators.MaxValueValidator(32),
    ])
    status = models.BooleanField()

    class Meta:
        db_table = 'net_interface'

0X02 常見的操作

增:

models.UserInfo.objects.create(name=new_name)

刪:

models.UserInfo.objects.get(id=xxx,None)
models.delete()

改:

obj = models.UserInfo.objects.get(id=xx,None)
obj = new_xxx
obj.save()

查:

querylist=models.Entry.objects.all()
print([e.title for e in querylist])
print([e.title for e in querylist])

entry = models.Entry.objects.get(id=?)

0X03 rest_framework的serializer

將複雜的數據結構,例如ORM中的QuerySet或者Model實例對象轉換成Python內置的數據類型,從而進一步方便數據和JSON,XML等格式的數據進行交互

1、DEMO:

models.py:

from django.db import models

# Create your models here.

class Graph(models.Model):
    id = models.AutoField(primary_key=True)
    graph_name = models.CharField(max_length=255)
    graph_content = models.TextField()
    graph_status = models.BooleanField(default=False)
    graph_conf = models.CharField(max_length=255)
    graph_position = models.IntegerField()
    create_time = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'graph'

serilizers.py:

# -*- coding: utf-8 -*-
# @Time    : 2019/5/8 11:07
# @Author  : Zcs
# @File    : serializers.py
from rest_framework import serializers
from .models import Graph


class GraphSerializer(serializers.ModelSerializer):
    class Meta:
        model = Graph
        fields = '__all__'

可以在django shell中查看定義的序列化器的內容:

2、您可以將exclude屬性設置爲要從序列化程序中排除的字段列表

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        exclude = ('users',)

好文章:https://www.cnblogs.com/pyspark/p/8607801.html

常見問題:

1、Cannot call `.is_valid()` as no `data=` keyword argument was passed when instantiating the serializer instance.

當把query_set序列化成Python數據類型時,將data作爲第一個參數傳給serilizer即可,但是反序列化時,要指定data=data

https://stackoverflow.com/questions/29731013/django-rest-framework-cannot-call-is-valid-as-no-data-keyword-argument/29731923

 

使用傳統的視圖,需要爲總體和實例個體分別實現視圖函數:

from django.urls import path
from snippets import views

urlpatterns = [
    path('snippets/', views.snippet_list),
    path('snippets/<int:pk>/', views.snippet_detail),
]
@csrf_exempt
def snippet_list(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)
@csrf_exempt
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

對於snippet_list來說,只有get方法和post方法。put方法和delete方法都是針對某個實例而言的,所以放在snippet_detail中。

 

可以添加序列化字段對時間進行格式化:

date_joined = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', read_only=True)

0X04 幾種url;view;serializer的組合

命名規範:

URL: path('ports/', VIEWSET_CONF['port_list']),
     path('ports/<str:pk>/', VIEWSET_CONF['port_detail']),

VIEWSET: class PortViewSet(ModelViewSet):

SERIALIZER: class TargetSerializer(serializers.ModelSerializer):

MODEL: class Schedule(models.Model):

1、普通URL + APIView + ModelSerializer

urlpatterns = [
    path('dev/', views.GraphView.as_view()),
]

"""
from rest_framework import routers
# router = routers.DefaultRouter()
# router.register('dev', views.DeViewset)  # 爲viewset註冊路由

這種方式是將dev_list和dev_detail分開的註冊方式,下面的方式2可以
將dev_list和dev_detail使用同一個viewset和serializer,通過請求方法的
不同調用不同的處理函數
"""
class GraphView(APIView):
    """
    GET: [{'graph_name':'', 'graph_content':'', 'graph_status':'', 'upper_limit':'', 'lower_limit':'', 'size':'',
           'graph_position':'', 'create_time'},
        ]

    POST:{'graph_name':'', 'graph_status':'', 'upper_limit':'', 'lower_limit':'', 'size':''},

    """

    def get(self, request):
        query_data = Graph.objects.all()  # 從數據庫取出數據,query_set
        serializer = GraphSerializer(query_data, many=True)  # 將query_set序列化成Python數據類型
        # data = JSONRenderer().render(serializer.data)  # 將Python數據類型轉成JSON
        # print(data)
        return Response(serializer.data)

    def post(self, request):
        """
        graph_conf 2|5|10000
        反序列化,將JSON形式數據轉換爲流的形式,然後將流數據轉化爲Python數據類型
        :param request:
        :return:
        """
        stream = BytesIO(request.body)  # 將JSON數據轉換爲流形式
        data = JSONParser().parse(stream)  # 解析流中的JSON數據並轉換爲Python數據結構
        serializer = GraphSerializer(data=data)
        if serializer.is_valid():  # 驗證前臺傳過來的數據是否合法,在save前必須調用該方法
            serializer.save(**data)  # 根據是否存在instance實例來決定執行create方法或update方法,無執行create
        #  save方法可以選擇傳參或者不傳參,不傳參的話默認會從serializer實例化時傳入的數據進行讀取
        #  但是這樣讀取的話,非model定義的字段會被捨棄掉,所以如果需要傳入非model定義的字段,需要在save方法傳入data
            return 0

        #  print(serializer.errors)

    def patch(self, request):
        stream = BytesIO(request.body)
        data = JSONParser().parse(stream)
        serializer = GraphSerializer(data=data)
        serializer.instance = Graph.objects.get(id=data['id'])
        if serializer.is_valid():
            serializer.save(**data)
        return 0

    def delete(self, request):
        """
        RESTful默認界面的DELETE方法會將URL中的id傳遞給後臺,如果沒有使用URL傳參的話,是不會傳過來數據的
        自己傳遞JSON過來使用以下方法解析即可
        :param request:
        :return:
        """
        stream = BytesIO(request.body)
        data = JSONParser().parse(stream)
        g_id = data['id']
        obj = Graph.objects.get(id=g_id)
        obj.delete()
        return Response({"status": 0})
class GraphSerializer(serializers.ModelSerializer):

    class Meta:
        model = Graph
        fields = '__all__'
        read_only_fields = ['graph_name', 'graph_content', 'graph_conf', 'graph_position', 'create_time']
        #  read_only_fields選項,表示該字段只用作查詢出結果,不需要用戶傳值
        #  如果未設置該字段,那麼表示所有字段都需要從serializer那傳值,如果未傳is_valid函數會返回False

        #  extra_kwargs = {'id': {'write_only': True}}
        #  提供extra_kwargs的方式來爲某個字段指定其關鍵字參數,而不需要去顯示指定(id=serializers.IntegerField())

    @staticmethod
    def create_graph(gspace, gtime, num):
        buff = ()
        for i in range(num):
            ret1 = random.randrange(gspace[0], gspace[1] + 1)
            ret2 = random.randrange(gtime[0], gtime[1] + 1)
            buff += ((ret1, ret2),)
        return buff

    #  驗證用戶傳到後臺的數據是否合法
    # def validate(self, attrs):
    #     return attrs

    def create(self, validated_data):
        print(validated_data)
        validated_data['graph_conf'] = str(validated_data['lower_limit']) + '|' + \
                                       str(validated_data['upper_limit']) + '|' + str(validated_data['size'])
        validated_data['graph_position'] = 0
        validated_data['graph_content'] = str(self.create_graph([1,4], [validated_data['lower_limit'],validated_data['upper_limit']],10))
        kwargs = {
            'graph_name': validated_data.get('graph_name'),
            'graph_content': validated_data.get('graph_content'),
            'graph_status': validated_data.get('graph_status'),
            'graph_conf': validated_data.get('graph_conf'),
            'graph_position': validated_data.get('graph_position'),
        }
        instance = Graph.objects.create(**kwargs)
        return instance

    def update(self, instance, validated_data):
        #  只允許更新graph_status參數
        instance.graph_status = validated_data['graph_status']
        instance.save()
        return instance

2.正則URL + viewsets.ModelViewSet + ModelSerializer

from django.urls import path, include
from . import views
from rest_framework.urlpatterns import format_suffix_patterns


dev_list = views.DeViewset.as_view({
    'get': 'list',
    'post': 'create'
})
dev_detail = views.DeViewset.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})

urlpatterns = format_suffix_patterns([
    #path('', include(router.urls)),
    path('graph/', views.GraphView.as_view()),
    path('devs/', dev_list),
    path('devs/<int:id>/', dev_detail)
])
class DeViewset(viewsets.ModelViewSet):
    """
    ModelViewset繼承了四個混類和一個泛類,自動會實現增刪查改的方法
    viewset相當於是視圖的集合,綜合了多個方法
    """
    queryset = Dev.objects.all().order_by('id')
    serializer_class = DevSerializer
    lookup_field = 'id'  # 定義通過哪個參數來定位實例
class DevSerializer(serializers.ModelSerializer):

    class Meta:
        model = Dev
        fields = '__all__'

drf中的view繼承關係:

0X05 當存在外鍵關係時,默認取出的數據只包含外鍵id,如何根據外鍵id取出其他所有數據?

models.py:定義一個私有方法

class Dev(models.Model):
    id = models.AutoField(primary_key=True)
    cpu = models.IntegerField()
    memory = models.IntegerField()
    disk = models.IntegerField()
    ip = models.CharField(max_length=15)
    dev_user = models.ForeignKey(User, on_delete=models.CASCADE, default='')

    class Meta:
        db_table = 'dev'

    @property
    def dev_user_name(self):
        return self.dev_user.username

serilizers.py:需要注意的是,dev_user_name變量名必須與model的方法名保持一致

class DevSerializer(serializers.ModelSerializer):
    dev_user_name = serializers.ReadOnlyField()

    class Meta:
        model = Dev
        fields = '__all__'

http://www.cnblogs.com/pgxpython/articles/10144398.html

 

在serializer層面也可以做到同樣的事情:

class DevSerializer(serializers.ModelSerializer):
    dev_user_name = serializers.ReadOnlyField()
    is_superuser = serializers.SerializerMethodField()

    class Meta:
        model = Dev
        fields = '__all__'

    @staticmethod
    def get_is_superuser(obj):
        return obj.dev_user.is_superuser

0X06 rest_framework中的權限管理

1、向視圖添加所需權限:

from rest_framework import permissions

class DeViewset(viewsets.ModelViewSet):
    """
    ModelViewset繼承了四個混類和一個泛類,自動會實現增刪查改的方法
    viewset相當於是視圖的集合,綜合了多個方法
    """
    queryset = Dev.objects.all().order_by('id')
    serializer_class = DevSerializer
    lookup_field = 'id'  # 定義通過哪個參數來定位實例
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    #  沒有認證的情況下只有只讀權限

可以通過邏輯符號組合多個permission類,前提是這些類都繼承於BasePermission

# -*- coding: utf-8 -*-
# @Time    : 2019/6/13 10:44
# @Author  : Zcs
# @File    : permissions.py
from rest_framework.permissions import BasePermission


# class UserReadAdminWrite(BasePermission):
#     """
#     普通用戶擁有隻讀權限,admin用戶擁有讀寫權限
#     """
#     def has_permission(self, request, view):
#         if request.user and request.user.is_staff:
#             return True
#         elif request.user and request.user.is_authenticated and request.method in ['GET']:
#             return True

# 三權分立,系統管理員,安全審計員,安全管理員
class SysAdminPermission(BasePermission):

    def has_permission(self, request, view):
        if request.user.is_authenticated and request.user.user_type == 1:
            return True

class AudAdminPermission(BasePermission):

    def has_permission(self, request, view):
        if request.user.is_authenticated and request.user.user_type == 2:
            return True

class SecAdminPermission(BasePermission):

    def has_permission(self, request, view):
        if request.user.is_authenticated and request.user.user_type == 3:
            return True

class SupAdminPermission(BasePermission):

    def has_permission(self, request, view):
        if request.user.is_authenticated and request.user.user_type == 4:
            return True
permission_classes = (SecAdminPermission | AudAdminPermission,)

0X07 serializer中的幾個fields屬性

valid_kwargs = {
                'read_only', 'write_only',
                'required', 'default', 'initial', 'source',
                'label', 'help_text', 'style',
                'error_messages', 'validators', 'allow_null', 'allow_blank',
                'choices'
            }

read_only_fields = ['is_use', 'uniq_id', 'max_']

1.設置了read_only_fields,那麼用戶無法從該接口修改該字段,提交了該字段不會報錯,但是不會修改該字段的值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章