别让人生,输给了心情。心情不是人生的所有,却能左右人生的所有。心情好,什么都好,心情很差,一切都乱了。咱们经常不是输给了别人,而是坏心情贬低了咱们的形象,下降了咱们的能力,扰乱了咱们的思惟,从而输给了本身。 控制好心情,生活才会到处祥和。好的心态塑造好心情,好心情塑造最出色的你。 静静的过本身的生活,心若不动,风又奈何。你若不伤,岁月无恙。
数据库设计规则是: 1.先建立基表:用户表、站点表、文章表、标签表、分类表、文章2标签第三张关系表、点赞点踩表、评论表 2.书写表中的基本含有的字段 3.添加外键(一对一,一对多,多对多) 4.第三张关系表 注意事项:建立外键关系的时候,to='表名',不要忘记引号,null=true,并非全部的外键都加的
必定要进行settings的相关配置: 1.数据库配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'bbszikao', 'USER':'root', 'PASSWORD': 'root', 'HOST':'127.0.0.1', 'PORT':3306, 'CHARSET':'utf8' } } 2.静态文件资源配置 STATICFILES_DIRS=[ os.path.join(BASE_DIR,'static') ] 3.models.py文件中,用户表继承AbstractUser类,须要对其进行settings配置 from django.contrib.auth.models import AbstractUser 配置auth模块的访问: AUTH_USER_MODEL='app01.Userinfo' 4.静态图片资源settings配置+urls路由访问配置,暴露给用户查看,用户注册能够访问到默认头像 #settings文件配置: MEDIA_ROOT=os.path.join(BASE_DIR,'media') #urls文件访问头像路由配置 url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
分为:register函数+register.html+myform.py文件 后端开发逻辑: # myform.py 文件 1.创建myform.py文件,利用forms表单,提交注册的数据信息 建立class MyRegForm(forms.Form):类 用户名,密码,确认密码,邮箱 from django import forms from app01 import models class MyRegForm(forms.Form): username = forms.CharField(max_length=8,min_length=3,label='用户名', error_messages={ 'max_length': '用户名最长8位', 'min_length': '用户名最短3位', 'required': "用户名不能为空" }, #标签设置 widget=forms.widgets.TextInput(attrs={'class': 'form-control'}) 创建局部钩子函数校验用户名是否存在 # 局部钩子 校验用户名是否已存在 def clean_username(self): username = self.cleaned_data.get('username') res = models.Userinfo.objects.filter(username=username) if res: self.add_error('username','用户名已存在') return username 创建全局钩子函数校验密码是否一致 # 全局钩子 校验两次密码是否一致 def clean(self): password = self.cleaned_data.get('password') confirm_password = self.cleaned_data.get('confirm_password') if not password == confirm_password: self.add_error('confirm_password','两次密码不一致') return self.cleaned_data # models.py 文件 2.把myform文件中的MyRegForm表单对象拿到, 而后把form_obj对象发送给前端register.html页面 form_obj=myform.MyRegForm() return render(request,'register.html',locals()) 后端开发逻辑以下: 把myform文件中的MyRegForm表单对象拿到 判断前端发送过来的请求方式是否是post请求 定义back_dic 字典 对用户在前端提交的post数据进行校验,生成form_obj对象 若是数据合法: 获取全部键值对 pop掉确认密码键值对 获取前端发送的文件请求(avatar),建立文件对象 判断用户是否上传文件: 上传文件了,把avatar添加到clean_data字典对象中 用户表建立用户(create_user) back_dic字典添加msg信息,注册成功 back_dic字典添加url,login路径 数据不合法: 字典添加code=2000 字典添加msg=form_obj.errors 返回json数据到前端,[须要导入JsonResponse模块(from django.http import JsonResponse)] 而后把form_obj对象发送给前端register.html页面
上传头像文件功能,注册按钮功能html
前端开发逻辑:register.html文件,前端只整理须要整理的 上传头像文件功能 <script> //上传头像文件相关的处理 $('#mdd').on('change',function () { //利用内置对象filereader完成文件的读取操做 let MyFileReader=new FileReader(); //获取用户上传的文件对象 let fileobj=$(this)[0].files[0]; //让文件阅读器读取文件,IO操做,异步 MyFileReader.readAsDataURL(fileobj); //将读取以后的内容替换到img标签src属性中 MyFileReader.onload=function () { $('#img').attr('src',MyFileReader.result) } }); // 注册按钮 $('#submit').click(function () { // 将用户输入的数据所有发送给后端 普通的键值对 文件 let MyFormData = new FormData(); // 不停的朝里面添加键值对 {#MyFormData.append('','')#} {#console.log($('#myform').serializeArray())#} // 普通键值对添加完毕 (利用form标签内部有一个自动序列化普通键值对方法) $.each($('#myform').serializeArray(),function (index,obj) { MyFormData.append(obj.name,obj.value) }); // 手动添加文件数据 MyFormData.append('avatar',$('#mdd')[0].files[0]); // 发送ajax请求 $.ajax({ url:'', type:'post', data:MyFormData, // 发送文件必定要指定两个参数 processData:false, // 不要让浏览器处理你的数据 contentType:false, // 不要使用任何的编码 django可以识别对象自身 success:function (data) { if (data.code == 1000){ // 跳转到登陆页面 window.location.href = data.url }else{ $.each(data.msg,function (index,obj) { {#console.log(index,obj)#} // index就是报错字段 obj就是错误信息 数组的形式 // 获取报错字段 手动拼接出该字段所对应的input框的id值 let targetId = '#id_' + index; $(targetId).next().text(obj[0]).parent().addClass('has-error') }) } } }) }); // input框获取焦点事件,---这个是鼠标放到input框上面后,错误信息消失 $('input').focus(function () { $(this).next().text('').parent().removeClass('has-error') }) </script>
1.所须要的模块 import random from PIL import Image,ImageDraw,ImageFont from io import BytesIO,StringIO ''' 内存管理器模块: BytesIO 保存数据,而且在获取的时候,是以二进制的方式给你 StringIO 保存数据,而且在获取的时候,是以字符串的方式给你 Image 生成图片 ImageDraw 在图片上写字 ImageFont 控制字的字体样式 ''' #io_obj=BytesIO() #你就将该对象当作是文件句柄便可 ''' 什么是文件句柄??? 在文件I/O中,要从一个文件读取数据,应用程序首先 要调用操做系统函数并传送文件名,并选一个到该文件 的路径来打开文件。该函数取回一个顺序号,即文件句柄 (file handle),该文件句柄对于打开的文件是惟一的 识别依据。要从文件中读取一块数据,应用程序须要调用 函数ReadFile,并将文件句柄在内存中的地址和要拷贝的 字节数传送给操做系统。当完成任务后,再经过调用系统函数 来关闭该文件。” ''' 2.图片验证码的开发分为两步: 随机取色+图片验证码 2.1随机取色 def get_random(): return random.randint(0,255),random.randint(0,255),random.randint(0,255) 2.2图片验证码函数 图片的宽高和随机取色---生成画板对象 Image.new 将生成好的图片对象交给ImageDraw---画笔对象 ImageDraw.Draw 字体样式 ---何种字体 ImageFont.truetype #随机验证码 ---何种要求(大小写英文加数字,5位) 定义code='' 循环5次: 大写字母 upper_str 小写字母 lower_str str(随机数) random_int 随机选取一个,random.choice([大写,小写,str(随机数)]) 往图片上写一个验证码 img_draw.text 存储写的字 code+=tmp 将code存到session中,供全局函数访问 request.session['code']=code 生成I/O文件句柄 io_obj=BytesIO() 图片对象调用save方法保存io_obj文件对象,png的格式进行保存 img_obj.save(io_obj,'png') return HttpResponse(io_obj.getvalue()) -------------------------------------------------------------------------------- 3.图片验证码代码以下: def get_code(request): # 图片的宽高和随机取色 ----画板 img_obj=Image.new('RGB',(360,35),get_random()) #将生成好的图片对象交给ImageDraw ---画笔 img_draw=ImageDraw.Draw(img_obj) #字体样式 ---何种字体 img_font=ImageFont.truetype('static/font/111.ttf',30) #随机验证码 ---何种要求(大小写英文加数字,5位) code='' for i in range(5): upper_str=chr(random.randint(65,90)) lower_str=chr(random.randint(97,122)) random_int=str(random.randint(0,9)) #随机选取一个 tmp=random.choice([upper_str,lower_str,random_int]) #往图片上写一个验证码 img_draw.text((i*60+60,0),tmp,get_random(),img_font) #存储写的字 code+=tmp print(code) #这个验证码后面其余视图函数可能要用到, #找个地方存一下,而且这个地方全局的视图函数都能访问 request.session['code']=code io_obj=BytesIO() #你就将该对象当作是文件句柄便可 ''' 在文件I/O中,要从一个文件读取数据,应用程序首先 要调用操做系统函数并传送文件名,并选一个到该文件 的路径来打开文件。该函数取回一个顺序号,即文件句柄 (file handle),该文件句柄对于打开的文件是惟一的 识别依据。要从文件中读取一块数据,应用程序须要调用 函数ReadFile,并将文件句柄在内存中的地址和要拷贝的 字节数传送给操做系统。当完成任务后,再经过调用系统函数 来关闭该文件。” ''' img_obj.save(io_obj,'png') return HttpResponse(io_obj.getvalue())
图片验证码随机变化的逻辑其实很简单,就是: 首先为验证码绑定点击事件, 其次拿到img中的src属性, 最后为src设置新的值,使得图片验证码不断的更换 <div class="col-md-6"> <img src="/get_code/" alt="" width="360" height="35" id="id_img"> </div> <script> //拿到img中的src, //而后为src设置新的值,使得图片验证码不断的更换 $('#id_img').click(function () { var oldPath = $(this).attr('src'); $(this).attr('src',oldPath+='?') }); </script>
urls.py文件中: # 登陆功能 url(r'^login/',views.login,name='login'), views.py文件中: 开发逻辑以下: 若是请求方式是post请求: 定义back_dic 获取post请求的用户名 获取post请求的密码 获取post请求的验证码 #先校验验证码是否正确,忽略大小写 #再校验用户名和密码是否正确 若是用户输入的验证码和后端保存的验证码相等: 再用auth模块校验用户名和密码是否相等,生成用户对象 若是相等: 利用auth模块保存用户对象的登陆状态 #就能够在任意位置经过request.user获取到当前登陆对象,而且request.user.is_authenticated()判断当前用户是否登陆 back_dic字典添加msg登陆成功 back_dic字典添加url,home主页路径 若是用户名和密码不相等: back_dic字典添加code=2000 back_dic字典添加msg用户名或密码错误 若是验证码不相等: back_dic字典添加code=3000 back_dic字典添加msg,验证码错误 经过JsonResponse返回字典 返回到登陆页面 # 保存用户登陆状态,这个不太会,对该知识点模糊要多注意了, # 经过auth模块的login方法将用户保存起来,就能够在任意位置经过request.user, # 获取到当前登陆对象,而且能够经过request.user.is_authenticated()判断当前用户是否登陆 auth.login(request, user_obj) auth模块很重要须要多复习复习
为登陆按钮绑定点击事件, 利用ajax请求,把数据发送到后端 ajax的固定格式为: $.ajax({ url:'', type:'post', data:{k,v键值对,'csrfmiddlewaretoken':'{{ csrf_token }}'}, success:function(data){ //此处的data就是后端返回过来的back_dic字典对象 若是code=1000: 跳转到对应页面的url链接 若是不等于1000: 渲染错误数据信息 信息错误后自动刷新验证码 } }) 提交post请求跳过csrf中间件拦截的两种方式: {% csrf_token %} ------- 在form表单中书写 'csrfmiddlewaretoken':'{{ csrf_token }}' ------- 在ajax中的data字典中书写 <div> <input type="button" class="btn btn-primary" value="登陆" id="id_submit"> <span style="color: red;" id="id_error"></span> //在此处渲染页面的错误信息 </div> <script> $('#id_submit').click(function () { $.ajax({ url:'', type:'post', data:{ //$('#标签名').val()是获取当前标签的值 'username':$('#id_username').val(), 'password':$('#id_password').val(), 'code':$('#id_code').val(), 'csrfmiddlewaretoken':'{{ csrf_token }}' }, success:function (data) { if(data.code == 1000){ // 跳转连接 window.location.href = data.url }else{ //渲染错误数据信息 $('#id_error').text(data.msg); //数据填写错误,提交后验证码再次刷新 var oldPath = $('#id_img').attr('src'); $('#id_img').attr('src',oldPath+='?') } } }) </script>
#home主页 第一步:查询全部文章,生成文章queryset对象, 第二步:把数据所有提交到home页面 def home(request): #查询全部文章,生成文章queryset对象, #把数据所有提交到home页面 article_queryset=models.Article.objects.all() return render(request,'home.html',locals())
主页搭建共分为四块, 第一块,导航条 第二块,左侧面板 2 第三块,中间面板 8 第四块,右侧面板 2
导入auth模块,导入登陆认证装饰器: from django.contrib import auth from django.shortcuts import reverse ----这个是用来反向解析的模块 from django.contrib.auth.decorators import login_required 给退出登陆函数添加@login_required装饰器,装饰器不要加括号 利用auth模块的退出登陆函数 ---auth.logout(request) 返回经过重定向反转解析到home主页 ---redirect(reverse('home')) 代码以下: @login_required def logout(request): auth.logout(request) return redirect(reverse('home')) ---退出登陆以后跳转到home主页
用户登陆状况下: 展现用户名: 拿到用户的用户名,------超连接;经过auth模块的is_authenticated判断用户是否已经登陆 修改密码 修改头像 后台管理 退出登陆 未登陆状况下: 展现登陆 --超连接,反向解析login,{% url 'login' %} 展现注册 --超连接,反向解析register,{% url 'register' %} 部分逻辑代码以下: {% if request.user.is_authenticated %} <li><a href='#'> {{ request.user.username }} </a></li> <li><a data-target="#myModal" data-toggle="modal">修改密码</a></li> <li><a href="#">修改头像</a></li> <li><a href="#">后台管理</a></li> <li role="separator" class="divider"></li> <li><a href="{% url 'logout' %}">退出登陆</a></li> {% else %} <li><a href="{% url 'login' %}">登陆</a></li> <li><a href="{% url 'register' %}">注册</a></li> {% endif %}
urls.py文件中开设修改密码的路由 url(r'^set_password/',views.set_password,name='set_pwd') views.py文件 from django.shortcuts import reverse ----这个是用来反向解析的模块 from django.contrib import auth from django.contrib.auth.decorators import login_required 首先给修改密码函数添加装饰器@login_required 若是前端请求方式是post请求: 获取前端发送过来的原密码old_password 获取前端发送过来的新密码new_password 获取前端发送过来的确认密码confirm_password #利用auth模块先判断前端发送过来的原密码是否正确, #check_password是auth模块自带的校验密码是否相等的功能 request.user.check_password(old_password) 若是原密码正确: 若是新密码和确认密码相等: 利用auth模块中的set_password设置新的密码 而后利用auth模块中的save方法进行保存 返回重定向解析到login登陆页面 若是新密码和确认密码不相等: 返回文本,两次密码不一致 若是原密码不正确: 返回文本,原密码错误 注意事项: 由于没有导入reverse模块,而且把reverse写成了reversed致使的bug 在退出登陆和修改密码两处须要用到的反向解析的地方代码都是写成reversed致使的项目bug,以下: 报错信息以下: NoReverseMatch at /set_password Reverse for '<reversed object at 0x0000000004EA0E48>' not found. '<reversed object at 0x0000000004EA0E48>' is not a valid view function or pattern name. 翻译以下: NoReverseMatch在/ set_password 没有找到'< Reverse object at 0x0000000004EA0E48>'。''不是有效的视图函数或模式名。
利用form表单,发送post请求 反向解析到set_pwd路径下,{% url 'set_pwd' %} 利用{% csrf_token %} 跳过csrf中间件 下面就是form表单中的5个div,分别是: 用户名,使用disable属性,默认展现,用户名不支持修改 原密码 新密码 确认密码 取消,修改
1.建立超级用户,进入后台管理---Tools---Run manage Task -----createsuperuser 2.去应用下的admin.py文件中注册你想要管理的模型类 导入models文件 注册管理的模型类: admin.site.register(models.Userinfo) admin.site.register(models.Blog) admin.site.register(models.Tag) admin.site.register(models.Category) admin.site.register(models.Article) admin.site.register(models.Article2Tag) admin.site.register(models.UpAndDown) admin.site.register(models.Comment) 3.为表添加中文别名: 在models.py文件中,对应的8张表中分别添加 class Meta: verbose_name_plural='用户表' #verbose_name='用户表' 会自动在末尾加s后缀 class Meta: verbose_name_plural='文章表' #verbose_name='用户表' 会自动在末尾加s后缀 后面依次以下!!!! 4.在模型表中为模型表添加双下str方法打印对象对应的名字, 双下str只能返回字符串。 def __str__(self): return self.username self点对应表中的字段名 5.admin数据录入: 顺序: 文章表 分类和我的站点 用户表 ----绑定对应的站点,在用户表中填写blank=True, blank告诉admin后台管理该字段能够为空,不会影响到数据库,不用执行迁移命令 标签表 文章2标签表
home函数 查询当前网站全部的文章,展现到前端页面上 def home(request): #查询当前网站全部的文章,展现到前端页面上 article_queryset = models.Article.objects.all() return render(request,'home.html',locals())
前端主要编写逻辑和主要逻辑代码以下: 经过for循环article_queryset把文章一篇篇的都读取出来,所有展现到前端页面 {% for article in article_queryset %} 文章的标题,利用跨表查询 <a href="#"> {{ article.title }} </a> 文章头像,利用跨表查询 <img class='media-object' src='/media/{{article.blog.userinfo.avatar}}/' height='60'> 文章简介 <div class='media-body'> {{ article.desc }} </div> 文章状况包括: 用户名(软件老王) <span><a href='#'>{{article.blog.userinfo.username}}</a></span> 发布于 <span>发布于 </span> 日期(年月日),须要用到日期过滤器 <span>{{ article.create_time|date:'Y-m-d' }}</span> 评论数(0) <span>{{ article.comment_num }}</span> 点赞数(0) <span>{{ article.up_num }}</span>
settings.py文件 media配置,可以将用户上传的全部的文件都统一保存在指定的文件夹下 MEDIA_ROOT=os.path.join(BASE_DIR,'media') urls.py文件 手动开设后端资源,将media文件夹下面全部的资源暴露给外界访问------固定写法规则,背下来 导入serve模块,导入settings配置文件 from django.views.static import serve from BBS import settings #谨慎使用 url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})
文章头像,利用跨表查询 <img class='media-object' src='/media/{{article.blog.userinfo.avatar}}/' height='60'>
urls.py文件 配置我的站点的路由 #我的站点 url(r'^(?P<username>\w+)/$',views.site,name='username') views.py文件 定义站点函数,接收有名分组username 经过username查询用户对象 若是用户对象不存在: 返回404页面 代码以下: def site(request,username): user_obj=models.Userinfo.objects.filter(username=username).first() if not user_obj: #404页面 return render(request,'error.html') 图片防盗链 经过判断当前请求以前的所在地址 若是是本网站的连接正常访问,若是不是本网站的连接就直接禁止 若是查看呢? 经过请求头里面来查看, refer ---表示你从哪里来的 user-agent ---标识你是不是一个浏览器
直接在博客园404页面右键检查打开,复制代码,拷贝到BBS项目中的error.html页面 返回网站首页,经过反向解析设置到主页 {% url 'home' %}
------------------------------------------------下面的尚未进行录音工做---------------------------------------------------------前端
urls.py文件 配置我的站点的路由 #我的站点 url(r'^(?P<username>\w+)/$',views.site,name='username') views.py文件 定义站点函数,接收有名分组username 经过username查询用户对象 若是用户对象不存在: 返回404页面 经过用户对象查询到该用户的站点 查询该用户站点下的全部文章 返回数据到site页面 代码以下: def site(request,username): #经过username查询用户对象 user_obj=models.Userinfo.objects.filter(username=username).first() #若是用户对象不存在: # 返回404页面 if not user_obj: return render(request,'error.html') #经过用户对象查询到该用户的站点 blog=user_obj.blog # 查询当前用户站点下的全部文章 article_list=models.Article.objects.filter(blog=blog) #返回数据到site页面 return render(request,'site.html',locals()) -------------------------------------------------------------------------------------------------------------------------------------------------------------------- 只需一招让你分清QuerySet对象,和用户字典对象 article_list = models.Article.objects.filter(blog=blog) user_obj = models.Userinfo.objects.filter(username=username).first() 上面的两个查询不太懂 #article_list是可迭代的QuerySet对象,支持for循环 #user_obj这个是用户字典对象,不支持for循环 全部支持for循环的数据类型必须是可迭代数据类型!!! # 查询当前用户站点下的全部文章 # article_list = models.Article.objects.filter(blog=blog).first() #全部支持for循环的数据类型必须是可迭代数据类型, #字典不可迭代,因此for循环取值会报错:TypeError: 'Article' object is not iterable, #因此article_list必须是能够迭代的对象,QuerySet对象能够迭代,是可迭代对象, #那么就要把.first()给去掉 ------------------这里是经过E盘本身练习的day53课件本身摸索总结出来的--------------------------- # 2.filter() 获得可迭代的QuerySet对象,支持for循环取容器内的元素,在django中推荐使用 # 筛选,至关于你原生sql语句里面的where关键字,返回的结果queryset对象 res=models.Books.objects.filter(pk=1,title='张三丰') #支持多个参数,and关系 print(res) #<QuerySet [<Books: 张三丰>]> print('************************') # 3.get() 在django中不推荐使用 # 筛选,获取的是数据对象自己,条件不存在的数据直接报错,而且查询条件必须是惟一的 res=models.Books.objects.get(title='西游记') print(type(res)) #若是是数据库中不存在的数据进行查询的话,会报错,由于数据库中没有该数据 <QuerySet [<Books: 张三丰>]> ************************ <class 'app01.models.Books'> ////////////////////////// (0.001) SELECT `app01_books`.`id`, `app01_books`.`title`, `app01_books`.`price`, `app01_books`.`kucun`, `app01_books`.`maichu` FROM `app01_books` WHERE `app01_books`.`price` = 766 LIMIT 21; args=(Decimal('766'),) <QuerySet [<Books: 西游记>, <Books: 西游记2>]> <class 'django.db.models.query.QuerySet'> ************************ 西游记 1000 <class 'app01.models.Books'> (0.001) SELECT `app01_books`.`id`, `app01_books`.`title`, `app01_books`.`price`, `app01_books`.`kucun`, `app01_books`.`maichu` FROM `app01_books` WHERE `app01_books`.`price` = 766 ORDER BY `app01_books`.`id` ASC LIMIT 1; args=(Decimal('766'),) ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// # 4.first() # 功能一:取queryset 相同数据中的第一个数据对象, 重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点 特殊功能以下: 还有一个特殊的功能能够把QuerySet对象转换为字典对象,方便字典经过对象点属性取值----88颗星的重点 # id=3,title='西游记' id=5,title='西游记',first取值会优先取id=3的数据 res = models.Books.objects.filter(price='766') print(res) print(type(res)) print('************************') res = models.Books.objects.filter(price='766').first() print(res) print(res.kucun) print(type(res)) '''上面的打印结果以下: < QuerySet[ < Books: 西游记 >, < Books: 西游记2 >] > <class 'django.db.models.query.QuerySet'> ************************ 西游记 1000 <class 'app01.models.Books'> '''
知道怎么回事便可! site.html我的站点页面 页面布局3,9 <div class="container-fluid"> <div class="row"> <div class="col-md-3"> {% load mytag %} {% my_menu username %} </div> <div class="col-md-9"> {% block content %} {% endblock %} </div> </div> </div> 模板继承 {% extends 'base.html' %} 循环展现我的站点下的全部文章 {% for article in article_list %} #posted @ 2019-09-28 17:42 武沛齐 阅读 (2714) 评论 (4) 编辑# <br> <div class="pull-right"> <span>posted </span> <span>@ </span> <span>{{ article.create_time|date:'Y-m-d' }} </span> <span>{{ article.blog.userinfo.username }} </span> <span>评论数({{ article.comment_num }}) </span> <span>点赞数({{ article.up_num }})</span> <span><a href="#">编辑</a></span> </div>
''' 侧边栏筛选的功能究竟是筛选什么? 筛选的是当前这个用户下面的全部的文章,再进行标签、分类、日期进行筛选 本质就是对已经查询出来的article_list再进行筛选操做。 ''' 1.查询当前用户每个分类和分类下的文章数 category_list=models.Category.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name','num') print(category_list) #<QuerySet [('luzhaoshan的分类1', 1)]> 2.查询当前用户每个标签和标签下的文章数 tag_list=models.Tag.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name','num') print(tag_list) #<QuerySet [('luzhaoshan的标签1', 1)]> 3.按照年月分组 date_list=models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(num=Count('pk')).values_list('month','num') print(date_list) #<QuerySet [(datetime.date(2019, 12, 1), 1)]> 备注:(2019,12,1)中的1是django中默认含有的 经过return返回到前端site.html页面,由于获取到的值都是列表套元组, 在前端能够经过列表取值的方式,取索引0是名字,取索引1是对应的值。
后端经过return返回到前端site.html页面,由于获取到的值都是列表套元组, 在前端能够经过列表取值的方式,取索引0是名字,取索引1是对应的值。 分为三块,文章分类,文章标签,日期归档 #<QuerySet [('luzhaoshan的分类1', 1)]> <h3 class="panel-title">文章分类</h3> {% for category in category_list %} <p><a href="#">{{ category.0 }}</a>({{ category.1 }})</p> {% endfor %} ----------------------------------------------------------------- #<QuerySet [('luzhaoshan的标签1', 1)]> <h3 class="panel-title">文章标签</h3> {% for tag in tag_list %} <p><a href="#">{{ tag.0 }}</a>({{ tag.1 }})</p> {% endfor %} -------------------------------------------------------------------- #<QuerySet [(datetime.date(2019, 12, 1), 1)]> 备注:(2019,12,1)中的1是django中默认含有的 <h3 class="panel-title">日期归档</h3> {% for date in date_list %} <p><a href="#">{{ date.0|date:'Y年 - m月' }}</a>({{ date.1 }})</p> {% endfor %}
urls.py文件 我的站点侧边栏筛选功能 url(r'^(?P<username>\w+)/category/(?P<param>\d+)/',views.site), url(r'^(?P<username>\w+)/category/(?P<param>\d+)/',views.site), url(r'^(P<username>\w+)/category/(?P<param>\d+)',views.site), 优化以下: url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>)',views.site) views.py文件 经过关键字参数(**kwargs)来接收有名分组 因此,site函数中使用: def site(request,username,**kwargs): user_obj = models.Userinfo.objects.filter(username=username).first() if not user_obj: # 404页面 return render(request, 'error.html') blog = user_obj.blog # 有当前用户全部的文章 article_list = models.Article.objects.filter(blog=blog) if kwargs: #若是关键字参数有值,则能够按照条件跳转对应的分类或者标签或者日期归档 ''' 侧边栏筛选的功能究竟是筛选什么? 筛选的是当前这个用户下面的全部的文章,再进行标签,分类,日期筛选, 本质其实就是对该站点进行一个再细分的细化。 ''' condition=kwargs.get('condition') param=kwargs.get('param') if condition=='category': #按照分类筛选 article_list=article_list.filter(category_id=param) #按照标签筛选 , 注意事项: 注意双下划线是在本表中的字段经过双下跨表到另一张表查询 tags是文章表中的外键字段,经过双下划线跨表到tag分类表中 —————————————————————————————————————————————————————————————————————————————————— 双下划线在本表中【普通的字段】进行查询,叫母表查询, 双下划线经过本表中的【外键字段】查询是跨表查询,是跨到另一张表中 —————————————————————————————————————————————————————————————————————————————————— tag_list=article_list.filter(tag__id=param) #双下划线条件查询,查询id=param的数据 #按照年月日期筛选 year,month=param.split('-') #__year 查询年份 __month 查询月份 #双下划线查询 article_list=article_list.filter(create_time__year=year,create_time__month=month)
<div class="panel-heading"> <h3 class="panel-title">文章分类</h3> </div> <div class="panel-body"> {% for category in category_list %} <p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }}</a>({{ category.1 }})</p> {% endfor %} </div> —————————————————————————————————————————————————————————————————————————————————— <div class="panel-heading"> <h3 class="panel-title">文章标签</h3> </div> <div class="panel-body"> {% for tag in tag_list %} <p><a href="/{{ username }}/tag/{{ tag.2 }}">{{ tag.0 }}</a>({{ tag.1 }})</p> {% endfor %} </div> —————————————————————————————————————————————————————————————————————————————————— <div class="panel-heading"> <h3 class="panel-title">日期归档</h3> </div> <div class="panel-body"> {% for date in date_list %} <p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}">{{ date.0|date:'Y年 - m月' }}</a>({{ date.1 }})</p> {% endfor %} </div> ——————————————————————————————————————————————————————————————————————————————————
### 侧边栏制做 开发逻辑: 1.根据文章id,查询出对应的文章,展现到前端便可 2.前端用到了模板的继承,把site.html抽出来作成模板 3.建立base.html页面继承site.html页面,而后对9的部分进行调整, 3.1划定区域经过 {% block content %} aa-这里是页面内容 {% endblock %} 3.2而后原来的site.html页面,就继承base.html页面 {% extends 'base.html' %} {% block content %} 而后把aa的内容删除掉,放在这个地方 {% endblock %} 3.3来到article_detail.html页面 继承base.html页面 {% extends 'base.html' %} {% block content %} 这里面是把文章拿过来: 文章标题, 文章内容, {% endblock %} 4.自定义标签过滤器 4.1建立templatetags文件,和文件下的mytag.py文件 写上: from django import template register=template.Library() @register.inclusion_tag('left_menu.html') 而后建立left_menu.html页面,里面内容全删除掉,空页面 4.2继续建立left_menu函数 def left_menu(username): 而后把site我的站点下的下面的代码剪切到这里,目的就是为了方便屡次调用 user_obj = models.Userinfo.objects.filter(username=username).first() 把用户对象拿过来 # 1.查询当前用户每个分类和分类下的文章数 category_list = models.Category.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name', 'num', 'pk') print(category_list) # <QuerySet [('luzhaoshan的分类1', 1)]> # 2.查询当前用户每个标签和标签下的文章数 tag_list = models.Tag.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name', 'num', 'pk') print(tag_list) # <QuerySet [('luzhaoshan的标签1', 1)]> # 3.按照年月分组 date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values( 'month').annotate(num=Count('pk')).values_list('month', 'num') print(date_list) # <QuerySet [(datetime.date(2019, 12, 1), 1)]> 备注:(2019,12,1)中的1是django中默认含有的 4.3经过return locals(),把当前全部的内容所有返回到left_menu.html页面 而后去base.html页面把3部分中的内容剪切拿过来,放到left_menu.html页面中 而后base.html页面3的部分中写上: {% load mytag %} {% left_menu username %} ### 点赞点踩样式拷贝 开发逻辑 拷贝图标到本地,防止图片防盗链,致使图片加载失败 拷贝点赞点踩的样式 ### 点赞点踩的业务逻辑 1.校验用户是否登陆,没有登陆的状况下,不可点赞和点踩,提示请先登陆 2.登陆的用户,不能给本身的文章点赞和点踩 3.已经给该文章点过赞和点过踩的用户,不能再点 4.不能给本身点赞和点踩 文章详情页: 点赞点踩经过ajax请求发送给后端,都给点赞和点踩按钮绑定相同的action, 而后经过绑定点击事件实现点赞和点踩的数量增长。 $('.action').click(function(){ 如何区分你点的是赞仍是点的是踩? $(this) 是当前被点击的这个对象 $(this).hasClass('diggit') 判断当前这个点击的对象有没有'diggit'这个属性 为了提升代码的阅读性,专门为点赞点踩开设一个url 经过ajax发送请求到后端 $.ajax({ url:'/up_down', type:'post', data:{ 'csrfmiddlewaretoken':'{{csrf_token}}', 'is_up':isUp, 'article_id':"{{article_obj.pk}}" }, success:function(data){ 如今就能够在updown视图函数中进行相关的逻辑处理了 } }) }) 进入到updown函数中:
updown函数中: 直接判断过来的请求是否是ajax请求,若是是ajax请求的话: 再判断请求的方法是否是POST请求,若是是POST请求的话: back_dic={'code':1000,'msg':''} is_up=request.POST.get('is_up') 是一个字符串格式的json数据 article_id=request.POST.get('article_id') import json is_up=json.loads(is_up) 是将json格式的字符串格式数据,转成python对应的格式boolean布尔类型 下面是点赞点踩业务逻辑: 1.校验用户是否登陆,没有登陆的状况下,不可点赞和点踩,提示请先登陆 if request.user.is_authenticated(): 2.当前这篇文章是否是当前登陆用户本身写的 经过拿到当前站点所对应的的用户对象 是否等于 当前登陆的用户对象 article_obj=models.Article.objects(pk=article_id).first() 若是两个对象不相等的话,表明该文章不是该登陆用户写的: if not article_obj.blog.userinfo == request.user: 3.那么,咱们继续判断当前这篇文章用户是否已经点过 到点赞点踩表中去查询数据便可 筛选数据,筛选用户是当前用户对象,而且文章对象是当前文章对象的数据, 若是有值表示已经点过了,若是没有值表示尚未点过。 经过.exists()返回布尔类型,TRUE仍是false is_click=models.UpAndDown.objects.filter(user=request.user,article=article_obj).exists() if not is_click: 若是没有点过,下面就要开始操做数据库,完成数据修改了 操做数据库完成数据修改 if is_up: 若是为true,为点赞数量加1,up_num=原来的数量基础上加1,经过拿字段咱们使用F查询 导入from django.db import F models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') +1 ) back_dic['msg'] = '点同意功' else: models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') +1 ) back_dic['msg]='点踩成功' 而后操做点赞点踩表插入数据库中,为每个字段附上值 models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up) 下面是点赞点踩的业务逻辑完善 后端和前端交互是ajax,那么后端须要定义一个字典 back_dic={'code':1000,'msg':''}放在上面 下面是业务完善: 若是点过了,就不能再点了 else: back_dic['code'] = 2000 back_dic['msg'] = '你已经点过了,不能再点了' else: back_dic['code'] = 3000 back_dic['msg'] = '你不能本身给本身点赞' else: back_dic['code'] = 4000 back_dic['msg'] = '请先<a href="/login/">登陆</a>' ### 因为请先登陆是一个超连接,那么咱们在后端能够添加a标签,而后后端取消转义便可 ### 后端取消转义导入模块 from django.utils.safestring import mark_safe ### 而后经过mark_safe包裹一下,那么最后优化以下所示: back_dic['msg'] = mark_safe('请先<a href="/login/">登陆</a>') 再经过return JsonResponse把字典数据返回给前端 return JsonResponse(back_dic) 5.操做数据库,完成数据修改 1.点赞点踩添加数据的时候,文章表里面对应的三个普通字段也得修改 而后下面就到了article_detail.html页面,用来处理后端发送过来的请求: success: function (data) { if (data.code == 1000) { // 给span标签渲染信息 $('.info').text(data.msg); // 点赞或点踩成功以后 应该将前端的数字也加一 let $span = $target.children(); let oldNum = $span.text(); $span.text(Number(oldNum) + 1) // 转整型相加 否则就是字符串拼接了 } else { $('.info').text(data.msg) }
#### 评论功能之根评论 若是请求是ajax请求: 请求方式是POST: 定义back_dic字典,back_dic={'code':1000,'msg':'' } 获取文章article_id, 获取评论的内容, 获取parent_id, 开启事务,from django.db import transaction with transaction.atomic(): models.Comment.objects.create(user=request.user,article_id=article_id,content=content,parent_id=parent_id) 继续操做Article表中的comment_num字段,数量加 1 models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1,parent_id=parent_id) back_dic['msg'] = '评论成功' return JsonResponse(back_dic) 后面就是前端上的处理了,这里不作总结,详情能够去看前端代码 ### 评论功能之子评论 前端点击回复按钮到底发生了几件事? 详情看前端代码 1.自动获取当前点击评论的评论人 2.拼接 @ + 人名 + \n 3.将拼接好的内容添加到评论框中,而且评论框自动聚焦