WX06--功能开发--动态发布页、首页、动态详情页02

day07 功能开发

1.发布逻辑

1.1 小程序

  • 选图片

  • 填内容

  • 提交-数据格式

    // v1.0
    
    {	
        // cover字段: 让前端自己处理传过来,不要后端切片自己构造存储。理由:1.省事 2.节约服务器操作
    	cover:"https://mini-1251317460.cos.ap-chengdu.myqcloud.com/08a9daei1578736867828.png",
        content:"小程序开发太简单了",
        // topic字段: 话题id
    	topic:1,
        address:"北京市",
        // user字段: 注意:不能用id、phone等传递,必须使用token能唯一校验的数据 (因为id和phone容易被人伪造构建数据)
        user:"token字段",
        
    	images:[
    		"https://mini-1251317460.cos.ap-chengdu.myqcloud.com/08a9daei1578736867828.png",
    		"https://mini-1251317460.cos.ap-chengdu.myqcloud.com/08a9daei1578736867828.png"
    	]
    }
    
    
    // v2.0  为了方便后续的images管理(删除等操作),应当将文件名也传递过来
    
    {
    	cover:"https://mini-1251317460.cos.ap-chengdu.myqcloud.com/08a9daei1578736867828.png",
    	content:"小程序开发太简单了",
    	address:"北京市",
    	topic:1,
    	
    	images:[
    		{
    			path:"https://mini-1251317460.cos.ap-chengdu.myqcloud.com/08a9daei1578736867828.png",
    			cos_key:"08a9daei1578736867828.png"
    		},
    		{
    			path:"https://mini-1251317460.cos.ap-chengdu.myqcloud.com/08a9daei1578736867828.png",
    			cos_key:"08a9daei1578736867828.png"
    		},
    	]
    }
    

1.2 API逻辑

from rest_framework.views import APIView
from rest_framework.generics import CreateAPIView
from rest_framework import serializers
from apps.api import models

class NewsDetailSerializer(serializers.Serializer):
    key = serializers.CharField()
    cos_path = serializers.CharField()
    
class NewsModelSerializer(serializers.ModelSerializer):
    images = NewsDetailModelSerializer(many=True)
    class Meta:
        model = models.News
        fields = "__all__"

class NewsView(CreateAPIView):
    """ 创建动态的API """
    serializer_class = NewsModelSerializer
class News(models.Model):
    """
    动态
    """
    # 这五项必传
    cover = models.CharField(verbose_name='封面', max_length=128)
    content = models.CharField(verbose_name='内容', max_length=255)
    topic = models.ForeignKey(verbose_name='话题', to='Topic', null=True, blank=True)
    address = models.CharField(verbose_name='位置', max_length=128, null=True, blank=True)
    user = models.ForeignKey(verbose_name='发布者', to='UserInfo', related_name='news')
	
    # 这四项可不传
    favor_count = models.PositiveIntegerField(verbose_name='赞数', default=0)
    viewer_count = models.PositiveIntegerField(verbose_name='浏览数', default=0)
    comment_count = models.PositiveIntegerField(verbose_name='评论数', default=0)
    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')

1.3 规则

{
    k1:v1,
    k2:v2,
    k3:{...},
    k4:[
        {....}
    ]
}

# 总结:k1,k2形式的,可直接字段存储;若是k3、k4这种,可考虑子序列化(嵌套序列化)进行校验或存储

2.restful api回顾

2.1 APIView ( 可以 )

# 自己根据逻辑,直接写

from rest_framework.response import Response
class UserModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"

class UserView(APIView):

    def get(self,request,*args,**kwargs):
        user_list = models.UserInfo.objects.all()
        ser = UserModelSerializer(instance=user_list,many=True)
        return Response(ser.data)

    def post(self,request,*args,**kwargs):
        ser = UserModelSerializer(data=request.data)
        if ser.is_valid():
            # models.UserInfo.objects.create(**ser.validated_data)
            ser.save(user_id=1)
            return Response(ser.data)
        return Response(ser.errors)

2.2 ListAPIView

ListAPIView,CreateAPIView,RetrieveAPIView,UpdateAPIView,DestroyAPIView

class NewTestModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.News
        fields = "__all__"

class NewTestView(CreateAPIView,ListAPIView):
    serializer_class = NewTestModelSerializer
    queryset = models.News.objects.filter(id__gt=4)

2.2.1 用户传递某些值

创建用户时,自己在后台生成一个UID。

class NewTestModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.News
        fields = "__all__"

class NewTestView(CreateAPIView,ListAPIView):
    serializer_class = NewTestModelSerializer
    queryset = models.News.objects.filter(id__gt=4)

    def perform_create(self, serializer):
        serializer.save(uid=str(uuid.uuid4()))  # save()时 自己额外传递

2.2.2 fields和exclude的区别?

通过fields和exclude定制页面展示部分字段数据。

需求:只显示用户表的id,name,age的数据,其他不显示。

class NewTestModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.News
        # fields = ["id","name",'age']
        exclude = ['gender']

class NewTestView(ListAPIView):
    serializer_class = NewTestModelSerializer
    queryset = models.User.objects.all()
    
[
    {id:1,name:'xxx',age:18},
    {id:1,name:'xxx',age:11},
    {id:1,name:'xxx',age:99},
]

需求:数据库有5个字段,显示7个字段。

class NewTestModelSerializer(serializers.ModelSerializer):
    xx = serializers.CharField(source='id')
    x1 = serializers.SerializerMethodField()
    class Meta:
        model = models.News
        # fields = "__all__"  # 为all时,是model的字段,加自定义的字段
        # fields = ['id','name','age','gender','phone','xx','x1']
        exclude = ['id','name']
	
    def get_x1(self,obj):
        return obj.id
    
class NewTestView(ListAPIView):
    serializer_class = NewTestModelSerializer
    queryset = models.User.objects.all()
    
[
    {id:1,name:'xxx',age:18...   xx:1,x1:1},
    {id:2,name:'xxx',age:11...   xx:2,x1:2},
    {id:3,name:'xxx',age:99,     xx:3,x1:3},
]

2.2.3 read_only

添加时不要,查看时候需要。

需求:编写两个接口 添加(3字段)、获取列表(5个字段)

class NewTestModelSerializer(serializers.ModelSerializer):
    # phone = serializers.CharField(source='phone',read_only=True)
    # email = serializers.CharField(source='email',read_only=True)
    class Meta:
        model = models.News
        fields = "__all__"
        read_only_fields = ['phone','email',]
        
class NewTestView(CreateAPIView, ListAPIView):
    serializer_class = NewTestModelSerializer
    queryset = models.User.objects.all()
    
添加:
	{
        name:'xx',
        age:'19',
        gender:1
    }
    
获取:
	[
        {name:'xx',age:'xx',gender:'',phone:'xx',email:xxx}
    ]

2.3.4 复杂需求

添加时用一个serializers、列表时用一个serializers

class NewTestModelSerializer1(serializers.ModelSerializer):
    class Meta:
        model = models.News
        fields = "__all__"
        
class NewTestModelSerializer2(serializers.ModelSerializer):
    class Meta:
        model = models.News
        fields = "__all__"

class NewTestView(CreateAPIView, ListAPIView):
    queryset = models.User.objects.all()
    
    # 重写get_serializer_class()
	def get_serializer_class(self):
        if self.request.method == 'POST':
            return NewTestModelSerializer1
        if self.request.method == 'GET':
            return NewTestModelSerializer2

2.3.5 serializers嵌套

class CreateNewsTopicModelSerializer(serializers.Serializer):
    key = serializers.CharField()
    cos_path = serializers.CharField()


class CreateNewsModelSerializer(serializers.ModelSerializer):
    imageList = CreateNewsTopicModelSerializer(many=True)

    class Meta:
        model = models.News
        exclude = ['user', 'viewer_count', 'comment_count',"favor_count"]

    def create(self, validated_data):
        # 把imageList切走
        image_list = validated_data.pop('imageList')

        # 创建New表中的数据
        news_object = models.News.objects.create(**validated_data)

        data_list = models.NewsDetail.objects.bulk_create(
            [models.NewsDetail(**info, news=news_object) for info in image_list]
        )
        news_object.imageList = data_list

        if news_object.topic:
            news_object.topic.count += 1
            news_object.save()

        return news_object

    
class NewsView(CreateAPIView):
    """
    发布动态
    """
    serializer_class = CreateNewsModelSerializer
    def perform_create(self, serializer):
        # 只能保存:News表中的数据()
        # 调用serializer对象的save(先调用create)
        new_object = serializer.save(user_id=1)
        return new_object

3. 首页展示

  • 小程序
    • 初始化
    • 下拉刷新
    • 上翻页
    • 瀑布流
  • 后端API
    • APIView
    • ListAPIView
      • filter:最大/最小过滤
      • pagination:定制返回数据条数

