基于serializers,Django搭建服务器

写在前面:基于rest_framework,利用serializers序列化,完善django服务器api接口搭建,实现和微信小程序互通

一、创建项目

django-admin startproject wxPro

二、注册app,进入到wxTest目录中

python manage.py startapp api

三、提前安装djangorestframework包

四、在settings.py中添加rest_framework
 

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

LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False

# 用户信息存入mysql的wx数据库
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'wx_pro', # 数据库名字可更改
        'PORT':'3306',
        'USER':'root',
        'PASSWORD':'123456',
        'HOST':'localhost'
    }
}
 
# 如果需要用到django-redis,需要添加
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379',
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS":{"max_connections":100}
             # "PASSWORD": "yoursecret",
        },
    },
}
 
# ---->设置上传文件的目录和外部访问的路径
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL = '/media/'

五、wxPro\urls.py中

from django.contrib import admin
from django.urls import path, include
from django.conf.urls import url
from django.views.static import serve
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls')),
    # 添加这行——————允许所有的media文件被访问
    url(r'media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
]
# 或者以下方法
# 添加这行——————允许所有的media文件被访问
# urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

六、wxPro\api\models.py中(表不断完善中...)

from django.db import models

class UserInfo(models.Model):
    '''
    用户信息表
    '''
    # id = models.AutoField(primary_key=True)
    # unique唯一
    phone = models.CharField(max_length=11,verbose_name='手机号',unique=True)
    # 用户昵称
    nickname = models.CharField(verbose_name='昵称',max_length=64)
    # 用户头像
    avatar = models.CharField(verbose_name='头像',max_length=255,null=True)
    # 用户登录后唯一标识
    # null=True, 表示数据库的该字段可以为空
    # blank=True,表示你的表单填写该字段的时候可以不填
    token = models.CharField(max_length=64,verbose_name='用户Token',null=True,blank=True)

class Topic(models.Model):
    '''
    话题表
    '''
    title = models.CharField(verbose_name='话题',max_length=32)
    count = models.PositiveIntegerField(verbose_name='关注度',default=0)

class News(models.Model):
    '''
    发布的动态表
    '''
    cover = models.CharField(verbose_name='封面',max_length=255)
    content = models.CharField(verbose_name='内容',max_length=255)
    topic = models.ForeignKey(verbose_name='话题',to='Topic',null=True,blank=True,on_delete=models.CASCADE)
    address = models.CharField(verbose_name='位置',max_length=128,null=True,blank=True)
    user = models.ForeignKey(verbose_name='发布者',to='UserInfo',related_name='news',on_delete=models.CASCADE)
    favor_count = models.PositiveIntegerField(verbose_name='点赞数',default=0)
    # favor = models.ManyToManyField(verbose_name='点赞记录',to='UserInfo',related_name='news_favor')
    viewer_count = models.PositiveIntegerField(verbose_name='浏览数',default=0)
    # viewer = models.ManyToManyField(verbose_name='浏览记录',to='UserInfo',related_name='news_viewer')
    comment_count = models.PositiveIntegerField(verbose_name='评论数',default=0)
    # auto_now_add为添加时的时间,更新对象时不会有变动
    create_date = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)

class NewsDetail(models.Model):
    '''
    动态详情
    '''
    key = models.CharField(verbose_name='文件名',max_length=128,help_text='根据文件名删除')
    cos_path = models.CharField(verbose_name='图片路径',max_length=128)
    news = models.ForeignKey(verbose_name='动态',to='News',on_delete=models.CASCADE)

class ViewerRecord(models.Model):
    '''
    浏览记录
    '''
    pass
class NewsFavorRecord(models.Model):
    '''
    动态收藏记录
    '''
    pass

class CommentRecord(models.Model):
    '''
    评论记录表
    '''
    news = models.ForeignKey(verbose_name='动态',to='News',on_delete=models.CASCADE)
    content = models.CharField(verbose_name='评论内容',max_length=255)
    user = models.ForeignKey(verbose_name='评论者',to='UserInfo',on_delete=models.CASCADE)
    create_date = models.DateTimeField(verbose_name='评论时间', auto_now_add=True)
    replay = models.ForeignKey(verbose_name='回复',to='self',on_delete=models.CASCADE,null=True,blank=True)
    depth = models.PositiveIntegerField(verbose_name='评论层级',default=1)
    favor_count = models.PositiveIntegerField(verbose_name='赞数',default=0)

class CommentFavorRecord(models.Model):
    '''
    评论赞记录表
    '''
    comment = models.ForeignKey(verbose_name='评论内容',to='CommentRecord',on_delete=models.CASCADE)
    user = models.ForeignKey(verbose_name='点赞用户',to='UserInfo',on_delete=models.CASCADE)

