1 注册

  1. # views.py
  2. def register(request):
  3. form_obj = MyRegForm()
  4. # print(request.is_ajax()) # 判断当前请求是否是ajax请求
  5. if request.method == 'POST':
  6. # 定义一个与ajax回调函数交互的字典
  7. back_dic = {"code":1000,'msg':""}
  8. # 校验数据 用户名 密码 确认密码
  9. form_obj = MyRegForm(request.POST)
  10. if form_obj.is_valid():
  11. clean_data = form_obj.cleaned_data # 用变量接收正确的结果 clean_data = {'username' 'password' 'confirm_password' 'email'}
  12. # 将确认密码键值对删除
  13. clean_data.pop('confirm_password')
  14. # 获取用户头像文件
  15. avatar_obj = request.FILES.get('avatar')
  16. # 判断用户头像是否为空
  17. if avatar_obj:
  18. # 添加到clean_data中
  19. clean_data['avatar'] = avatar_obj # clean_data = {'username' 'password' 'email' 'avatar'}
  20. models.UserInfo.objects.create_user(**clean_data)
  21. back_dic['msg'] = '注册成功'
  22. back_dic['url'] = '/login/'
  23. else:
  24. back_dic['code'] = 2000
  25. back_dic['msg'] = form_obj.errors
  26. return JsonResponse(back_dic)
  27. return render(request,'register.html',locals())
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  7. <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
  8. <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
  9. </head>
  10. <body>
  11. <div class="container">
  12. <div class="row">
  13. <h2 class="text-center">注册页面</h2>
  14. <div class="col-md-8 col-md-offset-2">
  15. <form id="myform">
  16. {% csrf_token %}
  17. {% for form in form_obj %}
  18. <div class="form-group">
  19. <label for="{{ form.id_for_label }}">{{ form.label }}</label>
  20. {{ form }}
  21. <span style="color: red" class="pull-right"></span>
  22. </div>
  23. {% endfor %}
  24. <div class="form-group">
  25. <label for="id_avatar">头像
  26. <img src="/static/img/头像1.jpg" alt="" width="200" style="margin-left: 10px" id="id_img">
  27. </label>
  28. <input type="file" name="myfile" id="id_avatar" style="display: none">
  29. </div>
  30. <input type="button" value="注册" class="btn btn-primary pull-right" id="id_submit">
  31. </form>
  32. </div>
  33. </div>
  34. </div>
  35. <script>
  36. $('#id_avatar').change(function () {
  37. // 1 先获取用户上传的头像文件
  38. var avatarFile = $(this)[0].files[0];
  39. // 2 利用文件阅读器对象
  40. var myFileReader = new FileReader();
  41. // 3 将文件交由阅读器对象读取
  42. myFileReader.readAsDataURL(avatarFile);
  43. // 4 修改img标签的src属性 等待文件阅读器对象读取文件之后再操作img标签
  44. myFileReader.onload = function(){
  45. $('#id_img').attr('src',myFileReader.result)
  46. }
  47. });
  48. // 点击按钮触发ajax提交动作
  49. $('#id_submit').on('click',function () {
  50. // 1 先生成一个内置对象 FormData
  51. var myFormData = new FormData();
  52. // 2 添加普通键值对
  53. {#console.log($('#myform').serializeArray())#}
  54. // 循环myform里的每一个对象
  55. $.each($('#myform').serializeArray(),function (index,obj) {
  56. myFormData.append(obj.name,obj.value)
  57. });
  58. // 3 添加文件数据
  59. myFormData.append('avatar',$('#id_avatar')[0].files[0]);
  60. // 4 发送数据
  61. $.ajax({
  62. url:'',
  63. type:'post',
  64. data:myFormData,
  65. // 两个关键性参数
  66. contentType:false,
  67. processData:false,
  68. success:function (data) {
  69. if (data.code===1000){
  70. // 注册成功之后 应该跳转到后端返回过来的url
  71. location.href = data.url
  72. }else{
  73. $.each(data.msg,function(index,obj){
  74. // 1 先手动拼接字段名所对应的input框的id值
  75. var targetId = '#id_' + index; // #id_username
  76. // 2 利用id选择器查找标签 并且将div标签添加报错类
  77. $(targetId).next().text(obj[0]).parent().addClass('has-error')
  78. })
  79. }
  80. }
  81. })
  82. });
  83. $('input').focus(function () {
  84. // 移除span标签内部的文本 还需要移除div标签的class中has-error属性
  85. $(this).next().text('').parent().removeClass('has-error')
  86. })
  87. </script>
  88. </body>
  89. </html>

2 登陆

  1. def login(request):
  2. if request.method == 'POST':
  3. back_dic = {"code": 1000, "msg": ''}
  4. username = request.POST.get('username')
  5. password = request.POST.get('password')
  6. code = request.POST.get('code')
  7. print(username, password, code)
  8. if request.session.get('code').upper() == code.upper():
  9. auth_obj = auth.authenticate(username=username, password=password)
  10. if auth_obj:
  11. auth.login(request, auth_obj)
  12. back_dic['msg'] = '登陆成功'
  13. back_dic['url'] = '/home/'
  14. # return HttpResponse('登陆成功')
  15. return JsonResponse(back_dic)
  16. else:
  17. back_dic['code'] = '2000'
  18. back_dic['msg'] = "用户名或密码错误"
  19. return JsonResponse(back_dic)
  20. else:
  21. back_dic['code'] = '3000'
  22. back_dic['msg'] = '验证码错误'
  23. return JsonResponse(back_dic)
  24. return render(request, 'login.html')
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  7. <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
  8. <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
  9. </head>
  10. <body>
  11. <div class="container">
  12. <h2 class="text-center">登陆</h2>
  13. <div class="col-md-8 col-md-offset-2">
  14. <form id="login_form">
  15. {% csrf_token %}
  16. <div class="form-group">
  17. <label for="id_username">用户名</label>
  18. <input type="text" name="username" class="form-control" id="id_username">
  19. </div>
  20. <div class="form-group">
  21. <label for="id_password">密码</label>
  22. <input type="password" name="password" class="form-control" id="id_password">
  23. </div>
  24. <div class="form-group">
  25. <label for="id_code">验证码</label>
  26. <div class="row">
  27. <div class="col-md-6">
  28. <input type="text" name="code" class="form-control" id="id_code">
  29. </div>
  30. <div class="col-md-6" >
  31. <img src="/get_code/" alt="" id="id_img">
  32. </div>
  33. </div>
  34. </div>
  35. <br>
  36. <input type="button" value="登陆" class="btn btn-primary" id="id_btn">&nbsp <span style="color: red"></span>
  37. </form>
  38. </div>
  39. </div>
  40. <script>
  41. $('#id_img').click(function () {
  42. var oldSrc = $(this).attr('src');
  43. $(this).attr('src', oldSrc += '?')
  44. });
  45. $('#id_btn').click(function () {
  46. var $btn = $(this);
  47. $.ajax({
  48. url:'',
  49. type: 'post',
  50. data: {
  51. 'username': $('#id_username').val(),
  52. 'password': $('#id_password').val(),
  53. 'csrfmiddlewaretoken': '{{ csrf_token }}',
  54. 'code': $('#id_code').val(),
  55. },
  56. success: function (data) {
  57. if (data.code===1000){
  58. location.href = data.url
  59. }else if (data.code===2000){
  60. $btn.next().text(data.msg)
  61. }else {
  62. $btn.next().text(data.msg)
  63. }
  64. }
  65. })
  66. })
  67. </script>
  68. </body>
  69. </html>

3 图片验证码相关

  1. url(r'^get_code/', views.get_code),

在登陆时,需要用到验证码

  1. from PIL import Image, ImageDraw, ImageFont
  2. """
  3. Image: 产生图片
  4. ImageDraw: 在图片上写字
  5. ImageFont: 控制图片上字体样式
  6. """
  7. from io import BytesIO, StringIO
  8. """
  9. BytesIO 能够临时帮你保存数据 获取的时候以二进制方式返回给你
  10. StringIO 能够临时帮你保存数据 获取的时候以字符串方式返回给你
  11. """
  12. import random
  13. def get_random():
  14. return random.randint(0,255), random.randint(0, 255), random.randint(0, 255)
  15. def get_code(request):
  16. # 在图片上写字
  17. img_obj = Image.new('RGB', (350, 35), get_random())
  18. # 产生针对该图片的画笔对象
  19. img_draw = ImageDraw.Draw(img_obj)
  20. # 产生一个字体样式对象
  21. img_font = ImageFont.truetype(r'app01\static\font\新叶念体.otf', 35)
  22. io_obj = BytesIO()
  23. code = ''
  24. for i in range(5):
  25. upper_str = chr(random.randint(65, 90))
  26. lower_str = chr(random.randint(97, 122))
  27. random_int = str(random.randint(0, 9))
  28. temp_str = random.choice([upper_str, lower_str, random_int])
  29. # 写到图片上
  30. img_draw.text((45+i*60, -2), temp_str, get_random(), font=img_font)
  31. code += temp_str
  32. print(code)
  33. img_obj.save(io_obj, 'png')
  34. # 将产生的随机验证码存储到session中 以便于后面的验证码校验
  35. request.session['code'] = code
  36. return HttpResponse(io_obj.getvalue())

4 首页相关,Django Admin后台录入数据

  1. url(r'^home/', views.home, name='_home'),

先创建一个超级管理员用户

  1. createsuperuser
  2. # 对密码有要求,不能太短

然后在 admin.py 文件下,导入模板,将所有表注册都管理员后台

  1. # admin.py
  2. from django.contrib import admin
  3. from app01 import models
  4. # Register your models here.
  5. admin.site.register(models.UserInfo)
  6. admin.site.register(models.Blog)
  7. admin.site.register(models.Tag)
  8. admin.site.register(models.Category)
  9. admin.site.register(models.Article2Tag)
  10. admin.site.register(models.Article)
  11. admin.site.register(models.UpAndDown)
  12. admin.site.register(models.Comment)

登陆admin后台后,可以看到所有的表是英文的,还带了s

把表名变成中文操作:

在每个表添加一个Mate类

  1. # models.py
  2. class Meta:
  3. # verbose_name = '用户表' # 带s
  4. verbose_name_plural = '用户表' # 不带s

然后开始录入数据

录入的时候发现分类是对象,分不出来谁是谁

前端展示对象,就相当于打印对象

可以利用_str_ 方法

  1. class Tag(models.Model):
  2. name = models.CharField(max_length=32)
  3. blog = models.ForeignKey(to='Blog',null=True)
  4. class Meta:
  5. verbose_name_plural = 'Tag标签表'
  6. def __str__(self):
  7. return self.name

最后还要在用户表里给用户选择站点

选择后提交会报错,显示手机号不能为空

这个时候可以去UserInfo用户表中phone字段添加一个属性

blank=True

  1. class UserInfo(AbstractUser):
  2. phone = models.BigIntegerField(null=True,blank=True)
  3. # blank告诉后台管理该字段可以为空

5 注销功能

  1. url(r'^logout/', views.logout, name='_logout'),

auth组件

6 修改密码

  1. url(r'^set_pwd/', views.set_password, name='_set_pwd'),

修改密码可以用一个弹框

  1. @login_required()
  2. def set_password(request):
  3. if request.is_ajax():
  4. back_dic = {"code": 1000, 'msg': ''}
  5. old_pwd = request.POST.get('old_pwd')
  6. new_pwd = request.POST.get('new_pwd')
  7. confirm_pwd = request.POST.get('confirm_pwd')
  8. print(old_pwd, new_pwd, confirm_pwd)
  9. if new_pwd == confirm_pwd:
  10. is_right = request.user.check_password(old_pwd)
  11. if is_right:
  12. request.user.set_password(confirm_pwd)
  13. request.user.save()
  14. back_dic['msg'] = '修改成功'
  15. back_dic['url'] = '/login/'
  16. # back_dic['url'] = reverse('_login')
  17. return JsonResponse(back_dic)
  18. else:
  19. back_dic['code'] = '2000'
  20. back_dic['msg'] = '原密码错误'
  21. return JsonResponse(back_dic)
  22. else:
  23. back_dic['code'] = '3000'
  24. back_dic['msg'] = '两次密码不一致'
  25. return JsonResponse(back_dic)

7 用户头像展示,media配置

网站所用的静态文件我们都默认放到了static文件夹下

而用户上传的文件也算静态资源,我们也应该找一个公共的地方专门存储用户上传的静态文件

media配置专门用来指定用户上传的静态文件存放路径

配置文件中只需要写下面一句配置即可

  1. # settings.py
  2. MEDIA_ROOT = os.path.join(BASE_DIR,'media')
  3. MEDIA_URL = '/media/'
  1. # urls.py
  2. from django.views.static import serve
  3. from day60 import settings
  4. # 固定写法
  5. url(r'^media/(?P<path>.*)',serve,{"document_root":settings.MEDIA_ROOT})

因为在models里指定了头像放在avatar下,settings里有指定了media下,

所以用户上传的头像都会保存到/media/avatar

8 个人站点,个人侧边栏

  1. url(r'^(?P<username>\w+)/$', views.site, name='_site')

可以利用ORM的聚合分组来查询,然后定义在前端,

聚合分组查询需要先导入模块

  1. from django.db.models import Count, Max, Min, Sum, Avg
  1. # views
  2. # 查看当前用户的分类及每个分类下的文章数
  3. category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name')
  4. # 查询当前用户的标签及每个标签下的文章数
  5. tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name')

年月日期分组,可以用官方提供的方法,注意导入模块

  1. from django.db.models.functions import TruncMonth
  2. -官方提供
  3. from django.db.models.functions import TruncMonth
  4. Article.objects
  5. .annotate(month=TruncMonth('timestamp')) # Truncate to month and add to select list
  6. .values('month') # Group By month
  7. .annotate(c=Count('id')) # Select the count of the grouping
  8. .values('month', 'c') # (might be redundant, haven't tested) select month and count
  1. # views
  2. date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values('c', 'month')
  1. # site.html
  2. {% for date in date_list %}
  3. <p><a href="#">
  4. {{ date.month|date:'Y年m月' }}({{ date.c }})
  5. </a></p>
  6. {% endfor %}

如果报错,在settings里调整市区,把 TIME_ZONE 改为 亚洲上海

  1. # settings.py
  2. TIME_ZONE = 'Asia/Shanghai'
  3. USE_TZ = False

9 侧边栏筛选

url匹配优化,将三个url合并

  1. # url(r'^(?P<username>\w+)/category/(\d+)/',views.site),
  2. # url(r'^(?P<username>\w+)/tag/(\d+)/',views.site),
  3. # url(r'^(?P<username>\w+)/archive/(.*)/',views.site),
  4. url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)/', views.site),
  1. if kwargs:
  2. print(kwargs) # {'condition': 'archive', 'param': '2022-03'}
  3. condition = kwargs.get('condition') # category tag archive
  4. param = kwargs.get('param') # 1 2 2019-11
  5. if condition == 'category':
  6. article_list = article_list.filter(category_id=param)
  7. elif condition == 'tag':
  8. article_list = article_list.filter(tags__pk=param)
  9. else:
  10. year, month = param.split('-')
  11. article_list = article_list.filter(create_time__year=year, create_time__month=month)
  1. # 点击跳转
  2. href="/{{ username }}/category/{{ category.pk }}/"
  3. href="/{{ username }}/tag/{{ tag.pk }}/"
  4. href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}/"

