• BBS那些事儿


    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">&nbsp <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'app01staticfont新叶念体.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')
    MEDIA_URL = '/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.自动拼接处想要回复评论的那个人的人名 @人名

    2.评论框自动聚焦

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

    • 子评论的渲染

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

    // 评论样式代码
    <div>
        <p>评论列表</p>
            <hr>
            <ul class="list-group">
            {% for comment in comment_list %}
            <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>
                <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>&nbsp;&nbsp;<span></span>
            </p>
        </div>
            {% else %}
                <span ><a href="{% url '_login' %}">登录&nbsp;&nbsp;</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){
                // 切割  获取第一个
     对应的索引
                var indexN = conTent.indexOf('
    ') + 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 + '
    ';
            $('#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',{
                             '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())
    
  • 相关阅读:
    关于MOTO E2
    Visual Studio 2008 SDK 1.1 Beta 发布
    开始了新的工作
    C# 2.0 的"语法多义性"
    最近画的框架草图
    SQL数据库向ORACLE迁移注意事项
    C#中很多新的特性,提供了更好的“即兴编程”的能力。
    看了点lua相关的东西,有点想法不知能否实现?先记下来慢慢思考。
    读《企业应用架框模式》
    porting cinvoke1.0 to E2 (armlinux)
  • 原文地址:https://www.cnblogs.com/kai-/p/12235600.html
Copyright © 2020-2023  润新知