新建项目

初始化数据库驱动配置

__init_.py:

import pymysql

pymysql.install_as_MySQLdb()

settings

ALLOWED_HOSTS = ['*']


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'rest_framework',
    'django.contrib.staticfiles',
    'App',
]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'RESTEND',
        'USER': 'root',
        'PASSWORD':'3825447403',
        'HOST':'localhost',
        'PORT':3306
    }
}

CACHES = {
    'default': {
        # 指定通过django-redis接入Redis服务
        'BACKEND': 'django_redis.cache.RedisCache',
        # Redis服务器的URL
        'LOCATION': ['redis://127.0.0.1:6379/1', ],
        # Redis中键的前缀(解决命名冲突)
        'TIMEOUT': 60*60*24,
        # 其他的配置选项
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            # 连接池(预置若干备用的Redis连接)参数
            'CONNECTION_POOL_KWARGS': {
                # 最大连接数
                'max_connections': 512,
            },
        }
    },
}

创建数据库RESTEND

级联需求

  • 存在级联数据
  • 用户和收获地址
  • 请求节流

功能分析

  • 数据开始

    • 模型定义
    • 用户和收获地址 一对多

      • 用户表
      • 地址表

        • ForeignKey
    • 级联数据如何实现序列化
  • 节流

实现用户注册

新建models

from django.db import models

class UserModel(models.Model):
    
    u_name = models.CharField(max_length=16,unique=True)
    u_password = models.CharField(max_length=256)
    
class Address(models.Model):
    a_address = models.CharField(max_length=128)
    a_user = models.ForeignKey(UserModel)

迁移数据库

新建serializers

serializers.py:

from rest_framework import serializers

from App.models import UserModel, Address


class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = UserModel
        fields = ['url', 'u_name','u_password']
        

class AddressSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Address
        fields = ['url','id','a_address']

视图函数

views:

from django.shortcuts import render

import uuid

from django.contrib import admin
from django.core.cache import cache
from rest_framework import exceptions
from rest_framework.generics import CreateAPIView, RetrieveAPIView
from rest_framework.response import Response

from App.models import UserModel
from RESTend.serializers import UserSerializer


class UsersAPIView(CreateAPIView):
    serializer_class = UserSerializer

    query = UserModel.objects.all()

    def post(self, request, *args, **kwargs):

        action = request.query_params.get('action')

        if action == 'login':
            u_name = request.data.get('u_name')
            u_password = request.data.get('u_password')
            try:
                user = UserModel.objects.get(u_name=u_name)

                if user.u_password != u_password:
                    raise exceptions.AuthenticationFailed
                token = uuid.uuid4().hex

                cache.set(token, user.id, timeout=60 * 60)

                data = {
                    'msg': 'login success',
                    'status': 200,
                    'token': token,
                }

                return Response(data)
            except UserModel.DoesNotExist:
                raise exceptions.NotFound

        elif action == 'register':
            return self.create(request, *args, **kwargs)
        else:
            raise exceptions.ParseError

class UserAPIView(RetrieveAPIView):
    serializer_class = UserSerializer

    query = UserModel.objects.all()

新建URLS

新建urls.py:

from django.urls import re_path

from App import views

urlpatterns = {
    re_path(r'users/$', views.UsersAPIView.as_view()),
    re_path(r'users/(?P<pk>\d+)/', views.UserAPIView.as_view(),name = 'usermodel-detail'),
}

总urls注册:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app/', include('App.urls')),
]

测试创建用户


实现地址模块

功能分析1

  • 地址添加、地址删除、地址修改
  • 使用viewsets.ModelViewSet作为继承的视图类
  • viewsets.ModelViewSet 的路由也可以使用as_view来处理,但是as_view是重写过的,所以需要额外指定请求类型和对应的方法。

    这里先设置models.py中的address模型中的a_user为空,之后级联的时候再删除这个参数,否侧单独测试不了address模块功能。

    class Address(models.Model):
      a_address = models.CharField(max_length=128)
      a_user = models.ForeignKey(UserModel, on_delete=models.CASCADE,null=True)

    实现1

urls

urlpatterns = [
    re_path(r'users/$', views.UsersAPIView.as_view()),
    re_path(r'users/(?P<pk>\d+)/', views.UserAPIView.as_view(),name='usermodel-detail'),
    
    re_path(r'address/$', views.AddressAPIView.as_view(
        {
            'post': 'create',
        }
    )),
    re_path(r'address/(?P<pk>\d+)/$', views.AddressAPIView.as_view(
        {
            'get':'retireve',
        }
    ), name='address-detail'),
]

views

class AddressAPIView(viewsets.ModelViewSet):

    serializer_class = AddressSerializer
    queryset = Address.objects.all()

测试



单独添加地址且查询成功

功能分析2

  • 在添加地址时,同时绑定提交地址的用户数据。
  • 只有已登录用户可以添加地址,未登录的人不可以添加地址,所以需要认证权限

    实现2

    新建auth认证器

    新建auth.py:

    class LoginAuthentication(BaseAuthentication):
      def authenticate(self, request):
          # get,post方法需要认证
          if request.method == "GET":
              try:
                  token = request.query_params.get('token')
                  user_id = cache.get(token)
                  user = UserModel.objects.get(pk = user_id)
                  return user,token
              except Exception:
                  return 

    将认证器添加入视图函数中
    views.py:

    class AddressAPIView(viewsets.ModelViewSet):
    
      serializer_class = AddressSerializer
      queryset = Address.objects.all()
    
      authentication_classes = (LoginAuthentication, )

    新建permissions权限器

    from rest_framework.permissions import BasePermission
    
    from App.models import UserModel
    
    
    class RequireLoginPermission(BasePermission):
      def has_permission(self, request, view):
          # 是否有权限访问,就是判断是否登录
          return isinstance(request.user, UserModel) #如果是UserModel的实例就有权限

