一、表结构关系图
二、建表
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
from django.db import models # Create your models here. from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): """ 用户信息表 """ phone = models.CharField(max_length=11, null=True, unique=True) # 手机号 avatar = models.FileField(upload_to="avatars/", default="avatars/default.png") # 头像 blog = models.OneToOneField(to="Blog", null=True) def __str__(self): return self.username class Meta: verbose_name = "用户信息" verbose_name_plural = verbose_name class Blog(models.Model): """ 博客信息 """ title = models.CharField(max_length=64) # 个人博客标题 theme = models.CharField(max_length=32) # 博客主题 def __str__(self): return self.title class Meta: verbose_name = "博客" verbose_name_plural = verbose_name class Category(models.Model): """ 个人博客文章分类 """ title = models.CharField(max_length=32) # 分类标题 blog = models.ForeignKey(to="Blog") # 外键关联博客,一个博客站点可以有多个分类 def __str__(self): return "{}-{}".format(self.blog.title, self.title) class Meta: verbose_name = "文章分类" verbose_name_plural = verbose_name class Tag(models.Model): """ 标签 """ title = models.CharField(max_length=32) # 标签名 blog = models.ForeignKey(to="Blog") # 所属博客 def __str__(self): return self.title class Meta: verbose_name = "标签" verbose_name_plural = verbose_name class Article(models.Model): """ 文章 """ title = models.CharField(max_length=50) # 文章标题 desc = models.CharField(max_length=255) # 文章描述 create_time = models.DateTimeField(auto_now_add=True) # 创建时间 category = models.ForeignKey(to="Category", null=True) # 文章分类 user = models.ForeignKey(to="UserInfo") # 作者 tags = models.ManyToManyField( # 文章的标签 to="Tag", through="Article2Tag", through_fields=("article", "tag"), ) def __str__(self): return self.title class Meta: verbose_name = "文章" verbose_name_plural = verbose_name class ArticleDetail(models.Model): """ 文章详情表 """ content = models.TextField() # 文章内容 article = models.OneToOneField(to="Article") class Meta: verbose_name = "文章详情" verbose_name_plural = verbose_name class Article2Tag(models.Model): """ 文章和标签的多对多关系表 """ article = models.ForeignKey(to="Article") tag = models.ForeignKey(to="Tag") def __str__(self): return "{}-{}".format(self.article, self.tag) class Meta: unique_together = (("article", "tag"),) verbose_name = "文章-标签" verbose_name_plural = verbose_name class ArticleUpDown(models.Model): """ 点赞表 """ user = models.ForeignKey(to="UserInfo", null=True) article = models.ForeignKey(to="Article", null=True) is_up = models.BooleanField(default=True) # 点赞还是踩灭 def __str__(self): return "{}-{}".format(self.user_id, self.article_id) class Meta: unique_together = (("article", "user"),) # 同一个人只能给一篇文章点一次赞 verbose_name = "点赞" verbose_name_plural = verbose_name class Comment(models.Model): """ 评论表 """ article = models.ForeignKey(to="Article") user = models.ForeignKey(to="UserInfo") content = models.CharField(max_length=255) # 评论内容 create_time = models.DateTimeField(auto_now_add=True) parent_comment = models.ForeignKey("self", null=True) # 自己关联自己 def __str__(self): return self.content class Meta: verbose_name = "评论" verbose_name_plural = verbose_name
1.多对多三种实现方式
2.联合唯一
三、登录
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录</title> <link rel="stylesheet" href="/static/css/bootstrap/bootstrap3.3.7-min.css"> <link rel="stylesheet" href="/static/css/login.css"> </head> <body> <div class="container col-md-4 col-md-push-3"> <form class="form-horizontal" novalidate> {% csrf_token %} <div class="form-group"> <label class="col-sm-3 control-label" for="inputUser">用户名:</label> <div class="col-sm-9"> <input type="text" class="form-control" id="inputUser" placeholder="请输入用户名" autofocus=""> </div> </div> <div class="form-group"> <label class="col-sm-3 control-label" for="inputPwd">密 码:</label> <div class="col-sm-9"> <input type="password" class="form-control" id="inputPwd" placeholder="请输入密码"> </div> </div> <div class="form-group"> <label class="col-sm-3 control-label" for="inputVcode">验证码:</label> <div class="col-sm-4"> <input type="text" class="form-control" id="inputVcode" placeholder="请输入验证码"> </div> <div class="col-sm-5" id="divImgVcode"> <img src="{% url 'v_code' %}" alt="" id="imgVcode"> </div> </div> <div class="form-group"> <div class="col-sm-offset-3 col-sm-3"> <div class="checkbox"> <label> <input type="checkbox" id="remember-me"> 记住我 </label> </div> </div> </div> <div class="form-group"> <div class="col-sm-push-4 col-sm-7"> <button type="button" class="btn btn-block btn-lg btn-info" id="btnLogin">登录</button> <p id="pErrMsg"></p> </div> </div> </form> </div> <script src="/static/js/jquery/jquery3.3.1-min.js"></script> <script src="/static/js/setAjax.js"></script> <script> $("#btnLogin").click(function () { let username = $("#inputUser").val(); let password = $("#inputPwd").val(); let vCode = $('#inputVcode').val(); $.ajax({ url:{% url 'login' %}, type: 'post', data: { username: username, password: password, vcode:vCode, }, success: function (res) { if (res.code !== 0) { // 认证失败 $("#pErrMsg").text(res.msg) } else { location.href = '{% url 'index' %}' } } }) }); $('input').focus(function () { $('#pErrMsg').text('') }); let orgUrlLenth = $('#imgVcode')[0].src.length; $('#imgVcode').click(function () { let timeStamp = new Date().valueOf(); this.src = this.src.substring(0, orgUrlLenth) + timeStamp; }) </script> </body> </html>
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
from django.shortcuts import render, redirect, HttpResponse from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required from PIL import Image, ImageDraw, ImageFont, ImageFilter from django.http import JsonResponse from django import views from io import BytesIO import random # Create your views here. class Login(views.View): def get(self, request): return render(request, 'login.html') def post(self, request): res = {'code': 0} username = request.POST.get('username') password = request.POST.get('password') vcode = request.POST.get('vcode') if vcode.upper() != request.session.get('vcode').upper(): res['code'] = 1 res['msg'] = '验证码有误' else: # auth认证 user = authenticate(request, username=username, password=password) if user: # 认证成功,进行登录 login(request, user) else: res['code'] = 1 res['msg'] = '用户名或密码有误' return JsonResponse(res) def v_code(request): # 随机颜色,相对较浅 def random_color1(): return random.randint(64, 255), random.randint(64, 255), random.randint(64, 255) # 随机颜色,相对较深 def random_color2(): return random.randint(32, 127), random.randint(32, 127), random.randint(32, 127) # 等概率随机生成 n 个数字、字母(大小写)组合的验证码列表 def random_code(n): rec_list = [] while n: n -= 1 rdm_first = random.randint(0, 1) if rdm_first == 0: rec_list.append(str(random.randint(0, 9))) else: rdm_second = random.randint(0, 1) if rdm_second == 0: rec_list.append(chr(random.randint(65, 90))) else: rec_list.append(chr(random.randint(97, 122))) return rec_list vcode_list = random_code(4) # 由最终验证码组成的字符串, 存入session中用来校验 vcode = ''.join(vcode_list) request.session['vcode'] = vcode # 生成一张图片 img_obj = Image.new( 'RGB', # 生成图片的模式 (162, 33), # 大小 random_color1() # 颜色 ) # 生成一个画笔,指定画板为img_obj draw_obj = ImageDraw.Draw(img_obj) # 加载本地字体文件,生成字体对象 font_obj = ImageFont.truetype('static/font/kumo.ttf', size=28) # 依次在画板上写上n个验证码 for i in range(len(vcode_list)): draw_obj.text( (i * 32 + 20, 0), # 坐标 vcode_list[i], # 内容 fill=random_color2(), # 画笔颜色,即字体颜色 font=font_obj, # 字体对象 ) # # 加干扰线 # width = 250 # 图片宽度(防止越界) # height = 35 # for i in range(5): # x1 = random.randint(0, width) # x2 = random.randint(0, width) # y1 = random.randint(0, height) # y2 = random.randint(0, height) # draw_obj.line((x1, y1, x2, y2), fill=random_color()) # # # 加干扰点 # for i in range(40): # draw_obj.point([random.randint(0, width), random.randint(0, height)], fill=random_color()) # x = random.randint(0, width) # y = random.randint(0, height) # draw_obj.arc((x, y, x+4, y+4), 0, 90, fill=random_color()) # # 进行模糊处理 # img_obj = img_obj.filter(ImageFilter.BLUR) iof = BytesIO() # 生成IO句柄 img_obj.save(iof, 'png') # 把图片对象放进IO句柄里,即保存到内存中 data = iof.getvalue() # 从内存中读取图片数据 # 从服务器静态文件夹内读取图片数据 # with open('static/img/oo.png', 'rb') as f: # data = f.read() return HttpResponse(data, content_type='image/png') @login_required def index(request): user_pro = request.user print(user_pro.userinfo.address) return HttpResponse('Index') def log_out(request): logout(request) return redirect('/login/')
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
from django.contrib import admin from django.urls import path, re_path from Authapp import views urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.Login.as_view(), name='login'), path('index/', views.index, name='index'), path('logout/', views.log_out, name='logout'), re_path('v-code/\d*', views.v_code, name='v_code'), ]
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static') ] LOGIN_URL = '/login/' AUTH_PROFILE_MODULE = 'Authapp.UserInfo'
注意:
1. ajax,auth,bootstrap(组长教的)
python基础 random.randint() 字母65-90、97-122 chr(数字) 颜色(0~255)三个一组(0,0,0)
2. 制作验证码
1.pillow import PIL
Image.new() img对象.filter(ImageFilter.BLUR)
ImageFont.truetype()
ImageDraw.Draw(img对象) draw对象.text() draw对象.line() draw对象.arc()
点击切换验证码实现方式:
1.一直加?
2.加时间戳
3.加?删?交替 import django.views.decorators.cache import never_cache (前段禁用缓存, 火狐浏览器有bug)
2.BytesIO
BytesIO()
img对象.save(IO对象,‘png等格式’)
IO对象.getvalue()
3.HttpResponse 可以为 src 提供资源
HttpResponse(data,content_type='image/png') 返回一张图片
3.前段JS focus blur jQuery对象$(“ #id ”)[ 0 ] 对象DOM length substring()
Date().valueOf() p标签 .text(" ") location.href="url" col-sm-offset-4 (push-pull)
三、注册
1. 上传头像,前端
上传文件的input 标签
<input type="file" accept="image/*" id="inpAvatar">
2. 上传头像后台(views视图函数)
接收文件:
avatar_file = request.FILES.get("avatar")
保存注册信息到数据库:
models.UserInfo.objects.create_user(**form_obj.cleaned_data, avatar=avatar_file)
(因为是File文件,录入数据库时,会按字段保存路径,按路径找目录保存文件)
3. 上传头像后台(models模型层)
UserInfo表中:
avatar是一个FileField 字段,FileField(upload_to="avatars/",)
avatar具体保存的是一个路径,而不是一个文件
具体保存文件的是:这个路径对应的目录(配置了media就另当别论)
(配置midia后)具体保存文件的目录是:在上传路径前面拼接上media后的新路径对应的目录
3. 关于配置media(不配置就是上面的那样)
关于static,通过settings设置别名,即让别名代表其绝对路径。放置可提供的静态文件。
不能是所有文件都是可以访问的,涉及安全、泄密等问题。像用户上传的东西要有
指定的目录存放。(部署时要做区分)(MEDIA_URL,MEDIA_ROOT是Django提供,也就是说别的就不认识了)
(这样用户上传的文件是存放在media/ 下了,但是用户数据库中相关字段存的还是原来的路径,所以用时要拼接)
1.settings.py
MEDIA_URL = '/media/' (# 别名)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') (只有一个目录,不像static可以添加多个)
(给用户上传的所有文件(FileField、request.FILES)指定一个存放目录)
TEMPLATES 中添加上下文环境(可选配置)(自动把 MEDIA_URL 注册到前端的模板中)
'django.template.context_processors.media', (这样前端拼接时候,可以做活路径)
2.urls.py
from django.views.static import serve
re_path('^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
四、主页面、个人主页、分类标签归档页
0.关于路由urls :各种路由都是开发者设计的。
1.评论点赞,bootstrap小图标
<span class="glyphicon glyphicon-comment">评论({{ article.comment_count }})</span>
<span class="glyphicon glyphicon-thumbs-up">点赞({{ article.up_count }})</span>
2.模板中,时间格式化
模板语言,过滤器
<span>发布于 {{ article.create_time|date:'Y-m-d H:i:s' }}</span>
3.二级路由 (名称空间)
项目urls: from django.urls import include from app01 import urls as app01_urls (别名)
app01中urls: from django.urls import path from app01 import views
urlpatterns = [ 各种后续path ]
4.orm 插入原生sql语句
1.一半一半 (利用extra函数在执行orm查询的同时,额外执行一段sql语句,子查询)
2. 对象连接数据库,光标对象执行原生sql
from django.db import connection
cusor = connection.cursor() # 连接数据库,获得一个游标对象
cursor.execute(" sql语句 ") # 游标处:输入sql语句
cursor.fetchall() # 取查询结果
5. 分组、聚合
详见 http://www.cnblogs.com/haiyan123/p/7745536.html
QuerySet.annotate() --> 分组,前面查的什么字段,就按照什么字段进行分组
分组时,给每个组对象新添加:组属性(组成员),例如annotate(c=Count(article__title))
分组是为QuerySet中每一个对象(即组名对象)生成一个独立的汇总值
是对分组完之后的结果进行的聚合
QuerySet.aggregate() --> 聚合,给QuerySet中的每个对象添加一个新属性,给一个组进行聚合
是QuerySet 的一个终止子句(也就是返回的不再是一个QuerySet集合的时候)
意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。
键的名称是按照 "字段名称__聚合函数的名称" 自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
6. 多条视图路由整合一条
路由中正则表达式(无名分组、有名分组不可混合使用)(位置传参必须要在关键字传参前面)
re_path(r"(\d+)/mid/(\w+)",....) 写了几个分组就得匹配几个参数,没有就算匹配不到
五、文章详情页
1.模板继承 {% extends "xx.html" %}
2.inclusion_tag
1. 返回一段HTML代码,用数据填充的
2. 用法:
1. 在app下面创建一个名为 templatetags 的Python Package
2. 写个例如“mytags.py”:
from django import template
register = template.Library()
@register.inclusion_tag(file="xx.html")
def show_menu(*args):
... (此处很多查询操作)
return {"k1" : "v1"}
(xx.html 中使用 k1这个变量)
(用这个自定义标签只是为了少书写查询语句的代码,并没有减少查询次数)
3.点赞
1.orm事务操作
from django.db import transaction
with transaction.atomic():
sql1
sql2
2.模板语言
1.{{ xx }}表示的是单值,不在是变量了,
所以在js中用到这样的值了,要用引号 "{{ request.user }}"
2.把 js 代码写入js文件中在导入的时候,因为render渲染时不会去渲染引入的js,所以要处理
(补)
4.QuerySet 惰性求值
1.len() 及 count()
2,.修改单个属性,两个方法的区别
3.debug-tool-bar
5.阶段问题transaction.atomic() {% extends 'xx.html' %} {% load mytags %}{% func 'xx.html' %}
分组、聚合 F、Q js中正则包裹 /RegExp / RegExp.test(str) RegExp.exec(str)
六、文章评论
五、代码集
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
def get_vcode(request): # 随机颜色,相对较浅 def random_color1(): return random.randint(64, 255), random.randint(64, 255), random.randint(64, 255) # 随机颜色,相对较深 def random_color2(): return random.randint(32, 127), random.randint(32, 127), random.randint(32, 127) # 等概率随机生成 n 个数字、字母(大小写)组合的验证码列表 def random_code(n): rec_list = [] while n: n -= 1 rdm_first = random.randint(0, 1) if rdm_first == 0: rec_list.append(str(random.randint(0, 9))) else: rdm_second = random.randint(0, 1) if rdm_second == 0: rec_list.append(chr(random.randint(65, 90))) else: rec_list.append(chr(random.randint(97, 122))) return rec_list vcode_list = random_code(4) # 由最终验证码组成的字符串, 存入session中用来校验 vcode = ''.join(vcode_list) request.session['vcode'] = vcode # 生成一张图片 img_obj = Image.new( 'RGB', # 生成图片的模式 (162, 33), # 大小 random_color1() # 颜色 ) # 生成一个画笔,指定画板为img_obj draw_obj = ImageDraw.Draw(img_obj) # 加载本地字体文件,生成字体对象 font_obj = ImageFont.truetype('static/font/kumo.ttf', size=28) # 依次在画板上写上n个验证码 for i in range(len(vcode_list)): draw_obj.text( (i * 32 + 20, 0), # 坐标 vcode_list[i], # 内容 fill=random_color2(), # 画笔颜色,即字体颜色 font=font_obj, # 字体对象 ) # # 加干扰线 # width = 250 # 图片宽度(防止越界) # height = 35 # for i in range(5): # x1 = random.randint(0, width) # x2 = random.randint(0, width) # y1 = random.randint(0, height) # y2 = random.randint(0, height) # draw_obj.line((x1, y1, x2, y2), fill=random_color()) # # # 加干扰点 # for i in range(40): # draw_obj.point([random.randint(0, width), random.randint(0, height)], fill=random_color()) # x = random.randint(0, width) # y = random.randint(0, height) # draw_obj.arc((x, y, x+4, y+4), 0, 90, fill=random_color()) # # 进行模糊处理 # img_obj = img_obj.filter(ImageFilter.BLUR) iof = BytesIO() # 生成IO句柄 img_obj.save(iof, 'png') # 把图片对象放进IO句柄里,即保存到内存中 data = iof.getvalue() # 从内存中读取图片数据 # 从服务器静态文件夹内读取图片数据 # with open('static/img/oo.png', 'rb') as f: # data = f.read() return HttpResponse(data, content_type='image/png')
ok