3.1 小程序-瀑布流

了解,能实现就行。

以下三种,主要了解思路,具体实现可试着自己找组件。

3.1.1 方式一:column-count

# 通过css的column-count,自动展示瀑布流
    缺点:数据填充是先左列 从上到下填充完,再下一列 从上到下
 
# wxml:
<view class="container">
  <view class="item">
    <image src="https://hbimg.huabanimg.com/fw236" mode="widthFix" ></image>
  </view>
  <view class="item">
    <image src="https://hbimg.huabanimg.com/fw236" mode="widthFix" ></image>
  </view>
  <view class="item">
    <image src="https://hbimg.huabanimg.com/fw236" mode="widthFix" ></image>
  </view>
  <view class="item">
    <image src="https://hbimg.huabanimg.com/fw236" mode="widthFix" ></image>
  </view>
</view>


# css:
.container {	
  /* 元素被划分的列数 */
  -moz-column-count:2; /* Firefox */
  -webkit-column-count:2; /* Safari and Chrome */
  column-count:2;    

  /* 规定列间的间隔为 20个相对像素:*/
  -moz-column-gap:20rpx; /* Firefox */
  -webkit-column-gap:20rpx; /* Safari and Chrome */
  column-gap:20rpx;
}

.container .item{
  / *在多列布局页面下的内容盒子如何中断*/
  break-inside: avoid-column; // 避免在元素内分列
  -webkit-column-break-inside: avoid; /* Safari and Chrome */
}

3.1.2 方式二:flex布局-横向 + 判断奇偶 (√)

# flex-横向 + 宽度50% => 实现两列  + for循环,判断数据
    
    
# wxml:
<view class='container'>
    <view class="item">
        <view wx:for="{{dataList}}" wx:key="id">
            <view wx:if="{{index % 2 == 0}}">
                <view>{{item.id}}</view>
                <image src="{{item.img}}" mode="widthFix"></image>
            </view>
        </view>
    </view>

    <view class="item">
        <view wx:for="{{dataList}}" wx:key="id">
            <view wx:if="{{index % 2 !== 0}}">
                <view>{{item.id}}</view>
                <image src="{{item.img}}" mode="widthFix"></image>
            </view>
        </view>
    </view>
</view>
    
    
# css:
.container {
  display: flex;
  flex-direction: row;
}
   
.container .item{
  width: 50%;
  overflow: hidden;
}

.container .item image{
  width: 100%;
}

3.1.3 方式三:flex布局-纵向+order排序实现

# 用 flexbox, :nth-child() 和 order 实现
	缺点:需要自己写容器的固定高度,不适用于小程序端
    
    
# wxml:
<view class='container'>
    <view class="item" wx:for="{{dataList}}" wx:key="id">
        <view>{{item.id}}</view>
        <image src="{{item.img}}" mode="widthFix"></image>
    </view>
</view>
    
    
# css:
/* 让内容按列纵向展示*/
.container {
  display: flex;
  flex-flow: column wrap;  /* 属性是flex-direction和flex-wrap属性的缩写*/ 
  align-content: space-between;
    
  /* 容器必须有固定高度,不然全在第一列
  * 且高度大于最高的列高 */
  height: 1000rpx;
}

/* 重新定义内容块排序优先级,让其横向排序 */
.item:nth-child(2n+1) { order: 1; }
.item:nth-child(2n  ) { order: 2; }


/* 强制使内容块分列的隐藏列 */
.container::before,
.container::after {
  content: "";
  flex-basis: 100%;
  width: 0;
  order: 2;
}
        
.container .item{
  width: 50%;
  overflow: hidden;
}

.container .item image{
  width: 100%;
}

3.2 View优化

优化目的:利用现成的类,来拓写功能,提高代码的可重用性

3.2.1 使用过滤类--进行最大/最小的过滤

class ReachBottomFilter(BaseFilterBackend):

    def filter_queryset(self, request, queryset, view):
        min_id = request.query_params.get('minId')
        if not min_id:
            return queryset
        return queryset.filter(id__lt=min_id)


class PullDownRefreshFilter(BaseFilterBackend):

    def filter_queryset(self, request, queryset, view):
        max_id = request.query_params.get('maxId')
        if not max_id:
            return queryset
        return queryset.filter(id__gt=max_id).reverse()  # 反向,或 order_by('id')
        # 因为queryset 整体是倒序拍的,若下拉新数据(新数据100条)的大于10条,则应该是110-100,而不是200-190
        # 加上 分页切片都是从头开始,则需要正向排序,此时返回数据 是100-110,前端再自己反向处理下

