Django ORM restframework学习记录

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,那么用户无法从该接口修改该字段,提交了该字段不会报错,但是不会修改该字段的值。

0X08 踩坑记录

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相同的序列化器。

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被引用的次数。


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:只能上传键值对,并且键值对都是间隔分开的。

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

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

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

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)
 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值