表设计
from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. class UserInfo(AbstractUser): nid=models.AutoField(primary_key=True) # blank=True admin中改字段可以不填,null=True是数据库层面可以为空 telephone = models.BigIntegerField(null=True,blank=True) # auto_now_add 创建时自动添加当前时间 create_date = models.DateField(auto_now_add=True) # upload_to需要传一个路径 # default 默认头像的路径 avatar = models.FileField(upload_to='avatar/', default='avatar/default.png') # 跟Blog表一对一 blog = models.OneToOneField(to='Blog', to_field='nid',null=True) class Meta: verbose_name='用户表' verbose_name_plural = verbose_name # 个人站点表 class Blog(models.Model): nid = models.AutoField(primary_key=True) site_name = models.CharField(max_length=32) site_title = models.CharField(max_length=64) # 存css文件的路径 theme = models.CharField(max_length=32) def __str__(self): return self.site_name class Category(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) blog = models.ForeignKey(to='Blog', to_field='nid') class Tag(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) blog = models.ForeignKey(to='Blog', to_field='nid') def __str__(self): return self.name class Article(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=64,verbose_name='文章标题') desc = models.CharField(max_length=255) # 存大文本 content = models.TextField() create_time = models.DateTimeField(auto_now_add=True) # 跟站点表一对多 blog = models.ForeignKey(to='Blog', to_field='nid',null=True) # 跟分类一对多 category = models.ForeignKey(to='Category', to_field='nid',null=True) # 跟标签多对多,手动创建第三张表 tag = models.ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag')) # 后来想到的 # 评论数,点赞,点踩数 comment_num=models.IntegerField(default=0) up_num=models.IntegerField(default=0) down_num=models.IntegerField(default=0) def __str__(self): return self.title class Article2Tag(models.Model): nid = models.AutoField(primary_key=True) article = models.ForeignKey(to='Article', to_field='nid') tag = models.ForeignKey(to='Tag', to_field='nid') #谁对哪篇文章,点赞还是点踩 class UpAndDown(models.Model): nid = models.AutoField(primary_key=True) user = models.ForeignKey(to='UserInfo') article = models.ForeignKey(to='Article', to_field='nid') is_up = models.BooleanField() #评论对评论自关联, #谁,什么时候,对哪篇文章,评论了什么内容,父评论是谁 class Comment(models.Model): nid = models.AutoField(primary_key=True) user = models.ForeignKey(to='UserInfo') article = models.ForeignKey(to='Article', to_field='nid') content = models.CharField(max_length=255) # 评论时间 create_time=models.DateTimeField(auto_now_add=True) # parent =models.ForeignKey(to='Comment',to_field='nid') # 自关联,存父评论id parent = models.ForeignKey(to='self', to_field='nid',null=True) # parent =models.IntegerField()
1 登陆功能:
-图形验证码
-ajax提交
-认证用的是auth
-认证通过,login
1 forms组件 1 定义 from django import forms from django.forms import widgets from django.core.exceptions import ValidationError # 2 写一个类 class RegForm(forms.Form): # 3 写属性 name=forms.CharField(max_length=8,min_length=3,label='用户名',error_messages={'max_length':'超长了'}, widget=widgets.TextInput(attrs={'class':'form-control'}) ) # 4 局部钩子函数 def clean_name(self): name=self.cleaned_data.get('name') if name.startswith('sb'): raise ValidationError('不能以sb开头') else: # 切记,如果正确,一定要返回name return name # 5 全局钩子函数 def clean(self): #一系列逻辑判断 #如果校验通过:返回cleaned_data #如果校验不通过:raise ValidationError('两次密码不一致'),错误放到__all__ pass 2 views中使用: def test(request): if request.method=='GET': regform=RegForm() else: regform=RegForm(request.POST) if regform.is_valid(): #一般情况需要存数据库了 pass else: error_all=regform.errors.get('__all__') # error_all=regform.errors['__all__'] return render(request,'register.html',locals()) 3 模板中使用 <form action=""> {% for foo in regform %} {{ foo.label }}:{{ foo }} <span>{{ foo.errors.0 }}</span> {% endfor %} <input type="submit"> <span>{{ error_all }}</span> </form> 2 cookie和session 1 cookie:由服务器产生,存放在客户端浏览器上的键值对 2 django中使用cookie: -设置值: obj=HttpResponse('ok') obj.set_cookie('key','value',max_age=10) -取值: request.COOKIES.get('key') request.COOKIES['key'] -删除值: obj=HttpResponse('ok') obj.delete_cookie('key') 3 session:保存在服务器上的键值对 -设置值: request.session['key']='value' request.session['key1']='value1' 1 生成一个随机字符串:dasfasdf 2 在django_session表中存入dasfasdf {'key':'value','key1':value1} 超时时间 3 把sessionid:dasfasdf写入到cookie -取值: request.session.get('key') -删除值: request.session.flush():全删除 request.session.delete():只删除数据库 -其它配置参数: 了解 3 Auth模块 1 Django自带的用户认证模块,可以快速的实现登录,注销,修改密码... 2 扩展auth表,需要继承AbstractUser 3 一定不要忘记在setting中配置:AUTH_USER_MODEL = "app名.UserInfo" 4 它提供的功能 -from django.contrib.auth import authenticate,login,logout -用户认证:user=authenticate(username=lqz,password=123) -用户一旦认证通过,调用login(request,user),以后从request.user中就能取出当前登录人对象 -退出:logout(request),request.user就是匿名用户 -校验是否通过认证(是否登录):request.user.is_authenticated() -创建普通用户 -models.UserInfo.objects.create_user(username=lqz) -创建超级用户 -models.UserInfo.objects.create_superuser(username=lqz) -修改密码 -用user对象.set_password(新密码) -一定要记住save -校验密码 -check_password(password) -登录认证装饰器(没有登陆的时候跳转) -login_required(login_url='/login/') -全局配置(在setting中配置): LOGIN_URL = '/login/' is_staff: 用户是否拥有网站的管理权限:create_superuser:is_staff是1 is_active: 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录。 今日内容: 1 中间件 -是什么?中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能 -怎么用: -自定义中间件: 1 写一个类,继承MiddlewareMixin, 2 在类中写方法: process_request 3 在settings中配置 -5个方法(process_request,process_response) ****** process_request(self,request) -执行顺序,settings中中间件自上而下执行 -请求来的时候会执行它 -request对象,就是本次请求的request对象,对它处理后,视图函数拿到的就是处理后的request对象 process_view(self, request, callback, callback_args, callback_kwargs) -callback是视图函数,callback_args, callback_kwargs是视图函数的参数 -可以调用callback方法 process_template_response(self,request,response)(忘掉) -只有视图函数返回的对象中有render方法的时候,才会执行 process_exception(self, request, exception) -视图函数出错,会执行它 ***** process_response(self, request, response) -执行顺序,settings中中间件自下而上执行 -响应走的时候,会执行它 -request对象,就是本次请求的request对象,response是响应对象(HttpResponse的对象) -如果process_request方法返回HttpResponse的对象,请求直接返回,按中间件方法执行顺序往回走 2 csrf xss攻击/csrf或xsrf跨站请求伪造 使用:中间件不注释,form表单中写{% csrf_token %} 3 bbs表设计 -一个项目从无到有 1 需求分析 -登录ajax,图形验证码 -注册forms和ajax,上传头像,头像预览 -博客首页 -个人站点 -点赞,点踩 -评论 -根评论 -子评论 -后台展示 -添加文章 -防xss攻击 2 设计程序以及程序的架构 -Django -数据库设计 3 分任务开发程序 4 测试 5 上线运行 -数据库设计 -用户表:UserInfo---auth模块 ... -telephone:手机号 -create_date:注册时间 -avatar:用户头像 -blog:跟Blog表一对一 -个人站点表:Blog -site_name:站点名称 -site_title:站点标题 -theme:站点主题样式 -分类表:Category -nid -name:分类名称 -blog:属于哪个站点 一对多 -标签表:Tag -nid -name:标签的名称 -blog:属于哪个站点 一对多 -文章表:Article -nid -title:文章标题 -desc:文章摘要 -content:文章内容 -create_time:文章发布时间 -blog:文章属于哪个站点, 一对多 -category:文章分类 一对多 -tag:文章标签 多对多 -Article2Tag:文章跟标签的中间表 -nid -article_id:文章id -tag_id:标签id -点赞点踩表:UpAndDown -nid -user:谁 一对多 -article:给那篇文章 一对多 -is_up:点赞或点踩 -评论表:Comment -nid -user:谁 一对多 -article:给那篇文章 一对多 -content:评论了什么内容 -parent_id:父评论的id 自关联 nid 用户id 文章id 内容 parent_id(父评论的id) 1 1 1 你好 null 2 2 1 写的真好 null 3 3 1 你傻么 1 作业: 1 csrf中间件不注释,用ajax提交post请求 2 用中间件实现只有登录了以后,才能访问order,userinfo这些路径,如果没有登录,重定向到登录页面
2 注册功能
-forms组件
-ajax提交
-错误信息渲染
1 注册: -forms组件渲染页面 -头像预览 -点击img触发头像上传 -$("#myform").serializeArray()的使用 -jq的循环 $.each(循环的对象,匿名函数(两个参数)): 循环对象如果是个列表,匿名函数的两个参数,一个是索引,一个是索引对应的值 循环对象如果是个字典,匿名函数的两个参数,一个是key,一个是value -上传文件: processData: false, contentType: false, -错误信息渲染 -定时器 -reg_form.is_valid()走了它之后,cleaned_data中才有值 -2 登陆 -生成验证码 -Pillow -生成图片 -在图片上写字 -指定写字的字体 -内存管理器BytesIO -验证码保存在session中 -验证码刷新: $("#id_imgcode")[0].src += '?' 127.0.0.1:8000/getcode/?random=随机数
3 首页
admin中添加数据要将用户与一个用户表进行关联
media的配置,设置用户上传头像路径和默认头像
开口:
url(r'^media/(?P<path>.*)', serve,{'document_root':settings.MEDIA_ROOT}),
-全部文章取出来(分页)for循环渲染
-首页设计: -用户是否登陆 {% if request.user.is_authenticated %} <li><a href="#">{{ request.user.username }}</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">个人中心 <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">修改密码</a></li> <li><a href="#">修改头像</a></li> <li role="separator" class="divider"></li> <li><a href="/logout/">退出</a></li> </ul> </li> {% else %} <li><a href="/login/">登陆</a></li> <li><a href="/register/">注册</a></li> {% endif %} -for循环显示所有文章 -文章作者显示: -{{ article.blog.userinfo.username }}:通过文章,正向查询站点(blog),通过站点反向查询用户,通过用户取出姓名 -文章评论和点赞数显示: -<span>评论({{ article.comment_set.count }})</span> -<span>点赞({{ article.upanddown_set.count}})</span> -通过文章反向查询(表名小写_set)所有评论,计算评论条数 比较复杂,所以用了在文章表中加入三个字段 comment_num=models.IntegerField(default=0) up_num=models.IntegerField(default=0) down_num=models.IntegerField(default=0) -admin的使用(快速录入数据): -注册表,才能在admin后台看到 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.UpAndDown) admin.site.register(models.Comment) -表名的中文显示: class Meta: verbose_name='用户表' verbose_name_plural = verbose_name -字段的中文显示(verbose_name): title = models.CharField(max_length=64,verbose_name='文章标题') -admin表单提交,是否必填(blank),注意区分null=True -telephone = models.BigIntegerField(null=True,blank=True) -mediaroot配置 -media文件:用户上传的静态文件 -static文件:网站所用的静态文件 -在settings中配置:MEDIA_ROOT = os.path.join(BASE_DIR, 'media'),用户上传的文件放到里面 -在路由中写: url(r'^media/(?P<path>.*)', serve,{'document_root':settings.MEDIA_ROOT}),
4 个人站点
-文章展示(当前站点的所有文章)
-左侧标签显示
-三个分组查询,TruncMonth
-抽取成inclusion_tag
-左侧过滤
-个人站点: -路由:url(r'^(?P<username>w+)',views.site), -视图函数: def site(request,username): #通过名字取数据库查询此人是否存在,如果存在,返回此人个人站点,如果不存在,返回404 user=models.UserInfo.objects.filter(username=username).first() if not user: return render(request,'error.html') # 取到此人个人站点 blog=user.blog # 取出该站点下所有文章(从blog取article,反向查询,一对多,按表名小写_set.all()) article_list=blog.article_set.all() return render(request,'site.html',locals()) -页面样式显示:(动态显示) <link rel="stylesheet" href="/static/css/{{ blog.theme }}"> -补充: 如果想django不自动加斜杠,需要配置APPEND_SLASH=False -防盗链: -就是根据refer信息 -自定义session(在中间件中处理) -自己的图片防盗链(头三次,可以访问) -访问频率控制(一分钟只能,同一个ip地址只能访问3次) -设置一个全局变量,记录60sip 访问次数
-个人站点左侧显示 -截断表分析 id 时间 文章内容 month 1 2019-01-25 03:04:07.844138 111 2019-01 2 2018-12-01 03:04:54.000000 222 2018-12 3 2018-12-01 03:05:17.000000 333 2018-12 4 2018-11-01 03:05:38.000000 444 2018-11 -官方提供 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 -重点总结: -查询当前站点分类下的文章数 annotate 方法不局限于用于本文提到的统计分类下的文章数,你也可以举一反三,只要是两个 model 类通过 ForeignKey 或者 ManyToMany 关联起来,那么就可以使用 annotate 方法来统计数量。 category_ret = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c') -查询当前站点标签下的文章数 tag_ret=models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name','c') -查询当前站点下每个月份下的文章数 -month_ret=models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values_list('month','c') -前端显示: {% for category in category_ret %} <p><a href="">{{ category.0 }}({{ category.1 }})</a></p> {% endfor %} -个人站点过滤 -抽取成inclusion_tag -路由设计: <p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }}({{ category.1 }})</a></p>
5 文章详情
-继承了母版
-marksafe展示html
6 点赞点踩
-事务
-点赞点踩 -前端: -样式:从博客园扣过来的 -ajax提交数据 -谁对哪篇文章点赞或点赞 -只写一个ajax,取出当前点击div内的span,后续直接在span上加一 -后端: -先判断是否登录 -再判断是否已经点过 -去article表中修改点赞或点踩数 -去点赞点踩表中插入一条数据
7 评论
-根评论
-根评论提交
-根评论render显示
-根评论ajax显示
-子评论
-子评论提交
-子评论render显示
-子评论ajax显示
-根评论 -提交 -跟点赞点踩类似 -render显示 -后端取出所有评论 -模板中渲染: <ul class="list-group"> -日期过滤:comment.create_time|date:'Y-m-d H:i:s' -ajax显示: -es6的字符串替换语法:`asdfasfd ${变量名}`,变量需要先定义 -$(".list-group").append(ss) 拼完追加到后面 -es6字符串替换 'you is %s'%'big' var user_name='lqz' ss=` <li class="list-group-item"> <p> <span>${ user_name }</span> <span>${ time }</span> </p> ${content} </li> `
1 子评论提交 -点击回复按钮,1 获取parent_id 2在文本框中输入:@人名 回车 -提交的时候:如果是子评论,需要切掉头部 var index=content.indexOf(' ')+1 content=content.slice(index) -提交的时候:需要把parent_id提交到后台 2 子评论render显示 -判断是否有父评论,如果有,显示父评论的人名 -@{{ comment.parent.user.username }} 3 子评论的ajax显示 -根据parent_id是否有值,判断要拼接的html样式 -评论提交成功,parent_id的值要置为空 -后台:如果是子评论格式,需要返回父评论评论人的名字
8 后台文章展示
-table的展示
9 新增文章
-富文本编辑器使用
-富文本编辑器上传图片
-处理xss攻击(bs4)
5 文章添加 -富文本编辑器 -从官网下载,放到static文件夹下 -window.editor = K.create('#id_content') -其它参数,具体看文档 -上传图片,携带csrf uploadJson:'/add_img/', extraFileUploadParams : { csrfmiddlewaretoken : '{{ csrf_token }}', } -xss攻击(bs4) -通过bs4模块,取出script标签,删除decompose() -取出html中150个字符:soup.text[0:150] beautifulsoup4 模块 -解析html页面的 -爬虫中用的多 -局部禁用csrf,局部使用 from django.views.decorators.csrf import csrf_protect,csrf_exempt @csrf_exempt 局部禁用,前提是csrf中间件使用着 @csrf_protect 局部使用,前提是csrf中间件没有使用 -CBV加装饰器 1 可以加在方法上 @method_decorator(auth_login) 2 可以加在类上 -@method_decorator(auth_login,name='dispatch'):全部使用 -@method_decorator(auth_login,name='get'):只在get上使用 3 csrf的装饰器,只能加在类上 @method_decorator(csrf_exempt,name='dispatch')