10 文章详情页

文章详情页和个人站点的侧边栏一样,可以用模板导入

利用模板导入site.html 时,发现用到一些逻辑渲染出来的,并不能继承

这时需要用到自定义标签 inclusion_tag

  1. url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/',views.article_detail)

在应用名下新建一个名字必须为templatetags的文件夹

在templatetags新建任意.py文件, my_tags.py

  1. # my_tags.py
  2. from django.template import Library
  3. register = Library() # 注意变量名必须为register,不可改变
  4. @register.inclusion_tag('left_menu.html', name='my_left')
  5. def index(username): # index函数里写 left_menu.html 里所需要的的数据
  6. username_obj = models.UserInfo.objects.filter(username=username).first()
  7. blog = username_obj.blog
  8. article_list = models.Article.objects.filter(blog=blog)
  9. # 查看当前用户的分类及每个分类下的文章数
  10. category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name', 'pk')
  11. # 查询当前用户的标签及每个标签下的文章数
  12. tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values('c', 'name', 'pk')
  13. date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
  14. 'month').annotate(c=Count('pk')).values('c', 'month')
  15. return locals()
  1. # left_menu.html
  2. # 将所需的数据代码复制过来
  3. <div class="panel panel-primary">
  4. <div class="panel-heading">
  5. <h3 class="panel-title text-center">文章分类</h3>
  6. </div>
  7. <div class="panel-body">
  8. {% for category in category_list %}
  9. <p><a href="/{{ username }}/category/{{ category.pk }}/">{{category.name}}
  10. ({{ category.c }})</a></p>
  11. {% endfor %}
  12. </div>
  13. </div>
  14. <div class="panel panel-primary">
  15. <div class="panel-heading">
  16. <h3 class="panel-title text-center">文章标签</h3>
  17. </div>
  18. <div class="panel-body">
  19. {% for tag in tag_list %}
  20. <p> <a href="/{{ username }}/tag/{{ tag.pk }}/">{{ tag.name }}
  21. ({{ tag.c }})</a></p>
  22. {% endfor %}
  23. </div>
  24. </div>
  25. <div class="panel panel-primary">
  26. <div class="panel-heading">
  27. <h3 class="panel-title text-center">日期归档</h3>
  28. </div>
  29. <div class="panel-body">
  30. {% for date in date_list %}
  31. <p><a href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}/">
  32. {{ date.month|date:'Y年m月' }}({{ date.c }})
  33. </a></p>
  34. {% endfor %}
  35. </div>
  36. </div>