3.2.2 使用分页类---来定制的数据展示条数

# 利用分页类--偏移分页,来定制的数据展示条数


# low版(自己版):
	直接在过滤的时候,固定的切片

class PageFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        max_id = request.query_params.get('maxId')  # 获取条件值
        min_id = request.query_params.get('minId')

        if max_id:
            queryset = queryset.filter(id__gt=max_id).order_by('id')
  
        if min_id:
            queryset = queryset.filter(id__lt=min_id)
        return queryset[:10]  # 返回过滤完的数据

    
    
# nice版:
	利用分页类--偏移分页的特性,limit: 取多少条,offset: 从第几个位置开始取
    这样前端,可携带参数 ?limit=20 或不传(default_limit),定制获取条数

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

class OldBoyLimitPagination(LimitOffsetPagination):
    """
    本质上帮助我们进行切片的处理:[0:N]
    """
    default_limit = 5
    max_limit = 50
    limit_query_param = 'limit'
    offset_query_param = 'offset'
	
    # 重写offset方法,固定成 每次都是第0个位置开始
    def get_offset(self, request):
        return 0
    
    # 重写get_paginated_response, 去掉默认携带的 count、next等参数
    def get_paginated_response(self, data):
        return Response(data)

3.3 扩展:分页的优化

# 记录最大值和最小值,进行过滤筛选

# 优点:
    防止切片全部数据扫描的问题。
    
    正常分页时,是queryset[:10]的操作 (0-1011-20)
    取到第n页数据时,也会将前n-1页的数据一起查出来,再切片后10条数据
    数据、分页越多,效率越低
    
# 缺点:
    只能上下页进行翻页,不能直接跳转第n页

4.详细页面(3点)

  • 写脚本构造数据
  • 最近的访客
  • 一级评论

4.1 写脚本构造数据

# 构造数据的方式:
	1.前端(或postman)构造数据,发送请求,存数据库  # 正常执行逻辑
    2.数据库中,手动添加 或 sql脚本
    3.Django后端,写orm脚本  # 别忘了这个

    
# 使用 Django orm 单独写一个数据初始化的脚本

import os
import sys
import django

# 将Django项目添加sys.path变量中
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)

# 将Django的配置 设置到 系统环境变量中
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demos.settings")
django.setup()

from api import models

for i in range(1,37):
    news_object = models.News.objects.create(
        cover="https://mini-1251317460.cos.ap-chengdu.myqcloud.com/08a9daei1578736867828.png",
        content="还有{0}天就放假".format(i),
        topic_id=1,
        user_id=1)
    
    models.NewsDetail.objects.create(
        key="08a9daei1578736867828.png",
        cos_path="https://mini-1251317460.cos.ap-chengdu.myqcloud.com/08a9daei1578736867828.png",
        news=news_object)

    models.NewsDetail.objects.create(
        key="0d3q0evq1578906084254.jpg",
        cos_path="https://mini-1251317460.cos.ap-chengdu.myqcloud.com/0d3q0evq1578906084254.jpg",
        news=news_object)
    
    
# 注意:
	# 1.Django中,脚本单独运行,要使用 读取Django的配置以及Django.setup()
    
    # 2.sys.path  和 os.environ 的区别:
    	sys.path 是python的package path,是当前脚本的加载目录和模块的查找目录
        	append是临时添加,如果退出当前会话,或者当前的shell,就会消失
        
        os.environ 是一系列的键、值对存储的字典,操作系统环境信息全部存储在该字典中
        	os.environ['HOMEPATH']         # 当前用户主目录
            os.environ['TEMP']             # 临时目录路径
            os.environ['PATHEXT']          # 可执行文件
            os.environ['SYSTEMROOT']       # 系统主目录
            os.environ['LOGONSERVER']      # 机器名
            os.environ['PROMPT']           # 设置提示符
            os.environ['PATH']             # 系统环境信息

小程序 暂时停止到这里。完成功能:截止到动态详细页 (评论部分的api 和前端 未实现)

作业

  1. 赞文章

  2. 赞评论

  3. 关注

  4. 访问记录

    进入详细页面时,先判断用户是否已经登录。
    未登录,不操作。
    登录:添加到访问记录中。 
    
posted @   Edmond辉仔  阅读(131)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示