zoukankan      html  css  js  c++  java
  • Django ---- blog项目学习所得

    一、登录功能

      1、采用ajax 提交form表单的方式

      2、后台生成随机验证码,登录时提交验证码

      3、用PLI库生成随机验证码,置于session中,登录时与前台提交的code进行upeer()的验证  

        <div class="col-lg-6">
             <img height="35" width="250" src="/get_code/" alt="">
         </div>

    二、首页

    1、index.html分别采用头和container的布局

    <div class="my_head">
    <div class="container-fluid">
        <div class="row">
            <div class="col-md-3">
                    <div class="panel panel-danger">
                    <div class="panel-heading">广告信息</div>
                    <div class="panel-body">
                        重金求子
                    </div>
                </div>
            <div class="col-md-6">
            <div class="col-md-3">        

    2、head处理时,如果已经登录,则显示username

         {% if request.user.is_authenticated %}
                            <li><a href="/login/">{{ request.user.username }}</a></li>
                            <li><a href="#"></a></li>
                            <li class="dropdown">
                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
                                   aria-haspopup="true" aria-expanded="false">更多信息 <span class="caret"></span></a>
                                <ul class="dropdown-menu">
                                    <li><a href="#">修改密码</a></li>
                                    <li><a href="#">修改头像</a></li>
                                    <li><a href="/logout/">退出登录</a></li>
                                </ul>
                            </li>
                        {% else %}
                            <li><a href="/login/">登录</a></li>
                            <li><a href="/register/">注册</a></li>
                        {% endif %}

    3、class="media-left"将div分别放于同一行左右侧

    class="glyphicon glyphicon-comment"图标的话可以采用bootcss的、也可以用static里的font-awesome的拿来即用
      <div class="col-md-6">
                {% for articles in articles_list %}
                    <p><h4><a href="">{{ articles.title }}</a></h4></p>
                    <div class="media-left"><img height="60" width="60"
                                                 src="/media/{{ articles.user.avatar }}"
                                                 alt=""></div>
                    <div class="media-right">
                        {{ articles.desc }}
                    </div>
                    <div style="margin-top: 10px">
                    <span><a href="">{{ articles.user.username }}</a></span>&nbsp
                    <span>发布于:{{ articles.create_time|date:'Y-m-d' }}</span>&nbsp
                    <span class="glyphicon glyphicon-comment">评论数({{ articles.comment_count}})</span>&nbsp
                    <span class="glyphicon glyphicon-thumbs-up">点赞数({{ articles.up_count}})</span>&nbsp
    
                    </div>
                    <hr>
                {% endfor %}
            </div>
    def index(request):
        articles_list = models.Article.objects.all()
        return render(request, 'index.html', locals())

    三、注册的头像设计

    1、为了使得点击字体或者图片都能发生jquery的change事件,label标签的for属性和input的id属性一样即可

     <div class="form-group">
    
                        <label for="my_file">头像
                            <img height="60px" width="60px" src="/static/img/default.png" id="id_img">
                        </label>
                        <input type="file" id="my_file">
                    </div>

    2、读取图片内容到img标签里

     $("#my_file").change(function () {
            alert(123)
            //$("#my_file")[0].files[0]  取出文件
            var obj=$("#my_file")[0].files[0]
            //生成一个文件阅读器
            var read=new FileReader()
            //把文件读到我的对象里
            read.readAsDataURL(obj)
            read.onload=function(){
                $("#id_img").attr('src',read.result)
            }
    
            {#$("#id_img").attr('aa','bb')#}
    
        })

    3、同时需要将input文件的字去掉

     <style>
            #my_file {
                display: none;
            }
        </style>

    4、form表单有两个功能,一个是校验字段,一个是生成input标签

       {% for foo in form_obj %}
                        <div class="form-group">
                            <label for="">{{ foo.label }}</label>
                            {{ foo }} <span class="errors"></span>
                        </div>
       {% endfor %}

    5、1和2的方式会在form里面直接提交form表单,此处想用ajax的方式提交,采用3的方法

    {#             1、   <input type="submit">#}
    {#             2、   <button></button>#}
                   3、 <input type="button" id="id_button" class="btn btn-success" value="注册">

    四、form组件局部钩子和全局钩子使用

    1、在view.py里创建forms类,有全局和局部钩子

    from django import forms
    from django.forms import widgets
    
    
    class RegForms(forms.Form):
        name = forms.CharField(max_length=20, min_length=2, label='用户名',
                               widget=widgets.TextInput(attrs={'class': 'form-control'}),
                               error_messages={'max_length': '太长了', 'min_length': '太短了'}
                               )
        pwd = forms.CharField(max_length=20, min_length=2, label='密码',
                              widget=widgets.PasswordInput(attrs={'class': 'form-control'}),
                              error_messages={'max_length': '太长了', 'min_length': '太短了'}
                              )
        re_pwd = forms.CharField(max_length=20, min_length=2, label='确认密码',
                                 widget=widgets.PasswordInput(attrs={'class': 'form-control'}),
                                 error_messages={'max_length': '太长了', 'min_length': '太短了'}
                                 )
        email = forms.EmailField(label='邮箱',
                                 widget=widgets.EmailInput(attrs={'class': 'form-control'}),
                                 )
    
        def clean_name(self):
            name = self.cleaned_data.get('name')
            user = models.UserInfo.objects.filter(username=name).first()
            if user:
                raise ValidationError('用户已经存在')
            else:
                return name
    
        def clean(self):
            pwd = self.cleaned_data.get('pwd')
            re_pwd = self.cleaned_data.get('re_pwd')
            if pwd == re_pwd:
                return self.cleaned_data
            else:
                raise ValidationError('两次密码不一致')

    2、form校验了request.post之后存在两种数据:

     # form_obj.cleaned_data
     # form_obj.errors
    def register(request):
        form_obj = RegForms()
        back_msg = {}
        if request.is_ajax():
            name = request.POST.get('name')
            pwd = request.POST.get('pwd')
            re_pwd = request.POST.get('re_pwd')
            email = request.POST.get('email')
            myfile = request.FILES.get('myfile')
            print(myfile)
            print(re_pwd)
            form_obj = RegForms(request.POST)
            if form_obj.is_valid():
                # form_obj.cleaned_data
                # form_obj.errors
                if myfile:
                    user = models.UserInfo.objects.create_user(username=name, password=pwd, email=email, avatar=myfile)
                else:
                    user = models.UserInfo.objects.create_user(username=name, password=pwd, email=email)
                back_msg['user'] = name
                back_msg['msg'] = '注册成功'
            else:
                back_msg['msg'] = form_obj.errors
                print(form_obj.errors)
                print(type(form_obj.errors))
            return JsonResponse(back_msg)
    
        return render(request, 'register.html', {'form_obj': form_obj})

    3、ajax提交文件的话,用formdata

      1、each函数,循环

      2、if(index=="__all__")判断是否为全局钩子,

      3、加class=has-error的属性,即被错误提示选中

      4、setTimeout(function ()  定时函数执行某件事

      $("#id_button").click(function () {
            var formdata=new FormData()
            var tt=$("#my_form").serializeArray()
            $.each(tt,function (index,value) {
                //console.log(value.name)
                //console.log(value.value)
                formdata.append(value.name,value.value)
            })
            formdata.append('myfile',$("#my_file")[0].files[0])
           // console.log(tt)
    
            $.ajax({
                url:'',
                type:'post',
                processData:false,
                contentType:false,
                data:formdata,
                success:function (data) {
                    //console.log(data)
                    if(data.user){
                        location.href='/login/'
                    }else{
                        $.each(data.msg,function (index,value) {
                            console.log(index)
                            console.log(value)
                            if(index=="__all__"){
                                $("#id_re_pwd").next().text(value[0])
                            }
                            $("#id_"+index).next().text(value[0]).parent().addClass('has-error')
                            //$("#id_"+index).next().parent().addClass('has-error')
                        })
                        setTimeout(function () {
                            $(".form-group").removeClass('has-error')
                            $('span').text("")
                        },1000)
                    }
    
                }
    
            })
    
        })

    五、注册

    1、var tt=$("#my_form").serializeArray()  自动的将my_form里的name和value打包起来

    2、传文件的话,必须

           processData:false,
                contentType:false,否则会以url-encoded的方法传送了
     $("#id_button").click(function () {
            var formdata=new FormData()
            var tt=$("#my_form").serializeArray()
            $.each(tt,function (index,value) {
                //console.log(value.name)
                //console.log(value.value)
                formdata.append(value.name,value.value)
            })
            formdata.append('myfile',$("#my_file")[0].files[0])
           // console.log(tt)
    
            $.ajax({
                url:'',
                type:'post',
                processData:false,
                contentType:false,
                data:formdata,
                success:function (data) {
                    //console.log(data)
                    if(data.user){
                        location.href='/login/'
                    }else{
                        $.each(data.msg,function (index,value) {
                            console.log(index)
                            console.log(value)
                            if(index=="__all__"){
                                $("#id_re_pwd").next().text(value[0])
                            }
                            $("#id_"+index).next().text(value[0]).parent().addClass('has-error')
                            //$("#id_"+index).next().parent().addClass('has-error')
                        })
                        setTimeout(function () {
                            $(".form-group").removeClass('has-error')
                            $('span').text("")
                        },1000)
                    }
    
                }
    
            })
    
        })

    4、create生成数据时,avatar=myfile,即直接传入文件对象即可

    def register(request):
        form_obj = RegForms()
        back_msg = {}
        if request.is_ajax():
            name = request.POST.get('name')
            pwd = request.POST.get('pwd')
            re_pwd = request.POST.get('re_pwd')
            email = request.POST.get('email')
            myfile = request.FILES.get('myfile')
            print(myfile)
            print(re_pwd)
            form_obj = RegForms(request.POST)
            if form_obj.is_valid():
                # form_obj.cleaned_data
                # form_obj.errors
                if myfile:
                    user = models.UserInfo.objects.create_user(username=name, password=pwd, email=email, avatar=myfile)
                else:
                    user = models.UserInfo.objects.create_user(username=name, password=pwd, email=email)
                back_msg['user'] = name
                back_msg['msg'] = '注册成功'
            else:
                back_msg['msg'] = form_obj.errors
                print(form_obj.errors)
                print(type(form_obj.errors))
            return JsonResponse(back_msg)
    
        return render(request, 'register.html', {'form_obj': form_obj})

    5、models里加入此句的意思 upload_to='avatars/', default="/avatars/default.png",上传到MEDIA_ROOT + avatars路径,前面不能再加media,否则路径多了一层media的路径

    settings.py
    
    # 设置用户上传头像的根路径
    MEDIA_ROOT=os.path.join(BASE_DIR,'media')

    6、默认图片在media/avatars/default.png

    class UserInfo(AbstractUser):
        """
        用户信息
        """
        nid = models.AutoField(primary_key=True)
        telephone = models.CharField(max_length=11, null=True, unique=True)
        avatar = models.FileField(upload_to='avatars/', default="/avatars/default.png")
        # auto_now_add=True
        '''
        auto_now_add
        配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
        auto_now
        配置上auto_now=True,每次更新数据记录的时候会更新该字段。
        '''
        create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
    
        blog = models.OneToOneField(to='Blog', to_field='nid', null=True, on_delete=models.CASCADE)
    
        def __str__(self):
            return self.username

    六、media文件夹的配置

    1、static文件夹下的内容外部人都可以通过url访问到

    STATIC_URL = '/static/'
    STATICFILES_DIRS=[
        os.path.join(BASE_DIR,'static'),
    ]

    # 设置用户上传头像的根路径
    MEDIA_ROOT=os.path.join(BASE_DIR,'media')

    2、用户上传的图片放在static不合适,需要一个专门的media的文件夹存储,直接127.0.0.1/media/avatars/default.png就可以访问到了

    from django.views.static import serve
    
      #
    def serve(request, path, document_root=None, show_indexes=False):
    url(r'^media/(?P<path>.*)', serve,{'document_root':settings.MEDIA_ROOT}),

    慎用此方法,如不小心,代码会暴露被外部访问到

    3、如此,即可在首页index.html里将路径改为   src="/media/{{ articles.user.avatar }},即可访问

      {% for articles in articles_list %}
                    <p><h4><a href="">{{ articles.title }}</a></h4></p>
                    <div class="media-left"><img height="60" width="60"
                                                 src="/media/{{ articles.user.avatar }}"
                                                 alt=""></div>
                    <div class="media-right">
                        {{ articles.desc }}
                    </div>
                    <div style="margin-top: 10px">
                    <span><a href="">{{ articles.user.username }}</a></span>&nbsp
                    <span>发布于:{{ articles.create_time|date:'Y-m-d' }}</span>&nbsp
                    <span class="glyphicon glyphicon-comment">评论数({{ articles.comment_count}})</span>&nbsp
                    <span class="glyphicon glyphicon-thumbs-up">点赞数({{ articles.up_count}})</span>&nbsp
    
                    </div>
    
                    <hr>
                {% endfor %}

    七、分组查询

    1、views.py

     url(r'^(?P<username>w+)/(?P<condition>tag|category|time|w+)/(?P<search>.*)', views.homesite),
     url(r'^(?P<username>w+)/', views.homesite),

    2、后台数据分析

     2.1 blog = user.blog  此为查找时的对象
     2.2 分组查询1 group by谁,用谁做基表
        #         2 filter在前,表示查询  ,filter在后,表示过滤,having
        #          3 values在前,表示group by  在后,取字段
    def homesite(request, username, **kwargs):
        print(kwargs)
        username=username
        user = models.UserInfo.objects.filter(username=username).first()
        if not user:
            return render(request, 'errors.html')
        blog = user.blog
        article_list = models.Article.objects.filter(user=user)
        if kwargs:
            condition=kwargs.get('condition')
            search=kwargs.get('search')
            if condition=='tag':
                article_list = models.Article.objects.filter(user=user).filter(tags__title=search)
            elif condition=='category':
                article_list = models.Article.objects.filter(user=user).filter(category__title=search)
            elif condition=='time':
                ll=search.split('-')
                article_list=models.Article.objects.filter(user=user).filter(create_time__year=ll[0],create_time__month=ll[1])
            else:
                return render(request, 'errors.html')
    
        # print(article_list)
        from django.db.models import Count
        # 查询每个标签下的文章数(分组查询)
        # 分组查询1 group by谁,用谁做基表
        #         2 filter在前,表示查询  ,filter在后,表示过滤,having
        #          3 values在前,表示group by  在后,取字段
        # 查询当前站点下每个标签下的文章数(分组查询)
        tag_count = models.Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list('title', 'c')
        # 查询当前站点下每个分类下的文章数
        category_count = models.Category.objects.filter(blog=blog).annotate(c=Count('article__nid')).values_list('title',
                                                                                                                 'c')
        from django.db.models.functions import TruncMonth
        # 查询当前站点每个月份下的文章数
        # time_count=models.Article.objects.annotate(y_m=TruncMonth('create_time'))
        # for i in time_count:
        #     print(i.title)
        #     print(i.y_m)
        time_count=models.Article.objects.filter(user=user).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate(
            c=Count('y_m')).values_list('y_m', 'c')
    
        print(tag_count)
        print(category_count)
        print(time_count)
    
        # 统计每个出版社书籍个数
        # Publish.object.all().annotate(Count('book__title'))
    
        return render(request, 'homesite.html', locals())

    2、homesite.html

       1、用admin添加数据

      2、{{ foo.0 }}({{ foo.1 }})       -------------  xxxxxxxxxxx(xx)

    <div class="container-fluid" style="margin-top: 10px">
        <div class="row">
            <div class="col-md-3">
    
                <div class="panel panel-danger">
                    <div class="panel-heading">我的标签</div>
                    <div class="panel-body">
                        {% for foo in tag_count %}
                            <p><a href="/{{ username }}/tag/{{ foo.0 }}">{{ foo.0 }}({{ foo.1 }})</a></p>
    
                        {% endfor %}
    
                    </div>
                </div>
                <div class="panel panel-info">
                    <div class="panel-heading">随笔分类</div>
                    <div class="panel-body">
                        {% for foo in category_count %}
                            <p><a href="/{{ username }}/category/{{ foo.0 }}">{{ foo.0 }}({{ foo.1 }})</a></p>
    
                        {% endfor %}
                    </div>
                </div>
                <div class="panel panel-danger">
                    <div class="panel-heading">随笔档案</div>
                    <div class="panel-body">
                        {% for foo in time_count %}
                            <p><a href="/{{ username }}/time/{{ foo.0|date:'Y-m' }}">{{ foo.0|date:'Y-m' }}({{ foo.1 }})</a></p>
    
                        {% endfor %}
    
                    </div>
                </div>
    
    
            </div>

    八、个人站点路由设计

    将此条至于最下方的目的,前面都匹配不了的时候,到此可以匹配成功进入视图函数,有则看,无资源则显示404

     url(r'^(?P<username>w+)/', views.homesite),

    九、个人站点过滤

    1、总的设计

    def homesite(request, username, **kwargs):
        print(kwargs)
        username=username
        user = models.UserInfo.objects.filter(username=username).first()
        if not user:
            return render(request, 'errors.html')
        blog = user.blog
        article_list = models.Article.objects.filter(user=user)
        if kwargs:
            condition=kwargs.get('condition')
            search=kwargs.get('search')
            if condition=='tag':
                article_list = models.Article.objects.filter(user=user).filter(tags__title=search)
            elif condition=='category':
                article_list = models.Article.objects.filter(user=user).filter(category__title=search)
            elif condition=='time':
                ll=search.split('-')
                article_list=models.Article.objects.filter(user=user).filter(create_time__year=ll[0],create_time__month=ll[1])
            else:
                return render(request, 'errors.html')
    
        # print(article_list)
        from django.db.models import Count
        # 查询每个标签下的文章数(分组查询)
        # 分组查询1 group by谁,用谁做基表
        #         2 filter在前,表示查询  ,filter在后,表示过滤,having
        #          3 values在前,表示group by  在后,取字段
        # 查询当前站点下每个标签下的文章数(分组查询)
        tag_count = models.Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list('title', 'c')
        # 查询当前站点下每个分类下的文章数
        category_count = models.Category.objects.filter(blog=blog).annotate(c=Count('article__nid')).values_list('title',
                                                                                                                 'c')
        from django.db.models.functions import TruncMonth
        # 查询当前站点每个月份下的文章数
        # time_count=models.Article.objects.annotate(y_m=TruncMonth('create_time'))
        # for i in time_count:
        #     print(i.title)
        #     print(i.y_m)
        time_count=models.Article.objects.filter(user=user).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate(
            c=Count('y_m')).values_list('y_m', 'c')
    
        print(tag_count)
        print(category_count)
        print(time_count)
    
        # 统计每个出版社书籍个数
        # Publish.object.all().annotate(Count('book__title'))
    
        return render(request, 'homesite.html', locals())

    2、按时间分类,用TruncMonth截断年月,会在原表的基础上在加一条时间为年月的数据,再以此分类(group by谁,用谁做基表)

      from django.db.models.functions import TruncMonth
        # 查询当前站点每个月份下的文章数
        # time_count=models.Article.objects.annotate(y_m=TruncMonth('create_time'))
        # for i in time_count:
        #     print(i.title)
        #     print(i.y_m)
        time_count=models.Article.objects.filter(user=user).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate(
            c=Count('y_m')).values_list('y_m', 'c')

    3、分类取得数据,页面展示article_list数据,只会显示符合的文章show

     if kwargs:
            condition=kwargs.get('condition')
            search=kwargs.get('search')
            if condition=='tag':
                article_list = models.Article.objects.filter(user=user).filter(tags__title=search)
            elif condition=='category':
                article_list = models.Article.objects.filter(user=user).filter(category__title=search)
            elif condition=='time':
                ll=search.split('-')
                article_list=models.Article.objects.filter(user=user).filter(create_time__year=ll[0],create_time__month=ll[1])
            else:
                return render(request, 'errors.html')

    4、相应的前台html是如下,加入a连接和url的处理,可以看得相应的数据

       <div class="panel panel-danger">
                    <div class="panel-heading">我的标签</div>
                    <div class="panel-body">
                        {% for foo in tag_count %}
                            <p><a href="/{{ username }}/tag/{{ foo.0 }}">{{ foo.0 }}({{ foo.1 }})</a></p>
    
                        {% endfor %}
    
                    </div>
                </div>
                <div class="panel panel-info">
                    <div class="panel-heading">随笔分类</div>
                    <div class="panel-body">
                        {% for foo in category_count %}
                            <p><a href="/{{ username }}/category/{{ foo.0 }}">{{ foo.0 }}({{ foo.1 }})</a></p>
    
                        {% endfor %}
                    </div>
                </div>
                <div class="panel panel-danger">
                    <div class="panel-heading">随笔档案</div>
                    <div class="panel-body">
                        {% for foo in time_count %}
                            <p><a href="/{{ username }}/time/{{ foo.0|date:'Y-m' }}">{{ foo.0|date:'Y-m' }}({{ foo.1 }})</a></p>
    
                        {% endfor %}
    
                    </div>
                </div>

    十、后台管理页面

     1、布局上还是采用了上为header,左三右九的布局

    <style>

    .home_head {
    height: 60px;
    background: #1b6d85;
    }
    </style>
    <div class="home_head"></div>
    <div class="container-fluid" style="margin-top: 10px">
        <div class="row">
            <div class="col-md-3"。。。。。。。。。。。。
            <div class="col-md-9"
        </div>
    </div>
    </body>

    2、中间采用bootstrap的标签页的方式,达到点击之后会用切换content和header标签的效果

         <div class="col-md-9">
                <div>
                    <!-- Nav tabs -->
                    <ul class="nav nav-tabs" role="tablist">
                        <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab"
                                                                  data-toggle="tab">文章</a></li>
                        <li role="presentation"><a href="#profile" aria-controls="profile" role="tab"
                                                   data-toggle="tab">随笔</a></li>
                        <li role="presentation"><a href="#messages" aria-controls="messages" role="tab"
                                                   data-toggle="tab">交友</a></li>
                        <li role="presentation"><a href="#settings" aria-controls="settings" role="tab"
                                                   data-toggle="tab">园子</a></li>
                    </ul>
    
                    <!-- Tab panes -->
                    <div class="tab-content">
                        <div role="tabpanel" class="tab-pane active" id="home">
                            {% block back_content %}
                            
                            {% endblock %}                           
                        </div>
                        <div role="tabpanel" class="tab-pane" id="profile">随笔</div>
                        <div role="tabpanel" class="tab-pane" id="messages">...</div>
                        <div role="tabpanel" class="tab-pane" id="settings">...</div>
                    </div>
                </div>

    3、需要用户登录了之后才能进入后台用户管理界面,故login_required装饰器判断是否登录,未登录则直接跳转到login_url='/login/'界面

    from django.contrib.auth.decorators import login_required
    @login_required(login_url='/login/')
    def back_home(request):
    
        article_list=models.Article.objects.filter(user=request.user)
    
        return render(request,'back/back_home.html',locals())

    4、左侧采用collapse方式,点击会展开

        <div class="col-md-3">
                <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
                    <div class="panel panel-default">
                        <div class="panel-heading" role="tab" id="headingOne">
                            <h4 class="panel-title">
                                <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"
                                   aria-expanded="true" aria-controls="collapseOne">
                                    操作
                                </a>
                            </h4>
                        </div>
                        <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
                             aria-labelledby="headingOne">
                            <div class="panel-body">
                                <a href="/addarticle/">添加文章</a>
                            </div>
                            <div class="panel-body">
                                <a href="">添加随笔</a>
                            </div>
                        </div>
                    </div>
                </div>

    5、用block 导入基准html

    {% extends 'back/back_base.html' %}
    
    {% block back_content %}
    
        <table class="table table-striped table-hover">
            <thead>
            <tr>
                <th>文章标题</th>
                <th>评论数</th>
                <th>点赞数</th>
                <th>操作</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            {% for article in article_list %}
                <tr>
                    <td>{{ article.title }}</td>
                    <td>{{ article.comment_count }}</td>
                    <td>{{ article.up_count }}</td>
                    <td><a href="">修改</a></td>
                    <td><a href="">删除</a></td>
                </tr>
            {% endfor %}
            </tbody>
        </table>
    {% endblock %}

    十一、后台管理保存文章

    1、在html页面使用了Kindedit编辑器,只需将库包至于static目录下即可,需保证

     <textarea name="content" id="editor_id" cols="50" rows="10"></textarea>的id与kindedit一样,即可被调用
    {% extends 'back/back_base.html' %}
    
    {% block back_content %}
        <div>
            <form action="" method="post">
                {% csrf_token %}
                <div class="text-info"><h4>添加文章</h4></div>
                <p>标题</p>
                <p><input type="text" class="form-control" name="title"></p>
                <p>内容(Kindedit编辑器,不支持拖放/粘贴上传图片)</p>
                <p>
                    <textarea name="content" id="editor_id" cols="50" rows="10"></textarea>
                </p>
                <p>
                    <button class="btn btn-primary">提交</button>
                </p>
    
            </form>
        </div>
    
        <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
        <script>
            KindEditor.ready(function (K) {
                window.editor = K.create('#editor_id',
    
                    {
                         '100%',
                        height: '600px',
                        resizeType: 0,
                    }
                );
            });
        </script>
    {% endblock %}

    2、上传文字需要用bs4清洗,html显示的时候只需要content内容,但是详情页面的时候需要html样式等,故存储str(content)

    # 安装 pip3 install lxml
    # pip3 install BeautifulSoup4
    @login_required(login_url='/login/')
    def addarticle(request):
    if request.method=='POST':
    title=request.POST.get('title')
    content=request.POST.get('content'
    )
    # content='<p>sddd</p><a>oooo</a><script>alert(123)</script>'
    #生成一个soup的对象,传两个参数,第一个是要解析的html,第二个是使用的解析器
    soup=BeautifulSoup(content,'html.parser')
    print(str(soup))
    # 拿到html内的文本内容
    desc=soup.text[0:150]+'...'
    # 查找html内所有的标签,放到一个列表里
    ll=soup.find_all()
    # print(ll) #[obj,obj...]
    for tag in ll:
    print(tag.name)
    print(type(tag))
    # tag.name 标签的名称
    if tag.name=='script':
    # 从整个html文档里删掉当前标签
    tag.decompose()
    # print(ll)
    # aa=soup.find(name='script')
    # print(aa)
    # print(str(soup)) #<p>sddd</p><a>oooo</a><script>alert(123)</script>
            models.Article.objects.create(title=title,content=str(soup),desc=desc,user=request.user)
    return redirect('/backhome/')

    return render(request,'back/article_add.html')

    3、在back_base界面, <a href="/addarticle/">添加文章</a>关联上述所诉逻辑

      <div class="col-md-3">
                <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
                    <div class="panel panel-default">
                        <div class="panel-heading" role="tab" id="headingOne">
                            <h4 class="panel-title">
                                <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"
                                   aria-expanded="true" aria-controls="collapseOne">
                                    操作
                                </a>
                            </h4>
                        </div>
                        <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
                             aria-labelledby="headingOne">
                            <div class="panel-body">
                                <a href="/addarticle/">添加文章</a>
                            </div>
                            <div class="panel-body">
                                <a href="">添加随笔</a>
                            </div>
                        </div>
                    </div>
                </div>

    十二、点赞、点踩

    1、articledetail页面的设计,继承自base.html,评论html直接引用自博客园的html和css样式

    {% extends 'base.html' %}
    
    {% block detail %}
    <style>
            #div_digg {
                float: right;
                margin-bottom: 10px;
                margin-right: 30px;
                font-size: 12px;
                 125px;
                text-align: center;
                margin-top: 10px;
            }
    
            .diggit {
                float: left;
                 46px;
                height: 52px;
                background: url(/static/img/upup.gif) no-repeat;
                text-align: center;
                cursor: pointer;
                margin-top: 2px;
                padding-top: 5px;
            }
    
            .buryit {
                float: right;
                margin-left: 20px;
                 46px;
                height: 52px;
                background: url(/static/img/downdown.gif) no-repeat;
                text-align: center;
                cursor: pointer;
                margin-top: 2px;
                padding-top: 5px;
            }
    
            .clear {
                clear: both;
            }
    
    
        </style>
        <div>
            <p><h4 class="text-center">{{ article.title }}</h4></p>
            <p>{{ article.content|safe }}</p>
    
            <div class="clearfix">
    
                <div id="div_digg">
                    <div class="diggit action">
                        <span class="diggnum" id="digg_count">{{ article.up_count }}</span>
                    </div>
                    <div class="buryit action">
                        <span class="burynum" id="bury_count">{{ article.down_count }}</span>
                    </div>
                    <div class="clear"></div>
                    <div class="diggword" id="digg_tips" style="color: red;"></div>
                </div>
    
            </div>

    2、is_up = $(this).hasClass('diggit')结果要么是true或者false,

    var obj = $(this).children('span'),当点击时要么是点up,down的,this代指diggit、buryit事件,这句话的意思是obj为其子节点为span的对象 
      <script>
            var par_id = '';
            $(".action").click(function () {
                var is_up = $(this).hasClass('diggit')
                //alert(is_up)
                var obj = $(this).children('span')
    
                $.ajax({
                    url: '/diggit/',
                    type: 'post',
                    //谁对那篇文章点赞或点踩
                    data: {
                        'csrfmiddlewaretoken': '{{ csrf_token }}',
                        'article_id':{{ article.pk }},
                        'is_up': is_up
                    },
                    success: function (data) {
                        console.log(data)
                        $(".diggword").text(data.msg)
                        if (data.status == 1) {
                            obj.text(Number(obj.text()) + 1)
                        }
                    }
                })
            })

    3、articledetail页面展示,因为展示方法重复了,可以把下面重用的包装成共用函数

      url(r'^(?P<username>w+)/article/(?P<pk>d+)', views.article_detail),
      url(r'^diggit/', views.diggit),
    def article_detail(request,username,pk):
        # 正常情况下应该有一堆安全性校验
        #
        user=models.UserInfo.objects.filter(username=username).first()
        blog=user.blog
        article=models.Article.objects.filter(pk=pk).first()
        # 获取当前文章下所有评论
        comment_list=models.Comment.objects.filter(article_id=pk)
    
        from django.db.models import Count
        # 查询每个标签下的文章数(分组查询)
        # 分组查询1 group by谁,用谁做基表
        #         2 filter在前,表示查询  ,filter在后,表示过滤,having
        #          3 values在前,表示group by  在后,取字段
        # 查询当前站点下每个标签下的文章数(分组查询)
        # tag_count = models.Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list('title', 'c')
        tag_count = models.Tag.objects.all().values('pk').filter(blog=blog).annotate(c=Count("article__title")).values_list(
            'title', 'c')
        # 查询当前站点下每个分类下的文章数
        category_count = models.Category.objects.filter(blog=blog).annotate(c=Count('article__nid')).values_list('title',
                                                                                                                 'c')
        from django.db.models.functions import TruncMonth
        # 查询当前站点每个月份下的文章数
        # time_count=models.Article.objects.annotate(y_m=TruncMonth('create_time'))
        # for i in time_count:
        #     print(i.title)
        #     print(i.y_m)
        time_count = models.Article.objects.filter(user=user).annotate(y_m=TruncMonth('create_time')).values(
            'y_m').annotate(
            c=Count('y_m')).values_list('y_m', 'c')
    
        return render(request,'article_detail.html',locals())

    4、注释的时候将logic写清楚

      post传过来的都是str类型的,需要转化成python认识的bool类型,通过json反序列化,is_up=json.loads(is_up)

    import json
    from django.db.models import F
    def diggit(request):
        back_msg={'status':None,'msg':None}
        if request.user.is_authenticated:
            #     先查询是否已经点过
            #    如果没有点过,去点赞表存数据
            #    去文章表修改点赞数据
            article_id=request.POST.get('article_id')
            # 注意:
            is_up=request.POST.get('is_up')
            print(type(is_up))
            is_up=json.loads(is_up)
            ret=models.ArticleUpDown.objects.filter(user=request.user,article_id=article_id)
            if not ret:
                models.ArticleUpDown.objects.create(user=request.user,article_id=article_id,is_up=is_up)
                if is_up:
                    models.Article.objects.filter(pk=article_id).update(up_count=F('up_count')+1)
                    back_msg['status']=1
                    back_msg['msg'] = '点赞成功'
                else:
                    models.Article.objects.filter(pk=article_id).update(down_count=F('down_count') + 1)
                    back_msg['status'] = 1
                    back_msg['msg'] = '点踩成功'
            else:
                back_msg['status'] = 0
                back_msg['msg'] = '您已经点过了'
        else:
            back_msg['status'] = 0
            back_msg['msg'] = '您没有登录'
        return JsonResponse(back_msg)

    十三、评论(render显示和ajax显示)

    1、提交评论

        $(".btn_submit").click(function () {
                var content = $("#comment_content").val()
                if (par_id) {
                    var num = content.indexOf('
    ') + 1
                    content = content.slice(num)
                    alert(content)
                }
                $.ajax({
                    url: '/comment/',
                    type: 'post',
                    data: {
                        'csrfmiddlewaretoken': '{{ csrf_token }}',
                        'article_id':{{ article.pk }},
                        'comment': content,
                        'par_id': par_id
                    },
                    success: function (data) {
                        $("#comment_content").val("")
                        console.log(data)
                

    为了确保数据写入,创建事务with transaction.atomic():,失败则回滚

    def comment(request):
        back_msg={'status':False,'msg':None}
        if request.user.is_authenticated:
            #    评论表添加数据
            #    文章表,修改评论个数
            article_id=request.POST.get('article_id')
            comment=request.POST.get('comment')
            par_id=request.POST.get('par_id')
            # 事务
            with transaction.atomic():
                ret=models.Comment.objects.create(user=request.user,article_id=article_id,content=comment,parent_comment_id=par_id)
                models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count')+1)
               
                back_msg['status']=True
                back_msg['user_name']=ret.user.username
                back_msg['time']=ret.create_time.strftime('%Y-%m-%d')
                back_msg['content']=ret.content
                back_msg['msg']='评论成功'
        else:
            back_msg['status'] = False
            back_msg['msg'] = '您没有登录'
    
        return JsonResponse(back_msg)

    2、render显示,刷新界面才显示评论更新上传了

        <div>
                <ul class="list-group comment_list">
                    {% for comment in comment_list %}
                        {#            #17楼 2018-02-25 18:47 隔壁古二蛋  #}
                        <li class="list-group-item">
                            <span>#{{ forloop.counter }}楼</span>
                            <span>{{ comment.create_time|date:'Y-m-d' }}</span>
                            <span>{{ comment.user.username }}</span>
                            <span><a class="pull-right my_reply" user="{{ comment.user.username }}"
                                     comment_id="{{ comment.pk }}">回复</a></span><p>{{ comment.content }}</p>
    
                        </li>
                    {% endfor %}
                </ul>
            </div>

    3、提交根评论及ajax显示,及render显示,

     {#   评论 #}
            <div>
                <p>发表评论</p>
                <p>
                    昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="刘清政">
                </p>
                <p>评论内容</p>
                <p><textarea name="" id="comment_content" cols="60" rows="10"></textarea></p>
                <button class="btn btn-primary btn_submit">回复</button>
            </div>

    es6字符串替换,<span>${ time }</span>,需要什么比如time,根据后台去取

                  if (data.status) {
                            var username = data.user_name;
                            var time = data.time;
                            var content = data.content
                            var par_name=data.par_name
                            var par_content=data.par_content
    
                            var ss=''
                            if(par_name){
                                ss = `
                              <li class="list-group-item">
                                <span>${ time }</span>
                                <span>${ username}</span>
                                <div class="well">
                                    <span>@ ${par_name}</span>
                                    <p>${par_content}</p>
                                </div>
                                <p>${ content }</p>
                               </li>
                            `
                            }else{
                               ss = `
                              <li class="list-group-item">
                                <span>${ time }</span>
                                <span>${ username}</span>
                                <p>${ content }</p>
                               </li>
                            `
                            }
                            $(".comment_list").append(ss)
                        }
                    }
                })
            })

    后台

    from django.db import transaction
    def comment(request):
        back_msg={'status':False,'msg':None}
        if request.user.is_authenticated:
            #    评论表添加数据
            #    文章表,修改评论个数
            article_id=request.POST.get('article_id')
            comment=request.POST.get('comment')
            par_id=request.POST.get('par_id')
            # 事物
            with transaction.atomic():
                ret=models.Comment.objects.create(user=request.user,article_id=article_id,content=comment,parent_comment_id=par_id)
                models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count')+1)
                if par_id:
                    back_msg['par_name']=ret.parent_comment.user.username
                    back_msg['par_content']=ret.parent_comment.content
                back_msg['status']=True
                back_msg['user_name']=ret.user.username
                back_msg['time']=ret.create_time.strftime('%Y-%m-%d')
                back_msg['content']=ret.content
                back_msg['msg']='评论成功'
        else:
            back_msg['status'] = False
            back_msg['msg'] = '您没有登录'
    
        return JsonResponse(back_msg)

    4、子评论,点击回复触发事件,同时将要用的父评论ID等信息直接放置于attr属性上,方便调用

           <ul class="list-group comment_list">
                    {% for comment in comment_list %}
                        {#            #17楼 2018-02-25 18:47 隔壁古二蛋  #}
                        <li class="list-group-item">
                            <span>#{{ forloop.counter }}楼</span>
                            <span>{{ comment.create_time|date:'Y-m-d' }}</span>
                            <span>{{ comment.user.username }}</span>
                            <span><a class="pull-right my_reply" user="{{ comment.user.username }}"
                                     comment_id="{{ comment.pk }}">回复</a></span>
                            {% if comment.parent_comment %}
                                <div class="well">
                                    <span>@{{ comment.parent_comment.user.username }}</span>
                                    <p>{{ comment.parent_comment.content }}</p>
                                </div>

    使得点击回复之后直接focus到textarea标签,写响应子评论

    父评论par_id,定义全局,使得后面都可调用 

        $(".my_reply").click(function () {
    
                var name = "@" + $(this).attr('user') + '
    '
                par_id = $(this).attr('comment_id')
                $("#comment_content").focus()
                $("#comment_content").val(name)
            })

    5、判断是否为子评论,若是,则截取第一段内容,将后面的保存在content里,若不是,则不处理

         $(".btn_submit").click(function () {
                var content = $("#comment_content").val()
                if (par_id) {
                    var num = content.indexOf('
    ') + 1
                    content = content.slice(num)   #slice去索引之后的值
                    alert(content)
                }

    判断是否有父评论,有则加@父id+comment

          <ul class="list-group comment_list">
                    {% for comment in comment_list %}
                        {#            #17楼 2018-02-25 18:47 隔壁古二蛋  #}
                        <li class="list-group-item">
                            <span>#{{ forloop.counter }}楼</span>
                            <span>{{ comment.create_time|date:'Y-m-d' }}</span>
                            <span>{{ comment.user.username }}</span>
                            <span><a class="pull-right my_reply" user="{{ comment.user.username }}"
                                     comment_id="{{ comment.pk }}">回复</a></span>
                            {% if comment.parent_comment %}
                                <div class="well">
                                    <span>@{{ comment.parent_comment.user.username }}</span>
                                    <p>{{ comment.parent_comment.content }}</p>
                                </div>
    
                            {% endif %}
                            <p>{{ comment.content }}</p>

    同时再做个ajax显示功能,及一直带有render的显示功能,需要什么参数,需要什么参数,去后台返回

        $(".btn_submit").click(function () {
                var content = $("#comment_content").val()
                if (par_id) {
                    var num = content.indexOf('
    ') + 1
                    content = content.slice(num)
                    alert(content)
                }
                $.ajax({
                    url: '/comment/',
                    type: 'post',
                    data: {
                        'csrfmiddlewaretoken': '{{ csrf_token }}',
                        'article_id':{{ article.pk }},
                        'comment': content,
                        'par_id': par_id
                    },
                    success: function (data) {
                        $("#comment_content").val("")
                        console.log(data)
                        if (data.status) {
                            var username = data.user_name;
                            var time = data.time;
                            var content = data.content
                            var par_name=data.par_name
                            var par_content=data.par_content
    
                            var ss=''
                            if(par_name){
                                ss = `
                              <li class="list-group-item">
                                <span>${ time }</span>
                                <span>${ username}</span>
                                <div class="well">
                                    <span>@ ${par_name}</span>
                                    <p>${par_content}</p>
                                </div>
                                <p>${ content }</p>
                               </li>
                            `
                            }else{
                               ss = `
                              <li class="list-group-item">
                                <span>${ time }</span>
                                <span>${ username}</span>
                                <p>${ content }</p>
                               </li>
                            `
                            }
                            $(".comment_list").append(ss)
    
    
                        }
    
                    }
                })
    
            })

    6、总的,有根评论、子评论的ajax、render显示功能

    {% extends 'base.html' %}
    
    {% block detail %}
        <style>
            #div_digg {
                float: right;
                margin-bottom: 10px;
                margin-right: 30px;
                font-size: 12px;
                 125px;
                text-align: center;
                margin-top: 10px;
            }
    
            .diggit {
                float: left;
                 46px;
                height: 52px;
                background: url(/static/img/upup.gif) no-repeat;
                text-align: center;
                cursor: pointer;
                margin-top: 2px;
                padding-top: 5px;
            }
    
            .buryit {
                float: right;
                margin-left: 20px;
                 46px;
                height: 52px;
                background: url(/static/img/downdown.gif) no-repeat;
                text-align: center;
                cursor: pointer;
                margin-top: 2px;
                padding-top: 5px;
            }
    
            .clear {
                clear: both;
            }
    
    
        </style>
        <div>
            <p><h4 class="text-center">{{ article.title }}</h4></p>
            <p>{{ article.content|safe }}</p>
    
            <div class="clearfix">
    
                <div id="div_digg">
                    <div class="diggit action">
                        <span class="diggnum" id="digg_count">{{ article.up_count }}</span>
                    </div>
                    <div class="buryit action">
                        <span class="burynum" id="bury_count">{{ article.down_count }}</span>
                    </div>
                    <div class="clear"></div>
                    <div class="diggword" id="digg_tips" style="color: red;"></div>
                </div>
    
            </div>
            {#    评论列表#}
            <div>
                <ul class="list-group comment_list">
                    {% for comment in comment_list %}
                        {#            #17楼 2018-02-25 18:47 隔壁古二蛋  #}
                        <li class="list-group-item">
                            <span>#{{ forloop.counter }}楼</span>
                            <span>{{ comment.create_time|date:'Y-m-d' }}</span>
                            <span>{{ comment.user.username }}</span>
                            <span><a class="pull-right my_reply" user="{{ comment.user.username }}"
                                     comment_id="{{ comment.pk }}">回复</a></span>
                            {% if comment.parent_comment %}
                                <div class="well">
                                    <span>@{{ comment.parent_comment.user.username }}</span>
                                    <p>{{ comment.parent_comment.content }}</p>
                                </div>
    
    
                            {% endif %}
    
                            <p>{{ comment.content }}</p>
    
    
                        </li>
                    {% endfor %}
                </ul>
            </div>
            {#   评论 #}
            <div>
                <p>发表评论</p>
                <p>
                    昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="刘清政">
                </p>
                <p>评论内容</p>
                <p><textarea name="" id="comment_content" cols="60" rows="10"></textarea></p>
                <button class="btn btn-primary btn_submit">回复</button>
            </div>
    
        </div>
    
        <script>
            var par_id = '';
            $(".action").click(function () {
                var is_up = $(this).hasClass('diggit')
                //alert(is_up)
                var obj = $(this).children('span')
    
                $.ajax({
                    url: '/diggit/',
                    type: 'post',
                    //谁对那篇文章点赞或点踩
                    data: {
                        'csrfmiddlewaretoken': '{{ csrf_token }}',
                        'article_id':{{ article.pk }},
                        'is_up': is_up
                    },
                    success: function (data) {
                        console.log(data)
                        $(".diggword").text(data.msg)
                        if (data.status == 1) {
                            obj.text(Number(obj.text()) + 1)
                        }
                    }
                })
            })
            $(".btn_submit").click(function () {
                var content = $("#comment_content").val()
                if (par_id) {
                    var num = content.indexOf('
    ') + 1
                    content = content.slice(num)
                    alert(content)
                }
                $.ajax({
                    url: '/comment/',
                    type: 'post',
                    data: {
                        'csrfmiddlewaretoken': '{{ csrf_token }}',
                        'article_id':{{ article.pk }},
                        'comment': content,
                        'par_id': par_id
                    },
                    success: function (data) {
                        $("#comment_content").val("")
                        console.log(data)
                        if (data.status) {
                            var username = data.user_name;
                            var time = data.time;
                            var content = data.content
                            var par_name=data.par_name
                            var par_content=data.par_content
    
                            var ss=''
                            if(par_name){
                                ss = `
                              <li class="list-group-item">
                                <span>${ time }</span>
                                <span>${ username}</span>
                                <div class="well">
                                    <span>@ ${par_name}</span>
                                    <p>${par_content}</p>
                                </div>
                                <p>${ content }</p>
                               </li>
                            `
                            }else{
                               ss = `
                              <li class="list-group-item">
                                <span>${ time }</span>
                                <span>${ username}</span>
                                <p>${ content }</p>
                               </li>
                            `
                            }
                            $(".comment_list").append(ss)
    
    
                        }
    
                    }
                })
    
            })
    
            $(".my_reply").click(function () {
    
                var name = "@" + $(this).attr('user') + '
    '
                par_id = $(this).attr('comment_id')
                $("#comment_content").focus()
                $("#comment_content").val(name)
            })
        </script>
    {% endblock %}
    from django.db import transaction
    def comment(request):
        back_msg={'status':False,'msg':None}
        if request.user.is_authenticated:
            #    评论表添加数据
            #    文章表,修改评论个数
            article_id=request.POST.get('article_id')
            comment=request.POST.get('comment')
            par_id=request.POST.get('par_id')
            # 事物
            with transaction.atomic():
                ret=models.Comment.objects.create(user=request.user,article_id=article_id,content=comment,parent_comment_id=par_id)
                models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count')+1)
                if par_id:
                    back_msg['par_name']=ret.parent_comment.user.username
                    back_msg['par_content']=ret.parent_comment.content
                back_msg['status']=True
                back_msg['user_name']=ret.user.username
                back_msg['time']=ret.create_time.strftime('%Y-%m-%d')
                back_msg['content']=ret.content
                back_msg['msg']='评论成功'
        else:
            back_msg['status'] = False
            back_msg['msg'] = '您没有登录'
    
        return JsonResponse(back_msg)
  • 相关阅读:
    设计模式总结
    JWT、OAUTH2与SSO资料补充
    dajie项目的坑
    fw-cloud-framework项目配置、启动问题
    Shiro源码分析
    (转)JPA & Restful
    Spring Boot以War包启动
    (转)Spring & SpringMVC学习
    MySQL的数据类型(二)
    MySQL的数据类型(一)
  • 原文地址:https://www.cnblogs.com/di2wu/p/10111881.html
Copyright © 2011-2022 走看看