博客系统作业
博客系统
1 搞清楚需求(产品经理)
1,基于用户认证组件和Ajax实现登录验证(图片验证码) 2,基于forms组件和Ajax实现注册功能 3,设计系统首页(文章列表渲染) 4,设计个人站点页面 5,文章详情页 6,实现文章点赞功能 7,实现文章评论功能 ---文章的评论 ---评论的评论 8,富文本编辑框和防止xss攻击
2 设计表结构
from django.contrib.auth.models import User,AbstractUser class UserInfo(AbstractUser): """用户信息""" nid=models.AutoField(primary_key=True) telephone=models.CharField(max_length=11,null=True,unique=True) avatar=models.FileField(upload_to='avatars/',default="/avatars/default.png") create_time=models.DateTimeField(verbose_name='创建时间',auto_now_add=True) blog=models.OneToOneField(to='blog',to_field='nid',null=True) def __str__(self): return self.username class Blog(models.Model): """博客信息表(站点表)""" nid=models.AutoField(primary_key=True) title=models.CharField(verbose_name='个人博客标题',max_length=64) site_name=models.CharField(verbose_name='站点名称',max_length=64) theme=models.CharField(verbose_name='博客主题',max_length=32) def __str__(self): return self.title class Category(models.Model): """博主个人文章分类表""" nid=models.AutoField(primary_key=True) title=models.CharField(verbose_name='分类标题',max_length=32) blog=models.ForeignKey(verbose_name='所属博客',to='Blog',to_field='nid') def __str__(self): return self.title class Tag(models.Model): nid=models.AutoField(primary_key=True) title=models.CharField(verbose_name='标签名称',max_length=32) blog=models.ForeignKey(verbose_name='所属博客',to='Blog',to_field='nid') def __str__(self): return self.title class Article(models.Model): nid=models.AutoField(primary_key=True) title=models.CharField(verbose_name='文章标题',max_length=50) desc=models.CharField(verbose_name='文章描述',max_length=255) create_time=models.DateTimeField(verbose_name='创建时间',auto_now_add=True) content=models.TextField() comment_count=models.IntegerField(default=0) up_count=models.IntegerField(default=0) down_count=models.IntegerField(default=0) user=models.ForeignKey(verbose_name='作者',to='UserInfo',to_field='nid') category=models.ForeignKey(to="Category",to_field='nid',null=True) tags=models.ManyToManyField( to='Tag', through='Article2Tag', through_fields=('article','tag'), ) def __str__(self): return self.title class Article2Tag(models.Model): nid=models.AutoField(primary_key=True) article=models.ForeignKey(verbose_name='文章',to='Article',to_field='nid') tag=models.ForeignKey(verbose_name='标签',to='Tag',to_field='nid') class Meta: unique_together=[ ('article','tag'), ] def __str__(self): v=self.article.title + '---' + self.tag.title return v class ArticleUpDown(models.Model): """点赞表""" nid=models.AutoField(primary_key=True) user=models.ForeignKey('UserInfo',null=True) article=models.ForeignKey('Article',null=True) is_up=models.BooleanField(default=True) class Meta: unique_together=[ ('article','user'), ] class Comment(models.Model): """评论表""" nid=models.AutoField(primary_key=True) article=models.ForeignKey(verbose_name='评论文章',to='Article',to_field='nid') user=models.ForeignKey(verbose_name='评论者',to='UserInfo',to_field='nid') content=models.CharField(verbose_name='评论内容',max_length=255) create_time=models.DateTimeField(verbose_name='创建时间',auto_now_add=True) parent_comment=models.ForeignKey('self',null=True) def __str__(self): return self.content
3 按着每一个功能进行开发
验证码
示例代码:
# #!/usr/bin/python3 # # -*- coding: utf-8 -*- # # @Time : 2018/8/30 9:51 # # @File : validCode.py # import random import sys, os def get_random_color(): return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) def get_valid_code_img(request): # 方式1: # pa_base = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) # pa_img = os.path.join(pa_base, r'media\avatars\lufei.jpg') # with open(pa_img, "rb") as f: # data = f.read() # 方式2: # pip install pillow # from PIL import Image # # img = Image.new("RGB", (270, 40), color=get_random_color()) # # with open("validCode.png", "wb") as f: # img.save(f, "png") # # with open("validCode.png", "rb") as f: # data = f.read() # 方式3: # from PIL import Image # from io import BytesIO # # img = Image.new("RGB", (270, 40), color=get_random_color()) # f = BytesIO() # img.save(f, "png") # data = f.getvalue() # 方式4: from PIL import Image, ImageDraw, ImageFont from io import BytesIO import random img = Image.new("RGB", (270, 40), color=get_random_color()) draw = ImageDraw.Draw(img) kumo_font = ImageFont.truetype("static/font/kumo.ttf", size=32) valid_code_str = "" for i in range(5): random_num = str(random.randint(0, 9)) random_low_alpha = chr(random.randint(95, 122)) random_upper_alpha = chr(random.randint(65, 90)) random_char = random.choice([random_num, random_low_alpha, random_upper_alpha]) draw.text((i * 50 + 20, 5), random_char, get_random_color(), font=kumo_font) # 保存验证码字符串 valid_code_str += random_char width = 270 height = 40 for i in range(10): x1 = random.randint(0, width) x2 = random.randint(0, width) y1 = random.randint(0, height) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=get_random_color()) for i in range(100): draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random_color()) print("valid_code_str", valid_code_str) # request.session["valid_code_str"] = valid_code_str ''' 1 sdajsdq33asdasd 2 COOKIE {"sessionid":sdajsdq33asdasd} 3 django-session session-key session-data sdajsdq33asdasd {"valid_code_str":"12345"} ''' f = BytesIO() img.save(f, "png") data = f.getvalue() return data
view中调用:
def get_valid_code_img(request): """ 基于PIL模块动态生成响应状态码图片 :param request: :return: """ img_data = validCode.get_valid_code_img(request) return HttpResponse(img_data)
登录页面login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/blog/bs/css/bootstrap.css"> </head> <body> <h3>登录页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} <div class="form-group"> <label for="user">用户名</label> <input type="text" id="user" class="form-control"> </div> <div class="form-group"> <label for="pwd">密码</label> <input type="password" id="pwd" class="form-control"> </div> <div class="form-group"> <label for="pwd">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" class="form-control" id="valid_code"> </div> <div class="col-md-6"> <img width="270" height="36" id="valid_code_img" src="/my_demo2/get_valid_code_img/" alt=""> </div> </div> </div> <input type="button" class="btn btn-default login_btn" value="submit"><span class="error"></span> <a href="/register/" class="btn btn-success pull-right">注册</a> </form> </div> </div> </div> <script src="/static/js/jquery-3.2.1.min.js"></script> <script> // 刷新验证码 $("#valid_code_img").click(function () { $(this)[0].src += "?" }); // 登录验证 $(".login_btn").click(function () { $.ajax({ url: "/my_demo2/get_valid_code_img/", type: "post", data: { user: $("#user").val(), pwd: $("#pwd").val(), valid_code: $("#valid_code").val(), csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), }, success: function (data) { console.log(data); if (data.user) { if (location.search) { location.href = location.search.slice(6) } else { location.href = "/index/" } } else { $(".error").text(data.msg).css({"color": "red", "margin-left": "10px"}); setTimeout(function () { $(".error").text(""); }, 1000) } } }) }) </script> </body> </html>
admin使用
1,在app应用下的admin.py中添加相关models中的所有类
from django.contrib import admin # Register your models here. from blog import models admin.site.register(models.UserInfo) admin.site.register(models.Tag) admin.site.register(models.Article) admin.site.register(models.Article2Tag) admin.site.register(models.ArticleUpDown) admin.site.register(models.Category) admin.site.register(models.Comment) admin.site.register(models.Blog)
2,google中打开网址:http://127.0.0.1:8000/admin,根据逻辑添加相关数据
media组件
settings.py
# 与用户上传相关的配置 MEDIA_ROOT = os.path.join(BASE_DIR, "media") MEDIA_URL = "/media/"
urls.py
# media配置: re_path(r"media/(?P<path>.*)$", serve, {"document_root": settings.MEDIA_ROOT}),
注册功能头像示例代码
# 头像文件获取 avator_obj = request.FILES.get("avator") extra = {} if avator_obj: extra["avatar"] = avator_obj UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra)
日期处理
mysql中
date_format ============date,time,datetime=========== create table t_mul_new(d date,t time,dt datetime); insert into t_mul_new values(now(),now(),now()); select * from t_mul; mysql> select * from t_mul; +------------+----------+---------------------+ | d | t | dt | +------------+----------+---------------------+ | 2017-08-01 | 19:42:22 | 2017-08-01 19:42:22 | +------------+----------+---------------------+ 1 row in set (0.00 sec) select date_format(dt,"%Y/%m/%d") from t_mul;
extra介绍
extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None) 有些情况下,Django的查询语法难以简单的表达复杂的 WHERE 子句,对于这种情况, Django 提供了 extra() QuerySet修改机制 — 它能在 QuerySet生成的SQL从句中注入新子句 extra可以指定一个或多个 参数,例如 select, where or tables. 这些参数都不是必须的,但是你至少要使用一个!要注意这些额外的方式对不同的数据库引擎可能存在移植性问题.(因为你在显式的书写SQL语句),除非万不得已,尽量避免这样做
extra 参数之select
The select 参数可以让你在 SELECT 从句中添加其他字段信息,它应该是一个字典,存放着属性名到 SQL 从句的映射。 queryResult=models.Article.objects.extra(select={'is_recent': "create_time > '2017-09-05'"}) 结果集中每个 Entry 对象都有一个额外的属性is_recent, 它是一个布尔值,表示 Article对象的create_time 是否晚于2017-09-05. 练习: # 查询当前站点每一个年月的名称以及对应的文章数 in sqlite: article_obj=models.Article.objects.extra(select={"standard_time":"strftime('%%Y-%%m-%%d',create_time)"}).values("standard_time","nid","title") print(article_obj) # <QuerySet [{'title': 'MongoDb 入门教程', 'standard_time': '2017-09-03', 'nid': 1}]> in mysql # ret=models.Article.objects.extra(select={"is_recent":"create_time > '2018-09-05'"}).values("title","is_recent") # print(ret) # 方式1: # date_list=models.Article.objects.filter(user=user).extra(select={"y_m_date":"date_format(create_time,'%%Y/%%m')"}).values("y_m_date").annotate(c=Count("nid")).values_list("y_m_date","c") # print(date_list) # 方式2: # from django.db.models.functions import TruncMonth # # ret=models.Article.objects.filter(user=user).annotate(month=TruncMonth("create_time")).values("month").annotate(c=Count("nid")).values_list("month","c") # print("ret----->",ret)
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
base页面(函数)
基础页面中
<div class="col-md-9"> {% block content %} {% endblock %} </div>
业务页面
{% extends 'base.html' %} {% block content %} 相关html代码 {% endblock %}
view函数(三个列表参数都需要传cate_list、date_list、tag_list)
def get_classification_data(username): user = UserInfo.objects.filter(username=username).first() blog = user.blog cate_list = models.Category.objects.filter(blog=blog).values("pk").annotate(c=Count("article__title")).values_list( "title", "c") tag_list = models.Tag.objects.filter(blog=blog).values("pk").annotate(c=Count("article")).values_list("title", "c") date_list = models.Article.objects.filter(user=user).extra( select={"y_m_date": "date_format(create_time,'%%Y/%%m')"}).values("y_m_date").annotate( c=Count("nid")).values_list("y_m_date", "c") return {"blog": blog, "cate_list": cate_list, "date_list": date_list, "tag_list": tag_list} def article_detail(request, username, article_id): """ 文章详情页 :param request: :param username: :param article_id: :return: """ context = get_classification_data(username) return render(request, "article_detail.html", context)
def home_site(request, username, **kwargs): """ 个人站点 :param request: :param username: :return: """ user = UserInfo.objects.filter(username=username).first() # 判断用户是否存在! if not user: return render(request, "not_found.html") # 查询当前站点对象 blog = user.blog article_list = models.Article.objects.filter(user=user) if kwargs: condition = kwargs.get("condition") param = kwargs.get("param") # 2012-12 if condition == "category": article_list = article_list.filter(category__title=param) elif condition == "tag": article_list = article_list.filter(tags__title=param) else: year, month = param.split("/") article_list = article_list.filter(create_time__year=year, create_time__month=month) print(article_list) print(article_list.query) # # 1 当前用户或者当前站点对应所有文章 # ret = models.Article.objects.filter(user=user).all() # print(ret) # # 每一个后的表模型.objects.values("pk").annotate(聚合函数(关联表__统计字段)).values("表模型的所有字段以及统计字段") # # 查询每一个分类名称以及对应的文章数 # ret_cate = models.Category.objects.values("pk").annotate(c=Count("article")).values("title", "c") # print(ret_cate) # # # 查询当前站点的每一个分类名称以及对应的文章数 cate_list = models.Category.objects.filter(blog=blog).values("pk").annotate(c=Count("article__title")).values_list( "title", "c") # print(cate_list) # # # 查询当前站点的每一个标签名称以及对应的文章数 tag_list = models.Tag.objects.filter(blog=blog).values("pk").annotate(c=Count("article")).values_list("title", "c") # print(tag_list) date_list = models.Article.objects.filter(user=user).extra( select={"y_m_date": "date_format(create_time,'%%Y/%%m')"}).values("y_m_date").annotate( c=Count("nid")).values_list("y_m_date", "c") # print(date_list) return render(request, "home_site.html", {"username": username, "blog": blog, "article_list": article_list, "tag_list": tag_list, "cate_list": cate_list, "date_list": date_list, })
base页面(标签)
blog应用下新建templatetags文件夹(py)
my_tag.py
from django import template from django.db.models import Count from blog import models register = template.Library() @register.simple_tag def multi_tag(x, y): return x * y @register.inclusion_tag("classification.html") def get_classification_style(username): user = models.UserInfo.objects.filter(username=username).first() blog = user.blog cate_list = models.Category.objects.filter(blog=blog).values("pk").annotate(c=Count("article__title")).values_list( "title", "c") tag_list = models.Tag.objects.filter(blog=blog).values("pk").annotate(c=Count("article")).values_list("title", "c") date_list = models.Article.objects.filter(user=user).extra( select={"y_m_date": "date_format(create_time,'%%Y/%%m')"}).values("y_m_date").annotate( c=Count("nid")).values_list("y_m_date", "c") return {"blog": blog, "cate_list": cate_list, "date_list": date_list, "tag_list": tag_list}
简单调用tag
{% load my_tags %} {% multi_tag 3 9 %}
{% load my_tags %}
{% get_classification_style username %}
视图函数
def home_site(request, username, **kwargs): """ 个人站点 :param request: :param username: :return: """ user = UserInfo.objects.filter(username=username).first() # 判断用户是否存在! if not user: return render(request, "not_found.html") # 查询当前站点对象 blog = user.blog article_list = models.Article.objects.filter(user=user) if kwargs: condition = kwargs.get("condition") param = kwargs.get("param") # 2012-12 if condition == "category": article_list = article_list.filter(category__title=param) elif condition == "tag": article_list = article_list.filter(tags__title=param) else: year, month = param.split("/") article_list = article_list.filter(create_time__year=year, create_time__month=month) return render(request, "home_site.html", {"username": username, "blog": blog, "article_list": article_list}) def article_detail(request, username, article_id): """ 文章详情页 :param request: :param username: :param article_id: :return: """ user = UserInfo.objects.filter(username=username).first() blog = user.blog return render(request, "article_detail.html", locals())
classification.html
<div> <div class="panel panel-warning"> <div class="panel-heading">我的标签</div> <div class="panel-body"> {% for tag in tag_list %} <p><a href="/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading">随笔分类</div> <div class="panel-body"> {% for cate in cate_list %} <p><a href="/{{ username }}/category/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-success"> <div class="panel-heading">随笔归档</div> <div class="panel-body"> {% for date in date_list %} <p><a href="/{{ username }}/archive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p> {% endfor %} </div> </div> </div>
评论es6显示
页面反引号,视图传参
html代码 success: function (data) { var create_time = data.create_time; var username = data.username; var content = data.content; var s = `<li class="list-group-item"> <div> <span>${create_time}</span> <a href=""><span>${username}</span></a> </div> <div class="comment_con"> <p>${content}</p> </div> </li>`; $("ul.comment_list").append(s); } view代码: def comment(request): article_id = request.POST.get("article_id") pid = request.POST.get("pid") content = request.POST.get("content") user_id = request.user.pk comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) response = {"create_time": comment_obj.create_time.strftime("%Y-%m-%d %X"), "username": request.user.username, "content": content, } return JsonResponse(response)
回复按钮
点击回复,焦点进入文本框立马失去焦点:a标签中属性href要去掉
html代码 <a class="pull-right reply_btn" username="{{ comment.user.username }}">回复</a> js代码 // 回复 $(".reply_btn").click(function () { $("#comment_content").focus(); var val = "@" + $(this).attr("username") + "\n"; $('#comment_content').val(val); });
评论数
视图返回[{},{}],页面遍历拼装渲染数据
视图: def get_comment_tree(request): article_id = request.GET.get("article_id") response = list(models.Comment.objects.filter(article_id=article_id).order_by("pk").values("pk", "content", "parent_comment_id")) return JsonResponse(response, safe=False) 页面 <p class="tree_btn">评论树</p> <div class="comment_tree"> </div> <script> $(".tree_btn").click(function () { $.ajax({ url: "/get_comment_tree/", type: "get", data: { article_id: "{{ article_obj.pk }}" }, success: function (comment_list) { console.log(comment_list); $.each(comment_list, function (index, comment_object) { var pk = comment_object.pk; var content = comment_object.content; var parent_comment_id = comment_object.parent_comment_id; var s = '<div class="comment_item" comment_id=' + pk + '><span>' + content + '</span></div>'; if (!parent_comment_id) { $(".comment_tree").append(s); } else { $("[comment_id=" + parent_comment_id + "]").append(s); } }); } }) }); </script> 样式: .comment_item { margin-left: 20px; }
事务
from django.db import transaction # 事务操作 with transaction.atomic(): comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) models.Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1)
发送邮件
单独启动线程发送比较效率高
视图代码: article_obj = models.Article.objects.filter(pk=article_id).first() # 发送邮件 from django.core.mail import send_mail from cnblog import settings # send_mail( # "您的文章%s新增了一条评论内容"%article_obj.title, # content, # settings.EMAIL_HOST_USER, # ["916852314@qq.com"] # ) # 效率高,时间短 import threading t = threading.Thread(target=send_mail, args=("您的文章%s新增了一条评论内容" % article_obj.title, content, settings.EMAIL_HOST_USER, ["916852314@qq.com"]) ) t.start() setting代码: EMAIL_HOST = 'smtp.exmail.qq.com' # 如果是 163 改成 smtp.163.com EMAIL_PORT = 465 # 如果是 163 改成 25 EMAIL_HOST_USER = '' # 帐号 EMAIL_HOST_PASSWORD = '' # 密码 # DEFAULT_FROM_EMAIL = EMAIL_HOST_USER # 这个打开后view中 settings.EMAIL_HOST_USER可以不加 EMAIL_USE_SSL = True
safe使用
文章内容直接存F12样式中的代码,显示加|safe
<div class="cont"> {{ article_obj.content|safe }} </div>
kindeditor编辑器
添加文章 首页:kindeditor.net/doc.php 上传路径:uploadJson:"/upload/", 是否可拉伸:resizeType:0, csrf阻止:extraFileUploadParams:{ csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val() }, 指定传入视图的键:filePostName:"upload_img" setting.py代码: # 文本编辑器上传图片url path('upload/', views.upload), views.py代码: def upload(request): """ 编辑器上传文件接受视图函数 :param request: :return: """ print(request.FILES) img_obj=request.FILES.get("upload_img") # 文件对象 print(img_obj.name) path=os.path.join(settings.MEDIA_ROOT,"add_article_img",img_obj.name) # 指定文件上传的路径 with open(path,"wb") as f: for line in img_obj: f.write(line) # 把上传的图像返回给编辑器 response = { "error":0, "url":"/media/add_article_img/%s"%img_obj.name #编辑器可以识别的路径,直接浏览器可以访问的路径 } import json # 编辑器需要json数据 return HttpResponse(json.dumps(response)) return HttpResponse("ok")
摘要样式乱:
desc = content[0:150] # 如果文章是标签html,可以把标签截取断,样式乱 desc=soup.text[0:150]+"..."
bs4防止xss攻击
xss提交html代码要使用html模式,不是文本模式
from bs4 import BeautifulSoup soup=BeautifulSoup(content,"html.parser") # html.parser解析器会把所有html标签解析掉 desc=soup.text[0:150]+"..." # 去标签之后的文本 视图代码: def add_article(request): """ 后台管理的添加书籍视图函数 :param request: :return: """ if request.method == "POST": title = request.POST.get("title") content = request.POST.get("content") # 防止xss攻击,过滤script标签 soup = BeautifulSoup(content, "html.parser") for tag in soup.find_all(): print(tag.name) if tag.name == "script": tag.decompose() # 构建摘要数据,获取标签字符串的文本前150个符号 desc = soup.text[0:150] + "..." models.Article.objects.create(title=title, desc=desc, content=content, user=request.user) return redirect("/cn_backend/") return render(request, "backend/add_article.html") 简单示例: from bs4 import BeautifulSoup s = "<h1>hello</h1><span>123</span><script>alert(123)</script>" soup = BeautifulSoup(s, "html.parser") # print(soup.text) print(soup.find_all()) # [<h1>hello</h1>, <span>123</span>, <script>alert(123)</script>] for tag in soup.find_all(): print(tag.name) if tag.name == "script": tag.decompose() # 删除掉script标签以及内容 print(str(soup)) # <h1>hello</h1><span>123</span>
pycharm无法安装新模块
pycharm无法安装新模块 报错: AttributeError: module 'pip' has no attribute 'main' 解决: 找到安装目录下 helpers/packaging_tool.py文件,找到如下代码: def do_install(pkgs): try: import pip except ImportError: error_no_pip() return pip.main(['install'] + pkgs) def do_uninstall(pkgs): try: import pip except ImportError: error_no_pip() return pip.main(['uninstall', '-y'] + pkgs) 修改为: def do_install(pkgs): try: #import pip try: from pip._internal import main except Exception: from pip import main except ImportError: error_no_pip() return main(['install'] + pkgs) def do_uninstall(pkgs): try: #import pip try: from pip._internal import main except Exception: from pip import main except ImportError: error_no_pip() return main(['uninstall', '-y'] + pkgs)