項目地址:https://github.com/SkyOceanchen/NB--BBScss
需求分析
架構師+產品經理+開發組組長
在去客戶公司談需求以前,先事先估摸着這個項目應該怎麼作
裏面有哪些坑的點 提早想比如較簡單的解決方案
在跟客戶談的時候 有意識的引導客戶朝着你已經想好的方案上去提需求html
項目設計
架構師乾的活
項目的報價(每一個程序員按照人頭 天天2000+左右)
語言的選擇
框架的選擇
數據庫的選擇(主庫用什麼 緩存庫)
功能劃分
開發部開發組長開會分發任務前端
分組開發
架構師和開發組長將項目總體的框架搭建出來
而後讓小組成員各自朝着各個部分填寫代碼便可python
測試
顯而易見的bug若是你本身沒有發現,測試部分的若是發現了 你可能就會面臨扣績效的場面
基本薪資 6000 扣1050
崗位津貼 4000
績效 2000git
....程序員
交付上線
交給大家公司的運維人員或者是客戶公司的運維人員github
我的站點表ajax
文章標籤表 標籤與我的站點一對多正則表達式
文章分類表 分類與我的站點是一對多數據庫
文章和我的站點 是一對多
文章與標籤是多對多的關係
文章與分類是一對多
用來存哪一個用戶給哪篇文章點了贊仍是點了踩
user 一對多用戶表
article 一對多文章表
is_up 普通字段
本質:
一張表中的一條數據可否對應另一張表的多條數據
另一張表的一條數據可以對應當前的表多條件
user_id article_id is_up
1 1 1
1 2 0
1 3 1
2 1 1
記錄哪一個用戶給哪篇文章評論了哪些內容
user 一對多用戶
article 一對多文章
content
parent 一對多評論表 自關聯
to='Comment'
to='self'
id user_id article_id parent_id
1 1 1
2 2 1 1
3 3 1 1
表 七張表
用戶表
擴展auth_user表
phone 電話號碼
avatar 用戶頭像
create_time 註冊時間
DateField(auto_now_add=True)
blog 一對一我的站點
我的站點表
站點名稱
站點標題
站點樣式
文章標籤表
標籤名字
blog 一對多我的站點
文章分類表
分類名字
blog 一對多我的站點
文章表
文章標題
文章摘要
文章內容
建立時間
1.一對多我的站點
2.多對多標籤
3.一對多分類
評論數 點贊數 點踩數 確實能夠經過跨表查詢得出 走數據庫頻率過高
評論數 普通字段
點贊數 普通字段
點踩數 普通字段
文章與標籤的第三張表
article 一對多
tag 一對多
點贊點踩表
記錄哪一個用戶給哪篇文章點了贊仍是點了踩
user 一對多
article 一對多
is_up 普通字段 0/1
評論表
記錄哪一個用戶給哪篇文章評論了上面內容
根評論
子評論
user 一對多
article 一對多
content 普通字段
create_time
parent 用來表示當前評論是不是跟評論仍是子評論 沒值就是根評論 有值 值就是根評論的主鍵值
自關聯
self
數據庫同步
avatar = models.FileField(upload_to='avatar/',default='avatar/default.png')
is_up = models.BooleanField() # 傳True/False 自動存成0/1
content = models.TextField() # 大段html內容
comment_num = models.BigIntegerField(default=0)
up_num = models.BigIntegerField(default=0)
down_num = models.BigIntegerField(default=0)
parent = models.ForeignKey(to='self',null=True) # 自關聯
1.註冊功能每每都會由不少校驗性的需求 因此這裏咱們用到了forms組件
項目中可能有多個地方須要用到不一樣的forms組件 爲了解耦合 可是建立一個py文件 專門用來存放項目用到的全部的forms組件
校驗 用戶名 密碼 確認密碼 郵箱
藉助於鉤子函數 校驗用戶名是否存在 密碼與確認密碼是否一致
2.建立路由 寫視圖函數
將實例化產生的forms類對象 利用模板語法傳遞到前端頁面
再利用forms組件自動渲染前端獲取用戶輸入的標籤
3.用戶輸入數據後 採用ajax的方式朝後臺提交數據
1.考慮到獲取用戶輸入的input框可能會有不少 在同ajax獲取的時候 可能會比較繁瑣
藉助了form標籤有一個方法 自動將內部全部的普通鍵值 打包成一個容器類型
$('#myform').serializeArray() 2.你須要提交的數據不僅僅只有普通鍵值 還有文件 能夠藉助 內置對象 FormData 如何獲取文件對象 $('#myfile')[0].files[0] # forms組件渲染錯誤的信息
4.用戶頭像前端動態展現
利用內置對象 文件閱讀器 new FileReader
等待什麼什麼加載完畢再執行 onload
window.onload fileReader.onload = function(){ 文件閱讀器加載完畢以後 再執行的操做 }
5.針對ajax提交的數據 後端在返回信息的時候 一般都是一個字典
在建立數據的時候利用**直接打散字典的形式 傳輸數據
6.將錯誤的信息傳遞給前端
前端渲染錯誤信息的時候 遇到了一個問題 如何將對應的信息渲染到對應的input框下面的span標籤中
研究forms組件渲染的input框的id值的特色 id_字段名
發現後端傳過來的字典的key就是一個個的字段名 本身拼接處id值
<script> $('#myfile').change(function () { // 文件閱讀器 //1 產生一個文件閱讀器對象 var fileReader = new FileReader(); //2 獲取用戶上傳的文件 var fileObj = $(this)[0].files[0]; //3 讓文件閱讀器讀取該文件 fileReader.readAsDataURL(fileObj); // 這一步是異步 提交完以後直接運行下一行 //4 利用文件閱讀器將文件展現出來 fileReader.onload = function () { $('#myimg').attr('src', fileReader.result) } }); $('#commit').click(function () { // 因爲你要發送的數據即有普通的鍵值 又有文件 因此考慮使用內置對象FormData var formDataObj = new FormData(); // 1 朝對象中添加普通的鍵值對 // $('#myform').serializeArray()自動獲取到內部全部的普通鍵值對 $.each($('#myform').serializeArray(),function (index,obj) { formDataObj.append(obj.name,obj.value) }); // 2 手動添加文件數據 formDataObj.append('avatar',$('#myfile')[0].files[0]); // 發送ajax請求 $.ajax({ url:'', type:'post', data:formDataObj, // 發送文件 須要修改兩個參數 contentType:false, processData:false, success:function (data) { if (data.code == 1000){ window.location.href = data.url }else{ $.each(data.msg,function (index,obj) { // index就是一個個的報錯字段名 obj就是數組 裏面是報錯信息 // 手動拼接對應的input框的id值 var targetId = '#id_' + index; // $('#id_username') $('#id_password') $(targetId).next().text(obj[0]).parent().addClass('has-error') }) } } }) }); $('input').focus(function () { $(this).next().text('').parent().removeClass('has-error') }) </script>
1.前端頁面搭建
圖片驗證碼
2.圖片驗證碼
img標籤 src能夠接收的數據
1.圖片的具體路徑
2.圖片的二進制數據
3.後端url(自動朝該url發送get請求)
3.後端代碼推導
操做圖片的模塊pillow
4.完成後跳轉到網站首頁
展現網站全部的博客內容
主頁
用戶沒有登陸導航條展現 登陸 註冊按鈕
用戶登陸的狀況下 展現用戶的用戶名和更多操做
圖片驗證碼的實現
from PIL import Image,ImageDraw,ImageFont import random from io import BytesIO,StringIO """ BytesIO, 可以存儲數據 並以二進制的格式再返回給你 StringIO 可以存儲數據 並以字符串的格式再返回給你 """ """ Image, 產生圖片的 ImageDraw, 產生畫筆的 ImageFont 控制字體樣式 """ def get_random(): return random.randint(0,255),random.randint(0,255),random.randint(0,255)
# 圖片驗證碼相關 def get_code(request): # 推到思路4(終極步驟) 圖片上寫字 img_obj = Image.new('RGB',(310,35),get_random()) img_draw = ImageDraw.Draw(img_obj) # 生成一個畫筆對象 img_font = ImageFont.truetype('static/font/111.ttf',30) # 字體的樣式 """ 全部的字體樣式都是由.ttf結尾的文件控制的 """ # 隨機生成驗證碼 a~z A~Z 0~9 code = '' for i in range(5): random_upper = chr(random.randint(65,90)) random_lower = chr(random.randint(97,122)) random_int = str(random.randint(0,9)) temp = random.choice([random_upper,random_lower,random_int]) # 將產生的隨機字符寫到圖片上 img_draw.text((i*45+45,0),temp,get_random(),img_font) code += temp print(code) # 將隨機驗證碼存儲取來 以便其餘函數調用 request.session['code'] = code io_obj = BytesIO() img_obj.save(io_obj,'png') return HttpResponse(io_obj.getvalue())
可以對註冊了的模型表 生成增刪改查起碼四個頁面
http://127.0.0.1:8000/admin/app01/userinfo/ 查看用戶
http://127.0.0.1:8000/admin/app01/userinfo/1/change/ 編輯用戶
http://127.0.0.1:8000/admin/app01/userinfo/add/ 添加用戶
http://127.0.0.1:8000/admin/app01/userinfo/2/delete/ 刪除用戶
http://127.0.0.1:8000/admin/app01/blog/ 查看我的站點
http://127.0.0.1:8000/admin/app01/blog/1/change/ 編輯我的站點
http://127.0.0.1:8000/admin/app01/blog/add/ 添加我的站點
http://127.0.0.1:8000/admin/app01/blog/2/delete/ 刪除我的站點
用戶頭像的渲染
網站默認的靜態文件資源 默認放在static文件夾下
用戶上傳的靜態文件資源 也應該單獨存放在某一個文件夾
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
from django.views.static import serve
from BBS import settings
url(r'^media/(?P
settings中的配置
urls中的配置
請求來的時候 會檢測當前請求的源地址是否來自於網站本身的
refer 放在請求頭裏面的數據 用來表示你是屬於哪一個網站
若是還想用 最簡單粗暴的方式就是先下載到本地(寫爬蟲自動下)
側邊欄展現
日期歸檔 content create_time month
1 111 2019-11-1 2019-11
1 111 2019-11-25 2019-11
1 111 2019-11-10 2019-11
1 111 2019-10-1 2019-10
官方提供的文檔
from django.db.models.functions import TruncMonth -官方提供 from django.db.models.functions import TruncMonth Article.objects .annotate(month=TruncMonth('create_time')) # Truncate to month and add to select list .values('month') # Group By month .annotate(c=Count('id')) # Select the count of the grouping .values('month', 'c') # (might be redundant, haven't tested) select month and count
當你寫的url再訪問的時候發現沒有命中
那麼你應該去urls.py中查看是否被前面的url中途攔截了 最省力的解決方式 就是調換位置
當頁面上的某一塊區域須要常常被使用 而且須要傳參數纔可以渲染出來
那麼你能夠考慮使用inclusion_tag
自定義inclusion_tag的步驟 跟自定義過濾器和標籤是同樣的
1.建立一個名字必須交templatetags文件夾
2.新建任意一個py文件
3.py文件內 寫固定的兩行代碼
from django.template import Library
register = Library()
工做原理 相似於 函數的調用過程
你在使用inclusion_tag的時候 能夠爲其傳參 它會將參數傳遞給一個html頁面
該頁面會利用傳遞過來的參數 渲染頁面 而後將渲染好的頁面 放在調用inclusion_tag的地方
from django.template import Library from app01 import models from django.db.models.functions import TruncMonth from django.db.models import Count register = Library() @register.inclusion_tag('left_menu.html') def index(username): user_obj = models.UserInfo.objects.filter(username=username).first() blog = user_obj.blog # 1.查詢當前用戶全部的分類及分類下的文章數 category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list( 'name', 'count_num', 'pk') # 2.查詢當前用戶全部的標籤及標籤下的文章數 tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name', 'count_num', 'pk') # 3.按照年月統計文章數 date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values( 'month').annotate(count_num=Count('pk')).order_by('-month').values_list('month', 'count_num') return locals() # return {'username':username,'blog':blog}
<div class="panel panel-primary"> <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 }}({{ category.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-warning"> <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 }}({{ tag.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">日期歸檔</h3> </div> <div class="panel-body"> {% for xxx in date_list %} <p><a href="/{{ username }}/archive/{{ xxx.0|date:'Y-m' }}/">{{ xxx.0|date:'Y年-m月' }}({{ xxx.1 }})</a></p> {% endfor %} </div> </div>
django admin後臺管理
你只須要在對應的應用下的admin.py註冊你想要操做的模型類便可
你須要建立一個超級用戶纔可以加入admin後臺管理
createsuperuser
admin展現配置
在admin中有一些默認要求必填 你能夠給字段加參數
blank=True
表名展現中文
class Meta:
verbose_name_plural = '表名'
對象展現註釋信息
def str(self):
return self.name # 該方法的返回值必須是字符串類型
admin擴展功能
class ArticleConfig(admin.ModelAdmin): list_display = ['title','create_time'] # 配置展現字段 list_display_links = ['title','create_time'] # 指定多個跳轉標籤 search_fields = ['title'] # 指定查詢 多個字段默認是or的關係 def patch_init(self,queryset): pass patch_init.short_description = '批量更新' actions = [patch_init,] # 自定義批量處理函數 list_filter = ['tags','category'] # 定義外鍵字段的過濾
用戶頭像展現
1.網站默認的靜態文件資源統一放在static文件夾
而用戶上傳的靜態文件資源也應該單獨放在某個文件夾
2.如何讓用戶上傳的全部的靜態文件所有自動放在某個文件夾下
配置文件中
MEDIR_ROOT = os.path.join(BASE_DIR,'某個文件夾的名字')
media
avatar
3.要想訪問後端資源 必須實現開設該資源的接口
手動在urls.py中開設接口
from django.views.static import serve
from bbs import settings
url(r'^media/(?P
"""該方法可以作到暴露後端任何資源 在使用是要謹慎"""
我的站點
左側的側邊欄前端樣式渲染
三道orm查詢題目
日期格式的分組查詢
利用django 官網提供的方法
左側的側邊欄篩選功能
url由三條優化成一條
當你的url過多的時候可能會形成混亂 url被其餘url攔截了
1.換位置
2.改正則表達式
多個路由公用一個視圖函數
1.對視圖函數的參數作擴展性設計
根據字典參數的不一樣對已經存在的queryset對象作進一步的篩選
如何再多個頁面上使用同一個樣式 而且改頁面樣式 是須要參數的
自定義inclusion_tag
與自定義過濾器 標籤 準備工做是如出一轍的
三步走
1.templatetags
2.任意名稱py文件
3.兩行代碼
工做原理:相似於給函數傳參並調用函數
拆分代碼是很簡單的 你只須要把代碼先拷貝過去
而後哪一個地方缺什麼就補什麼
評論
跟評論
頁面渲染
臨時渲染
子評論
用戶點擊回覆按鈕 發生了幾件事
1.自動獲取點擊的那一條評論的評論人用戶名 @人名
2.將拼接好的人名自動放入textarea框中 而且自動換行
3.textarea框自動聚焦
1.如何取出自帶的@人名
2.當你回覆了一個子評論以後 若是不刷新頁面 後續的評論都會變成子評論
1.直接參考博客園 拷貝前端點贊點踩樣式
1.拷貝的時候 要靠徹底了 html和css都要拷
2.圖片防盜鏈 將須要用到的圖片下載到本地
2.給點贊點踩圖標綁定點擊事件
1.爲了區分用戶究竟是點擊的贊仍是踩
咱們給贊和踩都設置了一個共同的類屬性
而後給這個共同的類屬性綁定點擊事件
而後再利用贊和踩自己由各自獨有的類屬性
利用this變量指代的是當前被點擊(操做)對象
再利用jQuery hasClass()判斷是否有對應的類屬性
2.朝後端發ajax請求
因爲處理點贊點踩的業務邏輯稍微有點複雜
推薦使用專門的視圖函數進行業務邏輯處理
article_id
is_up
3.後端點贊點踩業務邏輯
1.先判斷當前點贊點踩用戶是否登陸
request.user.is_authenticated()
2.判斷當前文章是不是當前改用戶本身寫的
經過article_id獲取用戶想要點的文章對象
再利用文章對象獲取對應的用戶對象與當前登陸用戶對象request.user進行比對
3.判斷當前用戶是否已經給當前這篇文章點過贊或者點過踩了
利用article_obj和request.user直接去點贊點踩表中過濾數據
若是二者and查詢有數據 說明當前用戶已經給當前文章點過了
4.操做數據庫 須要注意 有一個普通字段須要同步更新
is_up = json.loads(is_up) # 轉成後端python的布爾值類型
1.根據is_up決定究竟是up_num加仍是down_num加
2.直接操做點贊點踩表 完成記錄的添加
5.再去補全錯誤的邏輯代碼
"""
在寫代碼時候 建議遵循先把正確的邏輯所有寫完以後 再去考慮錯誤的邏輯
"""
4.前端代碼優化
用戶點了贊或者踩 在結果一系列校驗ok的狀況下
你應該將點贊點踩的數字在原來的基礎之上加一
在加的時候 千萬注意 不要弄成了字符串的加
利用Number轉成數字再相加
1.根評論
1.前端樣式搭建 直接參考bbs書寫或者拷貝便可 如何清除浮動帶來的影響 clearfix 2.點擊評論按鈕 發送ajax請求 article_id content 3.後端專門開設評論的視圖函數 處理業務邏輯 直接獲取數據 普通字段須要同步更新 利用事務 comment_num Comment 4.展現評論 評論樓的渲染 5.當用戶點擊提交評論按鈕 1.先將textarea清空 2.將用戶評論的內容 先臨時渲染到評論樓中 利用esc6新語法 模板字符串的替換 再利用標籤查找及內部元素的添加 append()
2.子評論
1.切入點就是回覆按鈕
點擊回覆按鈕應該作的事
1.拼接人名 @人名\n
2.將拼接好的內容添加到textarea框中
3.將textarea聚焦 focus()
2.提交跟評論個提交子評論的區別
如何區分根評論和子評論
提交子評論的時候 須要拿到評論的主鍵值 ’
利用標籤能夠自定義任何屬性的特色
給回覆按鈕 添加了用戶名和主鍵值兩個屬性
$(this).attr('username') $(this).attr('comment_id')
3.一個函數中的變量須要在林外一個函數中使用
最簡便的方式就是將改變量定義成全局變量
1.定義全局的commend_id
2.後端parent字段能夠爲空的 也就意味着我在給parent參數傳值的時候 能夠傳空
4.當提交一次子評論以後 須要將comment_id再次清空
還須要解決 用戶提交子評論中 @人名\n
5.前端子評論的渲染
{{ comment.parent.user.username }}
須要修改一下時區問題
from django.db.models.functions import TruncMonth -官方提供 from django.db.models.functions import TruncMonth Sales.objects .annotate(month=TruncMonth('timestamp')) # Truncate to month and add to select list .values('month') # Group By month .annotate(c=Count('id')) # Select the count of the grouping .values('month', 'c') # (might be redundant, haven't tested) select month and count 時區問題報錯
TIME_ZONE = 'Asia/Shanghai' USE_TZ = True
下面的配置改了admin後臺變中文
LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True