重新建一个 base.html 文件当作基模板,把 site.html 里的代码复制进行,然后对需要用到的部分用模板导入

  1. ...
  2. ...
  3. {% load my_tags %} #把刚才写的文件load进来
  4. {% my_left username %} # 这个地方依然可以接受 urls 的有名分组关键字
  5. ...
  6. ...

而在article_detail文章详情页里,直接继承 base.html 模板,侧边栏不显示的问题就解决了

  1. {% extends 'base.html' %}
  2. {% block site %}
  3. {% endblock %}

然后让文字在前端显示

11 点赞点踩

前端样式直接去复制。。。

怎么判断用户点了赞还是点了踩

给两个div添加一个相同的点击事件

然后用 hasClass('diggit')

如果点击的div有diggit属性就是ture, 没有就是false

从而可以判断,用户点的是赞还是踩

由于点赞点踩涉及业务逻辑比价多,所以新开了一个url

  1. url(r'^up_or_down/', views.up_or_down, name='updown'),

由于前段判断的布尔值是 字符串 类型的,所以要用json转成python数据类型格式

  1. # views.py
  2. from django.db.models import F
  3. import json
  4. def up_or_down(request):
  5. back_dic = {'code':1000, 'msg': ''}
  6. if request.is_ajax():
  7. article_id = request.POST.get('article_id')
  8. is_up = request.POST.get('is_up')
  9. is_up = json.loads(is_up)
  10. """
  11. 1.判断当前用户是否登录
  12. 2.当前文章是否是当前用户自己写的
  13. 3.当前用户是否已经给当前文章点过赞或踩了
  14. 4.操作数据库
  15. 操作两张表
  16. 数据库优化字段
  17. """
  18. if request.user.is_authenticated():
  19. article_obj = models.Article.objects.filter(pk=article_id).first()
  20. if not article_obj.blog.userinfo == request.user:
  21. is_click = models.UpAndDown.objects.filter(user=request.user, article=article_obj)
  22. if not is_click:
  23. if is_up:
  24. models.Article.objects.filter(pk=article_id).update(up_num=F('up_num')+1)
  25. back_dic['msg'] = '点赞成功'
  26. else:
  27. models.Article.objects.filter(pk=article_id).update(down_num=F('down_num')+1)
  28. models.UpAndDown.objects.create(user=request.user, article=article_obj, is_up=is_up)
  29. else:
  30. back_dic['code'] = 2000
  31. back_dic['msg'] = '您已经支持过'
  32. else:
  33. back_dic['code'] = 3000
  34. back_dic['msg'] = '不能给自己点赞'
  35. else:
  36. back_dic['code'] = 4000
  37. back_dic['msg'] = '请先<a href="/login/">登陆</a>'
  38. return JsonResponse(back_dic)
  1. # article_detail.html 点赞点踩js代码
  2. <script>
  3. $('.action').click(function () {
  4. var $divEle = $(this);
  5. $.ajax({
  6. url: '{% url 'updown' %}',
  7. type: 'post',
  8. data: {
  9. 'article_id': {{ article_obj.pk }},
  10. 'is_up': $(this).hasClass('diggit'),
  11. 'csrfmiddlewaretoken': '{{ csrf_token }}'
  12. },
  13. success: function (data) {
  14. if (data.code===1000){
  15. $('#digg_tips').text(data.msg);
  16. $divEle.children().text(Number($divEle.children().text()) + 1);
  17. {## children() 返回被选元素旗下的所有直接子元素#}
  18. {## next() 获取 当前元素紧邻其后的 同辈元素#}
  19. }else{
  20. $('#digg_tips').html(data.msg) # 这个地方用html() 可以识别html代码
  21. }
  22. }
  23. })
  24. })
  25. </script>

12 文章评论

  1. url(r'^comment/', views.comment, name='_comment'),

子评论,点击回复按钮之后

点击回复按钮发生了哪几件事

1.自动拼接处想要回复评论的那个人的人名 @人名\n

2.评论框自动聚焦

  • 子评论的内容需要做切割处理

  • 子评论的渲染

  • 提交完子评论之后页面不刷新 为何后续的评论都会变成子评论

  1. // 评论样式代码
  2. <div>
  3. <p>评论列表</p>
  4. <hr>
  5. <ul class="list-group">
  6. {% for comment in comment_list %}
  7. <li class="list-group-item"> <span>#{{ forloop.counter }}楼&nbsp;&nbsp;{{ comment.comment_time|date:'Y-m-d' }} <a href="/{{ comment.user }}/">&nbsp;&nbsp;{{ comment.user }}</a></span>
  8. <span class="pull-right"><a class="reply" UserName="{{ comment.user }}" CommentId="{{ comment.pk }}">回复</a></span>
  9. <div>
  10. {% if comment.parent_id %}
  11. {# 拿子评论父评论的用户名 #}
  12. <p>@{{ comment.parent.user.username }}</p>
  13. {% endif %}
  14. {{ comment.content }}
  15. </div>
  16. </li>
  17. <br>
  18. {% endfor %}
  19. </ul>
  20. </div>
  21. {% if request.user.is_authenticated %}
  22. <div>
  23. <p>发表评论</p>
  24. <p>
  25. 昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}">
  26. </p>
  27. <p>评论内容</p>
  28. <p>
  29. <textarea name="content" id="id_content" cols="30" rows="10"></textarea>
  30. </p>
  31. <p>
  32. <button class="btn btn-primary" id="id_comment">提交评论</button>&nbsp;&nbsp;<span></span>
  33. </p>
  34. </div>
  35. {% else %}
  36. <span ><a href="{% url '_login' %}">登录&nbsp;&nbsp;</a></span>
  37. <span><a href=" {% url '_register' %}">注册</a></span>
  38. {% endif %}
  1. # 评论,子评论js代码
  2. var ParentId = null;
  3. $('#id_comment').click(function () {
  4. var conTent = $('#id_content').val();
  5. var $btn = $(this);
  6. // 判断是否需要对conTent 进行处理
  7. if (ParentId){
  8. // 切割 获取第一个\n 对应的索引
  9. var indexN = conTent.indexOf('\n') + 1; // 切片是顾头不顾尾的,所以索引需要加 1
  10. conTent = conTent.slice(indexN) // 将indexN 之前的直接切除,只保留indexN后面的
  11. }
  12. $.ajax({
  13. url: '{% url "_comment" %}',
  14. type: 'post',
  15. data: {
  16. 'article_id': {{ article_obj.pk }},
  17. 'content': conTent,
  18. 'csrfmiddlewaretoken': '{{ csrf_token }}',
  19. 'parent_id': ParentId
  20. },
  21. success: function (data) {
  22. if (data.code===1000){
  23. var Userinfo = '{{ request.user.username }}';
  24. var Content = $('#id_content').val();
  25. // 将内容临时渲染到ul标签内
  26. var temp =`
  27. <li class="list-group-item">
  28. {#<span> <a href="/${Userinfo}/">${Userinfo}</a> </span>#}
  29. <span><span class="glyphicon glyphicon-comment"></span><a href="/${Userinfo}/">${Userinfo}</a></span>
  30. <div>
  31. ${Content}
  32. </div>
  33. </li>
  34. `;
  35. $('.list-group').append(temp);
  36. $('#id_content').val('');
  37. $btn.next().text(data.msg);
  38. ParentId = null;
  39. }
  40. }
  41. })
  42. });
  43. // 回复功能
  44. $('.reply').click(function () {
  45. var UserName = $(this).attr('UserName');
  46. var Comment_Id = $(this).attr('CommentId');
  47. var temp = '@' + UserName + '\n';
  48. $('#id_content').val(temp).focus();
  49. ParentId = Comment_Id;
  50. });
  51. </script>
  1. def comment(request):
  2. back_dic = {'code': 1000, 'msg': ''}
  3. if request.is_ajax():
  4. article_id = request.POST.get('article_id')
  5. content = request.POST.get('content')
  6. parent_id = request.POST.get('parent_id')
  7. article_obj = models.Article.objects.filter(pk=article_id).first()
  8. print(type(article_obj))
  9. if request.user.is_authenticated():
  10. models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num')+1)
  11. # 如果是对象,直接用models字段存,不是就用数据库里的字段存
  12. models.Comment.objects.create(content=content, article_id=article_id, user=request.user, parent_id=parent_id)
  13. back_dic['msg'] = '评论成功'
  14. return JsonResponse(back_dic)

13 后台管理

  1. url(r'^backend/', views.backend, name='_backend'),

后台管理可以在template 文件夹下单独再建立一个backend 后台管理文件夹

  1. @login_required
  2. def backend(request):
  3. article_list = models.Article.objects.filter(blog=request.user.blog).all()
  4. # 分页
  5. current_page = request.GET.get('page', 1)
  6. all_count = article_list.count()
  7. page_obj = Pagination(current_page=current_page, all_count=all_count, pager_count=9, per_page_num=3)
  8. page_queryset = article_list[page_obj.start:page_obj.end]
  9. return render(request, 'backend/backend.html', locals())

后台管理前端代码

  1. # backend.html
  2. {% extends 'backend/backend_base.html' %}
  3. {% block article %}
  4. <table class="table table-hover table-striped">
  5. <thead>
  6. <tr>
  7. <th>标题</th>
  8. <th>发布日期</th>
  9. <th>评论数</th>
  10. <th>点赞数</th>
  11. <th>操作</th>
  12. <th>操作</th>
  13. </tr>
  14. </thead>
  15. <tbody>
  16. {% for article in page_queryset %}
  17. <tr>
  18. <td><a href="/{{ request.user.username }}/article/{{ article.pk }}/">{{ article.title }}</a></td>
  19. <td>{{ article.create_time }}</td>
  20. <td>{{ article.comment_num }}</td>
  21. <td>{{ article.up_num }}</td>
  22. <td><a>编辑</a></td>
  23. <td><a>删除</a></td>
  24. </tr>
  25. {% endfor %}
  26. </tbody>
  27. </table>
  28. <div class="pull-right">
  29. {{ page_obj.page_html|safe }}
  30. </div>
  31. {% endblock %}

14 文章添加

KindEditor textarea 组件

文章添加问题

  1. desc = soup.text[0:150]

如何截取150文字,XSS攻击

可以利用BeautifulSoup的 decompose() 方法删除script标签即可

  1. url(r'^add_article/', views.add_article, name='_add_article'),

add_article.htnl 前端页面

  1. {% extends 'backend/backend_base.html' %}
  2. {% block article %}
  3. <p>添加文章</p>
  4. <form action="" method="post">
  5. {% csrf_token %}
  6. <p>标题</p>
  7. <p><input type="text" name="title" class="form-control"></p>
  8. <p>内容</p>
  9. <p>
  10. <textarea name="content" id="id_content" cols="30" rows="10"></textarea>
  11. </p>
  12. <div>
  13. <p>分类</p>
  14. {% for foo in categoty_list %}
  15. {{ foo.name }}<input type="radio" name="category" value="{{ foo.pk }}">
  16. {% endfor %}
  17. </div>
  18. <div>
  19. <p>标签</p>
  20. {% for tag in tag_list %}
  21. {{ tag.name }}<input type="checkbox" name="tag" value="{{ tag.pk }}">
  22. {% endfor %}
  23. </div>
  24. <input type="submit" class="btn btn-primary">
  25. </form>
  26. <script charset="utf-8" src="/static/kindeditor-4.1.11-zh-CN/kindeditor/kindeditor-all-min.js"></script>
  27. <script>
  28. KindEditor.ready(function(K) {
  29. window.editor = K.create('#id_content',{
  30. width: '100%',
  31. height: '450px',
  32. resizeType: 1
  33. });
  34. });
  35. </script>
  36. {% endblock %}

views.py

  1. @login_required
  2. def add_article(request):
  3. if request.method == 'POST':
  4. title = request.POST.get('title')
  5. content = request.POST.get('content')
  6. category_id = request.POST.get('category')
  7. tag_list = request.POST.getlist('tag')
  8. # 先生成一个该模块的对象
  9. soup = BeautifulSoup(content, 'html.parser')
  10. for tag in soup.find_all():
  11. # 筛选出script标签直接删除
  12. if tag.name == 'script':
  13. tag.decompose() # 删除该标签
  14. desc = soup.text[0:150]
  15. # 写入数据
  16. article_obj = models.Article.objects.create(title=title, desc=desc, content=str(soup), category_id=category_id, blog=request.user.blog)
  17. print(article_obj)
  18. # 手动操作文章与标签的第三张表
  19. b_list = []
  20. for tag_id in tag_list:
  21. b_list.append(models.Article2Tag(article=article_obj, tag_id=tag_id))
  22. models.Article2Tag.objects.bulk_create(b_list)
  23. return redirect(reverse('_backend'))
  24. categoty_list = models.Category.objects.filter(blog=request.user.blog)
  25. tag_list = models.Tag.objects.filter(blog=request.user.blog)

15 编辑器上传图片

  1. url(r'^upload_image/', views.upload_image),
  1. # views.py
  2. import os
  3. from day60 import settings
  4. @login_required
  5. def upload_image(request):
  6. back_dic = {'error': 0}
  7. if request.method == "POST":
  8. file_obj = request.FILES.get('imgFile')
  9. file_dir = os.path.join(settings.BASE_DIR, 'media', 'article_image')
  10. if not os.path.isdir(file_dir):
  11. os.mkdir(file_dir)
  12. file_path = os.path.join(file_dir, file_obj.name)
  13. with open(file_path, 'wb') as f:
  14. for chunk in file_obj.chunks():
  15. f.write(chunk)
  16. # // 成功时
  17. # {
  18. # "error": 0,
  19. # "url": "http://www.example.com/path/to/file.ext"
  20. # }
  21. # // 失败时
  22. # {
  23. # "error": 1,
  24. # "message": "错误信息"
  25. # }
  26. back_dic['url'] = f'/media/article_image/{file_obj.name}'
  27. return JsonResponse(back_dic)

需要在文章详情页添加以下代码

16 修改头像

  1. url(r'^set_avatar/', views.set_avatar, name='_set_avatar'),

修改头像 和 注册的 上传头像 类似

  1. // set_avatar.html
  2. {% extends 'backend/backend_base.html' %}
  3. {% block article %}
  4. <p>原头像
  5. <img src="/media/{{ request.user.avatar }}/" alt="">
  6. </p>
  7. <form action="" method="post" enctype="multipart/form-data">
  8. {% csrf_token %}
  9. <div class="form-group">
  10. <label for="id_avatar">头像
  11. <img src="/static/img/头像1.jpg" alt="" width="200" style="margin-left: 10px" id="id_img">
  12. </label>
  13. <input type="file" name="myfile" id="id_avatar" style="display: none">
  14. </div>
  15. <input type="submit" value="提交" class="btn btn-primary pull-right" id="id_submit">
  16. </form>
  17. <script>
  18. $('#id_avatar').change(function () {
  19. // 1 先获取用户上传的头像文件
  20. var avatarFile = $(this)[0].files[0];
  21. // 2 利用文件阅读器对象
  22. var myFileReader = new FileReader();
  23. // 3 将文件交由阅读器对象读取
  24. myFileReader.readAsDataURL(avatarFile);
  25. // 4 修改img标签的src属性 等待文件阅读器对象读取文件之后再操作img标签
  26. myFileReader.onload = function(){
  27. $('#id_img').attr('src',myFileReader.result)
  28. }
  29. });
  30. </script>
  31. {% endblock %}
  1. def set_avatar(request):
  2. if request.method == 'POST':
  3. avatar_obj = request.FILES.get('myfile')
  4. # models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=avatar_obj)
  5. request.user.avatar = avatar_obj
  6. request.user.save() # 更换头像可以直接用 request.user.save()方法
  7. return render(request, 'set_avatar.html', locals())

BBS那些事儿的更多相关文章

  1. FPGA功耗那些事儿(转载)

    在项目设计初期,基于硬件电源模块的设计考虑,对FPGA设计中的功耗估计是必不可少的.笔者经历过一个项目,整个系统的功耗达到了100w,而单片FPGA的功耗估计得到为20w左右,有点过高了,功耗过高则会 ...

  2. 说说无线路由器后门的那些事儿(1)-D-Link篇

    [原创]说说无线路由器后门的那些事儿(1)-D-Link篇 作 者: gamehacker 时 间: 2013-11-29,11:29:19 链 接: http://bbs.pediy.com/sho ...

  3. 说说Makefile那些事儿

    说说Makefile那些事儿 |扬说|透过现象看本质 工作至今,一直对Makefile半知半解.突然某天幡然醒悟,觉得此举极为不妥,只得洗心革面从头学来,以前许多不明觉厉之处顿时茅塞顿开,想想好记性不 ...

  4. 总结iOS开发中的断点续传那些事儿

    前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...

  5. setTimeout那些事儿

    一.setTimeout那些事儿之单线程 一直以来,大家都在说Javascript是单线程,浏览器无论在什么时候,都且只有一个线程在运行JavaScript程序. 但是,不知道大家有疑问没——就是我们 ...

  6. Javascript中关于cookie的那些事儿

    Javascript-cookie 什么是cookie? 指某些网站为了辨别用户身份.进行session跟踪而储存在用户本地终端上的数据(通常经过加密).简单点来说就是:浏览器缓存. cookie由什 ...

  7. webpack那些事儿

    webpack那些事儿01-webpack到底是什么 webpack那些事儿02-从零开始 webpack那些事儿03-热插拔 hot webpack那些事儿04-spa项目实战分析 webpack那 ...

  8. 关于JSON的那些事儿

    JSON的那些事儿 曾经有一段时间,XML是互联网上传输结构化数据的事实标准,其突出特点是服务器与服务器间的通信.但是业内不少人认为XML过于繁琐.冗长,后面为了解决这个问题也出现了一些方案,但是由于 ...

  9. MVC之前的那点事儿系列(10):MVC为什么不再需要注册通配符(*.*)了?

    文章内容 很多教程里都提到了,在部署MVC程序的时候要配置通配符映射(或者是*.mvc)到aspnet_ISPAI.dll上,在.NET4.0之前确实应该这么多,但是.NET4.0之后已经不要再费事了 ...

随机推荐

  1. 夯实Java基础(十九)——集合

    1.前言 集合在Java中的地位想必大家都知道,不用多BB了.无论是在我们现在的学习中还是在今后的工作中,集合这样一个大家族都无处不在,无处不用.在前面讲到的数组也是一个小的容器,但是数组不是面向对象 ...

  2. SAVE 、BGSAVE和BGREWRITEAOF执行区别

    rdbSave 会将数据库数据保存到 RDB 文件,并在保存完成之前阻塞调用者. save 命令直接调用 rdbSave ,阻塞 Redis 主进程:bgsave 用子进程调用 rdbSave ,主进 ...

  3. 设计模式课程 设计模式精讲 12-2 适配器模式coding

    1 重要 1.1 类适配器和对象适配器最大的区别 2 代码演练 2.1 代码演练1(类适配器模式) 2.2 代码演练2(对象适配模式) 2.3 代码演练3(具体应用场景) 1 重要 1.1 类适配器和 ...

  4. nmap 速查命令

    进行ping扫描,打印出对扫描做出响应的主机,不做进一步测试(如端口扫描或者操作系统探测) nmap -sP 192.168.1.0/24 仅列出指定网络上的每台主机,不发送任何报文到目标主机 nam ...

  5. 窥探QQ基础数据库架构演变史

    作为腾讯最核心最基础的后台服务之一,QQ基础数据库是存储QQ用户帐户信息和关系链信息的海量集群,它承载了百万级每秒的访问量.十亿级的账户数.百亿级关系链.如此大规模的集群,它是如何从300万的数量级一 ...

  6. JS 删除对象中指定的值

    1,通过delete删除 2,通过filter filter需要在循环的时候判断一下是true还是false,是true才会返回这个元素: let arr1 = [1,2,3]; let arr2 = ...

  7. 【剑指Offer面试编程题】题目1524:复杂链表的复制--九度OJ

    题目描述: 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点). 输入: 输入可能包含多个测试样例,输入以EOF结束. 对于每个测试案例,输入的第 ...

  8. java学习-初级入门-面向对象④-类与对象-类与对象的定义和使用2

    我们继续学习类与对象,上一篇我们定义了  坐标类(Point), 这次我们在Point的基础上,创建一个圆类(Circle). 案例:创建一个圆类 题目要求: 计算圆的周长和面积:求判断两个圆的位置关 ...

  9. SICOM SOP

    SOPs A Service-Object Pair (SOP) Class is defined by the union of an Information Object Definition ( ...

  10. python异常处理(学习)

    异常处理可以保证程序在恶劣的环境也能运行,或者给用户相关的提示 如下是运行异常的情况 如无异常 也可以创建异常处理类型