django--BBS項目,后端業務邏輯整理


經典的生活價值觀

別讓人生,輸給了心情。心情不是人生的全部,卻能左右人生的全部。心情好,什么都好,心情不好,一切都亂了。我們常常不是輸給了別人,而是壞心情貶低了我們的形象,降低了我們的能力,擾亂了我們的思維,從而輸給了自己。

控制好心情,生活才會處處祥和。好的心態塑造好心情,好心情塑造最出色的你。

靜靜的過自己的生活,心若不動,風又奈何。你若不傷,歲月無恙。

BBS 項目開發邏輯梳理

第一步:先進行數據庫設計

數據庫設計規則是:
	1.先創建基表:用戶表、站點表、文章表、標簽表、分類表、文章2標簽第三張關系表、點贊點踩表、評論表
    2.書寫表中的基本含有的字段
    3.添加外鍵(一對一,一對多,多對多)
    4.第三張關系表
注意事項:創建外鍵關系的時候,to='表名',不要忘記引號,null=true,並不是所有的外鍵都加的

第二步settings配置

一定要進行settings的相關配置:
	1.數據庫配置
    DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'bbszikao',
        'USER':'root',
        'PASSWORD': 'root',
        'HOST':'127.0.0.1',
        'PORT':3306,
        'CHARSET':'utf8'
    }
}
	
    2.靜態文件資源配置
    STATICFILES_DIRS=[
        os.path.join(BASE_DIR,'static')
    ]
    
    3.models.py文件中,用戶表繼承AbstractUser類,需要對其進行settings配置
    from django.contrib.auth.models import AbstractUser
    配置auth模塊的訪問:
    AUTH_USER_MODEL='app01.Userinfo'
    
    4.靜態圖片資源settings配置+urls路由訪問配置,暴露給用戶查看,用戶注冊可以訪問到默認頭像
    #settings文件配置:
    MEDIA_ROOT=os.path.join(BASE_DIR,'media')
    #urls文件訪問頭像路由配置
    url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),

第三步功能開發

注冊功能

后端開發邏輯:
分為:register函數+register.html+myform.py文件
后端開發邏輯:
# myform.py 文件
	1.建立myform.py文件,利用forms表單,提交注冊的數據信息
    創建class MyRegForm(forms.Form):類
        用戶名,密碼,確認密碼,郵箱
from django import forms
from app01 import models