views

class AddressAPIView(viewsets.ModelViewSet):

    serializer_class = AddressSerializer
    queryset = Address.objects.all()

    authentication_classes = (LoginAuthentication, )

    permission_classes = (RequireLoginPermission,)

测试添加地址


只有携带token才允许添加路径,但是此时保存的地址还没有存入对应的用户

功能分析3

在存入地址时,同时存入对应的用户id

实现3

在创建的存储过程中,修改存储内容,把对应用户添加进去,然后存储到数据库。

也就是重写CreateModelMinxin中的create方法

class AddressAPIView(viewsets.ModelViewSet):

    serializer_class = AddressSerializer
    queryset = Address.objects.all()

    authentication_classes = (LoginAuthentication, )

    permission_classes = (RequireLoginPermission,)

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        
        # 拿到用户,地址并修改
        user = request.user
        a_id = serializer.data.get('id')
        address = Address.objects.get(pk=a_id)
        address.a_user = user
        address.save()


        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

测试



携带token提交post请求创建地址



数据库可以观察到绑定了对应的用户id。

实现4

  • 让用户只能查询到自己的地址 ,而不是使用get请求address时查询到所有人地址

首先在urls中允许对address使用get请求。

    re_path(r'address/$', views.AddressAPIView.as_view(
        {
            'post': 'create',
            'get':'list',
        }
    )),

要实现该功能就是重写get请求时进行筛选的步骤,也就是重写
ModelViewSet继承的ListModelMixin中的list方法。

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.queryset.filter(a_user=request.user)) # 这里根据用户筛选地址

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

测试




可以看到用户只能查询到自己绑定的地址。

实现5

  • 当前如果知道用户id可以直接使用get直接访问获取到所有用户信息,所以get请求也需要进行登录认证
  • 当获取到自己用户的token进行get地址时,get其他用户地址时也应该被禁止,所以还需要一个新的认证规则,在get时判断token对应的用户和访问对应的用户是否匹配。

views:

class UserAPIView(RetrieveAPIView):
    serializer_class = UserSerializer

    query = UserModel.objects.all()

    authentication_classes = (LoginAuthentication,)

    permission_classes = (RequireLoginPermission,)

    # 判断tokenid和用户id是一致的,重写RetrieveModelMixin中的retrieve
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        # 如果tokenid和对应用户id不匹配
        if instance.id !=request.user.id:
            raise exceptions.AuthenticationFailed
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

测试

用1号用户的token 来 get 4号用户的信息


不匹配,所以访问失败。

接着访问1号用户:

现在不从instance查询id,而是直接从kwags中获取id
可以修改views.py中的retiteve:

 # 判断tokenid和用户id是一致的,重写RetrieveModelMixin中的retrieve
    def retrieve(self, request, *args, **kwargs):
        if kwargs.get('pk') != str(request.user.id):
            raise exceptions.AuthenticationFailed
        instance = self.get_object()
       # # 如果tokenid和对应用户id不匹配
       #  if instance.id != request.user.id:
       #      raise exceptions.AuthenticationFailed
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

级联查询

功能分析

在get用户信息时,将对应的地址也一并返回。

使用序列化关系中的Nested relationships(嵌套关系):https://q1mi.github.io/Django-REST-framework-documentation/api-guide/relations/#nested-relationships

在序列化器中添加address_set:
serializers.py:

from rest_framework import serializers

from App.models import UserModel, Address

class AddressSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Address
        fields = ['url', 'id', 'a_address']


class UserSerializer(serializers.HyperlinkedModelSerializer):
    # 调用AddressSerializer,实现嵌套序列化
    # 使用AddressSerializer的隐形属性address_set
    address_set = AddressSerializer(many=True,read_only=True)

    class Meta:
        model = UserModel
        fields = ['url', 'u_name', 'u_password', 'address_set']

14行添加address_set
models.py:

from django.db import models

class UserModel(models.Model):

    u_name = models.CharField(max_length=16,unique=True)
    u_password = models.CharField(max_length=256)

class Address(models.Model):
    a_address = models.CharField(max_length=128)
    a_user = models.ForeignKey(UserModel, on_delete=models.CASCADE,null=True,blank=True)

因为models中Address的a_user使用了ForeignKey所以UserModel会生成隐形属性address_set

如果我们将address_set修改成其他变量名,需要将新的变量名在models.py中注册。

class Address(models.Model):
    a_address = models.CharField(max_length=128)
    a_user = models.ForeignKey(UserModel, related_name='address_list',on_delete=models.CASCADE,null=True,blank=True)

a_user中添加related_name来命令。
serializers.py:

class UserSerializer(serializers.HyperlinkedModelSerializer):
    # 调用AddressSerializer,实现嵌套序列化
    # 使用AddressSerializer的隐形属性address_set
    address_list = AddressSerializer(many=True,read_only=True)

    class Meta:
        model = UserModel
        fields = ['url', 'u_name', 'u_password', 'address_list']

这样就可以使用address_list变量名来实现嵌套查询

测试:


这样就实现了级联查询。

最后修改:2024 年 03 月 13 日
如果觉得我的文章对你有用,请随意赞赏