1.6.2 drf实战案例2-详解版
1.6.2 drf实战案例2-详解版
项目准备:
路由分发:
"""dig URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.2/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path, include urlpatterns = [ path('api/', include('api.urls')), ]
app路由:
from django.urls import path from rest_framework import routers from api.views import account, topic, news, collect, recommend, comment # urlpatterns = [ # # /api/register/ # path('register/', account.RegisterView.as_view({"post": "create"})), # ] # router = routers.SimpleRouter() # # # /api/register/ # # x-list # # x-create # router.register(r'register', account.RegisterView) # # urlpatterns = [ # # ] # urlpatterns += router.urls router = routers.SimpleRouter() router.register(r'register', account.RegisterView, 'register') # 创建话题(认证) router.register(r'topic', topic.TopicView) # 我的资讯 router.register(r'news', news.NewsView) # 资讯首页 router.register(r'index', news.IndexView) # 收藏 router.register(r'collect', collect.CollectView) # 推荐 router.register(r'recommend', recommend.RecommendView) # 评论 router.register(r'comment', comment.CommentView) urlpatterns = [ # path('register/', account.RegisterView.as_view({"post": "create"})), path('auth/', account.AuthView.as_view()), ] urlpatterns += router.urls
全局配置
""" Django settings for dig project. Generated by 'django-admin startproject' using Django 3.2.6. For more information on this file, see https://docs.djangoproject.com/en/3.2/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.2/ref/settings/ """ import os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'django-insecure-xi3i8jw=!smfwo%pg$$w1y5a^#wqk)&tc_!oc8_7^jqtf#4r@w' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'django_filters', 'api.apps.ApiConfig' ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'dig.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'dig.wsgi.application' # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Password validation # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/3.2/topics/i18n/ # LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'zh-hans' # datetime.datetime.now() / datetime.datetime.utcnow() => utc时间 # TIME_ZONE = 'UTC' # datetime.datetime.now() - 东八区时间 / datetime.datetime.utcnow() => utc时间 TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True # 影响自动生成数据库时间字段; # USE_TZ = True,创建UTC时间写入到数据库。 # USE_TZ = False,根据TIME_ZONE设置的时区进行创建时间并写入数据库 USE_TZ = False # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.2/howto/static-files/ STATIC_URL = '/static/' # Default primary key field type # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' REST_FRAMEWORK = { # 版本配置 "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning", "DEFAULT_VERSION": "v1", "ALLOWED_VERSIONS": ["v1"], "VERSION_PARAM": "version", # # 认证配置 "DEFAULT_AUTHENTICATION_CLASSES": ["api.extension.auth.TokenAuthentication", ], "UNAUTHENTICATED_USER": lambda: None, "UNAUTHENTICATED_TOKEN": lambda: None, # # 分页配置 "DEFAULT_PAGINATION_CLASS": "api.extension.page.DigLimitOffsetPagination" } CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "PASSWORD": "qwe123", } } }
数据模型 models.py
class Meta:
1 抽象基类 abstract = True # 在数据库不会生成表,只是为其他类提供继承的基类
2 联合索引 indexes = [models.Index(fields=['username', "password"], name='idx_name_pwd')] # 联合索引
3 联合唯一索引(约束) constraints = [ models.UniqueConstraint(fields=['news', 'user'], name='uni_collect_news_user')] 详情查看:https://docs.djangoproject.com/en/3.2/ref/models/options/#index-together
from django.db import models class DeletedModel(models.Model): deleted = models.BooleanField(verbose_name="已删除", default=False) class Meta: # 在数据库不会生成表,只是为其他类提供继承的基类 abstract = True class UserInfo(DeletedModel): """ 用户表 """ username = models.CharField(verbose_name="用户名", max_length=32) phone = models.CharField(verbose_name="手机号", max_length=32, db_index=True) password = models.CharField(verbose_name="密码", max_length=64) token = models.CharField(verbose_name="token", max_length=64, null=True, blank=True, db_index=True) token_expiry_date = models.DateTimeField(verbose_name="token有效期", null=True, blank=True) status_choice = ( (1, "激活"), (2, "禁用"), ) status = models.IntegerField(verbose_name="状态", choices=status_choice, default=1) create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) class Meta: # The newer indexes option provides more functionality than index_together # index_together may be deprecated in the future. # https://docs.djangoproject.com/en/3.2/ref/models/options/#index-together # 联合索引 indexes = [ models.Index(fields=['username', "password"], name='idx_name_pwd') ] class Topic(DeletedModel): """ 话题 """ title = models.CharField(verbose_name="话题", max_length=16, db_index=True) is_hot = models.BooleanField(verbose_name="热门话题", default=False) user = models.ForeignKey(verbose_name="用户", to="UserInfo", on_delete=models.CASCADE) create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) class News(DeletedModel): """ 新闻资讯 """ zone_choices = ((1, "42区"), (2, "段子"), (3, "图片"), (4, "挨踢1024"), (5, "你问我答")) zone = models.IntegerField(verbose_name="专区", choices=zone_choices) title = models.CharField(verbose_name="文字", max_length=150) url = models.CharField(verbose_name="链接", max_length=200, null=True, blank=True) # xxxxx?xxxxxx.png,xxxxxxxx.jeg image = models.TextField(verbose_name="图片地址", help_text="逗号分割", null=True, blank=True) topic = models.ForeignKey(verbose_name="话题", to="Topic", on_delete=models.CASCADE, null=True, blank=True) user = models.ForeignKey(verbose_name="用户", to="UserInfo", on_delete=models.CASCADE) create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) status_choice = ( (1, "待审核"), (2, "已通过"), (3, "未通过"), ) status = models.IntegerField(verbose_name="状态", choices=status_choice, default=1) collect_count = models.IntegerField(verbose_name="收藏数", default=0) recommend_count = models.IntegerField(verbose_name="推荐数", default=0) comment_count = models.IntegerField(verbose_name="评论数", default=0) class Collect(models.Model): """ 收藏 """ news = models.ForeignKey(verbose_name="资讯", to="News", on_delete=models.CASCADE) user = models.ForeignKey(verbose_name="用户", to="UserInfo", on_delete=models.CASCADE) create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) class Meta: # unique_together = [['news', 'user']] 联合唯一索引 constraints = [ models.UniqueConstraint(fields=['news', 'user'], name='uni_collect_news_user') ] class Recommend(models.Model): """ 推荐 """ news = models.ForeignKey(verbose_name="资讯", to="News", on_delete=models.CASCADE) user = models.ForeignKey(verbose_name="用户", to="UserInfo", on_delete=models.CASCADE) create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) class Meta: constraints = [ models.UniqueConstraint(fields=['news', 'user'], name='uni_recommend_news_user') ] class Comment(models.Model): """ 评论表 """ news = models.ForeignKey(verbose_name="资讯", to="News", on_delete=models.CASCADE) user = models.ForeignKey(verbose_name="用户", to="UserInfo", on_delete=models.CASCADE) content = models.CharField(verbose_name="内容", max_length=150) depth = models.IntegerField(verbose_name="深度", default=0) root = models.ForeignKey(verbose_name="根评论", to="Comment", related_name="descendant", on_delete=models.CASCADE, null=True, blank=True) reply = models.ForeignKey(verbose_name="回复", to="Comment", related_name="reply_list", on_delete=models.CASCADE, null=True, blank=True) create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) # 针对根评论(更新时间) descendant_update_datetime = models.DateTimeField(verbose_name="后代更新时间", auto_now_add=True)
extension
import datetime from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from api.extension import return_code from api import models # 必须认证成功之后才能访问 class TokenAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get("token") if not token: raise AuthenticationFailed({"code": return_code.AUTH_FAILED, "error": "认证失败"}) user_object = models.UserInfo.objects.filter(token=token).first() if not user_object: raise AuthenticationFailed({"code": return_code.AUTH_FAILED, "error": "认证失败"}) if datetime.datetime.now() > user_object.token_expiry_date: raise AuthenticationFailed({"code": return_code.AUTH_OVERDUE, "error": "认证过期"}) return user_object, token def authenticate_header(self, request): return 'Bearer realm="API"' # 登录,可以访问 request.user # 不登录,也可以访问 request.user=None class UserAnonTokenAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get("token") if not token: return None user_object = models.UserInfo.objects.filter(token=token).first() if not user_object: return None if datetime.datetime.now() > user_object.token_expiry_date: return None return user_object, token def authenticate_header(self, request): return 'Bearer realm="API"'
from rest_framework.filters import BaseFilterBackend class SelfFilterBackend(BaseFilterBackend): def filter_queryset(self, request, queryset, view): return queryset.filter(user=request.user)
from rest_framework import mixins from rest_framework.response import Response from api.extension import return_code class DigCreateModelMixin(mixins.CreateModelMixin): def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) # 1. 异常处理 if not serializer.is_valid(): return Response({"code": return_code.VALIDATE_ERROR, 'detail': serializer.errors}) # 2. 优化perform_create res = self.perform_create(serializer) # 3. 返回数据的处理 return res or Response({"code": return_code.SUCCESS, 'data': serializer.data}) class DigListModelMixin(mixins.ListModelMixin): def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return Response({"code": return_code.SUCCESS, 'data': serializer.data}) serializer = self.get_serializer(queryset, many=True) return Response({"code": return_code.SUCCESS, 'data': serializer.data}) class DigDestroyModelMixin(mixins.DestroyModelMixin): def destroy(self, request, *args, **kwargs): instance = self.get_object() res = self.perform_destroy(instance) return res or Response({"code": return_code.SUCCESS}) class DigUpdateModelMixin(mixins.UpdateModelMixin): def destroy(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_serializer(instance, data=request.data, partial=partial) if not serializer.is_valid(): return Response({"code": return_code.VALIDATE_ERROR, 'detail': serializer.errors}) res = self.perform_update(serializer) return res or Response({"code": return_code.SUCCESS, 'data': serializer.data})
from rest_framework.pagination import LimitOffsetPagination class DigLimitOffsetPagination(LimitOffsetPagination): # xxxxxx?limit=10 # 下拉加载更多应用,不用offset,结合过滤 default_limit = 10 max_limit = 100 offset_query_param = None
# 成功 SUCCESS = 0 # 用户提交数据校验失败 VALIDATE_ERROR = 1001 # 认证失败 AUTH_FAILED = 2000 # 认证过期 AUTH_OVERDUE = 2001 # 无权访问 PERMISSION_DENIED = 3000 # 无权访问 TOO_MANY_REQUESTS = 4000
from rest_framework import status from rest_framework.throttling import SimpleRateThrottle from django.core.cache import cache as default_cache from rest_framework import exceptions from api.extension import return_code class ThrottledException(exceptions.APIException): status_code = status.HTTP_429_TOO_MANY_REQUESTS default_code = 'throttled' class NewsCreateRateThrottle(SimpleRateThrottle): cache = default_cache # 访问记录存放在django的缓存中(需设置缓存) scope = "user" # 构造缓存中的key cache_format = 'throttle_%(scope)s_%(ident)s' # 设置访问频率,例如:1/5m THROTTLE_RATES = {"user": "1/5m"} def parse_rate(self, rate): if rate is None: return (None, None) num, period = rate.split('/') # "1/5m" num_requests = int(num) duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[-1]] count = int(period[0:-1]) return (num_requests, duration * count) def get_cache_key(self, request, view): ident = request.user.pk return self.cache_format % {'scope': self.scope, 'ident': ident} def throttle_failure(self): wait = self.wait() detail = { "code": return_code.TOO_MANY_REQUESTS, "data": "访问频率限制", 'detail': "需等待{}秒后才能访问".format(int(wait)) } raise ThrottledException(detail) def throttle_success(self): # self.history.insert(0, self.now) # self.cache.set(self.key, self.history, self.duration) return True def done(self): """ 数据库中创建成功后,再来调用这个方法""" self.history.insert(0, self.now) self.cache.set(self.key, self.history, self.duration)
serializers
from rest_framework import serializers from rest_framework.exceptions import ValidationError from api import models class RegisterSerializer(serializers.ModelSerializer): confirm_password = serializers.CharField(label="确认密码", min_length=8, write_only=True) password = serializers.CharField(label="密码", min_length=8, write_only=True) class Meta: model = models.UserInfo fields = ['username', "phone", "password", "confirm_password"] def validate_username(self, value): exists = models.UserInfo.objects.filter(username=value, deleted=False).exists() if exists: raise ValidationError("用户名已存在") return value def validate_phone(self, value): exists = models.UserInfo.objects.filter(phone=value, deleted=False).exists() if exists: raise ValidationError("手机号已存在") return value def validate_confirm_password(self, value): password = self.initial_data.get('password') if password == value: return value raise ValidationError("两次密码不一致") class AuthSerializer(serializers.Serializer): username = serializers.CharField(label="用户名", write_only=True, required=False) # 不提交 phone = serializers.CharField(label="手机", write_only=True, required=False) # 不提交 password = serializers.CharField(label="密码", min_length=8, write_only=True) def validate_username(self, value): username = self.initial_data.get("username") phone = self.initial_data.get("phone") if not username and not phone: raise ValidationError("用户名或手机为空") if username and phone: raise ValidationError("提交数据异常") return value
from rest_framework import serializers from rest_framework.exceptions import ValidationError from api import models class CollectSubNewsSerializer(serializers.ModelSerializer): image_list = serializers.SerializerMethodField(read_only=True) class Meta: model = models.News fields = ['id', 'title', 'url', "image_list"] def get_image_list(self, obj): if not obj.image: return [] return obj.image.split(',') class CollectSerializer(serializers.ModelSerializer): news_info = CollectSubNewsSerializer(read_only=True, source="news") # news=2 class Meta: model = models.Collect fields = ['id', "news", "news_info"] extra_kwargs = {'news': {'write_only': True}} def validate_news(self, value): if value.deleted: raise ValidationError("资讯不存在") return value
from rest_framework import serializers from api import models class CreateCommentSerializer(serializers.ModelSerializer): create_datetime = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", read_only=True) # 'news', "reply", "content" # 根评论:news、content,reply=null,depth=0,root=null # 子评论:news、content,reply=回复的评论ID,depth=回复的评论深度+1,root=读回复的评论root=null或depth=0 ==读回复的评论;===读回复的评论.root # descendant_update_datetime根评论最近的更新时间; class Meta: model = models.Comment fields = ['news', "reply", "content", 'depth', "create_datetime"] read_only_fields = ['depth', ] extra_kwargs = {'news': {'write_only': True}} class ListCommentSerializer(serializers.ModelSerializer): create_datetime = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S") children = serializers.SerializerMethodField() class Meta: model = models.Comment fields = ['create_datetime', "reply", "content", 'children'] def get_children(self, obj): # 获取当前根评论的所有的子孙评论(后台) descendant_queryset = models.Comment.objects.filter(root=obj).order_by('id') descendant_dict = {} """ { 11:{"reply": 2, children:[ 13->{,"reply": 11, children:[ 15:{"reply": 13, children:[ 16:{"reply": 15, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 4, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 3, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 2, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 1, "create_datetime": "2021-09-01 22:32:22"} 12:{"reply": 2, children:[ 14->{"reply": 12, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 2, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 1, "create_datetime": "2021-09-01 22:32:22"} 13:{"reply": 11, children:[ 15:{"reply": 13, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 3, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 2, "create_datetime": "2021-09-01 22:32:22"} 14:{"reply": 12, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 2, "create_datetime": "2021-09-01 22:32:22"} 15:{"reply": 13, children:[ 16:{"reply": 15, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 4, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 3, "create_datetime": "2021-09-01 22:32:22"} 16:{"reply": 15, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 4, "create_datetime": "2021-09-01 22:32:22"} } """ for descendant in descendant_queryset: ser = CreateCommentSerializer(instance=descendant, many=False) row = {'children': []} row.update(ser.data) descendant_dict[descendant.id] = row # 根评论obj的1级评论 children_list = [ # # 11 # {"reply": 2, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 1, "create_datetime": "2021-09-01 22:32:22"}, # # 12 # {"reply": 2, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 1, "create_datetime": "2021-09-01 22:32:22"} ] for cid, item in descendant_dict.items(): depth = item['depth'] if depth == 1: children_list.append(item) continue reply_id = item['reply'] descendant_dict[reply_id]['children'].append(item) return children_list
from rest_framework import serializers from rest_framework.exceptions import ValidationError from api import models class NewsSerializer(serializers.ModelSerializer): image_list = serializers.SerializerMethodField(read_only=True) topic_title = serializers.CharField(source="topic.title", read_only=True) zone_title = serializers.CharField(source="get_zone_display", read_only=True) status = serializers.CharField(source="get_status_display", read_only=True) # title、url、image、'topic', "zone" # - 只有title,只创建文本 + 分区不能是图片 # - 有title,image, # - 有title,url class Meta: model = models.News fields = ['id', "title", "url", 'image', 'topic', "zone", "zone_title", 'image_list', "topic_title", 'collect_count', 'recommend_count', 'comment_count', "status"] read_only_fields = ['collect_count', 'recommend_count', 'comment_count'] extra_kwargs = { 'topic': {'write_only': True}, # 新增时,topic=1 'image': {'write_only': True}, # 图片地址 xxxx,xxxx,xxxx 'zone': {'write_only': True}, } def get_image_list(self, obj): if not obj.image: return [] return obj.image.split(',') def validate_topic(self, value): if not value: return value request = self.context['request'] exists = models.Topic.objects.filter(deleted=False, id=value.id, user=request.user).exists() if not exists: raise ValidationError("话题不存在") return value def validate_title(self, value): url = self.initial_data.get('url') image = self.initial_data.get('image') zone = self.initial_data.get('zone') if url and image: raise ValidationError("请求数据错误") if not url and not image: if zone == 3: raise ValidationError("分区选择错误") return value # serializer中获取request def create(self, validated_data): request = self.context["request"] # 1.创建新闻资讯 new_object = models.News.objects.create(recommend_count=1, **validated_data) # 2.推荐记录 models.Recommend.objects.create( news=new_object, user=request.user ) return new_object class IndexSubTopicSerializer(serializers.ModelSerializer): class Meta: model = models.Topic fields = ['id', 'title', 'is_hot'] class IndexSubUserSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = ['id', 'username', ] class IndexSerializer(serializers.ModelSerializer): image_list = serializers.SerializerMethodField() collect = serializers.SerializerMethodField() recommend = serializers.SerializerMethodField() zone = serializers.CharField(source='get_zone_display') topic = IndexSubTopicSerializer(read_only=True) user = IndexSubUserSerializer(read_only=True) class Meta: model = models.News fields = ['id', "title", "url", 'image_list', 'topic', "zone", "user", 'collect', 'recommend', 'comment_count', ] def get_image_list(self, obj): if not obj.image: return [] return obj.image.split(',') def get_collect(self, obj): request = self.context['request'] if not request.user: return {'count': obj.collect_count, 'has_collect': False} exists = models.Collect.objects.filter(user=request.user, news=obj).exists() return {'count': obj.collect_count, 'has_collect': exists} def get_recommend(self, obj): request = self.context['request'] if not request.user: return {'count': obj.recommend_count, 'has_recommend': False} exists = models.Recommend.objects.filter(user=request.user, news=obj).exists() return {'count': obj.recommend_count, 'has_recommend': exists}
from rest_framework import serializers from rest_framework.exceptions import ValidationError from api import models class RecommendSubNewsSerializer(serializers.ModelSerializer): image_list = serializers.SerializerMethodField(read_only=True) class Meta: model = models.News fields = ['id', 'title', 'url', "image_list"] def get_image_list(self, obj): if not obj.image: return [] return obj.image.split(',') class RecommendSerializer(serializers.ModelSerializer): news_info = RecommendSubNewsSerializer(read_only=True, source="news") class Meta: model = models.Recommend fields = ['id', "news", "news_info"] extra_kwargs = {'news': {'write_only': True}}
from rest_framework import serializers from rest_framework.exceptions import ValidationError from api import models class TopicSerializer(serializers.ModelSerializer): class Meta: model = models.Topic fields = ['id', "title", "is_hot"] # read_only_fields = ['is_hot'] extra_kwargs = {'is_hot': {'read_only': True}}
views
import uuid import datetime from django.db.models import Q from django.urls import reverse from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response from api.extension.mixins import DigCreateModelMixin from api.serializers.account import RegisterSerializer, AuthSerializer from api.extension import return_code from api import models from rest_framework.mixins import CreateModelMixin """ 1. 只需要提供POST方法 2. 请求进来执行 DigCreateModelMixin的create方法 3. 获取数据request.data,进行校验(RegisterSerializer) """ class RegisterView(DigCreateModelMixin, GenericViewSet): """ 用户注册 """ authentication_classes = [] permission_classes = [] serializer_class = RegisterSerializer def perform_create(self, serializer): serializer.validated_data.pop('confirm_password') # super().perform_create(serializer) serializer.save() # return XXX #可以自定义返回值 class AuthView(APIView): """ 用户登录 """ authentication_classes = [] permission_classes = [] # 2. 数据库校验用户名和密码的合法性 def post(self, request): # 1. 获取用户请求 & 校验 serializer = AuthSerializer(data=request.data) if not serializer.is_valid(): # { 'username':[错误信息,], 'phone':[xxxx,]} return Response({"code": return_code.VALIDATE_ERROR, 'detail': serializer.errors}) username = serializer.validated_data.get('username') phone = serializer.validated_data.get('phone') password = serializer.validated_data.get('password') user_object = models.UserInfo.objects.filter(Q(Q(username=username) | Q(phone=phone)), password=password).first() if not user_object: return Response({"code": return_code.VALIDATE_ERROR, "error": "用户名或密码错误"}) token = str(uuid.uuid4()) user_object.token = token # 设置token有效期:当前时间 + 2周 user_object.token_expiry_date = datetime.datetime.now() + datetime.timedelta(weeks=2) user_object.save() return Response({"code": return_code.SUCCESS, "data": {"token": token, "name": user_object.username}})
from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response from django_filters import FilterSet, filters from django_filters.rest_framework import DjangoFilterBackend from api import models from api.serializers.collect import CollectSerializer from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigCreateModelMixin, DigListModelMixin from api.extension import return_code class CollectFilterSet(FilterSet): latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt') class Meta: model = models.Collect fields = ["latest_id", ] class CollectView(DigCreateModelMixin, DigListModelMixin, GenericViewSet): """ 收藏接口 """ filter_backends = [SelfFilterBackend, DjangoFilterBackend] filterset_class = CollectFilterSet # 当前登录用户的所有收藏记录 queryset = models.Collect.objects serializer_class = CollectSerializer def perform_create(self, serializer): user = self.request.user instance = models.Collect.objects.filter(user=user, **serializer.validated_data).first() if not instance: instance = serializer.save(user=user) instance.news.collect_count += 1 instance.news.save() return Response({"code": return_code.SUCCESS, 'data': {'active': True}}) else: instance.delete() instance.news.collect_count -= 1 instance.news.save() return Response({"code": return_code.SUCCESS, 'data': {'active': False}})
import datetime from rest_framework.request import Request from rest_framework.viewsets import GenericViewSet from rest_framework.permissions import BasePermission from rest_framework.exceptions import PermissionDenied from django_filters import FilterSet, filters from django_filters.rest_framework import DjangoFilterBackend from api import models from api.serializers.comment import CreateCommentSerializer, ListCommentSerializer from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigCreateModelMixin, DigListModelMixin, DigDestroyModelMixin, DigUpdateModelMixin from api.extension.auth import UserAnonTokenAuthentication, TokenAuthentication from api.extension import return_code class CommentFilterSet(FilterSet): news = filters.NumberFilter(field_name='news', required=True) latest_id = filters.DateTimeFilter(field_name='descendant_update_datetime', lookup_expr='lte') class Meta: model = models.Comment fields = ["latest_id", 'news'] """ 1.先根据后代的更新时间,进行排序,获取根评论 10。 2.这些根评论关联的子评论,并构造父子关系。 """ class CommentView(DigListModelMixin, DigCreateModelMixin, GenericViewSet): """ 评论 """ filter_backends = [DjangoFilterBackend] filterset_class = CommentFilterSet authentication_classes = [TokenAuthentication, ] # ?news=2 -> news=2 # 获取某条新闻资讯的 根评论(根据后代更新时间排序) queryset = models.Comment.objects.filter(depth=0).order_by("-descendant_update_datetime") serializer_class = CreateCommentSerializer def perform_create(self, serializer): reply = serializer.validated_data.get('reply') if not reply: # 如果是根评论 'news', "reply", "content" instance = serializer.save(user=self.request.user) else: # 如果子评论 # 1.获取根评论 if not reply.root: # 给根评论回复 root = reply else: root = reply.root # 创建评论 instance = serializer.save(user=self.request.user, depth=reply.depth + 1, root=root) # 根评论的最新更新时间 root.descendant_update_datetime = datetime.datetime.now() root.save() instance.news.comment_count += 1 instance.news.save() def get_serializer_class(self): if self.request.method == "GET": return ListCommentSerializer return CreateCommentSerializer def get_authenticators(self): if self.request.method == "POST": return super().get_authenticators() return []
from rest_framework.viewsets import GenericViewSet from django_filters import FilterSet, filters from django_filters.rest_framework import DjangoFilterBackend from api import models from api.serializers.news import NewsSerializer, IndexSerializer from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigCreateModelMixin, DigListModelMixin from api.extension.auth import UserAnonTokenAuthentication from api.extension.throttle import NewsCreateRateThrottle class NewsFilterSet(FilterSet): latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt') class Meta: model = models.News fields = ["latest_id", ] class NewsView(DigListModelMixin, DigCreateModelMixin, GenericViewSet): filter_backends = [SelfFilterBackend, DjangoFilterBackend] filterset_class = NewsFilterSet # 下拉加载更多 # 未删除 & 属于当前用户创建的新闻资讯 queryset = models.News.objects.filter(deleted=False).order_by('-id') serializer_class = NewsSerializer # 自定义的类变量 throttle_objects = [NewsCreateRateThrottle(), ] def perform_create(self, serializer): # 1.创建新闻资讯 # 2.自己对自己的内容做推荐 # - 推荐数量+1 # - 推荐记录 用户&资讯 serializer.save(user=self.request.user) # 数据库中已增加成功,调用限流的那个done方法 for throttle in self.get_throttles(): throttle.done() def get_throttles(self): if self.request.method == "POST": return self.throttle_objects return [] class IndexFilterSet(FilterSet): latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt') class Meta: model = models.News fields = ["latest_id", 'zone'] # ?zone=1 # ?latest_id=99&limit=10 class IndexView(DigListModelMixin, GenericViewSet): filter_backends = [DjangoFilterBackend] filterset_class = IndexFilterSet authentication_classes = [UserAnonTokenAuthentication, ] # queryset = models.News.objects.filter(deleted=False, status=2).order_by('-id') queryset = models.News.objects.filter(deleted=False).order_by('-id') serializer_class = IndexSerializer
from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response from django_filters import FilterSet, filters from django_filters.rest_framework import DjangoFilterBackend from api import models from api.serializers.recommend import RecommendSerializer from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigCreateModelMixin, DigListModelMixin from api.extension import return_code class RecommendFilterSet(FilterSet): latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt') class Meta: model = models.Recommend fields = ["latest_id", ] class RecommendView(DigCreateModelMixin, DigListModelMixin, GenericViewSet): """ 推荐接口 """ filter_backends = [SelfFilterBackend, DjangoFilterBackend] filterset_class = RecommendFilterSet queryset = models.Recommend.objects serializer_class = RecommendSerializer def perform_create(self, serializer): user = self.request.user instance = models.Recommend.objects.filter(user=user, **serializer.validated_data).first() if not instance: instance = serializer.save(user=user) instance.news.recommend_count += 1 instance.news.save() return Response({"code": return_code.SUCCESS, 'data': {'active': True}}) else: instance.delete() instance.news.recommend_count -= 1 instance.news.save() return Response({"code": return_code.SUCCESS, 'data': {'active': False}})
from rest_framework.viewsets import GenericViewSet from django_filters import FilterSet, filters from django_filters.rest_framework import DjangoFilterBackend from api import models from api.serializers.topic import TopicSerializer from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigCreateModelMixin, DigListModelMixin, DigDestroyModelMixin, DigUpdateModelMixin class TopicFilterSet(FilterSet): # ?latest_id=99 -> id<99 # ?latest_id=99&limit=10 -> id<99 limit 10 latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt') class Meta: model = models.Topic fields = ["latest_id", ] class TopicView(DigListModelMixin, DigCreateModelMixin, DigDestroyModelMixin, DigUpdateModelMixin, GenericViewSet): """ 主题 """ # 当前登录用户的调教 filter_backends = [SelfFilterBackend, DjangoFilterBackend] filterset_class = TopicFilterSet queryset = models.Topic.objects.filter(deleted=False).order_by('-id') serializer_class = TopicSerializer def perform_create(self, serializer): serializer.save(user=self.request.user) def perform_destroy(self, instance): instance.deleted = True instance.save()
项目开始
1 注册和登录(account.py)
1 自定制mixin(可以自定义返回response) 2 返回码的使用 3 GenericViewSet,APIView使用 4 账号校验与序列化 5 权限校验
6 时间的存储与过期判断(修改settings.py,timedelta使用)
2 话题(topic.py)
1 全局认证的配置 2 增,删,改,察的功能 3 自定制perform_create,perform_destroy 4 序列化read_only的使用 5 子定制过滤条件(SelfFilterBackend) 6 加载更多(分页limit+DjangoFilterBackend## ?latest_id=99&limit=10 -> id<99 limit 10)
3 我的资讯
1 news.py序列化(write_only,read_only,source,validate_xxx,) 2 serializer.save(user=self.request.user)-->serializers中的create方法 2.1 serializer中调用request(request = self.context["request"])
3 缓存配置,限流(&实现1/5m,&只有post才添加记录,&添加成功才限流)
4 首页(news.py)
1 order_by使用(定义queryset) 2 过滤(DjangoFilterBackend+分页) # ?zone=1 # ?latest_id=99&limit=10 3 序列化 (外键对象序列化-->例如:user = IndexSubUserSerializer(read_only=True)) 4 登录与未登录的认证处理 结果:都可以通过,查看页面不同(序列化加一层判断,返回不同数据)
5 收藏和推荐
1 添加收藏(post),展示收藏(get) 2 序列化的嵌套(外键) 3 queryset过滤,分页 4 收藏记录的逻辑判断
6 评论
1 展示评论 ListCommentSerializer 2 创建评论 CreateCommentSerializer 3 认证类的区分,get无需认证,post需要认证 # 展示评论 1 过滤(按照新闻id,分页,时间过滤)
2 子评论的展示(apppend的运用)
作者:华王
博客:https://www.cnblogs.com/huahuawang/
浙公网安备 33010602011771号