目录
- 1 注册
- 2 登陆
- 3 图片验证码相关
- 4 首页相关,Django Admin后台录入数据
- 5 注销功能
- 6 修改密码
- 7 用户头像展示,media配置
- 8 个人站点,个人侧边栏
- 9 侧边栏筛选
- 10 文章详情页
- 11 点赞点踩
- 12 文章评论
- 13 后台管理
- 14 文章添加
- 15 编辑器上传图片
- 16 修改头像
1 注册
# views.py def register(request): form_obj = MyRegForm() # print(request.is_ajax()) # 判断当前请求是否是ajax请求 if request.method == 'POST': # 定义一个与ajax回调函数交互的字典 back_dic = {"code":1000,'msg':""} # 校验数据 用户名 密码 确认密码 form_obj = MyRegForm(request.POST) if form_obj.is_valid(): clean_data = form_obj.cleaned_data # 用变量接收正确的结果 clean_data = {'username' 'password' 'confirm_password' 'email'} # 将确认密码键值对删除 clean_data.pop('confirm_password') # 获取用户头像文件 avatar_obj = request.FILES.get('avatar') # 判断用户头像是否为空 if avatar_obj: # 添加到clean_data中 clean_data['avatar'] = avatar_obj # clean_data = {'username' 'password' 'email' 'avatar'} models.UserInfo.objects.create_user(**clean_data) back_dic['msg'] = '注册成功' 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())
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <div class="row"> <h2 class="text-center">注册页面</h2> <div class="col-md-8 col-md-offset-2"> <form id="myform"> {% csrf_token %} {% for form in form_obj %} <div class="form-group"> <label for="{{ form.id_for_label }}">{{ form.label }}</label> {{ form }} <span style="color: red" class="pull-right"></span> </div> {% endfor %} <div class="form-group"> <label for="id_avatar">头像 <img src="/static/img/头像1.jpg" alt="" width="200" style="margin-left: 10px" id="id_img"> </label> <input type="file" name="myfile" id="id_avatar" style="display: none"> </div> <input type="button" value="注册" class="btn btn-primary pull-right" id="id_submit"> </form> </div> </div> </div> <script> $('#id_avatar').change(function () { // 1 先获取用户上传的头像文件 var avatarFile = $(this)[0].files[0]; // 2 利用文件阅读器对象 var myFileReader = new FileReader(); // 3 将文件交由阅读器对象读取 myFileReader.readAsDataURL(avatarFile); // 4 修改img标签的src属性 等待文件阅读器对象读取文件之后再操作img标签 myFileReader.onload = function(){ $('#id_img').attr('src',myFileReader.result) } }); // 点击按钮触发ajax提交动作 $('#id_submit').on('click',function () { // 1 先生成一个内置对象 FormData var myFormData = new FormData(); // 2 添加普通键值对 {#console.log($('#myform').serializeArray())#} // 循环myform里的每一个对象 $.each($('#myform').serializeArray(),function (index,obj) { myFormData.append(obj.name,obj.value) }); // 3 添加文件数据 myFormData.append('avatar',$('#id_avatar')[0].files[0]); // 4 发送数据 $.ajax({ url:'', type:'post', data:myFormData, // 两个关键性参数 contentType:false, processData:false, success:function (data) { if (data.code===1000){ // 注册成功之后 应该跳转到后端返回过来的url location.href = data.url }else{ $.each(data.msg,function(index,obj){ // 1 先手动拼接字段名所对应的input框的id值 var targetId = '#id_' + index; // #id_username // 2 利用id选择器查找标签 并且将div标签添加报错类 $(targetId).next().text(obj[0]).parent().addClass('has-error') }) } } }) }); $('input').focus(function () { // 移除span标签内部的文本 还需要移除div标签的class中has-error属性 $(this).next().text('').parent().removeClass('has-error') }) </script> </body> </html>
2 登陆
def login(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') print(username, password, code) if request.session.get('code').upper() == code.upper(): auth_obj = auth.authenticate(username=username, password=password) if auth_obj: auth.login(request, auth_obj) back_dic['msg'] = '登陆成功' back_dic['url'] = '/home/' # return HttpResponse('登陆成功') return JsonResponse(back_dic) else: back_dic['code'] = '2000' back_dic['msg'] = "用户名或密码错误" return JsonResponse(back_dic) else: back_dic['code'] = '3000' back_dic['msg'] = '验证码错误' return JsonResponse(back_dic) return render(request, 'login.html')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <h2 class="text-center">登陆</h2> <div class="col-md-8 col-md-offset-2"> <form id="login_form"> {% csrf_token %} <div class="form-group"> <label for="id_username">用户名</label> <input type="text" name="username" class="form-control" id="id_username"> </div> <div class="form-group"> <label for="id_password">密码</label> <input type="password" name="password" class="form-control" id="id_password"> </div> <div class="form-group"> <label for="id_code">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" name="code" class="form-control" id="id_code"> </div> <div class="col-md-6" > <img src="/get_code/" alt="" id="id_img"> </div> </div> </div> <br> <input type="button" value="登陆" class="btn btn-primary" id="id_btn">  <span style="color: red"></span> </form> </div> </div> <script> $('#id_img').click(function () { var oldSrc = $(this).attr('src'); $(this).attr('src', oldSrc += '?') }); $('#id_btn').click(function () { var $btn = $(this); $.ajax({ url:'', type: 'post', data: { 'username': $('#id_username').val(), 'password': $('#id_password').val(), 'csrfmiddlewaretoken': '{{ csrf_token }}', 'code': $('#id_code').val(), }, success: function (data) { if (data.code===1000){ location.href = data.url }else if (data.code===2000){ $btn.next().text(data.msg) }else { $btn.next().text(data.msg) } } }) }) </script> </body> </html>
3 图片验证码相关
url(r'^get_code/', views.get_code),
在登陆时,需要用到验证码
from PIL import Image, ImageDraw, ImageFont """ Image: 产生图片 ImageDraw: 在图片上写字 ImageFont: 控制图片上字体样式 """ from io import BytesIO, StringIO """ BytesIO 能够临时帮你保存数据 获取的时候以二进制方式返回给你 StringIO 能够临时帮你保存数据 获取的时候以字符串方式返回给你 """ import random def get_random(): return random.randint(0,255), random.randint(0, 255), random.randint(0, 255) def get_code(request): # 在图片上写字 img_obj = Image.new('RGB', (350, 35), get_random()) # 产生针对该图片的画笔对象 img_draw = ImageDraw.Draw(img_obj) # 产生一个字体样式对象 img_font = ImageFont.truetype(r'app01\static\font\新叶念体.otf', 35) io_obj = BytesIO() 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)) temp_str = random.choice([upper_str, lower_str, random_int]) # 写到图片上 img_draw.text((45+i*60, -2), temp_str, get_random(), font=img_font) code += temp_str print(code) img_obj.save(io_obj, 'png') # 将产生的随机验证码存储到session中 以便于后面的验证码校验 request.session['code'] = code return HttpResponse(io_obj.getvalue())
4 首页相关,Django Admin后台录入数据
url(r'^home/', views.home, name='_home'),
先创建一个超级管理员用户
createsuperuser # 对密码有要求,不能太短
然后在 admin.py 文件下,导入模板,将所有表注册都管理员后台
# 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.Tag) admin.site.register(models.Category) admin.site.register(models.Article2Tag) admin.site.register(models.Article) admin.site.register(models.UpAndDown) admin.site.register(models.Comment)
登陆admin后台后,可以看到所有的表是英文的,还带了s
把表名变成中文操作:
在每个表添加一个Mate类
# models.py class Meta: # verbose_name = '用户表' # 带s verbose_name_plural = '用户表' # 不带s
然后开始录入数据
录入的时候发现分类是对象,分不出来谁是谁
前端展示对象,就相当于打印对象
可以利用__str__ 方法
class Tag(models.Model): name = models.CharField(max_length=32) blog = models.ForeignKey(to='Blog',null=True) class Meta: verbose_name_plural = 'Tag标签表' def __str__(self): return self.name
最后还要在用户表里给用户选择站点
选择后提交会报错,显示手机号不能为空
这个时候可以去UserInfo用户表中phone字段添加一个属性
blank=True
class UserInfo(AbstractUser): phone = models.BigIntegerField(null=True,blank=True) # blank告诉后台管理该字段可以为空
5 注销功能
url(r'^logout/', views.logout, name='_logout'),
auth组件
6 修改密码
url(r'^set_pwd/', views.set_password, name='_set_pwd'),
修改密码可以用一个弹框
@login_required() def set_password(request): if request.is_ajax(): back_dic = {"code": 1000, 'msg': ''} old_pwd = request.POST.get('old_pwd') new_pwd = request.POST.get('new_pwd') confirm_pwd = request.POST.get('confirm_pwd') print(old_pwd, new_pwd, confirm_pwd) if new_pwd == confirm_pwd: is_right = request.user.check_password(old_pwd) if is_right: request.user.set_password(confirm_pwd) request.user.save() back_dic['msg'] = '修改成功' back_dic['url'] = '/login/' # back_dic['url'] = reverse('_login') return JsonResponse(back_dic) else: back_dic['code'] = '2000' back_dic['msg'] = '原密码错误' return JsonResponse(back_dic) else: back_dic['code'] = '3000' back_dic['msg'] = '两次密码不一致' return JsonResponse(back_dic)
7 用户头像展示,media配置
网站所用的静态文件我们都默认放到了static文件夹下
而用户上传的文件也算静态资源,我们也应该找一个公共的地方专门存储用户上传的静态文件
media配置专门用来指定用户上传的静态文件存放路径
配置文件中只需要写下面一句配置即可
# settings.py MEDIA_ROOT = os.path.join(BASE_DIR,'media')
# urls.py from django.views.static import serve from day60 import settings # 固定写法 url(r'^media/(?P<path>.*)',serve,{"document_root":settings.MEDIA_ROOT})
因为在models里指定了头像放在avatar下,settings里有指定了media下,
所以用户上传的头像都会保存到/media/avatar
8 个人站点,个人侧边栏
url(r'^(?P<username>\w+)/$', views.site, name='_site')
可以利用ORM的聚合分组来查询,然后定义在前端,
聚合分组查询需要先导入模块
from django.db.models import Count, Max, Min, Sum, Avg
# views # 查看当前用户的分类及每个分类下的文章数 category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name') # 查询当前用户的标签及每个标签下的文章数 tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name')
年月日期分组,可以用官方提供的方法,注意导入模块
from django.db.models.functions import TruncMonth -官方提供 from django.db.models.functions import TruncMonth Article.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
# views date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values('c', 'month')
# site.html {% for date in date_list %} <p><a href="#"> {{ date.month|date:'Y年m月' }}({{ date.c }}) </a></p> {% endfor %}
如果报错,在settings里调整市区,把 TIME_ZONE 改为 亚洲上海
# settings.py TIME_ZONE = 'Asia/Shanghai' USE_TZ = False
9 侧边栏筛选
url匹配优化,将三个url合并
# url(r'^(?P<username>\w+)/category/(\d+)/',views.site), # url(r'^(?P<username>\w+)/tag/(\d+)/',views.site), # url(r'^(?P<username>\w+)/archive/(.*)/',views.site), url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)/', views.site),
if kwargs: print(kwargs) # {'condition': 'archive', 'param': '2022-03'} condition = kwargs.get('condition') # category tag archive param = kwargs.get('param') # 1 2 2019-11 if condition == 'category': article_list = article_list.filter(category_id=param) elif condition == 'tag': article_list = article_list.filter(tags__pk=param) else: year, month = param.split('-') article_list = article_list.filter(create_time__year=year, create_time__month=month)
# 点击跳转 href="/{{ username }}/category/{{ category.pk }}/" href="/{{ username }}/tag/{{ tag.pk }}/" href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}/"
10 文章详情页
文章详情页和个人站点的侧边栏一样,可以用模板导入
利用模板导入site.html 时,发现用到一些逻辑渲染出来的,并不能继承
这时需要用到自定义标签 inclusion_tag
url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/',views.article_detail)
在应用名下新建一个名字必须为templatetags的文件夹
在templatetags新建任意.py文件, my_tags.py
# my_tags.py from django.template import Library register = Library() # 注意变量名必须为register,不可改变 @register.inclusion_tag('left_menu.html', name='my_left') def index(username): # index函数里写 left_menu.html 里所需要的的数据 username_obj = models.UserInfo.objects.filter(username=username).first() blog = username_obj.blog article_list = models.Article.objects.filter(blog=blog) # 查看当前用户的分类及每个分类下的文章数 category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name', 'pk') # 查询当前用户的标签及每个标签下的文章数 tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name', 'pk') date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values( 'month').annotate(c=Count('pk')).values('c', 'month') return locals()
# left_menu.html # 将所需的数据代码复制过来 <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title text-center">文章分类</h3> </div> <div class="panel-body"> {% for category in category_list %} <p><a href="/{{ username }}/category/{{ category.pk }}/">{{category.name}} ({{ category.c }})</a></p> {% endfor %} </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title text-center">文章标签</h3> </div> <div class="panel-body"> {% for tag in tag_list %} <p> <a href="/{{ username }}/tag/{{ tag.pk }}/">{{ tag.name }} ({{ tag.c }})</a></p> {% endfor %} </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title text-center">日期归档</h3> </div> <div class="panel-body"> {% for date in date_list %} <p><a href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}/"> {{ date.month|date:'Y年m月' }}({{ date.c }}) </a></p> {% endfor %} </div> </div>
重新建一个 base.html 文件当作基模板,把 site.html 里的代码复制进行,然后对需要用到的部分用模板导入
... ... {% load my_tags %} #把刚才写的文件load进来 {% my_left username %} # 这个地方依然可以接受 urls 的有名分组关键字 ... ...
而在article_detail文章详情页里,直接继承 base.html 模板,侧边栏不显示的问题就解决了
{% extends 'base.html' %} {% block site %} {% endblock %}
然后让文字在前端显示
11 点赞点踩
前端样式直接去复制。。。
怎么判断用户点了赞还是点了踩
给两个div添加一个相同的点击事件
然后用 hasClass('diggit')
如果点击的div有diggit属性就是ture, 没有就是false
从而可以判断,用户点的是赞还是踩
由于点赞点踩涉及业务逻辑比价多,所以新开了一个url
url(r'^up_or_down/', views.up_or_down, name='updown'),
由于前段判断的布尔值是 字符串 类型的,所以要用json转成python数据类型格式
# views.py from django.db.models import F import json def up_or_down(request): back_dic = {'code':1000, 'msg': ''} if request.is_ajax(): article_id = request.POST.get('article_id') is_up = request.POST.get('is_up') is_up = json.loads(is_up) """ 1.判断当前用户是否登录 2.当前文章是否是当前用户自己写的 3.当前用户是否已经给当前文章点过赞或踩了 4.操作数据库 操作两张表 数据库优化字段 """ if request.user.is_authenticated(): article_obj = models.Article.objects.filter(pk=article_id).first() if not article_obj.blog.userinfo == request.user: is_click = models.UpAndDown.objects.filter(user=request.user, article=article_obj) if not is_click: if is_up: 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) models.UpAndDown.objects.create(user=request.user, article=article_obj, is_up=is_up) 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>' return JsonResponse(back_dic)
# article_detail.html 点赞点踩js代码 <script> $('.action').click(function () { var $divEle = $(this); $.ajax({ url: '{% url 'updown' %}', type: 'post', data: { 'article_id': {{ article_obj.pk }}, 'is_up': $(this).hasClass('diggit'), 'csrfmiddlewaretoken': '{{ csrf_token }}' }, success: function (data) { if (data.code===1000){ $('#digg_tips').text(data.msg); $divEle.children().text(Number($divEle.children().text()) + 1); {## children() 返回被选元素旗下的所有直接子元素#} {## next() 获取 当前元素紧邻其后的 同辈元素#} }else{ $('#digg_tips').html(data.msg) # 这个地方用html() 可以识别html代码 } } }) }) </script>
12 文章评论
url(r'^comment/', views.comment, name='_comment'),
子评论,点击回复按钮之后
点击回复按钮发生了哪几件事
1.自动拼接处想要回复评论的那个人的人名 @人名\n
2.评论框自动聚焦
子评论的内容需要做切割处理
子评论的渲染
提交完子评论之后页面不刷新 为何后续的评论都会变成子评论
// 评论样式代码 <div> <p>评论列表</p> <hr> <ul class="list-group"> {% for comment in comment_list %} <li class="list-group-item"> <span>#{{ forloop.counter }}楼 {{ comment.comment_time|date:'Y-m-d' }} <a href="/{{ comment.user }}/"> {{ comment.user }}</a></span> <span class="pull-right"><a class="reply" UserName="{{ comment.user }}" CommentId="{{ comment.pk }}">回复</a></span> <div> {% if comment.parent_id %} {# 拿子评论父评论的用户名 #} <p>@{{ comment.parent.user.username }}</p> {% endif %} {{ comment.content }} </div> </li> <br> {% endfor %} </ul> </div> {% if request.user.is_authenticated %} <div> <p>发表评论</p> <p> 昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"> </p> <p>评论内容</p> <p> <textarea name="content" id="id_content" cols="30" rows="10"></textarea> </p> <p> <button class="btn btn-primary" id="id_comment">提交评论</button> <span></span> </p> </div> {% else %} <span ><a href="{% url '_login' %}">登录 </a></span> <span><a href=" {% url '_register' %}">注册</a></span> {% endif %}
# 评论,子评论js代码 var ParentId = null; $('#id_comment').click(function () { var conTent = $('#id_content').val(); var $btn = $(this); // 判断是否需要对conTent 进行处理 if (ParentId){ // 切割 获取第一个\n 对应的索引 var indexN = conTent.indexOf('\n') + 1; // 切片是顾头不顾尾的,所以索引需要加 1 conTent = conTent.slice(indexN) // 将indexN 之前的直接切除,只保留indexN后面的 } $.ajax({ url: '{% url "_comment" %}', type: 'post', data: { 'article_id': {{ article_obj.pk }}, 'content': conTent, 'csrfmiddlewaretoken': '{{ csrf_token }}', 'parent_id': ParentId }, success: function (data) { if (data.code===1000){ var Userinfo = '{{ request.user.username }}'; var Content = $('#id_content').val(); // 将内容临时渲染到ul标签内 var temp =` <li class="list-group-item"> {#<span> <a href="/${Userinfo}/">${Userinfo}</a> </span>#} <span><span class="glyphicon glyphicon-comment"></span><a href="/${Userinfo}/">${Userinfo}</a></span> <div> ${Content} </div> </li> `; $('.list-group').append(temp); $('#id_content').val(''); $btn.next().text(data.msg); ParentId = null; } } }) }); // 回复功能 $('.reply').click(function () { var UserName = $(this).attr('UserName'); var Comment_Id = $(this).attr('CommentId'); var temp = '@' + UserName + '\n'; $('#id_content').val(temp).focus(); ParentId = Comment_Id; }); </script>
def comment(request): back_dic = {'code': 1000, 'msg': ''} if request.is_ajax(): article_id = request.POST.get('article_id') content = request.POST.get('content') parent_id = request.POST.get('parent_id') article_obj = models.Article.objects.filter(pk=article_id).first() print(type(article_obj)) if request.user.is_authenticated(): models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num')+1) # 如果是对象,直接用models字段存,不是就用数据库里的字段存 models.Comment.objects.create(content=content, article_id=article_id, user=request.user, parent_id=parent_id) back_dic['msg'] = '评论成功' return JsonResponse(back_dic)
13 后台管理
url(r'^backend/', views.backend, name='_backend'),
后台管理可以在template 文件夹下单独再建立一个backend 后台管理文件夹
@login_required def backend(request): article_list = models.Article.objects.filter(blog=request.user.blog).all() # 分页 current_page = request.GET.get('page', 1) all_count = article_list.count() page_obj = Pagination(current_page=current_page, all_count=all_count, pager_count=9, per_page_num=3) page_queryset = article_list[page_obj.start:page_obj.end] return render(request, 'backend/backend.html', locals())
后台管理前端代码
# backend.html {% extends 'backend/backend_base.html' %} {% block article %} <table class="table table-hover table-striped"> <thead> <tr> <th>标题</th> <th>发布日期</th> <th>评论数</th> <th>点赞数</th> <th>操作</th> <th>操作</th> </tr> </thead> <tbody> {% for article in page_queryset %} <tr> <td><a href="/{{ request.user.username }}/article/{{ article.pk }}/">{{ article.title }}</a></td> <td>{{ article.create_time }}</td> <td>{{ article.comment_num }}</td> <td>{{ article.up_num }}</td> <td><a>编辑</a></td> <td><a>删除</a></td> </tr> {% endfor %} </tbody> </table> <div class="pull-right"> {{ page_obj.page_html|safe }} </div> {% endblock %}
14 文章添加
KindEditor textarea 组件
文章添加问题
desc = soup.text[0:150]
如何截取150文字,XSS攻击
可以利用BeautifulSoup的 decompose() 方法删除script标签即可
url(r'^add_article/', views.add_article, name='_add_article'),
add_article.htnl 前端页面
{% extends 'backend/backend_base.html' %} {% block article %} <p>添加文章</p> <form action="" method="post"> {% csrf_token %} <p>标题</p> <p><input type="text" name="title" class="form-control"></p> <p>内容</p> <p> <textarea name="content" id="id_content" cols="30" rows="10"></textarea> </p> <div> <p>分类</p> {% for foo in categoty_list %} {{ foo.name }}<input type="radio" name="category" value="{{ foo.pk }}"> {% endfor %} </div> <div> <p>标签</p> {% for tag in tag_list %} {{ tag.name }}<input type="checkbox" name="tag" value="{{ tag.pk }}"> {% endfor %} </div> <input type="submit" class="btn btn-primary"> </form> <script charset="utf-8" src="/static/kindeditor-4.1.11-zh-CN/kindeditor/kindeditor-all-min.js"></script> <script> KindEditor.ready(function(K) { window.editor = K.create('#id_content',{ width: '100%', height: '450px', resizeType: 1 }); }); </script> {% endblock %}
views.py
@login_required def add_article(request): if request.method == 'POST': title = request.POST.get('title') content = request.POST.get('content') category_id = request.POST.get('category') tag_list = request.POST.getlist('tag') # 先生成一个该模块的对象 soup = BeautifulSoup(content, 'html.parser') for tag in soup.find_all(): # 筛选出script标签直接删除 if tag.name == 'script': tag.decompose() # 删除该标签 desc = soup.text[0:150] # 写入数据 article_obj = models.Article.objects.create(title=title, desc=desc, content=str(soup), category_id=category_id, blog=request.user.blog) print(article_obj) # 手动操作文章与标签的第三张表 b_list = [] for tag_id in tag_list: b_list.append(models.Article2Tag(article=article_obj, tag_id=tag_id)) models.Article2Tag.objects.bulk_create(b_list) return redirect(reverse('_backend')) categoty_list = models.Category.objects.filter(blog=request.user.blog) tag_list = models.Tag.objects.filter(blog=request.user.blog)
15 编辑器上传图片
url(r'^upload_image/', views.upload_image),
# views.py import os from day60 import settings @login_required def upload_image(request): back_dic = {'error': 0} if request.method == "POST": file_obj = request.FILES.get('imgFile') file_dir = os.path.join(settings.BASE_DIR, 'media', 'article_image') if not os.path.isdir(file_dir): os.mkdir(file_dir) file_path = os.path.join(file_dir, file_obj.name) with open(file_path, 'wb') as f: for chunk in file_obj.chunks(): f.write(chunk) # // 成功时 # { # "error": 0, # "url": "http://www.example.com/path/to/file.ext" # } # // 失败时 # { # "error": 1, # "message": "错误信息" # } back_dic['url'] = f'/media/article_image/{file_obj.name}' return JsonResponse(back_dic)
需要在文章详情页添加以下代码
16 修改头像
url(r'^set_avatar/', views.set_avatar, name='_set_avatar'),
修改头像 和 注册的 上传头像 类似
// set_avatar.html {% extends 'backend/backend_base.html' %} {% block article %} <p>原头像 <img src="/media/{{ request.user.avatar }}/" alt=""> </p> <form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} <div class="form-group"> <label for="id_avatar">头像 <img src="/static/img/头像1.jpg" alt="" width="200" style="margin-left: 10px" id="id_img"> </label> <input type="file" name="myfile" id="id_avatar" style="display: none"> </div> <input type="submit" value="提交" class="btn btn-primary pull-right" id="id_submit"> </form> <script> $('#id_avatar').change(function () { // 1 先获取用户上传的头像文件 var avatarFile = $(this)[0].files[0]; // 2 利用文件阅读器对象 var myFileReader = new FileReader(); // 3 将文件交由阅读器对象读取 myFileReader.readAsDataURL(avatarFile); // 4 修改img标签的src属性 等待文件阅读器对象读取文件之后再操作img标签 myFileReader.onload = function(){ $('#id_img').attr('src',myFileReader.result) } }); </script> {% endblock %}
def set_avatar(request): if request.method == 'POST': avatar_obj = request.FILES.get('myfile') # models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=avatar_obj) request.user.avatar = avatar_obj request.user.save() # 更换头像可以直接用 request.user.save()方法 return render(request, 'set_avatar.html', locals())