class MyRegForm(forms.Form):
    username = forms.CharField(max_length=8,min_length=3,label='用戶名',
                               error_messages={
                                   'max_length': '用戶名最長8位',
                                   'min_length': '用戶名最短3位',
                                   'required': "用戶名不能為空"
                               },
                     #標簽設置
                     widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
                               
    建立局部鈎子函數校驗用戶名是否存在
    # 局部鈎子 校驗用戶名是否已存在
    def clean_username(self):
        username = self.cleaned_data.get('username')
        res = models.Userinfo.objects.filter(username=username)
        if res:
            self.add_error('username','用戶名已存在')
        return username  
                               
                               
    建立全局鈎子函數校驗密碼是否一致
    # 全局鈎子 校驗兩次密碼是否一致
    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if not password == confirm_password:
            self.add_error('confirm_password','兩次密碼不一致')
        return self.cleaned_data

                               
# models.py 文件    
    2.把myform文件中的MyRegForm表單對象拿到,
    然后把form_obj對象發送給前端register.html頁面
    form_obj=myform.MyRegForm()
    return render(request,'register.html',locals())

后端開發邏輯如下:
    把myform文件中的MyRegForm表單對象拿到
    判斷前端發送過來的請求方式是不是post請求
    定義back_dic 字典
    對用戶在前端提交的post數據進行校驗,生成form_obj對象
    如果數據合法:
          獲取所有鍵值對
          pop掉確認密碼鍵值對
          獲取前端發送的文件請求(avatar),創建文件對象
          判斷用戶是否上傳文件:
              上傳文件了,把avatar添加到clean_data字典對象中
          用戶表創建用戶(create_user)
          back_dic字典添加msg信息,注冊成功
          back_dic字典添加url,login路徑
    
    數據不合法:
          字典添加code=2000  
          字典添加msg=form_obj.errors
    
    返回json數據到前端,[需要導入JsonResponse模塊(from django.http import JsonResponse)]
    
    然后把form_obj對象發送給前端register.html頁面

前端開發邏輯:

上傳頭像文件功能,注冊按鈕功能

前端開發邏輯:register.html文件,前端只整理需要整理的
上傳頭像文件功能
<script>
    //上傳頭像文件相關的處理
    $('#mdd').on('change',function () {
        //利用內置對象filereader完成文件的讀取操作
        let MyFileReader=new FileReader();
        //獲取用戶上傳的文件對象
        let fileobj=$(this)[0].files[0];
        //讓文件閱讀器讀取文件,IO操作,異步
        MyFileReader.readAsDataURL(fileobj);
        //將讀取之后的內容替換到img標簽src屬性中
        MyFileReader.onload=function () {
            $('#img').attr('src',MyFileReader.result)
        }
    });
    
    

        // 注冊按鈕
    $('#submit').click(function () {
        // 將用戶輸入的數據全部發送給后端     普通的鍵值對   文件
        let MyFormData = new FormData();
        // 不停的朝里面添加鍵值對
        {#MyFormData.append('','')#}
        {#console.log($('#myform').serializeArray())#}
        // 普通鍵值對添加完畢 (利用form標簽內部有一個自動序列化普通鍵值對方法)
        $.each($('#myform').serializeArray(),function (index,obj) {
            MyFormData.append(obj.name,obj.value)
        });
        // 手動添加文件數據
        MyFormData.append('avatar',$('#mdd')[0].files[0]);
        // 發送ajax請求
        $.ajax({
            url:'',
            type:'post',
            data:MyFormData,
            // 發送文件一定要指定兩個參數
            processData:false,  // 不要讓瀏覽器處理你的數據
            contentType:false,  // 不要使用任何的編碼  django能夠識別對象自身

            success:function (data) {
                if (data.code == 1000){
                    // 跳轉到登錄頁面
                    window.location.href = data.url
                }else{
                    $.each(data.msg,function (index,obj) {
                        {#console.log(index,obj)#} // index就是報錯字段  obj就是錯誤信息 數組的形式
                        // 獲取報錯字段  手動拼接出該字段所對應的input框的id值
                        let targetId = '#id_' + index;
                        $(targetId).next().text(obj[0]).parent().addClass('has-error')
                    })
                }
            }
        })

    });
    // input框獲取焦點事件,---這個是鼠標放到input框上面后,錯誤信息消失
    $('input').focus(function () {
        $(this).next().text('').parent().removeClass('has-error')
    })

</script>


圖片驗證碼功能

后端開發邏輯

1.所需要的模塊
import random
from PIL import Image,ImageDraw,ImageFont
from io import BytesIO,StringIO
'''
內存管理器模塊:
BytesIO 保存數據,並且在獲取的時候,是以二進制的方式給你
StringIO 保存數據,並且在獲取的時候,是以字符串的方式給你

Image       生成圖片
ImageDraw   在圖片上寫字
ImageFont   控制字的字體樣式

'''
#io_obj=BytesIO()    #你就將該對象看成是文件句柄即可
    '''
 什么是文件句柄???
    在文件I/O中,要從一個文件讀取數據,應用程序首先
    要調用操作系統函數並傳送文件名,並選一個到該文件
    的路徑來打開文件。該函數取回一個順序號,即文件句柄
    (file handle),該文件句柄對於打開的文件是唯一的
    識別依據。要從文件中讀取一塊數據,應用程序需要調用
    函數ReadFile,並將文件句柄在內存中的地址和要拷貝的
    字節數傳送給操作系統。當完成任務后,再通過調用系統函數
    來關閉該文件。”
    '''
2.圖片驗證碼的開發分為兩步:
	隨機取色+圖片驗證碼
    2.1隨機取色
    def get_random():
    	return random.randint(0,255),random.randint(0,255),random.randint(0,255)
    
    2.2圖片驗證碼函數
    	圖片的寬高和隨機取色---生成畫板對象   Image.new
        將生成好的圖片對象交給ImageDraw---畫筆對象   ImageDraw.Draw
        字體樣式	---何種字體   ImageFont.truetype
        #隨機驗證碼	---何種要求(大小寫英文加數字,5位)
        定義code=''
        循環5次:
        	大寫字母		upper_str
            小寫字母		lower_str
            str(隨機數)	 random_int
            
            隨機選取一個,random.choice([大寫,小寫,str(隨機數)])
            往圖片上寫一個驗證碼		img_draw.text
            存儲寫的字				code+=tmp
        將code存到session中,供全局函數訪問			request.session['code']=code
        生成I/O文件句柄							io_obj=BytesIO()	
        圖片對象調用save方法保存io_obj文件對象,png的格式進行保存								img_obj.save(io_obj,'png')
        return HttpResponse(io_obj.getvalue())
        
 --------------------------------------------------------------------------------        
3.圖片驗證碼代碼如下:
def get_code(request):
    # 圖片的寬高和隨機取色        ----畫板
    img_obj=Image.new('RGB',(360,35),get_random())
    #將生成好的圖片對象交給ImageDraw   ---畫筆
    img_draw=ImageDraw.Draw(img_obj)
    #字體樣式                       ---何種字體
    img_font=ImageFont.truetype('static/font/111.ttf',30)
    #隨機驗證碼      ---何種要求(大小寫英文加數字,5位)
    code=''
    for i in range(5):
        upper_str=chr(random.randint(65,90))
        lower_str=chr(random.randint(97,122))
        random_int=str(random.randint(0,9))
        #隨機選取一個
        tmp=random.choice([upper_str,lower_str,random_int])
        #往圖片上寫一個驗證碼
        img_draw.text((i*60+60,0),tmp,get_random(),img_font)
        #存儲寫的字
        code+=tmp
    print(code)
    #這個驗證碼后面其他視圖函數可能要用到,
    #找個地方存一下,並且這個地方全局的視圖函數都能訪問
    request.session['code']=code
    io_obj=BytesIO()    #你就將該對象看成是文件句柄即可
    '''
    在文件I/O中,要從一個文件讀取數據,應用程序首先
    要調用操作系統函數並傳送文件名,並選一個到該文件
    的路徑來打開文件。該函數取回一個順序號,即文件句柄
    (file handle),該文件句柄對於打開的文件是唯一的
    識別依據。要從文件中讀取一塊數據,應用程序需要調用
    函數ReadFile,並將文件句柄在內存中的地址和要拷貝的
    字節數傳送給操作系統。當完成任務后,再通過調用系統函數
    來關閉該文件。”
    '''
    img_obj.save(io_obj,'png')
    return HttpResponse(io_obj.getvalue())

    



圖片驗證碼實時刷新功能

前端開發邏輯

圖片驗證碼隨機變化的邏輯其實很簡單,就是:
    首先為驗證碼綁定點擊事件,
        其次拿到img中的src屬性,
        最后為src設置新的值,使得圖片驗證碼不斷的更換


<div class="col-md-6">
	<img src="/get_code/" alt="" width="360" height="35" id="id_img">
</div>

<script>
    //拿到img中的src,
    //然后為src設置新的值,使得圖片驗證碼不斷的更換
    $('#id_img').click(function () {
        var oldPath = $(this).attr('src');
        $(this).attr('src',oldPath+='?')
    });

</script>

登錄功能

后端開發邏輯

urls.py文件中:
	# 登錄功能
    url(r'^login/',views.login,name='login'),

views.py文件中:
開發邏輯如下:

如果請求方式是post請求:
	定義back_dic
    獲取post請求的用戶名
    獲取post請求的密碼
    獲取post請求的驗證碼
    #先校驗驗證碼是否正確,忽略大小寫
    #再校驗用戶名和密碼是否正確
    如果用戶輸入的驗證碼和后端保存的驗證碼相等:
    	再用auth模塊校驗用戶名和密碼是否相等,生成用戶對象
        如果相等:
        	利用auth模塊保存用戶對象的登錄狀態
            #就可以在任意位置通過request.user獲取到當前登錄對象,並且request.user.is_authenticated()判斷當前用戶是否登錄
            back_dic字典添加msg登錄成功
            back_dic字典添加url,home主頁路徑
        
        如果用戶名和密碼不相等:
        	back_dic字典添加code=2000
            back_dic字典添加msg用戶名或密碼錯誤
    
    如果驗證碼不相等:
    	back_dic字典添加code=3000
        back_dic字典添加msg,驗證碼錯誤
    通過JsonResponse返回字典
 返回到登錄頁面



# 保存用戶登錄狀態,這個不太會,對該知識點模糊要多注意了,
# 通過auth模塊的login方法將用戶保存起來,就可以在任意位置通過request.user,
# 獲取到當前登錄對象,並且可以通過request.user.is_authenticated()判斷當前用戶是否登錄
auth.login(request, user_obj)
auth模塊很重要需要多復習復習

前端開發邏輯

為登錄按鈕綁定點擊事件,
	利用ajax請求,把數據發送到后端
	ajax的固定格式為:
		$.ajax({
			url:'',
			type:'post',
			data:{k,v鍵值對,'csrfmiddlewaretoken':'{{ csrf_token }}'},	
			success:function(data){
				//此處的data就是后端返回過來的back_dic字典對象
				如果code=1000:
					跳轉到對應頁面的url連接
				如果不等於1000:
					渲染錯誤數據信息
					信息錯誤后自動刷新驗證碼
		}

	})

提交post請求跳過csrf中間件攔截的兩種方式:
{% csrf_token %}	------- 在form表單中書寫
'csrfmiddlewaretoken':'{{ csrf_token }}'	------- 在ajax中的data字典中書寫


<div>
    <input type="button" class="btn btn-primary" value="登錄" id="id_submit">
    <span style="color: red;" id="id_error"></span>  //在此處渲染頁面的錯誤信息
</div>

<script>
$('#id_submit').click(function () {
        $.ajax({
            url:'',
            type:'post',
            data:{
                //$('#標簽名').val()是獲取當前標簽的值
                'username':$('#id_username').val(),
                'password':$('#id_password').val(),
                'code':$('#id_code').val(),
                'csrfmiddlewaretoken':'{{ csrf_token }}'
            },
            success:function (data) {
                if(data.code == 1000){
                    // 跳轉鏈接
                    window.location.href = data.url
                }else{
                    //渲染錯誤數據信息
                    $('#id_error').text(data.msg);
                    //數據填寫錯誤,提交后驗證碼再次刷新
                    var oldPath = $('#id_img').attr('src');
                    $('#id_img').attr('src',oldPath+='?')

                }
            }
        })
    
</script>

首頁搭建功能

后端開發邏輯

#home主頁
第一步:查詢所有文章,生成文章queryset對象,
第二步:把數據全部提交到home頁面

def home(request):
    #查詢所有文章,生成文章queryset對象,
    #把數據全部提交到home頁面
    article_queryset=models.Article.objects.all()
    return render(request,'home.html',locals())

前端開發邏輯

主頁搭建共分為四塊,
	第一塊,導航條
    第二塊,左側面板	2
    第三塊,中間面板	8
    第四塊,右側面板	2

退出登錄功能

后端開發邏輯

導入auth模塊,導入登錄認證裝飾器:
from django.contrib import auth
from django.shortcuts import reverse  ----這個是用來反向解析的模塊
from django.contrib.auth.decorators import login_required
給退出登錄函數添加@login_required裝飾器,裝飾器不要加括號
	利用auth模塊的退出登錄函數			---auth.logout(request)
    返回通過重定向反轉解析到home主頁	  ---redirect(reverse('home'))

    
代碼如下:
@login_required
def logout(request):
    auth.logout(request)
    return redirect(reverse('home'))		---退出登錄之后跳轉到home主頁

前端開發邏輯

用戶登錄情況下:
	展示用戶名:
    	拿到用戶的用戶名,------超鏈接;通過auth模塊的is_authenticated判斷用戶是否已經登錄
        
    修改密碼
    修改頭像
    后台管理
    退出登錄
未登錄情況下:
	展示登錄	--超鏈接,反向解析login,{% url 'login' %}
    展示注冊	--超鏈接,反向解析register,{% url 'register' %}
 

部分邏輯代碼如下:
{% if request.user.is_authenticated %}
<li><a href='#'>  {{  request.user.username	}}	</a></li>
    <li><a data-target="#myModal" data-toggle="modal">修改密碼</a></li>
    <li><a href="#">修改頭像</a></li>
    <li><a href="#">后台管理</a></li>
    <li role="separator" class="divider"></li>
    <li><a href="{% url 'logout' %}">退出登錄</a></li>
{% else %}
    <li><a href="{% url 'login' %}">登錄</a></li>
    <li><a href="{% url 'register' %}">注冊</a></li>
{% endif %}

修改密碼功能

后端開發邏輯

urls.py文件中開設修改密碼的路由

url(r'^set_password/',views.set_password,name='set_pwd')

views.py文件
from django.shortcuts import reverse	----這個是用來反向解析的模塊
from django.contrib import auth
from django.contrib.auth.decorators import login_required
首先給修改密碼函數添加裝飾器@login_required
如果前端請求方式是post請求:
	獲取前端發送過來的原密碼old_password
    獲取前端發送過來的新密碼new_password
    獲取前端發送過來的確認密碼confirm_password
    #利用auth模塊先判斷前端發送過來的原密碼是否正確,
    #check_password是auth模塊自帶的校驗密碼是否相等的功能
    request.user.check_password(old_password)
    如果原密碼正確:
    	如果新密碼和確認密碼相等:
            利用auth模塊中的set_password設置新的密碼
            然后利用auth模塊中的save方法進行保存
            返回重定向解析到login登錄頁面
        
        如果新密碼和確認密碼不相等:
        	返回文本,兩次密碼不一致
    
    如果原密碼不正確:
    	返回文本,原密碼錯誤

        
注意事項:
	因為沒有導入reverse模塊,並且把reverse寫成了reversed導致的bug
    在退出登錄和修改密碼兩處需要用到的反向解析的地方代碼都是寫成reversed導致的項目bug,如下:
	
    報錯信息如下:
    	NoReverseMatch at /set_password
Reverse for '<reversed object at 0x0000000004EA0E48>' not found. '<reversed object at 0x0000000004EA0E48>' is not a valid view function or pattern name.
		翻譯如下:
    		NoReverseMatch在/ set_password

沒有找到'< Reverse object at 0x0000000004EA0E48>'。''不是有效的視圖函數或模式名。
    

前端開發邏輯

利用form表單,發送post請求
    反向解析到set_pwd路徑下,{% url 'set_pwd' %}
    利用{% csrf_token	%}	跳過csrf中間件
    下面就是form表單中的5個div,分別是:
    	用戶名,使用disable屬性,默認展示,用戶名不支持修改
        原密碼
        新密碼
        確認密碼
        取消,修改

admin數據錄入功能

后端開發邏輯

1.創建超級用戶,進入后台管理---Tools---Run manage Task -----createsuperuser

2.去應用下的admin.py文件中注冊你想要管理的模型類
		導入models文件
    	注冊管理的模型類:
        	admin.site.register(models.Userinfo)
            admin.site.register(models.Blog)
            admin.site.register(models.Tag)
            admin.site.register(models.Category)
            admin.site.register(models.Article)
            admin.site.register(models.Article2Tag)
            admin.site.register(models.UpAndDown)
            admin.site.register(models.Comment)
            
3.為表添加中文別名:
      在models.py文件中,對應的8張表中分別添加
            class Meta:
                verbose_name_plural='用戶表'
                #verbose_name='用戶表'	會自動在末尾加s后綴

            class Meta:
                verbose_name_plural='文章表'
                #verbose_name='用戶表'	會自動在末尾加s后綴
            后面依次如下!!!!

4.在模型表中為模型表添加雙下str方法打印對象對應的名字,
雙下str只能返回字符串。
		def __str__(self):
        	return self.username
        
        self點對應表中的字段名
        

5.admin數據錄入:
	順序:
		文章表
        	分類和個人站點
            
        用戶表	----綁定對應的站點,在用戶表中填寫blank=True,
        		blank告訴admin后台管理該字段可以為空,不會影響到數據庫,不用執行遷移命令
        
        標簽表
        文章2標簽表
        

首頁文章展示功能

后端開發邏輯

home函數
查詢當前網站所有的文章,展示到前端頁面上
def home(request):
    #查詢當前網站所有的文章,展示到前端頁面上
    article_queryset = models.Article.objects.all()
    return render(request,'home.html',locals())

前端開發邏輯

前端主要編寫邏輯和主要邏輯代碼如下:
通過for循環article_queryset把文章一篇篇的都讀取出來,全部展示到前端頁面
{% for article in article_queryset	%}

	文章的標題,利用跨表查詢	
    <a href="#">
    	{{ article.title }}
    </a>
    
    文章頭像,利用跨表查詢
    <img class='media-object' src='/media/{{article.blog.userinfo.avatar}}/' height='60'>
    
    文章簡介
    <div class='media-body'> {{	article.desc }}	</div>
    
    文章情況包括:
    	用戶名(軟件老王)
        <span><a href='#'>{{article.blog.userinfo.username}}</a></span>
        
        發布於
        <span>發布於&nbsp;&nbsp;</span>
        
        日期(年月日),需要用到日期過濾器
        <span>{{ article.create_time|date:'Y-m-d' }}</span>
        
        評論數(0)
        <span>{{ article.comment_num }}</span>
        
        點贊數(0)
        <span>{{ article.up_num	}}</span>
        

用戶頭像展示功能

后端開發邏輯

settings.py文件

media配置,能夠將用戶上傳的所有的文件都統一保存在指定的文件夾下
MEDIA_ROOT=os.path.join(BASE_DIR,'media')

urls.py文件
手動開設后端資源,將media文件夾下面所有的資源暴露給外界訪問------固定寫法規則,背下來
導入serve模塊,導入settings配置文件
from django.views.static import serve
from BBS import settings
#謹慎使用
url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})

前端開發邏輯

文章頭像,利用跨表查詢
    <img class='media-object' src='/media/{{article.blog.userinfo.avatar}}/' height='60'>
    

圖片防盜鏈,404頁面功能

后端開發邏輯

urls.py文件

配置個人站點的路由
#個人站點
    url(r'^(?P<username>\w+)/$',views.site,name='username')

views.py文件
定義站點函數,接收有名分組username
	通過username查詢用戶對象
    如果用戶對象不存在:
    	返回404頁面

代碼如下:
def site(request,username):
    user_obj=models.Userinfo.objects.filter(username=username).first()
	if not user_obj:	#404頁面
        return render(request,'error.html')

圖片防盜鏈
	通過判斷當前請求之前的所在地址
    如果是本網站的鏈接正常訪問,如果不是本網站的鏈接就直接禁止
    如果查看呢?
    	通過請求頭里面來查看,
        refer	---表示你從哪里來的
        user-agent	---標識你是否是一個瀏覽器
        

前端開發邏輯

直接在博客園404頁面右鍵檢查打開,復制代碼,拷貝到BBS項目中的error.html頁面

返回網站首頁,通過反向解析設置到主頁
{% url 'home' %}

------------------------------------------------下面的還沒有進行錄音工作---------------------------------------------------------

個人站點頁面搭建

后端開發邏輯

urls.py文件

配置個人站點的路由
#個人站點
    url(r'^(?P<username>\w+)/$',views.site,name='username')

views.py文件
定義站點函數,接收有名分組username
	通過username查詢用戶對象   
    如果用戶對象不存在:
    	返回404頁面
        
	通過用戶對象查詢到該用戶的站點
    查詢該用戶站點下的所有文章
    返回數據到site頁面

代碼如下:
def site(request,username):
    #通過username查詢用戶對象   
    user_obj=models.Userinfo.objects.filter(username=username).first()
    
    #如果用戶對象不存在:
    #	返回404頁面
    if not user_obj:
        return render(request,'error.html')
    #通過用戶對象查詢到該用戶的站點
    blog=user_obj.blog
    # 查詢當前用戶站點下的所有文章
    article_list=models.Article.objects.filter(blog=blog)
    
    #返回數據到site頁面
    return render(request,'site.html',locals())

--------------------------------------------------------------------------------------------------------------------------------------------------------------------
只需一招讓你分清QuerySet對象,和用戶字典對象

article_list = models.Article.objects.filter(blog=blog)
user_obj = models.Userinfo.objects.filter(username=username).first()
上面的兩個查詢不太懂
#article_list是可迭代的QuerySet對象,支持for循環
#user_obj這個是用戶字典對象,不支持for循環
所有支持for循環的數據類型必須是可迭代數據類型!!!
# 查詢當前用戶站點下的所有文章
    # article_list = models.Article.objects.filter(blog=blog).first()
    #所有支持for循環的數據類型必須是可迭代數據類型,
    #字典不可迭代,所以for循環取值會報錯:TypeError: 'Article' object is not iterable,
    #所以article_list必須是可以迭代的對象,QuerySet對象可以迭代,是可迭代對象,
    #那么就要把.first()給去掉

------------------這里是通過E盤自己練習的day53課件自己摸索總結出來的---------------------------
# 2.filter() 得到可迭代的QuerySet對象,支持for循環取容器內的元素,在django中推薦使用
    # 篩選,相當於你原生sql語句里面的where關鍵字,返回的結果queryset對象
    res=models.Books.objects.filter(pk=1,title='張三豐') #支持多個參數,and關系
    print(res)      #<QuerySet [<Books: 張三豐>]>
    print('************************')

# 3.get()    在django中不推薦使用
    # 篩選,獲取的是數據對象本身,條件不存在的數據直接報錯,並且查詢條件必須是唯一的
    res=models.Books.objects.get(title='西游記')
    print(type(res))      #如果是數據庫中不存在的數據進行查詢的話,會報錯,因為數據庫中沒有該數據

    <QuerySet [<Books: 張三豐>]>
    ************************
    <class 'app01.models.Books'>
    //////////////////////////
    (0.001) SELECT `app01_books`.`id`, `app01_books`.`title`, `app01_books`.`price`, `app01_books`.`kucun`, `app01_books`.`maichu` FROM `app01_books` WHERE `app01_books`.`price` = 766 LIMIT 21; args=(Decimal('766'),)
    <QuerySet [<Books: 西游記>, <Books: 西游記2>]>
    <class 'django.db.models.query.QuerySet'>
    ************************
    西游記
    1000
    <class 'app01.models.Books'>
    (0.001) SELECT `app01_books`.`id`, `app01_books`.`title`, `app01_books`.`price`, `app01_books`.`kucun`, `app01_books`.`maichu` FROM `app01_books` WHERE `app01_books`.`price` = 766 ORDER BY `app01_books`.`id` ASC LIMIT 1; args=(Decimal('766'),)

////////////////////////////////////////////////////////////////////////////// 
////////////////////////////////////////////////////////////////////////////// 
////////////////////////////////////////////////////////////////////////////// 
   
# 4.first()
    # 功能一:取queryset 相同數據中的第一個數據對象,
    重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點重點
    					
        						特殊功能如下:
        
    還有一個特殊的功能可以把QuerySet對象轉換為字典對象,方便字典通過對象點屬性取值----88顆星的重點
    # id=3,title='西游記'  id=5,title='西游記',first取值會優先取id=3的數據
    res = models.Books.objects.filter(price='766')
    print(res)
    print(type(res))

    print('************************')

    res = models.Books.objects.filter(price='766').first()
    print(res)
    print(res.kucun)
    print(type(res))

'''上面的打印結果如下:
    < QuerySet[ < Books: 西游記 >, < Books: 西游記2 >] >
    <class 'django.db.models.query.QuerySet'>

    ************************

    西游記
    1000
    <class 'app01.models.Books'>
'''










前端開發邏輯

知道怎么回事即可!
site.html個人站點頁面
頁面布局3,9
<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            {% load mytag %}
            {% my_menu username %}
        </div>
        <div class="col-md-9">
            {% block content %}

            {% endblock %}
        </div>
    </div>
</div>

模板繼承
{% extends 'base.html' %}

循環展示個人站點下的所有文章
 {% for article in article_list %}
#posted @ 2019-09-28 17:42 武沛齊 閱讀 (2714) 評論 (4) 編輯#
<br>
<div class="pull-right">
    <span>posted&nbsp;&nbsp;</span>
    <span>@&nbsp;&nbsp;</span>
    <span>{{ article.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span>
    <span>{{ article.blog.userinfo.username }}&nbsp;&nbsp;</span>

    <span>評論數({{ article.comment_num }})&nbsp;&nbsp;</span>
    <span>點贊數({{ article.up_num }})</span>
    <span><a href="#">編輯</a></span>
</div>

個人站點側邊欄展示功能

后端開發邏輯

共分為三塊文章分類,文章標簽和日期歸檔
通過orm查詢和django官方提供的日期處理方法來實現
 '''
    側邊欄篩選的功能到底是篩選什么?
    篩選的是當前這個用戶下面的所有的文章,再進行標簽、分類、日期進行篩選
    本質就是對已經查詢出來的article_list再進行篩選操作。
    
    '''
    1.查詢當前用戶每一個分類和分類下的文章數
    category_list=models.Category.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name','num')
    print(category_list)        #<QuerySet [('luzhaoshan的分類1', 1)]>

    2.查詢當前用戶每一個標簽和標簽下的文章數
    tag_list=models.Tag.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name','num')
    print(tag_list)             #<QuerySet [('luzhaoshan的標簽1', 1)]>

    3.按照年月分組
    date_list=models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(num=Count('pk')).values_list('month','num')
    print(date_list)            #<QuerySet [(datetime.date(2019, 12, 1), 1)]>
    備注:(2019,12,1)中的1是django中默認含有的

    通過return返回到前端site.html頁面,因為獲取到的值都是列表套元組,
    在前端可以通過列表取值的方式,取索引0是名字,取索引1是對應的值。
    

前端開發邏輯

 后端通過return返回到前端site.html頁面,因為獲取到的值都是列表套元組,
    在前端可以通過列表取值的方式,取索引0是名字,取索引1是對應的值。
分為三塊,文章分類,文章標簽,日期歸檔
#<QuerySet [('luzhaoshan的分類1', 1)]>
    
    <h3 class="panel-title">文章分類</h3>
    {% for category in category_list %}
    	 <p><a href="#">{{ category.0 }}</a>({{ category.1 }})</p>
    {% endfor %}
-----------------------------------------------------------------
 #<QuerySet [('luzhaoshan的標簽1', 1)]>
    
 	<h3 class="panel-title">文章標簽</h3>
    {% for tag in tag_list %}
    	 <p><a href="#">{{ tag.0 }}</a>({{ tag.1 }})</p>
    {% endfor %}
--------------------------------------------------------------------
#<QuerySet [(datetime.date(2019, 12, 1), 1)]>
備注:(2019,12,1)中的1是django中默認含有的

 	<h3 class="panel-title">日期歸檔</h3>
	{% for date in date_list %}
    	 <p><a href="#">{{ date.0|date:'Y年 - m月' }}</a>({{ date.1 }})</p>
    {% endfor %}


個人站點側邊欄篩選功能

后端開發邏輯

urls.py文件

個人站點側邊欄篩選功能
url(r'^(?P<username>\w+)/category/(?P<param>\d+)/',views.site),
url(r'^(?P<username>\w+)/category/(?P<param>\d+)/',views.site),
url(r'^(P<username>\w+)/category/(?P<param>\d+)',views.site),


優化如下:
url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>)',views.site)

views.py文件
通過關鍵字參數(**kwargs)來接收有名分組
所以,site函數中使用:
def site(request,username,**kwargs):
    user_obj = models.Userinfo.objects.filter(username=username).first()

    if not user_obj:  # 404頁面
        return render(request, 'error.html')
    blog = user_obj.blog
    # 有當前用戶所有的文章
    article_list = models.Article.objects.filter(blog=blog)
    if kwargs:			#如果關鍵字參數有值,則可以按照條件跳轉對應的分類或者標簽或者日期歸檔
        '''
        側邊欄篩選的功能到底是篩選什么?
        篩選的是當前這個用戶下面的所有的文章,再進行標簽,分類,日期篩選,
        本質其實就是對該站點進行一個再細分的細化。
        '''
		condition=kwargs.get('condition')
        param=kwargs.get('param')
        if condition=='category':
            #按照分類篩選
            article_list=article_list.filter(category_id=param)
            #按照標簽篩選	,
      注意事項: 
    		注意雙下划線是在本表中的字段通過雙下跨表到另外一張表查詢 
   			tags是文章表中的外鍵字段,通過雙下划線跨表到tag分類表中
            
            ——————————————————————————————————————————————————————————————————————————————————
            雙下划線在本表中【普通的字段】進行查詢,叫母表查詢,
            雙下划線通過本表中的【外鍵字段】查詢是跨表查詢,是跨到另外一張表中
 ——————————————————————————————————————————————————————————————————————————————————            
            tag_list=article_list.filter(tag__id=param)	#雙下划線條件查詢,查詢id=param的數據
            #按照年月日期篩選
            year,month=param.split('-')
            #__year   查詢年份 	__month   查詢月份			#雙下划線查詢	
            				            article_list=article_list.filter(create_time__year=year,create_time__month=month)
            


前端開發邏輯

	<div class="panel-heading">
    	<h3 class="panel-title">文章分類</h3>
    </div>
    <div class="panel-body">
        {% for category in category_list %}
        <p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }}</a>({{ category.1 }})</p>
        {% endfor %}
    </div>

    
 ——————————————————————————————————————————————————————————————————————————————————     
    
    
    <div class="panel-heading">
    	<h3 class="panel-title">文章標簽</h3>
    </div>
    <div class="panel-body">
        {% for tag in tag_list %}
        <p><a href="/{{ username }}/tag/{{ tag.2 }}">{{ tag.0 }}</a>({{ tag.1 }})</p>
        {% endfor %}
    </div>

    
 ——————————————————————————————————————————————————————————————————————————————————     
    
    <div class="panel-heading">
    	<h3 class="panel-title">日期歸檔</h3>
    </div>
    <div class="panel-body">
        {% for date in date_list %}
        <p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}">{{ date.0|date:'Y年 - m月' }}</a>({{ date.1 }})</p>
        {% endfor %}
    </div>
    
 —————————————————————————————————————————————————————————————————————————————————— 


側邊欄制作

后端開發邏輯

    ### 側邊欄制作
    開發邏輯:
    1.根據文章id,查詢出對應的文章,展示到前端即可
    2.前端用到了模板的繼承,把site.html抽出來做成模板
    3.創建base.html頁面繼承site.html頁面,然后對9的部分進行調整,
        3.1划定區域通過
            {% block content %}
            aa-這里是頁面內容
            {% endblock %}

        3.2然后原來的site.html頁面,就繼承base.html頁面
            {% extends 'base.html' %}
            {% block content %}
            然后把aa的內容刪除掉,放在這個地方
            {% endblock %}

        3.3來到article_detail.html頁面
            繼承base.html頁面
            {% extends 'base.html' %}

            {% block content %}
            這里面是把文章拿過來:
                文章標題,
                文章內容,
            {% endblock %}

    4.自定義標簽過濾器
        4.1創建templatetags文件,和文件下的mytag.py文件
            寫上:
                from django import template
                register=template.Library()
                @register.inclusion_tag('left_menu.html')
                然后創建left_menu.html頁面,里面內容全刪除掉,空頁面

        4.2繼續創建left_menu函數
            def left_menu(username):
            然后把site個人站點下的下面的代碼剪切到這里,目的就是為了方便多次調用
                user_obj = models.Userinfo.objects.filter(username=username).first()
                    把用戶對象拿過來
                 # 1.查詢當前用戶每一個分類和分類下的文章數
                category_list = models.Category.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name', 'num',
                                                                                                                     'pk')
                print(category_list)  # <QuerySet [('luzhaoshan的分類1', 1)]>

                # 2.查詢當前用戶每一個標簽和標簽下的文章數
                tag_list = models.Tag.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name', 'num', 'pk')
                print(tag_list)  # <QuerySet [('luzhaoshan的標簽1', 1)]>

                # 3.按照年月分組
                date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
                    'month').annotate(num=Count('pk')).values_list('month', 'num')
                print(date_list)  # <QuerySet [(datetime.date(2019, 12, 1), 1)]>   備注:(2019,12,1)中的1是django中默認含有的

        4.3通過return locals(),把當前所有的內容全部返回到left_menu.html頁面
            然后去base.html頁面把3部分中的內容剪切拿過來,放到left_menu.html頁面中
            然后base.html頁面3的部分中寫上:
                {% load mytag %}
                {% left_menu username %}

        ### 點贊點踩樣式拷貝
        開發邏輯
        拷貝圖標到本地,防止圖片防盜鏈,導致圖片加載失敗
        拷貝點贊點踩的樣式

        ### 點贊點踩的業務邏輯
        1.校驗用戶是否登錄,沒有登錄的情況下,不可點贊和點踩,提示請先登錄
        2.登錄的用戶,不能給自己的文章點贊和點踩
        3.已經給該文章點過贊和點過踩的用戶,不能再點
        4.不能給自己點贊和點踩
        文章詳情頁:
        點贊點踩通過ajax請求發送給后端,都給點贊和點踩按鈕綁定相同的action,
        然后通過綁定點擊事件實現點贊和點踩的數量增加。
        $('.action').click(function(){
            如何區分你點的是贊還是點的是踩?
            $(this) 是當前被點擊的這個對象
            $(this).hasClass('diggit') 判斷當前這個點擊的對象有沒有'diggit'這個屬性
            為了提高代碼的閱讀性,專門為點贊點踩開設一個url
            通過ajax發送請求到后端
            $.ajax({
                url:'/up_down',
                type:'post',
                data:{
                    'csrfmiddlewaretoken':'{{csrf_token}}',
                    'is_up':isUp,
                    'article_id':"{{article_obj.pk}}"
                },
                success:function(data){
                    現在就可以在updown視圖函數中進行相關的邏輯處理了
                }
            })

        })

        進入到updown函數中:
    

