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-10、11-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 和前端 未实现)
作业
-
赞文章
-
赞评论
-
关注
-
访问记录
进入详细页面时,先判断用户是否已经登录。 未登录,不操作。 登录:添加到访问记录中。
分类:
微信小程序
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库