提前建好数据库:wx_pro,执行以下命令

终端输入:python manage.py makemigrations(生成迁移文件)

终端输入:python manage.py migrate(将结构变化应用到数据库)

七、wxPro\utils\(创建utils文件夹,封装的一些功能存放)

1、wxPro\utils\pagination.py

from rest_framework.response import Response
from rest_framework.pagination import LimitOffsetPagination

# http://127.0.0.1:8000/api/news/?limit=5 ,分页功能
class LimitPagination(LimitOffsetPagination):
    '''
    分页功能
    '''
    # 默认取5条,最大取50条
    default_limit = 5
    max_limit = 50
    limit_query_param = 'limit'
    offset_query_param = 'offset'
    # 从0位置开始取
    def get_offset(self, request):
        return 0
    # 使页面不显示
    def get_paginated_response(self, data):
        return Response(data)

2、wxPro\utils\filters.py

# 实现小程序通过下拉、上刷页面数据动态加载,按照id排序
from rest_framework.filters import BaseFilterBackend

# 下拉页面获取比当前数据最小的id小的10条数据
class MinFilterBackend(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        minId = request.query_params.get('minId','')
        if minId:
            return queryset.filter(id__lt=minId).order_by('-id')
        return queryset

# 刷新页面获取比当前页面最大的id大的10条数据
class MaxFilterBackend(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        maxId = request.query_params.get('maxId','')
        if maxId:
            return queryset.filter(id__gt=maxId).order_by('id')
        return queryset

 

八、wxPro\api\views\(创建views文件夹,创建视图)

1、wxPro\api\views\news.py

from django.forms import model_to_dict
from rest_framework.generics import ListAPIView
from rest_framework import serializers

from utils.filters import MaxFilterBackend, MinFilterBackend
from utils.pagination import LimitPagination
from .. import models

class NewsModelSerializer(serializers.ModelSerializer):
    topic = serializers.SerializerMethodField()
    user = serializers.SerializerMethodField()
    class Meta:
        model = models.News
        # 需要展示的字段
        fields = ['id','cover','content','topic','user','favor_count']

    def get_topic(self,obj):
        if not obj.topic:
            return
        # return {"id":obj.topic_id,"title":obj.topic.title}
        return model_to_dict(instance=obj.topic,fields=['id','title'])

    def get_user(self,obj):
        # return {"id":obj.user_id,"nickname":obj.user.nickname,"avatar":obj.user.avatar}
        return model_to_dict(instance=obj.user, fields=['id', 'nickname','avatar'])

class NewsView(ListAPIView):
    '''
    获取动态列表,方法二ListAPIView
    '''
    serializer_class = NewsModelSerializer
    queryset = models.News.objects.all().order_by('-id')
    pagination_class = LimitPagination
    filter_backends = [MinFilterBackend,MaxFilterBackend]

# from rest_framework.views import APIView
# from rest_framework.response import Response
# class NewsView(APIView):
#     '''
#     获取动态列表,方法一APIView
#     '''
#     def get(self, request, *args, **kwargs):
#         minId = request.query_params.get('minId','')
#         maxId = request.query_params.get('maxId','')
#
#         if minId:
#             queryset = models.News.objects.filter(id__lt=minId).order_by('-id')[0:10]
#         elif maxId:
#             queryset = models.News.objects.filter(id__gt=maxId).order_by('id')[0:10]
#         else:
#             queryset = models.News.objects.all().order_by('-id')[0:10]
#         ser = NewsModelSerializer(instance=queryset,many=True)
#         return Response(ser.data,status=200)
class NewsDetailView(APIView):
    '''
    心情详情页功能
    '''
    pass

2、wxPro\api\views\topic.py

from rest_framework.generics import ListAPIView
from rest_framework.serializers import ModelSerializer

from utils.filters import MinFilterBackend, MaxFilterBackend
from utils.pagination import LimitPagination
from .. import models

class TopicSerializer(ModelSerializer):
    '''
    序列化
    '''
    class Meta:
        model = models.Topic
        fields = "__all__"

class TopicView(ListAPIView):
    '''
    获取话题列表,支持下拉、上刷动态加载,按照关注度倒序
    '''
    serializer_class = TopicSerializer
    queryset = models.Topic.objects.all().order_by('-count')
    pagination_class = LimitPagination
    filter_backends = [MinFilterBackend,MaxFilterBackend]

3、wxPro\api\views\image.py

import os
import json

from django.http import JsonResponse
from rest_framework.response import Response
from rest_framework.views import APIView

from django.conf import settings

class DelView(APIView):
    '''
    删除本地图片功能
    '''

    def get(self, request, *args, **kwargs):
        # image:{"key":"ffc80dd45d634a29406215334879a55e.png","cos_path":"http://127.0.0.1:8000/media/ffc80dd45d634a29406215334879a55e.png"}
        imagePath = request.query_params.get('image', '')
        imagePath = json.loads(imagePath)
        MEDIA_ROOT = settings.MEDIA_ROOT
        try:
            # 1f75e5dfcf258de22566433799ce67a1.jpg
            file_path = os.path.join(os.getcwd(), MEDIA_ROOT + imagePath['key'])
            file_path = file_path.replace('/', '\\')
            os.remove(file_path)
            return Response({"status": True, "message": "图片删除成功"})
        except Exception as e:
            return Response({"status": False, "message": "图片删除失败:" + str(e)})


# 传图功能
class UploadView(APIView):
    '''
    上传图片功能
    '''

    def post(self, request, *args, **kwargs):
        # 传入的文件路径列表
        imageList = request.FILES.get('image', '')
        # imageList = request.FILES['image']
        # 文件类型,可以判断如果不是图片格式则不上传
        file_type = os.path.splitext(imageList.name)[1]
        if not imageList:
            print("上传文件不能为空")
            return JsonResponse({"status": False, "message": "上传文件不能为空"})
        file_format = ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp', '.tif', '.tiff']
        if file_type.lower() not in file_format:
            print("上传文件类型非图片格式")
            return JsonResponse({"status": False, "message": "上传文件类型非图片格式"})
        #  获得一个唯一名字:uuid +hash
        new_name = self.get_random_str()
        # 组合成文件新名字
        new_file_name = new_name + file_type
        MEDIA_ROOT = settings.MEDIA_ROOT
        if not os.path.exists(MEDIA_ROOT):
            os.makedirs(MEDIA_ROOT)
        # 文件路径
        file_path = os.path.join(MEDIA_ROOT, new_file_name)
        # 将网络地址返回给小程序
        return_img = "http://127.0.0.1:8000/media/" + str(new_file_name)
        # 开始写入本地磁盘
        try:
            f = open(file_path, 'wb')
            # 多次写入
            for i in imageList.chunks():
                f.write(i)
            f.close()
            print("上传图片成功")
            # 不能用Response,result.data.status获取不到,result.data是string类型,不是object
            # return Response({"status": True, "message": "上传图片成功","data": file_path})
            # 如果返回的是JsonResponse,小程序要进行解析console.log(JSON.parse(res.data))
            return JsonResponse({"status": True, "message": "上传图片成功",
                                 "cos_path": return_img, "key": new_file_name})
        except Exception as e:
            print("上传图片失败:", e)
            return JsonResponse({"status": False, "message": "上传图片失败:" + str(e)})

    # 获取UUID
    def get_random_str(self):
        import hashlib
        import uuid
        """获取UUID"""
        uuid_val = uuid.uuid4()  # UUID('c1127b61-0714-4eab-8fb0-0138b6a84b4c')
        uuid_str = str(uuid_val).encode('utf-8')  # b'c1127b61-0714-4eab-8fb0-0138b6a84b4c'
        md5 = hashlib.md5()  # <md5 _hashlib.HASH object @ 0x00000253D50DCDB0>
        md5.update(uuid_str)
        return md5.hexdigest()  # 'fb8dfbe57e508bc4ade13f1ac4e7212b'

4、wxPro\api\views\auth.py

import re
import random

import uuid
import chardet

from rest_framework.response import Response
from rest_framework.views import APIView

from django_redis import get_redis_connection
from .. import models


# 退出
class LogoutView(APIView):
    '''
    退出登录功能
    '''

    def post(self, request, *args, **kwargs):
        # 传入的token
        token = request.data.get('token', '')
        phone = request.data.get('phone', '')
        if not token:
            return Response({"status": False, "message": "退出登录失败"})
        # 去数据库里
        data_token = models.UserInfo.objects.filter(token=token).first()
        data_token = data_token.token
        if token != data_token:
            return Response({"status": False, "message": "退出登录失败"})
        conn = get_redis_connection()
        # 成功退出时,清掉redis中的验证码,以便用户能登录成功后马上退出再次重新获取验证码
        conn.delete(phone)
        return Response({"status": True, "message": "已成功退出"})


class LoginView(APIView):
    '''
    # 登录功能,返回status
    '''

    def post(self, request, *args, **kwargs):
        '''
        1.校验手机号
        2.校验验证码,redis获取验证码
            2.1 无验证码
            2.2 有验证码,但是输入错误
            2.3 有验证码,输入正确
        3.获取数据库中用户信息(创建或者获取)
        4.将一些信息返回给小程序
        '''
        # 传入的phone
        phone = request.data.get('phone', '')
        if not phone:
            return Response({"status": False, "message": "手机号不能为空"})
        if not phone.isdecimal():
            return Response({"status": False, "message": "手机号格式错误"})
        if len(phone) != 11:
            return Response({"status": False, "message": "手机号长度错误"})
        patt = r'^1[3-9]\d{9}$'
        getPhoneNum = re.match(patt, phone)
        if not getPhoneNum:
            return Response({"status": False, "message": "手机号格式错误"})
        phone = getPhoneNum.group()
        # 传入的验证码
        code = request.data.get('code', '')
        if not code:
            return Response({"status": False, "message": "验证码不能为空"})
        if len(code) != 4 or not code.isdecimal():
            return Response({"status": False, "message": "验证码为4位数字"})

        conn = get_redis_connection()
        # redis库里存的验证码,和用户输入的验证码进行比对,redis取值是bytes类型,因为设置了60秒过期,会有取不到的情况
        redis_code = conn.get(phone)
        if not redis_code:
            # 如果查不到,无效
            return Response({"status": False, "message": "验证码无效"})

        # 查看原有变量的编码类型
        ret = chardet.detect(redis_code)
        redis_code = redis_code.decode(ret['encoding'])
        # 如果不等输入有误
        if redis_code != code:
            return Response({"status": False, "message": "验证码输入错误"})

        # 获取数据库中用户信息(创建或者获取)
        # 第一个方法
        # users = models.UserInfo.objects.filter(phone=phone).first()
        # print("users:",users)
        # 如果没有用户,则创建
        # if not users:
        #     models.UserInfo.objects.create(phone=phone,token=str(uuid.uuid4()))
        # else:
        #     users.token = str(uuid.uuid4())
        #     users.save()
        # 第二个方法flag:如果能查到返回False,查不到则创建返回True
        user_obj, flag = models.UserInfo.objects.get_or_create(phone=phone)
        user_obj.token = str(uuid.uuid4())
        user_obj.save()
        if flag:
            return Response({"status": True, "message": "注册成功", "data": {"token": user_obj.token, "phone": phone}})
        return Response({"status": True, "message": "登录成功", "data": {"token": user_obj.token, "phone": phone}})
        # return Response({"status": True,"message": "登录成功"})


class MessageView(APIView):
    def get(self, request, *args, **kwargs):
        '''
        接收短信验证码功能,返回四位数字fourNum
        '''
        # 1、获取手机号
        getPhoneNum = request.query_params.get('phone', '')
        # 2、手机号格式验证,否则浏览器直接输入http://127.0.0.1:8000/api/message/?phone=12000000000,也可以获取验证码
        # 正则判断
        patt = r'^1[3-9]\d{9}$'
        getPhoneNum = re.match(patt, getPhoneNum)
        if not getPhoneNum:
            print("手机号格式错误")
            return Response("手机号格式错误")

        phone = getPhoneNum.group()
        print("传递的手机号为:", phone)

        # 防止用户连续点击获取验证码,加了一个判断或者防止获取完验证码一分钟内退出页面再次重新进入页面,重新获取
        conn = get_redis_connection()
        redis_code = conn.get(phone)
        if redis_code:
            # 如果查到,则代表刚刚获取过不能再次获取
            return Response({"status": False, "message": "已获取过验证码"})

        # 模拟发送成功和失败,只有发送成功才生成验证码,进而存入redis
        # 如果发送失败,直接返回提示
        simulation = random.choice([True, False])
        if not simulation:
            print("验证码发送失败")
            return Response({"status": False, "message": "验证码发送失败"})

        # 3、生成四位随机验证码
        random_code = random.randint(1000, 9999)
        print("生成验证码为:", random_code)
        # 4、验证码发送到手机,购买服务器进行短信发送,腾讯云(可将此功能单独封装)
        #    4.1 注册腾讯云,开通腾讯云短信
        #    4.2 创建应用 SDK APPID=XXXXX
        #    4.3 申请签名(个人可通过公众号)获取到ID 和名称
        #    4.4 申请腾讯云API获得SecretID和Secretkey
        #    4.5 参考以下文档:Tencent Cloud SDK 3.0 for Python
        # https://cloud.tencent.com/document/product/382/38778
        # https://github.com/TencentCloud/tencentcloud-sdk-python/blob/master/examples/sms/v20190711/SendSms.py

        # 5、保留验证码+手机号(设置超时时间过期60秒,可以用redis)
        #   5.1 搭建redis服务器(云redis)
        #   5.2 自行搭建本地
        # import redis
        # pool = redis.ConnectionPool(host='',port=6379)
        # r = redis.Redis(connection_pool=pool)
        # r.set(phone,random_code,ex=60)
        #   5.3 使用django-redis,首先settings中添加配置CACHES
        # 需要打开redis
        conn.set(phone, random_code, ex=60)
        return Response({"status": True, "message": "验证码发送成功", "fourNum": random_code})

5、wxPro\api\views\pay.py

from rest_framework.views import APIView
class PayView(APIView):
    '''
    支付功能
    '''
    pass

九、在wxPro\api中添加urls.py

from django.urls import path
from django.conf.urls import url
from .views import pay
from .views import auth
from .views import topic
from .views import news
from .views import image
urlpatterns = [
    path('news/', news.NewsView.as_view()), # 发布心情功能
    url(r'^news/(?P<pk>\d+)/$', news.NewsDetailView.as_view()), # 发布的心情详情
    path('topic/', topic.TopicView.as_view()),  # 获取话题
    path('login/', auth.LoginView.as_view()),  # 登录
    path('message/', auth.MessageView.as_view()),  # 获取验证码
    path('logout/', auth.LogoutView.as_view()),  # 退出登录
    path('upload/', image.UploadView.as_view()),  # 传图功能
    path('del/', image.DelView.as_view()),  # 删除上传的图片
    # path('pay/', pay.PayView.as_view()), # 支付
]

十、wxPro\scripts\(创建scripts文件夹,批量创建话题和动态心情功能)--非必须

1、wxPro\scripts\init_auth.py

# 批量创建用户数据
import os
import sys
import django


base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# D:\develop\study\Django\wxPro\scripts\init_topic.py
print(os.path.abspath(__file__))
# D:\develop\study\Django\wxProNews
# print(base_dir)
sys.path.append(base_dir)

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wxPro.settings")
django.setup()

# 导入的包必须在上述以下才可以执行,不能放在最上面
from api import models

# 批量创建
for i in range(0,10):
    phone = '1300000000'+str(i)
    nickname = "昵称-"+str(i)
    avatar = "https://p3.itc.cn/images01/20210326/0bd5fbad58224edba52b5e0da5ea1cf9.jpeg"
    models.UserInfo.objects.create(phone=phone,nickname=nickname,avatar=avatar)

2、wxPro\scripts\init_topic.py

# 批量创建话题数据
import os
import sys
import django


base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# D:\develop\study\Django\wxPro\scripts\init_topic.py
print(os.path.abspath(__file__))
# D:\develop\study\Django\wxPro
print(base_dir)
sys.path.append(base_dir)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wxPro.settings")
django.setup()

# 导入的包必须在上述以下才可以执行,不能放在最上面
from api import models
# 单独创建
# models.Topic.objects.create(title='高考')
# models.Topic.objects.create(title='春运')
# models.Topic.objects.create(title='中国诗词大会')
# 批量创建
for i in range(1,10):
    models.Topic.objects.create(title='话题{0}'.format(i),count=i)

3、wxPro\scripts\init_news.py

# 批量创建news动态数据
import os
import sys
import django
import random
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wxPro.settings")
django.setup()

# 导入的包必须在上述以下才可以执行,不能放在最上面
from api import models
for i in range(61, 80):
    news_object = models.News.objects.create(
        cover="http://127.0.0.1:8000/media/bb9bfdf662f1e44903d4aeca0b2461af.png",
        content="还有{0}天放假".format(i),
        topic_id=random.randint(1,2),
        user_id=1
    )
    models.NewsDetail.objects.create(
        key="bb9bfdf662f1e44903d4aeca0b2461af.png",
        cos_path="http://127.0.0.1:8000/media/bb9bfdf662f1e44903d4aeca0b2461af.png",
        news=news_object
    )
    models.NewsDetail.objects.create(
        key="a0c49d6de8e81be58400f09421b0195a.png",
        cos_path="http://127.0.0.1:8000/media/a0c49d6de8e81be58400f09421b0195a.png",
        news=news_object
    )

十一、运行服务

python manage.py runserver

运行后:http://127.0.0.1:8000/api/topic/?limit=10

http://127.0.0.1:8000/api/news/?limit=5

画面截图参考:https://blog.csdn.net/z564359805/article/details/115551325

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值