1. 程式人生 > 其它 >06--功能開發--動態釋出頁、首頁、動態詳情頁02

06--功能開發--動態釋出頁、首頁、動態詳情頁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 和前端 未實現)

作業

  1. 贊文章

  2. 贊評論

  3. 關注

  4. 訪問記錄

    進入詳細頁面時,先判斷使用者是否已經登入。
    未登入,不操作。
    登入:新增到訪問記錄中。