點贊點踩業務邏輯

updown函數中:

     直接判斷過來的請求是不是ajax請求,如果是ajax請求的話:
                再判斷請求的方法是不是POST請求,如果是POST請求的話:
                    back_dic={'code':1000,'msg':''}
                    is_up=request.POST.get('is_up')     是一個字符串格式的json數據
                    article_id=request.POST.get('article_id')
                    import json
                    is_up=json.loads(is_up)     是將json格式的字符串格式數據,轉成python對應的格式boolean布爾類型

                    下面是點贊點踩業務邏輯:
                    1.校驗用戶是否登錄,沒有登錄的情況下,不可點贊和點踩,提示請先登錄
                    if request.user.is_authenticated():
                        2.當前這篇文章是不是當前登錄用戶自己寫的
                        通過拿到當前站點所對應的的用戶對象  是否等於 當前登錄的用戶對象
                        article_obj=models.Article.objects(pk=article_id).first()
                        如果兩個對象不相等的話,代表該文章不是該登錄用戶寫的:
                        if not article_obj.blog.userinfo  ==  request.user:
                            3.那么,我們繼續判斷當前這篇文章用戶是否已經點過
                            到點贊點踩表中去查詢數據即可
                            篩選數據,篩選用戶是當前用戶對象,並且文章對象是當前文章對象的數據,
                            如果有值表示已經點過了,如果沒有值表示還沒有點過。
                            通過.exists()返回布爾類型,TRUE還是false
                            is_click=models.UpAndDown.objects.filter(user=request.user,article=article_obj).exists()
                            if not is_click:    如果沒有點過,下面就要開始操作數據庫,完成數據修改了
                                操作數據庫完成數據修改
                                if is_up:
                                    如果為true,為點贊數量加1,up_num=原來的數量基礎上加1,通過拿字段我們使用F查詢
                                    導入from django.db import F
                                    models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') +1 )
                                    back_dic['msg'] = '點贊成功'
                                else:
                                    models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') +1 )
                                    back_dic['msg]='點踩成功'
                                然后操作點贊點踩表插入數據庫中,為每一個字段附上值
                                models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up)

                    下面是點贊點踩的業務邏輯完善
                        后端和前端交互是ajax,那么后端需要定義一個字典
                        back_dic={'code':1000,'msg':''}放在上面
                            下面是業務完善:
                            如果點過了,就不能再點了
                            else:
                                back_dic['code'] = 2000
                                back_dic['msg'] = '你已經點過了,不能再點了'
                        else:
                            back_dic['code'] = 3000
                            back_dic['msg'] = '你不能自己給自己點贊'
                    else:
                        back_dic['code'] = 4000
                        back_dic['msg'] = '請先<a href="/login/">登錄</a>'
                        ### 由於請先登錄是一個超鏈接,那么我們在后端可以添加a標簽,然后后端取消轉義即可
                        ### 后端取消轉義導入模塊 from django.utils.safestring import mark_safe
                        ### 然后通過mark_safe包裹一下,那么最后優化如下所示:
                        back_dic['msg'] = mark_safe('請先<a href="/login/">登錄</a>')
                    再通過return JsonResponse把字典數據返回給前端
                    return JsonResponse(back_dic)
                    5.操作數據庫,完成數據修改
                        1.點贊點踩添加數據的時候,文章表里面對應的三個普通字段也得修改
         然后下面就到了article_detail.html頁面,用來處理后端發送過來的請求:
                        success: function (data) {
                            if (data.code == 1000) {
                                // 給span標簽渲染信息
                                $('.info').text(data.msg);
                                // 點贊或點踩成功之后 應該將前端的數字也加一
                                let $span = $target.children();
                                let oldNum = $span.text();
                                $span.text(Number(oldNum) + 1)  // 轉整型相加 不然就是字符串拼接了
                            } else {
                                $('.info').text(data.msg)
                            }
                            

評論功能之根評論

    #### 評論功能之根評論
    如果請求是ajax請求:
        請求方式是POST:
            定義back_dic字典,back_dic={'code':1000,'msg':'' }
            獲取文章article_id,
            獲取評論的內容,
            獲取parent_id,
            開啟事務,from django.db import transaction
            with transaction.atomic():
                models.Comment.objects.create(user=request.user,article_id=article_id,content=content,parent_id=parent_id)
                繼續操作Article表中的comment_num字段,數量加 1
                models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1,parent_id=parent_id)
                back_dic['msg'] = '評論成功'
            return JsonResponse(back_dic)
            后面就是前端上的處理了,這里不做總結,詳情可以去看前端代碼

    ### 評論功能之子評論
    前端點擊回復按鈕到底發生了幾件事?   詳情看前端代碼
        1.自動獲取當前點擊評論的評論人
        2.拼接 @ + 人名 + \n
        3.將拼接好的內容添加到評論框中,並且評論框自動聚焦
        


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM