django项目(博客一)
一 项目开发流程
1.需求
分析架构师+产品经理+开发者组长
在跟客户谈需求之前,会大致先了解客户的需求,然后自己先设计一套比较好的方案
在跟客户沟通交流中引导客户往我们之前想好的方案上面靠
形成一个初步的方案
2.项目设计
架构师干的活
编程语言选择
框架选择
数据库选择
主库:MySQL postgreSQL
缓存数据库:redis mongodb memcache
功能划分
将整个项目划分成几个功能模块
找组长开会
给每个组分发任务
项目报价
技术这块需要多少人,多少天(一个程序员一天1500~2000元)
产品经理公司层面 再加点钱
公司财务签字
公司老板签字
产品经理去跟客户沟通
后续需要加功能 继续加钱
3.分组开发
组长找组员开会,安排各自功能模块
我们就是在架构师设计好的框架里面填写代码(码畜)
我们在写代码的时候,写完需要自己先测试是否有bug
如果是一些显而易见的bug,你没有避免而是直接交个给了测试部门测出来
那你可能就需要被扣绩效了(一定要跟测试小姐姐搞好关系)
薪资组成 15K(合理合规合法的避税)
底薪
绩效
岗位津贴
生活补贴
4.测试
测试部门测试你的代码
压力测试
5.交付上线
1)交给对方的运维
2)直接上线到我们的服务器上,收取维护费用
3)其他(三方)...
一个项目中最重要的不是业务逻辑的书写,数据库表的设计是整个项目的重点所在 而是前期表的设计,只要将表设计好了,后续的功能书写才会一帆风顺 bbs表设计 1.用户表(UserInfo) 继承AbstractUser 扩展 phone 电话号码 avatar 用户头像 create_time 创建时间 外键字段 一对一个人站点表 2.个人站点表(Blog) site_name 站点名称 site_title 站点标题 site_theme 站点样式 3.文章标签表(Tag) name 标签名 外键字段 一对多个人站点表 4.文章分类表(Category) name 分类名 外键字段 一对多个人站点表 5.文章表(Article) title 文章表题 desc 文章简介 content 文章内容 create_time 发布时间 数据库字段设计优化 (虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率低) up_num 点赞数 down_num 点踩数 comment_num 评论数 外键字段 一对多个人站点 多对多文章标签 一对多文章分类 6.点赞点踩表(UpAndDown) 记录那个用户给那篇文章点了赞还是点了踩 user ForeignKey(to='User') article ForeignKey(to='Article') is_up BooleanField() 7.文章评论表(Coment) 记录那个用户给那篇文章写了哪些评论内容 user ForeignKey(to='User') article ForeignKey(to='Article') content charField() comment_time DateField() # 自关联 parent ForeignKey(to='comment',null=True) 3 ORM专门提供的自关联写法 parent ForeignKey(to='self',null=True) 根评论子评论的概念 根评论是直接评论当前发布的内容 子评论是评论别人的评论 根评论与子评论是一对多的关系
数据库表创建及同步
from django.db import models from django.contrib.auth.models import AbstractUser # 用户信息表 ''' 需要在配置文件settings.py中告诉django要用UserInfo替代auth_user AUTH_USER_MODEL='app01.UserInfo' '应用名.表名' ''' class UserInfo(AbstractUser): # null=True,字段可以为空 phone = models.BigIntegerField(verbose_name='手机号', null=True, blank=True) ''' null=True 数据库该字段可以为空 blank=True admin后台管理该字段可以为空 ''' # 头像 ''' 给avatar字段传文件对象,用户上传文件存储的文件,该上传文件会自动存储到avatar文件下,然后avatar字段只保存文件路径avatar/xx.png,default='avatar/xx,png',意思是用户没有上传头像,就是该默然的头像 ''' avatar = models.FileField(upload_to='avatar/', default='avatar/xx.png', verbose_name='头像') create_time = models.DateField(auto_now_add=True, verbose_name='创建时间') # 外键字段 blog = models.OneToOneField(to='Blog', null=True) class Meta: verbose_name_plural = '用户表' # 修改admin后台管理默认的表名,为后台选择,配置和设置的 # verbose_name = '用户表 ' # 末尾还是会自动加s def __str__(self): return self.username # 返回是字符串格式,后台选择的字段的时候,不然会显示object. # 个人站点表 class Blog(models.Model): site_name = models.CharField(max_length=32, verbose_name='站点名 ') site_title = models.CharField(max_length=32, verbose_name='站点标题') # css/js存储路径 site_theme = models.CharField(max_length=64, verbose_name='站点样式') class Meta: verbose_name_plural = '个人站点表' def __str__(self): return self.site_name # 分类表 class Category(models.Model): name = models.CharField(max_length=32, verbose_name='分类名') blog = models.ForeignKey(to='Blog', null=True) class Meta: verbose_name_plural = '分类表' def __str__(self): return self.name # 标签表 class Tag(models.Model): name = models.CharField(max_length=32, verbose_name='标签名') blog = models.ForeignKey(to='Blog', null=True) class Meta: verbose_name_plural = '标签表' def __str__(self): return self.name # 文章表 class Article(models.Model): title = models.CharField(max_length=64, verbose_name='文章标题名') desc = models.CharField(max_length=255, verbose_name='文章简介') content = models.TextField(verbose_name='文章内容') create_time = models.DateField(verbose_name='文章发表时间') up_num = models.BigIntegerField(verbose_name='点赞数', default=0) down_num = models.BigIntegerField(verbose_name='点踩数', default=0) comment_num = models.BigIntegerField(verbose_name='评论数', default=0) # 外键字段 blog = models.ForeignKey(to='Blog', null=True) category = models.ForeignKey(to='Category', null=True) tags = models.ManyToManyField(to='Tag', through='ArticleToTag', through_fields=('article', 'tag')) # through_field中,随放在第一个,就看是在那个字段里面建 class Meta: verbose_name_plural = '文章表' def __str__(self): return self.title # 标签表和文章表多对多关系,采用半自动建表,自己建第三张表,提高扩展性 class ArticleToTag(models.Model): article = models.ForeignKey(to='Article') tag = models.ForeignKey(to='Tag') class Meta: verbose_name_plural = '标签和文章对应表' # 点赞点踩表 class UpAndDown(models.Model): # 点击用户和文章,该表和对应的用户表和文章表一对多,该表是多的一方 user = models.ForeignKey(to='UserInfo') article = models.ForeignKey(to='Article') is_up = models.BooleanField() class Meta: verbose_name_plural = '点赞点踩表' # 评论表 class Comment(models.Model): user = models.ForeignKey(to='UserInfo') article = models.ForeignKey(to='Article') content = models.CharField(max_length=255, verbose_name='评论内容') content_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间') # 根评论 parent = models.ForeignKey(to='self', null=True) # 自关联 class Meta: verbose_name_plural = '评论表'
''' form组件验证注册数据是否合法 之前是直接在views.py中书写的forms组件代码 但是为了解耦合,应该将所有的forms组件代码单独写到一个地方 如果项目从始至终只用到了一个forms组件那么可以直接建一个py文件书写即可 但如果项目需要用到多个forms组件,那么可以创建一个文件夹在文件夹内根据 forms组件功能的不同创建不同的py文件 myforms文件夹 regform.py loginform.py userform.py .... '''
# forms组件 myforms.py from django import forms from app01 import models class MyRegForm(forms.Form): username = forms.CharField(label='用户名', min_length=3, max_length=8, error_messages={ 'required': '用户名不能为空', 'min_length': '用户名最少3位', 'max_length': '用户名最大8位', }, # 还需要让标签有bootstrap样式 widget=forms.widgets.TextInput(attrs={'class': 'form-control'}) # 给前端页面,css样式 ) password = forms.CharField(label='密码', min_length=3, max_length=8, error_messages={ 'required': '密码不能为空', 'min_length': '密码最少3位', 'max_length': '密码最大8位', }, # 还需要让标签有bootstrap样式 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) ) confirm_password = forms.CharField(label='确认密码', min_length=3, max_length=8, error_messages={ 'required': '确认密码不能为空', 'min_length': '确认密码最少3位', 'max_length': '确认密码最大8位', }, # 还需要让标签有bootstrap样式 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) ) email = forms.EmailField(label='邮箱', error_messages={ 'required': '邮箱不能为空', 'invalid': '邮箱格式不正确' }, widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}) ) # 钩子函数,把数据钩出来后,记得return回去 # 局部钩子:校验用户名是否已存在 def clean_username(self): username = self.cleaned_data.get('username') # 去数据库中校验 is_exist = models.UserInfo.objects.filter(username=username) if is_exist: # 提示信息 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 # 扩展,正则表达式校验
# 视图函数 views.py from django.shortcuts import render from app01.myforms import MyRegForm from app01 import models from django.http import JsonResponse # Create your views here. def register(request): form_obj = MyRegForm() if request.method == 'POST': back_dic = {'code': 1000, 'msg': ''} # 校验数据是否合法,实例化对象必须form_obj和空实例化对象相同,保证没有,注册没有成功的情况下,页面还是在注册页面 form_obj = MyRegForm(request.POST) # 判断数据是否合法 if form_obj.is_valid(): # print(form_obj.cleaned_data) # {'username': 'xiaobao', 'password': '123', 'confirm_password': '123', 'email': 'xiaobao@163.com'},未含有文件数据 clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量 # 将字典里面的confirm_password键值对删除 clean_data.pop('confirm_password') # 用户头像,单独从request.FILES中取 file_obj = request.FILES.get('avatar') ''''针对用户头像一定要判断是否传值,不能直接添加到字典里面去,如果用户头像没有上传头像,会用默认头像''' if file_obj: # 将文件对象添加到字典中,为整个字典存数据库,做好准备 clean_data['avatar'] = file_obj # 直接操作数据库保存数据,**clean_data,把字典打散成key=value,符合存数据的格式 models.UserInfo.objects.create_user(**clean_data) # 注册成功,跳转到登录页面 back_dic['url'] = '/login/' else: back_dic['code'] = 2000 back_dic['msg'] = form_obj.errors return JsonResponse(back_dic) return render(request, 'register.html', locals()) # 扩展 一般情况下存储用户文件的时候为了避免文件名冲突的情况 会给文件名加一个前缀 uuid 随机字符串 ...
<script src="/static/js/my_setup.js"></script> <!--引入该文件后,ajax发送数据时,不用再添加csr验证数据--> <!--前端页面(头像展示、不合法信息展示)--> <div class="container-fluid"> <div class="row"> <!--中间8份,两边各空2份--> <div class="col-md-8 col-md-offset-2"> <h1 class="text-center">注册</h1> <form id="myform"> <!--没有用form表单提交数据 单纯的用一下form标签而已--> <!--csrf跨站伪造验证--> {% csrf_token %} <!--form组件,检验后,返回的数据--> {% for form in form_obj %} <div class="form-group"> <label for="{{ form.auto_id }}">{{ form.label }}</label> {{ form }} <span style="color: red" class="pull-right"></span> </div> {% endfor %} <div class="form-group"> <!--label标签,扩展input框的点击范围--> <label for="my_file">头像 {% load static %} <img src="{% static 'img/default.png' %}" id="my_img" alt="" width="100" style="margin-left: 10px"> </label> <input type="file" id="my_file" name="avatar" style="display: none"> </div> <!--type属性是button,不是用form表单 提交数据--> <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit"> </form> </div> </div> </div> <script> // 用户选择头像的注册的时候,可以展现出来,用到文件阅读器对象,FileReader $("#my_file").change(function () { // 文件阅读器对象 // 1.先生成一个文件阅读器对象 let myFileReaderObj=new FileReader(); // 2.获取用户上传的头像文件 let fileObj=$(this)[0].files[0]; // 3.将文件对象交给阅读器对象读取 myFileReaderObj.readAsDataURL(fileObj) // 异步操作 IO操作 // 4.利用文件阅读器将文件展示到前端页面 修改src属性 // 等待文件阅读器加载完毕之后再执行 myFileReaderObj.onload=function () { $('#my_img').attr('src',myFileReaderObj.result) } }) // 将前端数据,通过ajax发送给后端,利用了form的形式,没有form表单向后端发送数据,主要利用serializeArray()方法 $('#id_commit').click(function () { // 发送ajax请求 发送的数据中既包含普通的键值也包含文件,formDataObj是对象形式 let formDataObj=new FormData(); // 1.添加普通的键值对 {#console.log($('#myform').serializeArray()) // [{},{},{}] 只包含普通键值对#} // jQuery循环input的值 $.each($('#myform').serializeArray(),function (index,obj) { formDataObj.append(obj.name,obj.value) }); // 2.添加文件数据 formDataObj.append('avatar',$('#my_file')[0].files[0]); // 3.发送ajax请求 $.ajax({ url:"", type:'post', data:formDataObj, // 需要指定两个关键性的参数,发送带文件数据的参数 contentType:false, processData:false, success:function (args) { if (args.code==1000){ // 跳转登录页面 window.location.href=args.url }else { // 如何将对应的错误提示信息展示到对应的input框下面 // forms组件渲染的标签的id值都是 id_字段名 $.each(args.msg,function (index,obj) { {#console.log(index,obj) // username ['用户名不能为空']#} let targetId='#id_'+index; // jQuery中的链式操作 $(targetId).next().text(obj[0]).parent().addClass('has-error') }) } } }) }) // 给所有的input框绑定获取焦点事件, $('input').focus(function () { // 将input下面的span标签和input外面的div标签修改内容及属性,点击input框,取掉错误样式提示 $(this).next().text('').parent().removeClass('has-error') }) </script>
''' img标签的src属性 1.图片路径 2.url 3.图片的二进制数据 我们的计算机上面所有能够输出各式各样的字体样式,内部其实对应的是一个个.ttf结尾的文件,百度.ttf下载 重难点是验证码功能 ''' from django.contrib import auth # from django.middleware.csrf import get_token ,rotate_token # 自己发现的,解决登录get请求没走完,crsf没有向前端发送crsftoken,无法登录 def login(request): # get_token(request) if request.method == 'POST': back_dic = {'code': 1000, 'msg': ''} username = request.POST.get('username') password = request.POST.get('password') code = request.POST.get('code') # 1.先校验验证码是否正确,自己决定是否忽略,统一大小写再比较 if request.session.get('code').upper() == code.upper(): # 用auth模块,校验用户名和密码,需要导入auth模块,不用UserInfo模型表,用就要统一用auth模块,校验用户信息 user_obj = auth.authenticate(request, username=username, password=password) # 校验通过 if user_obj: # 保存用户状态 auth.login(request, user_obj) back_dic['url'] = '/home/' else: back_dic['code'] = 2000 back_dic['msg'] = '用户名或密码错误' else: back_dic['code'] = 3000 back_dic['msg'] = '验证码错误' return JsonResponse(back_dic) return render(request, 'login.html')
''' 图片相关的模块 pip3 install pillow ''' from PIL import Image, ImageDraw, ImageFont ''' Image:生成图片 ImageDraw:能够在图片上乱涂乱画 ImageFont:控制字体样式 ''' from io import BytesIO, StringIO ''' 内存管理模块,作用是省掉从硬盘存储中进行文件读写的IO操作 BytesIO:临时帮你存储数据,返回的时候数据是二进制 StringIO:临时帮你存储数据,返回的时候数据是字符串 ''' import random def get_random(): return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) # urls.py中设置get_code,并且验证码图的src属性链接get_code,get请求时,调用视图函数get_code def get_code(request): # 推导步骤1:直接获取后端现成的图片二进制数据发送给前端,给img的src属性 # with open(r'static/img/567.jpg', 'rb') as f: # data = f.read() # return HttpResponse(data) # 推导步骤2:利用pillow模块动态产生图片 img_obj = Image.new('RGB', (610, 35), 'green') img_obj = Image.new('RGB', (610, 35), (123, 23, 23)) # 生成随机验收的图片 img_obj = Image.new('RGB', (610, 35), get_random()) # 先将图片对象保存起来 with open('xxx.png', 'wb') as f: img_obj.save(f, 'png') # 再将图片对象读取出来 with open('xxx.png', 'rb') as f: data = f.read() return HttpResponse(data) # 推导步骤3:文件存储繁琐IO操作效率低,借助于内存管理器模块 # img_obj = Image.new('RGB', (610, 35), get_random()) # io_obj = BytesIO() # 生成一个内存管理器对象,也可以看成是文件句柄 # img_obj.save(io_obj, 'png') # 图片对象通过io_obj以png格式存在内存中 # return HttpResponse(io_obj.getvalue()) # 从内存管理器中读取二进制的图片数据返回给前端 # 最终步骤4:写图片验证码 img_obj = Image.new('RGB', (610, 35), get_random()) img_draw = ImageDraw.Draw(img_obj) # 产生一个画笔对象,在img_obj上画画 img_font = ImageFont.truetype('static/font/Adorable.TTF', 30) # 随机验证码 五位数的随机验证码 数字 小写字母 大写字母 code = '' for i in range(5): # 随机大写字母 random_upper = chr(random.randint(65, 90)) # 随机小写字母 random_lower = chr(random.randint(97, 122)) # 字符串类型的随机数 random_int = str(random.randint(0, 9)) # 从上面三个里面随机选择一个 tmp = random.choice([random_lower, random_upper, random_int]) # 将产生的随机字符串写入到图片 ''' 为什么一个个写而不是生成好了之后再写 因为一个个写能够控制每个字体的间隙,而生成好之后再写的话 间隙就没法控制 ''' img_draw.text((i * 60 + 60, 0), tmp, get_random(), img_font)
# 第一个参数,验证码反正图片上的位置,所以要一个个生成,好控制随机字符串之间的间距,动态的生成位置,x轴和y轴设置;
二的一个参数,随机字符串;第三的一个参数随机颜色;第四的一个随机字符的字体样式 # 拼接随机字符串 code += tmp print(code) # 随机验证码在登录的视图函数里面需要用到,要比对,所有要找地方存起来并且其他视图函数也能拿到,将随机字符串存储到session表中 request.session['code'] = code io_obj = BytesIO() img_obj.save(io_obj, 'png') return HttpResponse(io_obj.getvalue())
<script src="/static/js/my_setup.js"></script> <div class="container-fluid"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h1 class="text-center">登录</h1> <div class="form-group"> <label for="username">用户名</label> <input type="text" name="username" id="id_username" class="form-control"> </div> <div class="form-group"> <label for="password">密码</label> <input type="text" name="password" id="id_password" class="form-control"> </div> <div class="form-group"> <label for="">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" name="code" id="id_code" class="form-control"> </div> <div class="col-md-6"> <!--img中的src属性中,值改变 ,就要向后端发送get请求,发送了,就 重新生成验证码,就给img绑定点击事件,换新验证码--> <img src="/get_code/" alt="" width="610" height="35" id="id_img"> </div> </div> </div> <input type="button" class="btn btn-success" value="登录" id="id_commit"> <span style="color: red" id="id_error"></span> </div> </div> </div> <script> // 看不清验证码,点击验证码图片,刷新新的验证码 $('#id_img').click(function () { // 1.先获取标签之前的src let oldVal=$(this).attr('src'); $(this).attr('src',oldVal+='?') }) // 点击按钮发送ajax请求 $('#id_commit').click(function () { $.ajax({ url:'', type:'post', data:{ 'username':$('#id_username').val(), 'password':$('#id_password').val(), 'code':$('#id_code').val(), // 自己结合自己需求,合理选择 //'csrfmiddlewaretoken':'{{ csrf_token }}',这里引用了外部js文件,myset.js,就不用写csrfmiddlewarettoken }, success:function (args) { if (args.code==1000){ // 跳转到页面,args是对象 window.location.href=args.url }else { // 渲染错误信息 $('#id_error').text(args.msg) } } }) }) </script>
1.动态展示用户名称(导航条) <ul class="nav navbar-nav navbar-right"> <!--利用auth模块,判断用户是否登录,登录了,展示跟多操作,未登录展示注册和登录--> {% 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><a href="#">后台管理</a></li> <li role="separator" class="divider"></li> <li><a href="#">退出登录</a></li> </ul> </li> {% else %} <li><a href="{% url 'reg' %}">注册</a></li> <li><a href="{% url 'login' %}">登录</a></li> {% endif %} </ul> 2. 修改密码 模态框展示,模态框代码放在更多下拉单下,不展示代码,再加ajax后端数据传输 <li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li> <!--data-toggle="modal" data-target=".bs-example-modal-lg"绑定模态框弹出的点击事件,点击了,就弹出模态框--> <!-- Large modal --> <div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel"> <div class="modal-dialog modal-lg" role="document"> <div class="modal-content"> <h3 class="text-center">修改密码</h3> <div class="row"> <div class="col-md-8 col-md-offset-2"> <div class="form-group"> <label for="">用户名</label> <input type="text" disabled value="{{ request.user.username }}" class="form-control"> </div> <div class="form-group"> <label for="old_password">原密码</label> <input type="password" id="old_password" class="form-control"> <span style="color: red" class="pull-right"></span> </div> <div class="form-group"> <label for="">新密码</label> <input type="password" id="new_password" class="form-control"> </div> <div class="form-group"> <label for="">确认密码</label> <input type="password" id="confirm_password" class="form-control"> <span style="color: red" class="pull-right"></span> </div> <div class="form-group"> <button type="button" class="btn btn-default btn-sm" data-dismiss="modal"> 取消 </button> <button type="button" class="btn btn-primary btn-sm" id="set_commit"> 确认修改 </button> </div> </div> </div> </div> </div> </div> <script> // 通过ajax,向后端发送修改密码的数据 $(function () { // 模态框中,点击修改密码事件 $('#set_commit').click(function () { $.ajax({ url: '/set_password/', type: 'post', data: { 'old_password': $('#old_password').val(), 'new_password': $('#new_password').val(), 'confirm_password': $('#confirm_password').val() }, success: function (args) { if (args.code == 1000) { window.location.href = args.url } else if (args.code == 2000) { // 渲染错误信息 $('#old_password').next().text(args.msg) } else if (args.code == 3000) { $('#confirm_password').next().text(args.msg) } } }) }) <script> 3. 退出登录 auth模块中的logout <li><a href="{% url 'logout' %}" id="logout">退出登录</a></li>
# 需要用到auth模块 from django.contrib import auth from django.contrib.auth.decorators import login_required @login_required def set_password(request): if request.is_ajax(): back_dic = {'code': 1000, 'msg': ''} username = request.POST.get('username') old_password = request.POST.get('old_password') new_password = request.POST.get('new_password') confirm_password = request.POST.get('confirm_password') if new_password == confirm_password: # 校验老密码 is_right = request.user.check_password(old_password) # 自己加密比对 if is_right: request.user.set_password(confirm_password) request.user.save() # 存数据库 back_dic['url'] = '/home/' else: back_dic['code'] = 2000 back_dic['msg'] = '原密码错误' else: back_dic['code'] = 3000 back_dic['msg'] = '新密码两次不一致' return JsonResponse(back_dic)
@login_required def logout(request): auth.logout(request) return redirect('home')
''' django提供了一个可视化的界面来让你方便的对你的模型表 进行数据的增删改查操作 想要使用admin后台管理操作模型表 需要先注册模型表,再告诉admin需要操作哪些表 在应用下的admin.py中注册你的模型表 ''' from django.contrib import admin from app01 import models # Register your models here. admin.site.register(models.UserInfo) admin.site.register(models.Blog) admin.site.register(models.Category) admin.site.register(models.Tag) admin.site.register(models.Article) admin.site.register(models.Article2Tag) admin.site.register(models.UpAndDown) admin.site.register(models.Comment) # admin会给每一个注册了的模型表自动生成增删改查四条url http://127.0.0.1:8000/admin/app01/userinfo/ 查 http://127.0.0.1:8000/admin/app01/userinfo/add/ 增 http://127.0.0.1:8000/admin/app01/userinfo/1/change/ 改 http://127.0.0.1:8000/admin/app01/userinfo/3/delete/ 删 ''' 关键点就在于url.py中的第一条自带的url 前期需要手动录入数据,给开发提供数据 ''' ''' 1.首先在文章表中,绑定关系。数据绑定尤其需要注意的是用户个人站点不要忘记绑定了。绑定中,掠清表的关系 2.标签 3.标签和文章 千万不要把别人的文章绑定其他人标签上 '''
home.html 所有文章的展示
用了bootstrap中的媒体对象,for循环每个文章对象,展示出来
{% for article_obj in article_queryset %} <li class="media"> <h3 class="media-heading"><a href="#">{{ article_obj.title }}</a></h3> <div class="media-left"> <a href="#"> <img class="media-object" src="/media/{{article_obj.blog.userinfo.avatar}}" alt="..." width="50px" height="60px"> </a> </div> <div class="media-body"> {{ article_obj.desc }} </div> <br> <div> <span><a href="{% url 'site' article_obj.blog.userinfo.username %}">{{ article_obj.blog.userinfo.username }}</a> </span> <span>发布于</span> <span>{{ article_obj.create_time|date:'Y-m-d' }} </span> <span><span class="glyphicon glyphicon-comment"></span>评论数({{ article_obj.comment_num }}) </span> <span><span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article_obj.up_num }}) </span> </div> </li> <hr> <!--分割线--> {% endfor %}
views.py 首页视图函数
def home(request): # 查询本网站所有的文章数据展示到前端页面,可以用到分页器 article_queryset = models.Article.objects.all() return render(request, 'home.html', locals())
分页器
views.py 视图函数,先根目录 建utils文件夹,复制mypage.py
from utils.mypage import Pagination def home(request): # 查询本网站所有的文章数据展示到前端页面,可以用到分页器 article_queryset = models.Article.objects.all() current_page = request.GET.get('page', 1) all_count = article_queryset.count() # 传值,实例化对象 page_obj = Pagination(current_page=current_page, all_count=all_count) # 对article_queryset进行切片 page_queryset = article_queryset[page_obj.start:page_obj.end] return render(request, 'home.html', locals())
home.html
<li class="text-center" >{{ page_obj.page_html|safe }}</li>
''' 1. 网址所使用的静态文件默认放在static文件夹下 2. 用户上传的静态文件也应该单独放在某个文件夹下 media配置 该配置可以让用户上传的所有文件都固定存放在某一个指定的文件夹下 # 配置文件setting.py配置用户上传的文件存储位置 MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 'media'文件名,随你自己 会自动创建多级目录,并自动将已建好的avtar文件夹放到media下 如何开设后端指定文件夹资源 首先需要自己去url.py中书写下面固定 ''' # urls.py from django.views.static import serve from bbs import settings # 暴露后端指定文件夹资源 url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT}) # 前端头像src属性设置,media文件暴露后,可以直接访问 # home.html <img class="media-object" src="/media/{{article_obj.blog.userinfo.avatar}}" alt="..." width="50px" height="60px"> # 总结:media文件,专门存放用户上传文件,在settying.py文配置路径,打开令牌,可以直接访问,和static同理。
# 如何避免别的网站直接通过本网站的url访问本网站资源(别人的网站的图片链接引用自己的图片,别人的网站每次访问自己的网站,浪费自己的服务器资源) # 简单的防盗 可以做到请求来的时候先看看当前请求是从那个网站过来的 如果是本网站那么正常访问 如果是其他网站直接拒绝 请求头里面有一个专门记录请求来自于那个网址的参数 Referer: http://127.0.0.1:8000/xxx/ # 如何避免 1.要么修改请求头referer 2.直接写爬虫把对方网址的所有资源直接下载到我们自己的服务器上
七 个人站点
home.html 入口
在home页面显示个人用户名的a标签,href属性设置get链接
<span><a href="{% url 'site' article_obj.blog.userinfo.username %}">{{ article_obj.blog.userinfo.username }}</a> </span>
urls.py
home.html的get请求到路由层,通过有名分组,将get来的用户名传值给
视图函数site
# 个人站点页面搭建,有名分组 url(r'^(?P<username>\w+)/$', views.site, name='site'),
views.py
GET请求通过路由,将用户名传给视图函数,对该用户就行校验
对应个人站点表,从文章表中筛选出对应的文章,并给前端site.html页面的主板内容区进行展示
def site(request, username, **kwargs): ''' :param request: :param username: :param kwargs: 如果该参数有值,也就意味着需要对article_list做额外的筛选操作 :return: ''' # 先校验当前用户名对应的个人站点是否存在,auth模块未提供校验用户是否存在,只能取数据的UserInfo变中比对 user_obj = models.UserInfo.objects.filter(username=username).first() # 用户如果不存在应该返回一个404页面 if not user_obj: return render(request, 'errors.html') # 获取个人站点数据 blog = user_obj.blog # 子对象查询,用户查个人站点,正向查询,按字段,一对一关系,不加.all(),结果个人站点对象 article_list = models.Article.objects.filter( blog=blog) # filter里面一个个人站点对象,可以接受,个人的文章展示。queryset对象,侧边栏的筛选其实就是对article_list进行筛选
site.html
3份9份比例,3份是左侧边栏,筛选功能展示区,9份是个人文章展示区,样式选取复制home页面
<div class="col-md-9"> <ul class="media-list"> {% for article_obj in article_list %} <li class="media"> <h3 class="media-heading"><a href="#">{{ article_obj.title }}</a></h3> <div class="media-left"> <a href="#"> <img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="50px" height="60px"> </a> </div> <div class="media-body"> {{ article_obj.desc }} </div> <div class="pull-right"> <span>posted</span> <span>@</span> <span>{{ article_obj.create_time|date:'Y-m-d' }} </span> <span><a href="#">{{ article_obj.blog.userinfo.username }}</a> </span> <span><span class="glyphicon glyphicon-comment"></span>评论数({{ article_obj.comment_num }}) </span> <span><span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article_obj.up_num }}) </span> <span><a href="#">编辑</a></span> </div> </li> <hr> <!--分割线--> {% endfor %} </ul> </div>
个人站点样式独特
在暴露文件夹media中加上传个人用户上传的css文件,在site.html中link引入css,路径对应上传路径,生成自己上传的css样式
全是每个用户都可以有自己的站点样式 <link rel="stylesheet" href="/media/css/{{blog.site_theme}}/">
八 个人站点侧边栏筛选功能
http://127.0.0.1:8000/lq1/category/1/
http://127.0.0.1:8000/lq1/tag/2/
http://127.0.0.1:8000/lq1/archive/2023-05/
url:用户名/表名 /数字/ 来访问
urls.py
url向视图函数传参,不知道到传几个参数,就用**kwgs
# 侧边栏筛选功能,前两个匹配主键值,匹配数值,后一个是匹配年月日,匹配字符串 # url(r'^(?P<username>\w+)/category/(\d+)/', views.site), # url(r'^(?P<username>\w+)/tag/(\d+)/', views.site), # url(r'^(?P<username>\w+)/archive/(\w+)/', views.site), # 上述的三条url其实可以合并成一条,正则有名分组, # (?P<condition>category|tag|archive)条件名在三个中选择,或的关系,.*匹配零和多个字符 url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)/', views.site)
veiws.py
按照分类名、标签名、年月进行展示,需要分别到数据库里进行筛选查询
article_list同名,就不用再site.html中改动,重点是数据的查询
def site(request, username, **kwargs): ''' :param request: :param username: :param kwargs: 如果该参数有值,也就意味着需要对article_list做额外的筛选操作 :return: ''' # 先校验当前用户名对应的个人站点是否存在,auth模块未提供校验用户是否存在,只能取数据的UserInfo变中比对 user_obj = models.UserInfo.objects.filter(username=username).first() # 用户如果不存在应该返回一个404页面 if not user_obj: return render(request, 'errors.html') # 获取个人站点数据 blog = user_obj.blog # 子对象查询,用户查个人站点,正向查询,按字段,一对一关系,不加.all(),结果个人站点对象 article_list = models.Article.objects.filter( blog=blog) # filter里面一个个人站点对象,可以接受,个人的文章展示。queryset对象,侧边栏的筛选其实就是对article_list进行筛选 if kwargs: print(kwargs) # {'condition': 'tag', 'param': '1'} condition = kwargs.get('condition') print(type(condition), condition) param = kwargs.get('param') print(type(param), param) # 判断用户到底想按照那个条件筛选数据 if condition == 'category': # 文章表查分类表,一对多关系,正向查询,按字段 article_list = article_list.filter(category_id=param) print(article_list.query) print(article_list) elif condition == 'tag': # 文章和标签,多对多,半自动,新建了第三张变,需要跨表,正向查字段名,挂表需加__tags__id article_list = article_list.filter(tags__id=param) print(article_list.query) print(article_list) else: year, month = param.split('-') # 解压赋值,列表赋值给两个 article_list = article_list.filter(create_time__year=year, create_time__month=month) # 神奇的双下划线 from django.db.models import Count # 1 查询当前用户所有的分类及分类下的文章数 category_queryset = models.Category.objects.filter(blog=blog).annotate( count_num=Count('article__pk')).values_list('name', 'count_num', 'pk') # pk是分组后新的虚拟结果的主键值,也就是queryset的索引值 # values是列表套字典,values_list是列表套元祖 # 2 查询当前用户所有的标签及标签下的文章数 tag_queryset = models.Tag.objects.filter(blog=blog).annotate( count_num=Count('article__pk')).values_list('name', 'count_num', 'pk') # 3 按照年月统计所有的文章 from django.db.models.functions import TruncMonth # 第一个.annotate是按文章进行分组,第二个.annotate是跟在values('month'),month的虚拟表基础上在进行分组,计数取值 article_month = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values( 'month').annotate( num=Count('pk') ).values_list('month', 'num') print(article_month) return render(request, 'site.html', locals())
https://www.cnblogs.com/wupeiqi/tag/Python/ 标签 https://www.cnblogs.com/wupeiqi/category/675825.html 分类 https://www.cnblogs.com/wupeiqi/archive/2020/06.html 日期 # 日期归档,看django官网的用法,按月进行分组 from django.db.models.functions import TruncMonth, TruncYear Experiment.objects .annotate(month=TruncMonth("start_datetime")) # 将表Experiment,按月份截取出来,并添加查询列表中,该查询列表类似于sql中的虚拟表,该虚拟表增加了month字段 .values("month") # 按month分组 .annotate(experiments=Count("id")) # annotate跟随的后面,就是按什么分组,跟着Experiment后面,就是按Experiment表分组,跟着values后面,就是按month分组 .values('month','experiment') # 对月份,和个数取值 # 时区问题报错,setting.py文件里设置,修改时区 TIME_ZONE = 'Asia/Shanghai' USE_TZ = False
<div class="col-md-3"> <div class="panel panel-info"> <div class="panel-heading text-center">文章分类</div> <div class="panel-body"> {% for category in category_queryset %} <p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }}({{ category.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading text-center">文章标签</div> <div class="panel-body"> {% for tag in tag_queryset %} <a href="/{{ username }}/tag/{{ tag.2 }}"><p>{{ tag.0 }}<{{ tag.1 }}></p></a> {% endfor %} </div> </div> <div class="panel panel-success"> <div class="panel-heading text-center">日期归档</div> <div class="panel-body"> {% for month in article_month %} <p><a href="/{{ username }}/archive/{{ month.0|date:'Y-m' }}">{{ month.0|date:'Y年m月' }}({{ month.1 }})</a></p> {% endfor %} </div> </div> </div>