如何更優雅地寫Django REST framework

DRF(Django REST framework)是一個高度封裝的框架,這致使想完成一件事情能夠經過重寫父類函數的方式從DRF的各個層次來寫,都可以實現目的。前端

好比寫視圖函數,能夠用繼承APIView的方式或者繼承Viewsets的方式,甚至直接寫視圖函數python

可是想要更加乾淨簡潔的代碼,仍是須要找到實現的最佳方式數據庫

如下是個人一些我的總結,歡迎討論django

models.py

1.PositiveSmallIntegerField後端

  • Positive對應unsigned
  • Small對應smallint(5)
  • 對於一些數據量較小的系統可使用這個Field做爲id

二、字段定義中verbose_name定義的是django自帶接口ui的字段說明,help_text定義的是swagger的字段說明數組

三、tag = models.ForeignKey(Tag, related_name="project_tag")框架

  • 定義一個外鍵會在數據庫中生成一個名爲tag_id的字段
  • 可是在模型實例中,tag是Tag模型的實例
  • 也就是說,Django的ORM會把tag.id=tag_id的Tag模型實例取出來放到tag字段

四、user = models.ForeignKey(User, unique=True)函數

  • 至關於user = models.OneToOneField(User)
  • 外鍵 on_delete = models.CASCADE 級聯刪除是默認的選項

五、ImageField和FileField其實是CharFieldsui

serializers.py

一、serializers中對字段作出的限制只會影響前端傳到後端的數據,而不會影響後端傳到前端的數據rest

例如

class MySerializer(serializers.ModelSerializer):
    TYPE = (
        # (0, "級別一"), #在model中這行沒有註釋掉
        (1, "級別二"),
        (2, "級別三")
    )
    # 這樣能夠限制前端不能傳my_type=0的數據,可是my_type=0的數據能夠在前端接收到
    my_type = ChoiceField(choices=TYPE,required=True)

二、通常來講,update和create的操做都會在serializers中實現

  • 不少剛開始接觸DRF的同窗會習慣在view中寫update和create,其實,在serializers中實現是一種更好的方法,
    由於,這樣你的代碼不用繞來繞去。不用費勁獲取serializer的值再費勁存到serializer裏,直接在serializer中實現就好了。
    別看create和update函數的源碼那麼長,其實不用管它們,整個重寫就行了

  • update與create函數框架
def create(self, validated_data):
    ...
    return instance
    
def update(self, instance, validated_data):
    ...
    return instance

validated_data是通過驗證的前端數據,instance是用id獲取的對應數據庫數據的模型實例
它們都要返回一個模型實例,做爲返回前端的數據

  • update和create方法由serializer.save()函數調用
  • 在serializer中self.context["request"]至關於view中的self.request
  • 在Model.objects的建立或篩選中,能夠直接拿一個模型實例賦值給外鍵字段或相比較,好比
Model1.objects.create(user=self.context["request"].user,
                       foreign_key=foreign_key_instance)
  • 若是要用一個已有的模型實例的數據建立一條新數據,我曾用過一個不優雅的寫法
for key in update_data:
    setattr(instance, key, update_data[key])
# 把對象轉爲字典,做爲新建數據的參數
dic = instance.__dict__
del dic['id']
del dic['_state']
new_instance = Model1.objects.create(**dic)

先把更新後的實例對象轉爲字典,再刪掉id等在數據表插入新數據時不應傳的數據,再將字典做爲objects.create的參數

其實有一個更巧妙的方法

instance.id = None
for attr, value in update_data.items():
    setattr(instance, attr, value)
instance.save()

instance.save()以後,instance將會變成新插入數據的模型實例

  • 某些狀況須要父類函數的寫法,不須要複製代碼,用super就能夠了
super(Model1Serializer, self).update(instance, validated_data)

views.py

一、perform_create中的serializer.save()語句能夠帶參數,好比

user_id = self.request.user.id
serializer.save(user=User.objects.get(id=user_id))

實現從request中獲取user的值,而不是從表單

二、儘可能使用objects.filter而不是get

  • filter返回一個數組,get返回一個數據庫實例
  • 若是get()中的過濾條件沒有匹配出數據,get().delete()會報錯,filter則會取出一個空數組,不會報錯

三、過濾器的使用

  • 應避免在get_queryset()中使用複雜的邏輯,好比
def get_queryset(self):
    key_1 = self.request.key1
    key_2 = self.request.key2
    my_type = self.request.query_params.get('type', None)
    if my_type == 1:
        return Model.objects.filter(foreign_key_1=key_1)
    elif my_type == 2:
        return Model.objects.filter(foreign_key_2=key_2)
    # 默認狀況,返回全部
    return Model.objects.all()

其實這就是一個根據查詢參數過濾的過程,徹底可使用過濾器實現,這樣在Django自帶ui中也會有過濾器的說明

  • 要使用過濾器,首先安裝庫
    pip install django-filter

python2要特別指定django-filter==1.1

  • 而後在settings的INSTALLED_APPS中加上django_filters
  • 新建一個名爲filters.py的文件,定義一個過濾器
class MyFilter(django_filters.rest_framework.FilterSet):
    MY_TYPE = (
        (1, "類別一"),
        (2, "類別二")
    )
    
    type = django_filters.ChoiceFilter(help_text="類型",
                                       label="類型",
                                       choices=MY_TYPE,
                                       method="type_filter"
                                       )

    def type_filter(self,queryset,name,value):
        key_1 = self.request.key1
        key_2 = self.request.key2
        if value == 1:
            return queryset.filter(foreign_key_1=key_1)
        elif value == 2:
            return queryset.filter(foreign_key_2=key_2)

    class Meta:
        model = Tag
        fields = ['type']
  • 在viewset中加上
filter_backends = (DjangoFilterBackend, )
filter_class = MyFilter

而get_queryset函數只須要一句return Model.objects.all()就好

注意type_filter的queryset就是get_queryset所返回的

相關文章
相關標籤/搜索