如何更優雅地寫Django REST framework


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

比如寫視圖函數,可以用繼承APIView的方式或者繼承Viewsets的方式,甚至直接寫視圖函數

但是想要更加干凈簡潔的代碼,還是需要找到實現的最佳方式

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

models.py

1.PositiveSmallIntegerField

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

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

3、tag = models.ForeignKey(Tag, related_name="project_tag")

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

4、user = models.ForeignKey(User, unique=True)

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

5、ImageField和FileField實際上是CharFields

serializers.py

1、serializers中對字段做出的限制只會影響前端傳到后端的數據,而不會影響后端傳到前端的數據

例如

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

2、一般來說,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

1、perform_create中的serializer.save()語句可以帶參數,比如

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

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

2、盡量使用objects.filter而不是get

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

3、過濾器的使用

  • 應避免在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所返回的


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM