一 博客系统分析
一、需求分析 - 报障 用户: 提交报账单 自己报障记录 处理着: 查看所有人报障单 处理报账单 - 知识库(博客) 主页: 展示最新文章 展示最热文章 展示评论最多文章 分类查看 个人博客: 个人博客主页 个人博客文章详细:赞,踩,评论 个人博客分类:标签、分类、时间 个人博客主题定制:后台修改 后台管理: 个人信息管理 个人标签 个人分类 个人文章 二、数据库设计: 用户表: uid,username,pwd,email,img, 博客表: bid,surfix,theme,title,summary, FK(用户表,unique)=OneToOne(用户表) 互粉表: id 明星ID(用户表) 粉丝ID(用户表) 报障单:UUID title detail user(用户表) processor(用户表 null) status(待处理,处理中,已处理) 创建时间 处理事件 分类表:caption Fk(博客bid) 标签表:caption Fk(博客bid) 文章:id,title,summary,ctime,FK(个人分类表),主站分类(choices) 文章详细:detail OneToOne(文章) 文章标签关系: 文章ID 标签ID 文章赞踩关系: 文章ID 用户ID 赞或踩(True,False) 联合唯一索引 文章评论表:id,content,FK(文章),FK(user),ctime,parent_comment_id 三、程序目录结构 project - APP(repository) - 数据仓库(操作数据Model) - APP(backend) - 后台管理 - APP(web) - 首页,个人博客 - utils - 工具包(公共模块)
创建App命令:python manage.py startapp 名称
并把相应的文件设置到setting配置中
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'repository', 'backend', ]
修改AaronBlogurl.py配置文件
from django.contrib import admin from django.conf.urls import url from django.conf.urls import include urlpatterns = [ url(r'^',include('web.urls')), ]
添加static文件夹,并引入Bootstrap等文件
model实体类
from django.db import models # Create your models here. class UserInfo(models.Model): '''用户表''' uid = models.BigAutoField(primary_key=True) username = models.CharField(verbose_name="用户名", max_length=64, unique=True) pwd = models.CharField(verbose_name='密码', max_length=64) email = models.EmailField(verbose_name='邮箱', unique=True) img = models.ImageField(verbose_name='头像', null=True) class BlogInfo(models.Model): '''博客信息''' bid = models.BigAutoField(primary_key=True) surfix = models.CharField(verbose_name='博客后缀名', max_length=64) theme = models.CharField(verbose_name='博客主题', max_length=64) title = models.CharField(verbose_name='博客标题', max_length=1000) summary = models.CharField(verbose_name='博客简介', max_length=1000) user = models.OneToOneField(to="UserInfo", to_field="uid", on_delete=models.CASCADE, null=True) class UserFans(models.Model): '''互粉表''' starUser = models.ForeignKey(verbose_name='博主', to="UserInfo", to_field="uid", related_name="starUsers", on_delete=models.CASCADE, null=True) fansUser = models.ForeignKey(verbose_name='粉丝', to="UserInfo", to_field="uid", related_name='fansUsers', on_delete=models.CASCADE, null=True) class Meta: unique_together = [ ('starUser', 'fansUser'), ] class ReportObstacles(models.Model): '''报障单''' uuid = models.UUIDField(primary_key=True) title = models.CharField(verbose_name="报障标题", max_length=1000) detail = models.TextField(verbose_name='报障详情') reportUser = models.ForeignKey(verbose_name='报修人', to="UserInfo", to_field="uid", related_name="reportUsers", on_delete=models.CASCADE, null=True) processUser = models.ForeignKey(verbose_name='处理人', to="UserInfo", to_field="uid", related_name="processUsers", on_delete=models.CASCADE, null=True) createTime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) processTime = models.DateTimeField(verbose_name='处理时间', auto_now_add=True) type_status = [ (1, '待处理'), (2, '处理中'), (3, '已处理'), ] status = models.IntegerField(choices=type_status, default=None) class Tag(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='标签名称', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='BlogInfo', to_field='bid', on_delete=models.CASCADE, null=True) class Classification(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='分类名称', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='BlogInfo', to_field='bid', on_delete=models.CASCADE, null=True) class Article(models.Model): nid = models.BigAutoField(primary_key=True) title = models.CharField(verbose_name='文章标题', max_length=128) summary = models.CharField(verbose_name='简介', max_length=256) blog = models.ForeignKey(verbose_name='所属博客', to='BlogInfo', to_field='bid', on_delete=models.CASCADE, null=True) create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) classification_id = models.ForeignKey(verbose_name='文章分类', to='Classification', to_field="nid", on_delete=models.CASCADE, null=True) masterStation_type = [ (1, "Python"), (2, "Linux"), (3, "OpenStack"), (4, "GoLang"), ] ms_Type = models.IntegerField(verbose_name='主站分类', choices=masterStation_type, default=None) class Article_Detail(models.Model): detail = models.TextField(verbose_name='文章详细', max_length=models.Max) article_id = models.OneToOneField(verbose_name='文章id', to='Article', to_field='nid', on_delete=models.CASCADE, null=True) class Article_Tag(models.Model): # 文章标签分类 article_id = models.ForeignKey(verbose_name='文章ID', to='Article', to_field='nid', on_delete=models.CASCADE, null=True) tag_id = models.ForeignKey(verbose_name='标签ID', to='Tag', to_field='nid', on_delete=models.CASCADE, null=True) class Article_upDown(models.Model): # 文章标签分类 article_id = models.ForeignKey(verbose_name='文章ID', to='Article', to_field='nid', on_delete=models.CASCADE, null=True) user = models.ForeignKey(verbose_name='赞或踩用户', to='UserInfo', to_field='uid',on_delete=models.CASCADE, null=True) up = models.BooleanField(verbose_name='是否赞', default=True) class Meta: unique_together = [ ('article_id', 'user') ] class Article_Comment(models.Model): '''评论表''' id = models.BigAutoField(verbose_name='评论ID', primary_key=True) user = models.ForeignKey(verbose_name='评论人', to='UserInfo', to_field='uid', on_delete=models.CASCADE, null=True) comment = models.CharField(verbose_name='评论内容', max_length=1000) createTime = models.DateTimeField(verbose_name='评论时间', auto_now_add=True) reply = models.ForeignKey(verbose_name='回复评论', to='self', related_name='back', on_delete=models.CASCADE, null=True)
"""AaronBlog URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/2.1/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.conf.urls import url from django.conf.urls import include urlpatterns = [ url(r'^',include('web.urls')), ]
from django.conf.urls import url from web.Views import home urlpatterns=[ url(r'^all/(?P<ms_Type>d+).html$',home.index,name='index'), url(r'^', home.index), ]
二:页面设置
公用的css样式
img{
border: 0;
}
.left{
float: left;
}
.right{
float: right;
}
.article-item{ font-size: 13px; border-bottom: 1px dashed #dddddd; padding: 5px 0; } .article-item h3{ margin-top: 10px; font-size: 20px; } .article-item .avatar{ border: 1px solid #d5d5d5;margin-right: 5px; } .article-item .avatar img{ 70px;height: 70px;padding: 1px; } .article-item .footers{ padding: 5px 0 0 0; } .article-item .footers .ele{ margin: 0 10px; }
1 index页面设置:
1.1 导航条
导航条设置成母版页,参考Bootstrap中的导航条,并引入相应的js和css
<nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <a class="navbar-brand" href="#">逍遥小天狼</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> {% for item in article_tag_list %} <li><a href="#">{{ item.1 }}</a></li> {% endfor %} </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="#">登录</a></li> <li><a href="#">注册</a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav>
1.2 数据展示,其中需要注意如何把统计后的赞、踩数统一返还给前端
article_list = models.Article.objects.filter(**kwargs).annotate(authorsNum=Count('article_updown'))
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.css"/> <link rel="stylesheet" href="/static/plugins/font-awesome-4.7.0/css/font-awesome.css"/> <link rel="stylesheet" href="/static/css/common.css"> <link rel="stylesheet" href="/static/css/row_avatar.css"> <script type="text/javascript" src="/static/js/jquery-3.3.1.min.js"></script> <script type="text/javascript" src="/static/plugins/bootstrap/js/bootstrap.js"></script> </head> <body> {% load staticfiles %} {% include 'Home/headTar.html' %} <div class="container"> <div> <div class="col-md-8"> <div class="article-list"> {% for article in article_list %} <div class="article-item clearfix"> <h3><a href="#">{{ article.title }}</a></h3> <div class="clearfix"> <a class="avatar left" href="#"> <img src="/static/imgs/1.jpg"> </a> {{ article.summary }} </div> <div class="footers"> <a href="#"> <i class="fa fa-user-o" aria-hidden="true"></i> <span>{{ article.blog.user.username }}</span> </a> <span>发布于 2019-02-06</span> <a href="#" class="ele"> <i class="fa fa-commenting-o" aria-hidden="true"></i> <span></span> </a> <a href="#" class="ele"> <i class="fa fa-thumbs-o-up" aria-hidden="true"></i> <span>{{ article.authorsNum }}</span> </a> </div> </div> {% endfor %} </div> </div> </div> </div> </body> </html>
from django.shortcuts import HttpResponse,render from repository import models from django.db.models import Count def index(request, *args, **kwargs): """ 博客首页,展示全部博文 """ # 设置标签列表(注意,该标签直接从model中获取) ms_Type = models.Article.masterStation_type # 判断是否有条件(主站分类:ms_Type)传递过来, article_list = models.Article.objects.filter(**kwargs).annotate(authorsNum=Count('article_updown')) current_ms_Type=0 if kwargs: current_ms_Type= int(kwargs['ms_Type']) # 统计每一篇文章的赞、踩个数 # bookList =models.Article.objects.annotate(authorsNum=Count('article_updown')) return render(request,"Home/index.html",{'masterStation_type':ms_Type,'article_list':article_list,'current_ms_Type':current_ms_Type})
1.3 分页功能 分页我们在python-26中有详细讲过,这里我们用自已定义一个分页组件
# 2.1 所需要的参数 # 序号 字段名 备注 # a data_count 列表的总个数 # b current_page_num当前页码 # C page_rows 每页显示多少行数据(每页显示10条) # D page_num_size 页码条显示个数(建议为奇数,最多页面7个) # 2.2 方法具有的内容 # 序号 字段名 备注 # 1 start 当前页列表显示的初始值 # 2 end 当前页列表显示的结束值 # 3 page_count 总页数 # 4 page_par_range 页码条显示范围 class AaronPager(object): def __init__(self, data_count, current_page_num, page_rows=10, page_num_size=7,base_url='/'): # 数据总个数 self.data_count = data_count # 当前页 try: v = int(current_page_num) if v <= 0: v = 1 self.current_page_num = v except Exception as e: self.current_page_num = 1 # 每页显示的行数 self.page_rows = page_rows # 最多显示页面 self.page_num_size = page_num_size #设置前台页面传递过来的url self.base_url=base_url def start(self): return (self.current_page_num - 1) * self.page_rows def end(self): return self.current_page_num * self.page_rows @property def page_count(self): # 总页数 a, b = divmod(self.data_count, self.page_rows) if b == 0: return a return a + 1 def page_par_range(self): # 总页数( self.page_count) 总页码数(self.page_num_size) 当前页(self.current_page_num) # 如果总页数<总页码数 if self.page_count < self.page_num_size: return range(1, self.page_count + 1) # 如果当前页数<总页码数/2 part = self.page_num_size // 2 if self.current_page_num < part: return range(1, self.page_num_size + 1) # 如果当前页+part >总页码数 if self.current_page_num + part > self.page_count: return range(self.page_count - self.page_num_size+1, self.page_count+1) return range(self.current_page_num - part, self.current_page_num + part+1) def page_str(self): page_list = [] first = "<li><a href='%s?p=1'>首页</a></li>" %(self.base_url,) page_list.append(first) if self.current_page_num == 1: prev = "<li><a href='#'>上一页</a></li>" else: prev = "<li><a href='%s?p=%s'>上一页</a></li>" %(self.base_url,self.current_page_num - 1) page_list.append(prev) for i in self.page_par_range(): if i==self.current_page_num: temp = "<li class='active'><a href='%s?p=%s'>%s</a></li>" %(self.base_url,i,i) else: temp = "<li><a href='%s?p=%s'>%s</a></li>" %(self.base_url,i,i) page_list.append(temp) if self.current_page_num == self.page_count: nex = "<li><a href='#'>下一页</a></li>" else: nex = "<li><a href='%s?p=%s'>下一页</a></li>" %(self.base_url,self.current_page_num + 1) page_list.append(nex) last = "<li><a href='%s?p=%s'>尾页</a></li>" %(self.base_url,self.page_count) page_list.append(last) return ''.join(page_list)
给url起个别名,reverse方法通过别名生成url,以便于生成页码的链接
from django.conf.urls import url from web.Views import home urlpatterns=[ url(r'^all/(?P<ms_Type>d+).html$',home.index,name='index'), url(r'^', home.index), ]
home.py 文件做相应的修改
from django.shortcuts import HttpResponse, render from repository import models from django.db.models import Count from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from web.Views.Tools import AaronPager from django.urls import reverse def index(request, *args, **kwargs): """ 博客首页,展示全部博文 """ # 设置标签列表(注意,该标签直接从model中获取) ms_Type = models.Article.masterStation_type # 判断是否有条件(主站分类:ms_Type)传递过来, article_list = models.Article.objects.filter(**kwargs).annotate(authorsNum=Count('article_updown')) current_ms_Type = 0 data_count = article_list.count() cur_page = request.GET.get('p') # reverse方法可以通过别名反向生成url base_url = '/' if kwargs: current_ms_Type = int(kwargs['ms_Type']) base_url = reverse('index', kwargs=kwargs) # 统计每一篇文章的赞、踩个数 # bookList =models.Article.objects.annotate(authorsNum=Count('article_updown')) aaron_page = AaronPager.AaronPager(data_count, cur_page, 10, 7,base_url ) article_list = article_list[aaron_page.start():aaron_page.end()] return render(request, "Home/index.html", {'masterStation_type': ms_Type, 'article_list': article_list, 'current_ms_Type': current_ms_Type,'aaron_page':aaron_page,})
最后前台页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.css"/> <link rel="stylesheet" href="/static/plugins/font-awesome-4.7.0/css/font-awesome.css"/> <link rel="stylesheet" href="/static/css/common.css"> <link rel="stylesheet" href="/static/css/row_avatar.css"> <script type="text/javascript" src="/static/js/jquery-3.3.1.min.js"></script> <script type="text/javascript" src="/static/plugins/bootstrap/js/bootstrap.js"></script> </head> <body> {% load staticfiles %} {% include 'Home/headTar.html' %} <div class="container"> <div> <div class="col-md-8"> <div class="article-list"> {% for article in article_list %} <div class="article-item clearfix"> <h3><a href="#">{{ article.title }}</a></h3> <div class="clearfix"> <a class="avatar left" href="#"> <img src="/static/imgs/1.jpg"> </a> {{ article.summary }} </div> <div class="footers"> <a href="#"> <i class="fa fa-user-o" aria-hidden="true"></i> <span>{{ article.blog.user.username }}</span> </a> <span>发布于 2019-02-06</span> <a href="#" class="ele"> <i class="fa fa-commenting-o" aria-hidden="true"></i> <span></span> </a> <a href="#" class="ele"> <i class="fa fa-thumbs-o-up" aria-hidden="true"></i> <span>{{ article.authorsNum }}</span> </a> </div> </div> {% endfor %} </div> <ul class="pagination pagination-sm"> {{ aaron_page.page_str|safe }} </ul> </div> </div> </div> </body> </html>
1.4 注册功能 我们通过Form表单实现
创建form类
from django import forms from django.forms import fields from django.core.validators import RegexValidator from django.core.exceptions import NON_FIELD_ERRORS, ValidationError class AccountInfoForm(forms.Form): username = fields.CharField( required=True, label='用户名:', min_length=6, max_length=16, error_messages={ 'required': '用户名不能为空', 'min_length': '至少为6个字符', 'max_length': '最多为16个字符', } ) pwd = fields.CharField( label='密码:', required=True, min_length=6, max_length=16, validators=[RegexValidator(r'^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[!@#$\%^&*()])[0-9a-zA-Z!@#$\%^&*()]{8,32}$','密码过于简单(包含数字、字母的8位以上数字)')], error_messages={ 'required': '密码不能为空', 'min_length': '至少为6个字符', 'max_length': '最多为16个字符', } ) confirmPwd = fields.CharField( label='确认密码:', required=True, min_length=6, max_length=16, error_messages={ 'required': '密码不能为空', 'min_length': '至少为6个字符', 'max_length': '最多为16个字符', } ) email = fields.EmailField( label='邮箱:', required=True, min_length=6, max_length=18, error_messages={ 'required': '邮箱不能为空', 'min_length': '至少为6个字符', 'max_length': '最多为18个字符', 'invalid': '邮箱格式不正确', } ) def clean(self): value_data = self.cleaned_data v1=value_data.get("pwd") v2=value_data.get("confirmPwd") if v1 != v2: self.add_error("confirmPwd", "密码不一致") raise ValidationError("密码不一致") return self.cleaned_data
配置url地址
from django.conf.urls import url from web.Views import home from web.Views import account from django.urls import path urlpatterns=[ url(r'^register.html$', account.register), url(r'^all/(?P<ms_Type>d+).html$',home.index,name='index'), url(r'^', home.index), ]
后台逻辑
from django.shortcuts import render, redirect, HttpResponse from repository.models import UserInfo from web.Forms.Account import AccountInfoForm def register(request): # 注册用户 if request.method == 'GET': obj = AccountInfoForm() print(obj) return render(request, "Account/register.html", {"obj": obj}) else: obj = AccountInfoForm(request.POST) if obj.is_valid(): UserInfo.objects.create(**obj.cleaned_data) return redirect('/') else: return render(request, "Account/register.html", {"obj": obj})
前台页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/register.html" method="post" novalidate> {% csrf_token %} <p>{{ obj.username.label }}{{ obj.username }}{{ obj.errors.username.0 }}</p> <p> 密 码 :{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p> <p>确认密码:{{ obj.confirmPwd }}{{ obj.errors.confirmPwd.0 }}</p> <p> 邮 箱 :{{ obj.email }}{{ obj.errors.email.0 }}</p> <input type="submit" value="注册"/> </form> </body> </html>
1.5 验证码模块
首先安装bs4: pip install bs4
引入生成验证码的模块
#!/usr/bin/env python # -*- coding:utf-8 -*- import random from PIL import Image, ImageDraw, ImageFont, ImageFilter _letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z _upper_cases = _letter_cases.upper() # 大写字母 _numbers = ''.join(map(str, range(3, 10))) # 数字 init_chars = ''.join((_letter_cases, _upper_cases, _numbers)) def create_validate_code(size=(120, 30), chars=init_chars, img_type="GIF", mode="RGB", bg_color=(255, 255, 255), fg_color=(0, 0, 255), font_size=18, font_type="simsun.ttc", length=4, draw_lines=True, n_line=(1, 2), draw_points=True, point_chance=2): """ @todo: 生成验证码图片 @param size: 图片的大小,格式(宽,高),默认为(120, 30) @param chars: 允许的字符集合,格式字符串 @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG @param mode: 图片模式,默认为RGB @param bg_color: 背景颜色,默认为白色 @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF @param font_size: 验证码字体大小 @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf @param length: 验证码字符个数 @param draw_lines: 是否划干扰线 @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效 @param draw_points: 是否画干扰点 @param point_chance: 干扰点出现的概率,大小范围[0, 100] @return: [0]: PIL Image实例 @return: [1]: 验证码图片中的字符串 """ width, height = size # 宽高 # 创建图形 img = Image.new(mode, size, bg_color) draw = ImageDraw.Draw(img) # 创建画笔 def get_chars(): """生成给定长度的字符串,返回列表格式""" return random.sample(chars, length) def create_lines(): """绘制干扰线""" line_num = random.randint(*n_line) # 干扰线条数 for i in range(line_num): # 起始点 begin = (random.randint(0, size[0]), random.randint(0, size[1])) # 结束点 end = (random.randint(0, size[0]), random.randint(0, size[1])) draw.line([begin, end], fill=(0, 0, 0)) def create_points(): """绘制干扰点""" chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100] for w in range(width): for h in range(height): tmp = random.randint(0, 100) if tmp > 100 - chance: draw.point((w, h), fill=(0, 0, 0)) def create_strs(): """绘制验证码字符""" c_chars = get_chars() strs = ' %s ' % ' '.join(c_chars) # 每个字符前后以空格隔开 font = ImageFont.truetype(font_type, font_size) font_width, font_height = font.getsize(strs) draw.text(((width - font_width) / 3, (height - font_height) / 3), strs, font=font, fill=fg_color) return ''.join(c_chars) if draw_lines: create_lines() if draw_points: create_points() strs = create_strs() # 图形扭曲参数 params = [1 - float(random.randint(1, 2)) / 100, 0, 0, 0, 1 - float(random.randint(1, 10)) / 100, float(random.randint(1, 2)) / 500, 0.001, float(random.randint(1, 2)) / 500 ] img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲 img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强(阈值更大) return img, strs
__author__ = 'Administrator' from django.utils.safestring import mark_safe class Pagination(object): def __init__(self, current_page, data_count, per_page_count=10, pager_num=7): try: self.current_page = int(current_page) except Exception as e: self.current_page = 1 self.data_count = data_count self.per_page_count = per_page_count self.pager_num = pager_num @property def start(self): return (self.current_page - 1) * self.per_page_count @property def end(self): return self.current_page * self.per_page_count @property def total_count(self): v, y = divmod(self.data_count, self.per_page_count) if y: v += 1 return v def page_str(self, base_url): page_list = [] if self.total_count < self.pager_num: start_index = 1 end_index = self.total_count + 1 else: if self.current_page <= (self.pager_num + 1) / 2: start_index = 1 end_index = self.pager_num + 1 else: start_index = self.current_page - (self.pager_num - 1) / 2 end_index = self.current_page + (self.pager_num + 1) / 2 if (self.current_page + (self.pager_num - 1) / 2) > self.total_count: end_index = self.total_count + 1 start_index = self.total_count - self.pager_num + 1 if self.current_page == 1: prev = '<li><a class="page" href="javascript:void(0);">上一页</a></li>' else: prev = '<li><a class="page" href="%s?p=%s">上一页</a></li>' % (base_url, self.current_page - 1,) page_list.append(prev) for i in range(int(start_index), int(end_index)): if i == self.current_page: temp = '<li class="active"><a class="page active" href="%s?p=%s">%s</a></li>' % (base_url, i, i) else: temp = '<li><a class="page" href="%s?p=%s">%s</a></li>' % (base_url, i, i) page_list.append(temp) if self.current_page == self.total_count: nex = '<li><a class="page" href="javascript:void(0);">下一页</a></li>' else: nex = '<li><a class="page" href="%s?p=%s">下一页</a></li>' % (base_url, self.current_page + 1,) page_list.append(nex) # jump = """ # <input type='text' /><a onclick='jumpTo(this, "%s?p=");'>GO</a> # <script> # function jumpTo(ths,base){ # var val = ths.previousSibling.value; # location.href = base + val; # } # </script> # """ % (base_url,) # # page_list.append(jump) page_str = mark_safe("".join(page_list)) return page_str
#!/usr/bin/env python # -*- coding:utf-8 -*- from bs4 import BeautifulSoup class XSSFilter(object): __instance = None def __init__(self): # XSS白名单 self.valid_tags = { "font": ['color', 'size', 'face', 'style'], 'b': [], 'div': [], "span": [], "table": [ 'border', 'cellspacing', 'cellpadding' ], 'th': [ 'colspan', 'rowspan' ], 'td': [ 'colspan', 'rowspan' ], "a": ['href', 'target', 'name'], "img": ['src', 'alt', 'title'], 'p': [ 'align' ], "pre": ['class'], "hr": ['class'], 'strong': [] } def __new__(cls, *args, **kwargs): """ 单例模式 :param cls: :param args: :param kwargs: :return: """ if not cls.__instance: obj = object.__new__(cls, *args, **kwargs) cls.__instance = obj return cls.__instance def process(self, content): soup = BeautifulSoup(content, 'html.parser') # 遍历所有HTML标签 for tag in soup.find_all(recursive=True): # 判断标签名是否在白名单中 if tag.name not in self.valid_tags: tag.hidden = True if tag.name not in ['html', 'body']: tag.hidden = True tag.clear() continue # 当前标签的所有属性白名单 attr_rules = self.valid_tags[tag.name] keys = list(tag.attrs.keys()) for key in keys: if key not in attr_rules: del tag[key] return soup.decode() if __name__ == '__main__': html = """<p class="title"> <b>The Dormouse's story</b> </p> <p class="story"> <div name='root'> Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister c1" style='color:red;background-color:green;' id="link1"><!-- Elsie --></a> <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tilffffffffffffflie</a>; and they lived at the bottom of a well. <script>alert(123)</script> </div> </p> <p class="story">...</p>""" obj = XSSFilter() v = obj.process(html) print(v)
生成验证码的代码很简单,
前端的HTML添加一个图片路径 <p> 验证码:<img src="/check_code.html"></p>
配置url信息 url(r'^check_code.html$',check_code.make_code),
import json from io import BytesIO from django.shortcuts import HttpResponse from django.shortcuts import render from django.shortcuts import redirect from utils.check_code import create_validate_code from repository import models def make_code(request): stream=BytesIO() img,code = create_validate_code() img.save(stream,'PNG') request.session['CheckCode']=code print(code) return HttpResponse(stream.getvalue())
1.6 把验证码也通过Form验证
前端的HTML添加一个图片路径 < p> 验证码:{{ obj.checkCode }}{{ obj.errors.checkCode.0 }} <img src="/check_code.html" onclick="this.src=this.src+'?'"></p>
难点在于,如何在Form表单中获取Session中保存的CheckCode,从而与用户输入的进行对比,以检验用户输入的是否正确
方法,通过创建一个基础类BaseFrom,把页面上的request对象传进去
#!/usr/bin/env python # -*- coding:utf-8 -*- class BaseForm(object): def __init__(self, request, *args, **kwargs): self.request = request super(BaseForm, self).__init__(*args, **kwargs)
然后在clean_checkCode方法中就可使用了
from django import forms from django.forms import fields from django.core.validators import RegexValidator from django.core.exceptions import NON_FIELD_ERRORS, ValidationError from .base import BaseForm class AccountInfoForm(BaseForm,forms.Form): username = fields.CharField( required=True, label='用户名:', min_length=6, max_length=16, error_messages={ 'required': '用户名不能为空', 'min_length': '至少为6个字符', 'max_length': '最多为16个字符', } ) pwd = fields.CharField( label='密码:', required=True, min_length=6, max_length=16, validators=[ RegexValidator(r'^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[!@#$\%^&*()])[0-9a-zA-Z!@#$\%^&*()]{8,32}$', '密码过于简单(包含数字、字母的8位以上数字)')], error_messages={ 'required': '密码不能为空', 'min_length': '至少为6个字符', 'max_length': '最多为16个字符', } ) confirmPwd = fields.CharField( label='确认密码:', required=True, min_length=6, max_length=16, error_messages={ 'required': '密码不能为空', 'min_length': '至少为6个字符', 'max_length': '最多为16个字符', } ) email = fields.EmailField( label='邮箱:', required=True, min_length=6, max_length=18, error_messages={ 'required': '邮箱不能为空', 'min_length': '至少为6个字符', 'max_length': '最多为18个字符', 'invalid': '邮箱格式不正确', } ) def clean(self): value_data = self.cleaned_data v1 = value_data.get("pwd") v2 = value_data.get("confirmPwd") if v1 != v2: self.add_error("confirmPwd", "密码不一致") raise ValidationError("密码不一致") return self.cleaned_data checkCode = fields.CharField( error_messages={ 'required': '验证码不能为空', } ) def clean_checkCode(self): value_data = self.cleaned_data if self.request.session['CheckCode'].upper() != value_data.get("checkCode").upper(): self.add_error("checkCode", "验证码错误") raise ValidationError(message='验证码错误', code='invalid')
注意后台逻辑初始化的时候需要传递两个参数
from django.shortcuts import render, redirect, HttpResponse from repository.models import UserInfo from web.Forms.Account import AccountInfoForm def register(request): # 注册用户 if request.method == 'GET': obj = AccountInfoForm(request) return render(request, "Account/register.html", {"obj": obj}) else: obj = AccountInfoForm(request,request.POST) if obj.is_valid(): UserInfo.objects.create(**obj.cleaned_data) return redirect('/') else: return render(request, "Account/register.html", {"obj": obj})
1.7 用户登录
登录成功以后,主要做(页面跳转、记录session、判断是否免登录)
1.7.1 添加login.html页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/css/account.css"/> <script type="text/javascript" src="/static/js/jquery-3.3.1.min.js"></script> </head> <body> <div class="register"> <div style="font-size: 25px; font-weight: bold;text-align: center;"> 用户登录 </div> <form action="/login.html" method="post" novalidate> {% csrf_token %} <p>用户名:{{ obj.username }}{{ obj.errors.username.0 }}</p> <p> 密 码 :{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p> <p> 验证码:{{ obj.checkCode }}{{ obj.errors.checkCode.0 }} <img src="/check_code.html" onclick="this.src=this.src+'?'"></p> <div class="checkbox"> <label> {{ obj.rbm }} 一个月免登录 </label> <a href="#">忘记密码?</a> </div> <input type="submit" value="登录"/> </form> </div> <script type="text/javascript"> $(function () { $("#id_pwd,#id_confirmPwd").attr("type","password") }) </script> </body> </html>
1.7.2 配置url
url(r'^login.html$', account.login),
1.7.3 设置DjangoForm表单 ,主要是LoginForm
from django import forms from django.forms import fields from django.core.validators import RegexValidator from django.core.exceptions import NON_FIELD_ERRORS, ValidationError from .base import BaseForm from repository.models import UserInfo class AccountInfoForm(BaseForm, forms.Form): username = fields.CharField( required=True, label='用户名:', min_length=6, max_length=16, error_messages={ 'required': '用户名不能为空', 'min_length': '至少为6个字符', 'max_length': '最多为16个字符', } ) pwd = fields.CharField( label='密码:', required=True, min_length=8, max_length=32, validators=[ RegexValidator(r'^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[!@#$\%^&*()])[0-9a-zA-Z!@#$\%^&*()]{8,32}$', '密码过于简单(包含数字、字母的8位以上数字)')], error_messages={ 'required': '密码不能为空', 'min_length': '至少为6个字符', 'max_length': '最多为16个字符', } ) confirmPwd = fields.CharField( label='确认密码:', required=True, min_length=8, max_length=32, error_messages={ 'required': '确认密码不能为空', 'min_length': '至少为8个字符', 'max_length': '最多为32个字符', } ) email = fields.EmailField( label='邮箱:', required=True, min_length=6, max_length=18, error_messages={ 'required': '邮箱不能为空', 'min_length': '至少为6个字符', 'max_length': '最多为18个字符', 'invalid': '邮箱格式不正确', } ) checkCode = fields.CharField( error_messages={ 'required': '验证码不能为空', } ) def clean(self): value_data = self.cleaned_data v1 = value_data.get("pwd") v2 = value_data.get("confirmPwd") if v1 != v2: self.add_error("confirmPwd", "密码不一致") raise ValidationError("密码不一致") return value_data def clean_checkCode(self): value_data = self.cleaned_data if self.request.session['CheckCode'].upper() != value_data.get("checkCode").upper(): self.add_error("checkCode", "验证码错误") raise ValidationError(message='验证码错误', code='invalid') return value_data def clean_username(self): value =self.cleaned_data.get("username") if UserInfo.objects.filter(username=value): self.add_error("username", "用户名已存在") return value def clean_email(self): value = self.cleaned_data['email'] if UserInfo.objects.filter(email=value): self.add_error("email", "邮箱已存在") return value class LoginForm(BaseForm,forms.Form): username = fields.CharField( error_messages={ 'required': '用户名不能为空', } ) pwd = fields.CharField( error_messages={ 'required': '密码不能为空', }) checkCode = fields.CharField( error_messages={'required': '验证码不能为空.'} ) rbm = fields.BooleanField( required=False, ) def clean_checkCode(self): value_data = self.cleaned_data if self.request.session['CheckCode'].upper() != value_data.get("checkCode").upper(): self.add_error("checkCode", "验证码错误") raise ValidationError(message='验证码错误', code='invalid') return value_data def clean(self): userName =self.cleaned_data.get("username") pwd =self.cleaned_data.get("pwd") # 可以使用Q查询 Q(username=userName) & Q(pwd=pwd) userInfo = UserInfo.objects.filter(username=userName,pwd=pwd) print(self.cleaned_data.get('rbm')) if userInfo: # 记录session,可用于修改headTar中的状态。 self.request.session['user_info'] = userInfo.values( 'username').first() # 如果勾选了30免登陆,设置session的过期时间 if self.cleaned_data.get('rbm'): self.request.session.set_expiry(60 * 60 * 24 * 7) else: self.add_error("username", "用户名不存在或密码错误") raise ValidationError(message='用户名不存在或密码错误', code='invalid') return self.cleaned_data
1.7.4 设置后台处理逻辑,主要login
from django.shortcuts import render, redirect, HttpResponse from repository.models import UserInfo from web.Forms.Account import AccountInfoForm,LoginForm def register(request): # 注册用户 if request.method == 'GET': obj = AccountInfoForm(request) return render(request, "Account/register.html", {"obj": obj}) else: obj = AccountInfoForm(request,request.POST) if obj.is_valid(): del obj.cleaned_data["confirmPwd"] del obj.cleaned_data["checkCode"] # 我不知道这个数据值什么时候丢的, # obj.cleaned_data["email"]=request.POST.get("email") #发现是clean_XX方法没有了写返回值 UserInfo.objects.create(**obj.cleaned_data) return redirect('/') else: return render(request, "Account/register.html", {"obj": obj}) def login(request): #登陆用户 if request.method == 'GET': obj = LoginForm(request) return render(request, "Account/login.html", {"obj": obj}) else: obj = LoginForm(request, request.POST) if obj.is_valid(): del obj.cleaned_data["checkCode"] return redirect('/') else: return render(request, "Account/login.html", {"obj": obj}) def logout(request): request.session['user_info'] = None return redirect('/')
1.7.5 一个月免登陆
def login(request): #登陆用户 if request.method == 'GET': obj = LoginForm(request) return render(request, "Account/login.html", {"obj": obj}) else: obj = LoginForm(request, request.POST) if obj.is_valid(): del obj.cleaned_data["checkCode"] if(obj.cleaned_data["rbm"]) : request.session.set_expiry(60 * 60 * 24 * 7) # 通过用户信息 获取博客信息 ,进一步拿到博客的后缀名,跳转到个人主页 # UserInfo(UserID)==》BlogInfo(surfix) userInfo=UserInfo.objects.filter(username=obj.cleaned_data["username"]).select_related("bloginfo").first() if userInfo.bloginfo.surfix: base_url = '/' base_url = reverse('home', kwargs={'site':userInfo.bloginfo.surfix}) return redirect(base_url) else: return redirect('/') else: return render(request, "Account/login.html", {"obj": obj})
1.8 个人主页
登录成功以后,需要进行页面跳转。
1.8.1 左侧结构
配置url信息: url(r'^(?P<site>w+).html$', home.home,name='home'),
前后台页面
def home(request, site): """ 博主个人首页 :param request: :param site: 博主的网站后缀如:http://xxx.com/Aaron.html :return: """ # 根据后缀名获取博客信息 blogInfo=models.BlogInfo.objects.filter(surfix=site).select_related("user").first() if not blogInfo: return redirect('/') else: # 获取分类信息 这种写法想当于 blog = blogInfo.id category_list = models.Classification.objects.filter(blog=blogInfo) # 获取标签信息 tag_list=models.Tag.objects.filter(blog=blogInfo) #获取日期,这种系原生的sql date_list = models.Article.objects.raw( 'select nid, count(nid) as num,strftime("%Y-%m",create_time) as ctime from repository_article group by strftime("%Y-%m",create_time)') # 获取文章信息 article_list=models.Article.objects.filter(blog=blogInfo).order_by('-nid').all() print(article_list) # return render(request, "Home/home.html", { 'blog': blogInfo, 'tag_list': tag_list, 'category_list': category_list, 'date_list': date_list, 'article_list': article_list})
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.css"/> <link rel="stylesheet" href="/static/plugins/font-awesome/css/font-awesome.css"/> <link rel="stylesheet" href="/static/css/edmure.css"/> <link rel="stylesheet" href="/static/css/common.css"/> <link rel="stylesheet" href="/static/css/row_avatar.css"/> <link rel="stylesheet" href="/static/css/theme/{{ blog.theme }}.css"/> {% block css %}{% endblock %} <script type="text/javascript" src="/static/js/jquery-1.12.4.js"></script> <script type="text/javascript" src="/static/plugins/bootstrap/js/bootstrap.js"></script> </head> <body> <div class="pg-header"> <div class="title">{{ blog.title }}</div> <div class="menu-list"> <a class="menu-item" href="/">首页</a> <a class="menu-item" href="/{{ blog.site }}.html">个人首页</a> <a class="menu-item" href="/backend/index.html">管理</a> </div> </div> <div class="pg-body"> <div class="body-menu"> <div class="notice"> <div class="notice-header">公告</div> <div class="notice-body"> <ul> <li>昵称:{{ blog.user.nickname }}</li> <li>粉丝:{{ blog.user.fans.count }}</li> <li>关注:{{ blog.user.f.count }}</li> <li>邮箱:{{ blog.user.email }}</li> </ul> <div class="memo"> 真实是人生的命脉,是一切价值的根基。 </div> </div> </div> <div class="tags"> <div class="tags-header">标签</div> <div class="tags-body"> <ul> {% for tag in tag_list %} <li><a href="/{{ blog.site }}/tag/{{ tag.nid }}.html">{{ tag.title }}({{ tag.article_set.count }})</a></li> {% endfor %} </ul> </div> </div> <div class="types"> <div class="types-header">分类</div> <div class="types-body"> <ul> {% for tag in category_list %} <li><a href="/{{ blog.site }}/category/{{ tag.nid }}.html">{{ tag.title }}({{ tag.article_set.count }})</a></li> {% endfor %} </ul> </div> </div> <div class="dates"> <div class="dates-header">时间</div> <div class="dates-body"> <ul> {% for tag in date_list %} <li><a href="/{{ blog.site }}/date/{{ tag.ctime }}.html">{{ tag.ctime}}({{ tag.num}})</a></li> {% endfor %} </ul> </div> </div> </div> <div class="body-content"> {% block content %}{% endblock %} </div> </div> {% block js %}{% endblock %} </body> </html>
1.8.2 筛选功能
配置条件:url(r'^(?P<site>w+)/(?P<condition>((tag)|(date)|(category)))/(?P<val>w+-*w*).html$', home.filter),
后台处理逻辑:
from django.shortcuts import HttpResponse,redirect, render from repository import models from django.db.models import Count from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from web.Views.Tools import AaronPager from django.urls import reverse import time def index(request, *args, **kwargs): """ 博客首页,展示全部博文 """ # 设置标签列表(注意,该标签直接从model中获取) ms_Type = models.Article.masterStation_type # 判断是否有条件(主站分类:ms_Type)传递过来, article_list = models.Article.objects.filter(**kwargs).annotate(authorsNum=Count('article_updown')) current_ms_Type = 0 data_count = article_list.count() cur_page = request.GET.get('p') # reverse方法可以通过别名反向生成url base_url = '/' if kwargs: current_ms_Type = int(kwargs['ms_Type']) base_url = reverse('index', kwargs=kwargs) # 统计每一篇文章的赞、踩个数 # bookList =models.Article.objects.annotate(authorsNum=Count('article_updown')) aaron_page = AaronPager.AaronPager(data_count, cur_page, 10, 7,base_url ) article_list = article_list[aaron_page.start():aaron_page.end()] return render(request, "Home/index.html", {'masterStation_type': ms_Type, 'article_list': article_list, 'current_ms_Type': current_ms_Type,'aaron_page':aaron_page,}) def home(request, site): """ 博主个人首页 :param request: :param site: 博主的网站后缀如:http://xxx.com/Aaron.html :return: """ # 根据后缀名获取博客信息 blogInfo=models.BlogInfo.objects.filter(surfix=site).select_related("user").first() if not blogInfo: return redirect('/') else: # 获取分类信息 这种写法想当于 blog = blogInfo.id category_list = models.Article.objects.filter(blog=blogInfo).select_related("classification_id").values("classification_id","classification_title").annotate(num=Count('classification_id')) str = 'select a.nid as nid,a.title as title,count(b.nid) as num from repository_classification a LEFT JOIN repository_article b on b.blog_id = a.blog_id and a.nid=b.classification_id_id where a.blog_id =%(n1)d GROUP BY (a.nid)' %{"n1":blogInfo.bid} # category_list = models.Classification.objects.raw(str) # 获取标签信息 #tag_list=models.Tag.objects.filter(blog=blogInfo).annotate(Num=Count('article.article_set')) str = ' select a.nid as nid,a.title as title ,count(c.nid) as num from repository_Tag a INNER JOIN repository_article_tag b on a.nid=b.tag_id_id LEFT JOIN repository_article c on c.blog_id = a.blog_id and c.nid=b.article_id_id where a.blog_id =%(n1)d GROUP BY (a.nid)' %{"n1":blogInfo.bid} tag_list = models.Tag.objects.raw(str) #获取日期,这种系原生的sql str='select nid, count(nid) as num,strftime("%%Y-%%m",create_time) as ctime from repository_article where blog_id =%(n1)d group by strftime("%%Y-%%m",create_time)' %{"n1":blogInfo.bid} date_list = models.Article.objects.raw(str) # 获取文章信息 article_list=models.Article.objects.filter(blog=blogInfo).order_by('-nid').all() # return render(request, "Home/home.html", { 'blog': blogInfo, 'tag_list': tag_list, 'category_list': category_list, 'date_list': date_list, 'article_list': article_list}) def filter(request,site,condition,val): blog = models.BlogInfo.objects.filter(surfix=site).select_related('user').first() if not blog: return redirect('/') # tag_list=models.Tag.objects.filter(blog=blog) #标签 str = ' select a.nid as nid,a.title as title ,count(c.nid) as num from repository_Tag a INNER JOIN repository_article_tag b on a.nid=b.tag_id_id LEFT JOIN repository_article c on c.blog_id = a.blog_id and c.nid=b.article_id_id where a.blog_id =%(n1)d GROUP BY (a.nid)' % { "n1": blog.bid} tag_list = models.Tag.objects.raw(str) # category_list=models.Classification.objects.filter(blog=blog).select_related("article").annotate(Num=Count('article')) #分类 str = 'select a.nid as nid,a.title as title,count(b.nid) as num from repository_classification a LEFT JOIN repository_article b on b.blog_id = a.blog_id and a.nid=b.classification_id_id where a.blog_id =%(n1)d GROUP BY (a.nid)' % { "n1": blog.bid} category_list = models.Classification.objects.raw(str) str = 'select nid, count(nid) as num,strftime("%%Y-%%m",create_time) as ctime from repository_article where blog_id =%(n1)d group by strftime("%%Y-%%m",create_time)' % { "n1": blog.bid} date_list = models.Article.objects.raw(str) template_name="Home/home_summary_list.html" if condition=="tag": template_name="Home/home_title_list.html" article_list=models.Article.objects.filter(tags=val,blog=blog).all() elif condition=="category": article_list=models.Article.objects.filter(classification_id=val,blog=blog).all() print(article_list) elif condition == 'date': article_list = models.Article.objects.filter(blog=blog).extra( where=['strftime("%%Y-%%m",create_time)=%s'], params=[val, ]).all() else: article_list = [] return render( request, template_name, { 'blog': blog, 'tag_list': tag_list, 'category_list': category_list, 'date_list': date_list, 'article_list': article_list } )
前台处理页面:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.css"/> <link rel="stylesheet" href="/static/plugins/font-awesome/css/font-awesome.css"/> <link rel="stylesheet" href="/static/css/edmure.css"/> <link rel="stylesheet" href="/static/css/common.css"/> <link rel="stylesheet" href="/static/css/row_avatar.css"/> <link rel="stylesheet" href="/static/css/theme/{{ blog.theme }}.css"/> {% block css %}{% endblock %} <script type="text/javascript" src="/static/js/jquery-1.12.4.js"></script> <script type="text/javascript" src="/static/plugins/bootstrap/js/bootstrap.js"></script> </head> <body> <div class="pg-header"> <div class="title">{{ blog.title }}</div> <div class="menu-list"> <a class="menu-item" href="/">首页</a> <a class="menu-item" href="/{{ blog.surfix }}.html">个人首页</a> <a class="menu-item" href="/backend/index.html">管理</a> </div> </div> <div class="pg-body"> <div class="body-menu"> <div class="notice"> <div class="notice-header">公告</div> <div class="notice-body"> <ul> <li>昵称:{{ blog.user.nickname }}</li> <li>粉丝:{{ blog.user.fans.count }}</li> <li>关注:{{ blog.user.f.count }}</li> <li>邮箱:{{ blog.user.email }}</li> </ul> <div class="memo"> 真实是人生的命脉,是一切价值的根基。 </div> </div> </div> <div class="tags"> <div class="tags-header">标签</div> <div class="tags-body"> <ul> {% for tag in tag_list %} <li><a href="/{{ blog.surfix }}/tag/{{ tag.nid }}.html">{{ tag.title }}({{ tag.num }})</a></li> {% endfor %} </ul> </div> </div> <div class="types"> <div class="types-header">分类</div> <div class="types-body"> <ul> {% for tag in category_list %} <li><a href="/{{ blog.surfix }}/category/{{ tag.nid }}.html">{{ tag.title }}({{ tag.num}})</a></li> {% endfor %} </ul> </div> </div> <div class="dates"> <div class="dates-header">时间</div> <div class="dates-body"> <ul> {% for tag in date_list %} <li><a href="/{{ blog.surfix }}/date/{{ tag.ctime }}.html">{{ tag.ctime}}({{ tag.num}})</a></li> {% endfor %} </ul> </div> </div> </div> <div class="body-content"> {% block content %}{% endblock %} </div> </div> {% block js %}{% endblock %} </body> </html>
{% extends 'Home/home_layout.html' %} {% block content %} <div class="title-list"> {% for row in article_list %} <div class="row-item"> <div class="title"> <a href="/{{ blog.site }}/{{ row.nid }}.html">{{ row.title }}</a> </div> <div class="tips"> <span>{{ blog.user.nickname }}</span> <span>{{ row.create_time|date:"Y-m-d H:i:s" }} </span> <span> <i class="fa fa-commenting-o" aria-hidden="true"></i> <span>{{ row.comment_count }}</span> </span> <span> <i class="fa fa-thumbs-o-up" aria-hidden="true"></i> <span>{{ row.up_count }}</span> </span> </div> </div> {% endfor %} </div> {% endblock %}
{% extends 'Home/home_layout.html' %} {% block content %} <div class="summary-list"> {% for row in article_list %} <div class="summary-item" style="border-bottom: 1px solid #dddddd;padding: 8px 0;"> <div class="summary-title"> <a href="/{{ blog.site }}/{{ row.nid }}.html">{{ row.title }}</a> </div> <div class="summary-content"> {{ row.summary }} </div> <div class="summary-footer"> <span>{{ row.create_time|date:"Y-m-d H:i:s" }}</span> <span>{{ row.user.nickname }}</span> <span>阅读({{ row.read_count }})</span> <a>评论({{ row.comment_count }})</a> <a href="/backend/edit-article-{{ row.nid }}.html">编辑</a> </div> </div> {% endfor %} </div> {% endblock %}
1.8.3 右侧展示页面
1.8.3.1 标签下面的页面 home_title_list.html
1.8.3.2 分类下面的页面 home_summary_list.html
1.8.3.3 文章最终页
为了查询的快捷,可以为文章表添加四个冗余字段
class Article(models.Model): nid = models.BigAutoField(primary_key=True) title = models.CharField(verbose_name='文章标题', max_length=128) summary = models.CharField(verbose_name='简介', max_length=256) blog = models.ForeignKey(verbose_name='所属博客', to='BlogInfo', to_field='bid', on_delete=models.CASCADE, null=True) create_time = models.DateTimeField(verbose_name='创建时间', null=False) read_count = models.IntegerField(verbose_name='阅读数量',default=0) comment_count = models.IntegerField(verbose_name='评论数量',default=0) up_count = models.IntegerField(verbose_name='点赞数量',default=0) down_count = models.IntegerField(verbose_name='踩数量',default=0) classification_id = models.ForeignKey(verbose_name='文章分类', to='Classification', to_field="nid", on_delete=models.CASCADE, null=True) masterStation_type = [ (1, "Python"), (2, "Linux"), (3, "OpenStack"), (4, "GoLang"), ] ms_Type = models.IntegerField(verbose_name='主站分类', choices=masterStation_type, default=None) tags=models.ManyToManyField( to='Tag', through='Article_Tag', through_fields=('article_id','tag_id') )
from django.shortcuts import HttpResponse,redirect, render
from repository import models
from django.db.models import Count
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from web.Views.Tools import AaronPager
from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt,csrf_protect
import time
import json
def index(request, *args, **kwargs):
"""
博客首页,展示全部博文
"""
# 设置标签列表(注意,该标签直接从model中获取)
ms_Type = models.Article.masterStation_type
# 判断是否有条件(主站分类:ms_Type)传递过来,
article_list = models.Article.objects.filter(**kwargs).annotate(authorsNum=Count('article_updown'))
current_ms_Type = 0
data_count = article_list.count()
cur_page = request.GET.get('p')
# reverse方法可以通过别名反向生成url
base_url = '/'
if kwargs:
current_ms_Type = int(kwargs['ms_Type'])
base_url = reverse('index', kwargs=kwargs)
# 统计每一篇文章的赞、踩个数
# bookList =models.Article.objects.annotate(authorsNum=Count('article_updown'))
aaron_page = AaronPager.AaronPager(data_count, cur_page, 10, 7,base_url )
article_list = article_list[aaron_page.start():aaron_page.end()]
return render(request, "Home/index.html",
{'masterStation_type': ms_Type, 'article_list': article_list, 'current_ms_Type': current_ms_Type,'aaron_page':aaron_page,})
def home(request, site):
"""
博主个人首页
:param request:
:param site: 博主的网站后缀如:http://xxx.com/Aaron.html
:return:
"""
# 根据后缀名获取博客信息
blogInfo=models.BlogInfo.objects.filter(surfix=site).select_related("user").first()
if not blogInfo:
return redirect('/')
else:
# 获取分类信息 这种写法想当于 blog = blogInfo.id
#category_list = models.Article.objects.filter(blog=blogInfo).select_related("classification_id").values("classification_id","title").annotate(num=Count('classification_id'))
str = 'select a.nid as nid,a.title as title,count(b.nid) as num from repository_classification a LEFT JOIN repository_article b on b.blog_id = a.blog_id and a.nid=b.classification_id_id where a.blog_id =%(n1)d GROUP BY (a.nid)' %{"n1":blogInfo.bid}
category_list = models.Classification.objects.raw(str)
# 获取标签信息
#tag_list=models.Tag.objects.filter(blog=blogInfo).annotate(Num=Count('article.article_set'))
str = ' select a.nid as nid,a.title as title ,count(c.nid) as num from repository_Tag a INNER JOIN repository_article_tag b on a.nid=b.tag_id_id LEFT JOIN repository_article c on c.blog_id = a.blog_id and c.nid=b.article_id_id where a.blog_id =%(n1)d GROUP BY (a.nid)' %{"n1":blogInfo.bid}
tag_list = models.Tag.objects.raw(str)
#获取日期,这种系原生的sql
str='select nid, count(nid) as num,strftime("%%Y-%%m",create_time) as ctime from repository_article where blog_id =%(n1)d group by strftime("%%Y-%%m",create_time)' %{"n1":blogInfo.bid}
date_list = models.Article.objects.raw(str)
# 获取文章信息
article_list=models.Article.objects.filter(blog=blogInfo).order_by('-nid').all()
#
return render(request, "Home/home.html", { 'blog': blogInfo,
'tag_list': tag_list,
'category_list': category_list,
'date_list': date_list,
'article_list': article_list})
def filter(request,site,condition,val):
blog = models.BlogInfo.objects.filter(surfix=site).select_related('user').first()
if not blog:
return redirect('/')
# tag_list=models.Tag.objects.filter(blog=blog) #标签
str = ' select a.nid as nid,a.title as title ,count(c.nid) as num from repository_Tag a INNER JOIN repository_article_tag b on a.nid=b.tag_id_id LEFT JOIN repository_article c on c.blog_id = a.blog_id and c.nid=b.article_id_id where a.blog_id =%(n1)d GROUP BY (a.nid)' % {
"n1": blog.bid}
tag_list = models.Tag.objects.raw(str)
str = 'select a.nid as nid,a.title as title,count(b.nid) as num from repository_classification a LEFT JOIN repository_article b on b.blog_id = a.blog_id and a.nid=b.classification_id_id where a.blog_id =%(n1)d GROUP BY (a.nid)' % {
"n1": blog.bid}
category_list = models.Classification.objects.raw(str)
str = 'select nid, count(nid) as num,strftime("%%Y-%%m",create_time) as ctime from repository_article where blog_id =%(n1)d group by strftime("%%Y-%%m",create_time)' % {
"n1": blog.bid}
date_list = models.Article.objects.raw(str)
template_name="Home/home_summary_list.html"
if condition=="tag":
template_name="Home/home_title_list.html"
article_list=models.Article.objects.filter(tags=val,blog=blog).all()
elif condition=="category":
article_list=models.Article.objects.filter(classification_id=val,blog=blog).all()
print(article_list)
elif condition == 'date':
article_list = models.Article.objects.filter(blog=blog).extra(
where=['strftime("%%Y-%%m",create_time)=%s'], params=[val, ]).all()
else:
article_list = []
return render(
request,
template_name,
{
'blog': blog,
'tag_list': tag_list,
'category_list': category_list,
'date_list': date_list,
'article_list': article_list
}
)
def detail(request, site, nid):
cur_page = request.GET.get('p')
blogInfo=models.BlogInfo.objects.filter(surfix=site).select_related("user").first()
# 这样关联如果可以的话,那实在是太厉害了
article=models.Article.objects.filter(blog=blogInfo,nid=nid).select_related("article_detail","classification_id").first();
comment_list = models.Article_Comment.objects.filter(article=article).select_related("reply")
aaron_page = AaronPager.AaronPager(comment_list.count(), cur_page, 5, 7,nid + '.html')
comment_list = comment_list[aaron_page.start():aaron_page.end()]
# tag_list=models.Tag.objects.filter(blog=blog) #标签
str = ' select a.nid as nid,a.title as title ,count(c.nid) as num from repository_Tag a INNER JOIN repository_article_tag b on a.nid=b.tag_id_id LEFT JOIN repository_article c on c.blog_id = a.blog_id and c.nid=b.article_id_id where a.blog_id =%(n1)d GROUP BY (a.nid)' % {
"n1": blogInfo.bid}
tag_list = models.Tag.objects.raw(str)
# category_list=models.Classification.objects.filter(blog=blog).select_related("article").annotate(Num=Count('article')) #分类
str = 'select a.nid as nid,a.title as title,count(b.nid) as num from repository_classification a LEFT JOIN repository_article b on b.blog_id = a.blog_id and a.nid=b.classification_id_id where a.blog_id =%(n1)d GROUP BY (a.nid)' % {
"n1": blogInfo.bid}
category_list = models.Classification.objects.raw(str)
str = 'select nid, count(nid) as num,strftime("%%Y-%%m",create_time) as ctime from repository_article where blog_id =%(n1)d group by strftime("%%Y-%%m",create_time)' % {
"n1": blogInfo.bid}
date_list = models.Article.objects.raw(str)
return render(request, 'Home/home_detail.html',
{
'blog': blogInfo,
'article': article,
'comment_list': comment_list,
'tag_list': tag_list,
'category_list': category_list,
'date_list': date_list,
'aaron_page': aaron_page,
})
@csrf_exempt
def PublishComment(request):
print(1111111111)
return render(request, "Aaron/1.html", {"res": "评论成功"})
{% extends 'Home/home_layout.html' %} {% block css %} <link rel="stylesheet" href="/static/plugins/font-awesome-4.7.0/css/font-awesome.min.css"/> <script src="/static/plugins/kindeditor/kindeditor-all.js"></script> <script type="text/javascript" src="/static/js/jquery-3.3.1.min.js"></script> {% endblock %} {% block content %} {% csrf_token %} <div class="art-title"> <a>{{ article.title }}</a> </div> <div class="art-content"> {{ article.article_detail.detail|safe }} </div> <div class="art-recommend clearfix"> <div class="recommend"> <a href="#" class="up" style="margin: 5px 10px;display: inline-block;padding: 5px 15px;border: 1px solid #dddddd;text-align: center;"> <i class="fa fa-thumbs-o-up" aria-hidden="true" style="font-size: 25px"></i> <div>{{ article.up_count }}</div> </a> <a href="#" class="down" style="margin: 5px 30px 5px 10px;display: inline-block;padding: 5px 15px;border: 1px solid #dddddd;text-align: center;"> <i class="fa fa-thumbs-o-down fa-3" aria-hidden="true" style="font-size: 25px"></i> <div>{{ article.down_count }}</div> </a> </div> </div> <div class="art-tips clearfix"> <div class="tips"> <span class="ctime">{{ article.create_time }}</span> <a class="author">{{ blog.user.username}}</a> <span class="comment-count">评论({{ article.comment_count }})</span> <span class="read-count">阅读({{ article.read_count }})</span> </div> </div> <div id="AllanboltSignature"> <div style="border-bottom: #e0e0e0 1px dashed; border-left: #e0e0e0 1px dashed; padding: 10px; font-family: 微软雅黑; font-size: 11px; border-top: #e0e0e0 1px dashed; border-right: #e0e0e0 1px dashed; " id="PSignature"> <div style="float:left;70px;"> <img src="/static/imgs/o_Warning.png" style="65px;height:65px"> </div> <div style="float:left;padding-top:10px;"> <div style="padding: 1px">作者:<a href="http://www.cnblogs.com/wupeiqi/" target="_blank">{{ blog.user.username }}</a></div> <div style="padding: 1px">出处:<a href="http://www.cnblogs.com/wupeiqi/" target="_blank">http://www.cnblogs.com/{{ blog.site }}.html/</a> </div> <div style="padding: 1px">本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接</div> </div> <div style="clear:both;"></div> </div> </div> <div class="art-comment"> <div class="comment-title"> 评论列表 </div> <div class="comment-list"> {% for comment in comment_list %} <div class="comment-item"> <div class="reply-title clearfix"> <div class="user-info"> <span>{{ comment.user.username }}</span> <span>{{ comment.create_time }}</span> </div> <div class="reply"> <a href="#">回复</a> </div> </div> <div class="reply-body"> {% if comment.reply %} <div class="reply-user">@{{ comment.reply.user.username }}</div> {% endif %} <div class="content"> {{ comment.comment }} </div> </div> </div> {% endfor %} </div> <div class="comment-list-pager"> <ul class="pagination pagination-sm"> {{ aaron_page.page_str|safe }} </ul> </div> <div class="comment-area"> <div class="replay-comment-user"></div> <div class="reply-area" style="position: relative;"> {% if not request.user %} <div style="text-align:center;line-height:200px;position: absolute;top:0;left:0;right:0;bottom: 0;background-color: rgba(255,255,255,.6)"> 您需要登录后才可以回帖 <a href="/login.html">登录</a> | <a href="/register.html">立即注册</a> </div> {% endif %} <textarea name="content" style=" 100%;height:200px;visibility:hidden;"></textarea> </div> <div> <div class="reply-btn"> <span><span>25</span>/255字</span> <a class="btn btn-primary" onclick="PublishComment()">发表回复</a> </div> </div> </div> </div> {% endblock %} {% block js %} <script type="text/javascript" src="/static/js/jquery-3.3.1.min.js"></script> <script> var editor; KindEditor.ready(function (K) { editor = K.create('textarea[name="content"]', { resizeType: 1, allowPreviewEmoticons: false, allowImageUpload: false, items: [ 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold', 'italic', 'underline', 'removeformat', '|', 'justifyleft', 'justifycenter', 'justifyright', 'insertorderedlist', 'insertunorderedlist', '|', 'emoticons', 'image', 'link'] }); }); function PublishComment(){ var st = "123"; $.ajaxSetup({ data: {csrfmiddlewaretoken: '{{ csrf_token }}'}, }); $.ajax({ url: 'PublishComment.html', type: 'POST', dataType: 'json', data: {"ajaxTest": st}, success: function (arg) { alert(arg) } }) } </script> {% endblock %}