项目-博客园

1.建表部分

这里使用了抽象类,抽象类是一个自带的数据库,但是有一些信息不完善,可以补充需要的信息制作更完善的数据库和ORM。

谁使用更抽象类,就要继承AbstractUser。抽象类有很多好处,django内部封装了很多方法可以直接操作数据,很方便。

创建的信息为了做到查询效率最好,做了一部分的分表操作,所以在使用中涉及到正查和反查(跨表)(后面再做叙述)。

from django.db import models

# Create your models here.
from django.contrib.auth.models import AbstractUser


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")
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)

    blog = models.OneToOneField(to='Blog', to_field='nid', null=True)

    def __str__(self):
        return self.username


class Blog(models.Model):
    """
    博客信息
    """

    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='个人博客标题', max_length=64)
    site = models.CharField(verbose_name='个人博客后缀', max_length=32, unique=True)
    theme = models.CharField(verbose_name='博客主题', max_length=32)

    def __str__(self):
        return self.title


class Category(models.Model):
    """
    博主个人文章分类表
    """
    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='分类标题', max_length=32)
    blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid')

    def __str__(self):
        return self.title


class Tag(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='标签名称', max_length=32)
    blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid')

    def __str__(self):
        return self.title


class Article(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=50, verbose_name='文章标题')
    desc = models.CharField(max_length=255, verbose_name='文章描述')
    create_time = models.DateTimeField(verbose_name='创建时间')

    comment_count = models.IntegerField(default=0)
    up_count = models.IntegerField(default=0)
    down_count = models.IntegerField(default=0)

    category = models.ForeignKey(to='Category', to_field='nid', null=True)
    user = models.ForeignKey(verbose_name='作者', to='UserInfo', to_field='nid')
    tags = models.ManyToManyField(
        to="Tag",
        through='Article2Tag',
        through_fields=('article', 'tag'),
    )

    def __str__(self):
        return self.title


class ArticleDetail(models.Model):
    """
    文章详细表
    """
    nid = models.AutoField(primary_key=True)
    content = models.TextField()
    article = models.OneToOneField(to='Article', to_field='nid')


class Article2Tag(models.Model):
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid')
    tag = models.ForeignKey(verbose_name='标签', to="Tag", to_field='nid')

    class Meta:
        unique_together = [
            ('article', 'tag'),
        ]

    def __str__(self):
        v = self.article.title + "--" + self.tag.title
        return v


class ArticleUpDown(models.Model):
    """
    点赞表
    """

    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey('UserInfo', null=True)
    article = models.ForeignKey("Article", null=True)
    is_up = models.BooleanField(default=True)

    class Meta:
        unique_together = [
            ('article', 'user'),
        ]


class Comment(models.Model):
    """

    评论表

    """
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid')
    user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid')
    content = models.CharField(verbose_name='评论内容', max_length=255)
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)

    parent_comment = models.ForeignKey('self', null=True)

    def __str__(self):
        return self.content

2.Reg部分

前端页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        form{
            margin-top: 100px;
        }
        #avatar{
            display: none;
        }
    </style>
</head>
<body>

<div class="container" >
    <div class="row">
    <div class="col-md-8 col-md-offset-2">
        <form action="" novalidate>
            {% for i in form %}
            <div class="form-group">
                <label for="">{{ i.label }}</label>
                <div>
                    {{ i }} <span class="error"></span>
                </div>

            </div>
            {% endfor %}

        <div class="form-group">
            <label for="avatar">头像<img width="100px" height="100px" src="/static/img/default.png" class="avatar" alt=""></label>

            <input type="file" class="btn btn-default btn-group" value="上传头像" id="avatar">
        </div>

            <div class="form-group">

                <input type="button" class="btn btn-default btn-group reg_btn" value="提交">
            </div>
        </form>
        </div>
    </div>

</div>
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
<script src="/static/jquery-3.2.1.min.js"></script>
<script>

    $('#avatar').change(function () {
        var choose_file = $(this)[0].files[0];
        var reader = new FileReader();
        reader.readAsDataURL(choose_file);

        reader.onload = function () {
            $('.avatar').attr('src',reader.result)
        }
    })



       $(".reg_btn").click(function () {
        var formdata=new FormData();
        formdata.append("name",$("#id_name").val());
        formdata.append("pwd",$("#id_pwd").val());
        formdata.append("repeat_pwd",$("#id_repeat_pwd").val());
        formdata.append("email",$("#id_email").val());
        formdata.append("avatar",$("#avatar")[0].files[0]);
        formdata.append("csrfmiddlewaretoken",$("[name='csrfmiddlewaretoken']").val());


        $.ajax({
            url:"",
            type:"post",
            processData:false,
            contentType:false,
            data:formdata,
            success:function (data) {

                //console.log(data)
                if (data.user){
                    // 注册成功
                    location.href="/login/"
                }
                else {// 注册失败
                    console.log(data.error_dict);
                    // q清空错误信息
                    $("form span").html("");
                    $("form .form-group").removeClass("has-error")
                    // 加载错误信息
                    $.each(data.error_dict,function (field,error_list) {

                      if(field=="__all__"){
                          $("#id_repeat_pwd").next().html(error_list[0]).css("color",'red');
                          $("#id_repeat_pwd").parent().addClass("has-error")


                      }

                      $("#id_"+field).next().html(error_list[0]).css("color",'red');
                      $("#id_"+field).parent().addClass("has-error")


                    })



                }

            }
        })
    })


</script>
</body>
</html>

Views部分

其中涉及到几个关键点:

0.使用ajax方法,不用刷新页面。全部校验合格以后才跳转。

1. 使用form表单做前端验证方便快捷,只需在框架下写出应满足的规则即可。

2. 除class定义了基本方法静态属性以后,还可以自定义内部方法。此处用到的是局部校验和全局校验,分别写两个函数(钩子-全局钩子和局部钩子)。

其中一个是局部钩子,把form表单中的一个数据和后台比对,另一个是全局钩子,用以比较前后输入的数据是否一致。

这两个钩子就是class类的内部方法,在前台把数据打包封装好传过来以后,用request.POST实例化一个对象,然后在使用实例化对象is_valid()的时候类的内部会执行校验,

不但会校验自身属性的要求,还会校验自定义方法(局部钩子,全局钩子)。错误的话把错误放在error.message里面,传回去显示在界面上。

3.前台数据传输,没有文件的时候就好说,把数据放在字典里或者直接使用serialize()方法直接传输就好,有文件就要使用一下方法了。

注意的要点是:

1.前端使用form表单的serialize()方法时,发送的数据的key是标签文件的name属性,这个key值必须和form表单的对应项名字一样,否则会报错。

2.还有就是传输带有文件的数据时,要使用Formdata方法,在提交数据的时候必须实例化一个formdata对象,即var formdata = new FormData(),然后要传输的数据都formdata.append(key,value) value使用Jquery获取val().

3.在ajax提交数据的时候必须要有session数据,首先要在前端页面上写上{{csrf_token}}提交一次,获取csrf的数据值,然后把他也作为一个数据append到formdata数据中。

4.在ajax传输配置里面还要加上processData:false,contentType:false,这两项要不传输会报错。

5.没有csrf_token的时候,写上{%csrf_token%},在html页面中就能获取到含有csrf_token的那句话,根据他的格式获取csrf_token.

6.cleaned_data(没有问题的数据以key,value的形式都存在这里面) error(存的都是有问题信息的key value)

7.error_dict里面有可能有好几个错误,所以取[0],保证能拿到一个错误。

8.出现了全局错误(两次密码不匹配) 会放到error里面 其key的名字是__all__ error[''__all__'']可以取到其错误,错误提示是自己定义的抛出的错误。

9.做的更完善的方法就是每一个选项能校验的都做局部校验,但是写起来就比较麻烦。(实际项目中好的项目是每个都校验)

10.因为用的是auth,所以创建用户使用create_user(信息)创建。

11.运用label标签中的关联选项(for),点击label标签就等于点击了for关联的那个input标签(input标签可以隐藏)。

12.上传文件使用var reader = FileReader()创建对象,reader.readAsDataURL(choose_file);读取文件路径。reader.onload = function () {$('.avatar').attr('src',reader.result)} 根据读取的结果上传图片(onload是加载完以后执行的)。

13.后端接收文件的时候是request.FILES.get(图片文件的名字-传输字典的key)。后端存的是文件本地存储的地址。

14.文件的接收还要配置环境MEDIA_ROOT=os.path.join(BASE_DIR,"blog","media")=======MEDIA_URL="/media/" 意思是把media的路径定位到项目文件的blog的media文件下,然后访问/media/就等于是访问media环境路径下的文件,根据后边的值取找对应的文件。url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), url索引路径按照这个配。并不是执行那个函数,而是取找相应的文件。这样的配置完的路径就可以直接从外部访问,等价于开放。其他的外部则访问不到。之前的媒体文件默认上传到根目录,有了这条就上传到这里面了。

15.在models里创建头像文件的时候默认了上传到avatars/ 所以自然就会生成一个文件夹 然后装上传的文件。有默认值,有新值以后覆盖。

16.create_user(avatar=avatar)一步操作完成了两件事1.把文件路径读取出来 2.把文件对象上传到models里制定的文件下。(原因就是他的格式是FileFiled 直接用他的对象名就可以完成这个两个操作)直接读取文件的操作应该是打开文件 写文件 文件关闭。这一步被涵盖了。

class RegForm(forms.Form):
    name = forms.CharField(label='用户名',max_length=32,widget=widgets.TextInput(attrs={'class':'form-control'}))
    pwd = forms.CharField(label='密码',max_length=32,widget=widgets.PasswordInput(attrs={'class':'form-control'}))
    repeat_pwd = forms.CharField(label='确认密码',max_length=32,widget=widgets.PasswordInput(attrs={'class':'form-control'}))
    email = forms.CharField(label='邮箱',max_length=32,widget=widgets.EmailInput(attrs={'class':'form-control'}))

    def clean_name(self):
        val = self.cleaned_data.get('name')
        ret = UserInfo.objects.filter(username=val)
        if not ret:
            return val
        else:
            raise ValidationError('该用户已存在')

    def clean(self):
        if self.cleaned_data.get("pwd")==self.cleaned_data.get("repeat_pwd"):
            return self.cleaned_data
        else:
            raise ValidationError("两次密码不一致!")


def reg(request):
    if request.method == 'POST':
        dic = {'user':None,'error_dict':None}
        print(request.POST)
        form = RegForm(request.POST)
        if form.is_valid():
            print(form.cleaned_data)
            print(request.FILES)
            dic['user'] = form.cleaned_data['name']
            user = form.cleaned_data.get("name")
            pwd = form.cleaned_data.get("pwd")
            email = form.cleaned_data.get("email")
            avatar = request.FILES.get("avatar")
            if avatar:
                UserInfo.objects.create_user(username=user,password=pwd,email=email,avatar=avatar)
            else:
                UserInfo.objects.create_user(username=user,password=pwd,email=email)
        else:
            print(form.errors)

            dic['error_dict'] = form.errors
        return JsonResponse(dic)

    form = RegForm()
    return render(request,'reg.html',{'form':form})

3.Login部分

前端页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
    <style>
        .container{
            margin-top: 100px;
        }

    </style>
</head><body>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" id="form">
                  <div class="form-group">
                    <label for="user">用户名</label>
                    <input type="text" class="form-control" id="user" name="user" placeholder="Username">
                  </div>



                  <div class="form-group">
                    <label for="pwd">密码</label>
                    <input type="password" class="form-control" id="pwd" name="pwd" placeholder="Password">
                  </div>



                  <div class="form-group">
                    <label for="pwd">验证码</label>

                     <div class="row">
                         <div class="col-md-6">
                             <input type="text" class="form-control" id="valid_code" name="valid_code">
                         </div>
                         <div class="col-md-6">
                             <img width="250" height="40" id="image" src="/get_valid_img/" alt="">
                         </div>

                     </div>
                  </div>


                <input type="button" class="btn btn-default login_btn" value="提交"><span class="error" style="color: red;margin-left: 20px"></span>
            </form>

        </div>
    </div>
</div>



<script>
    $('#image').click(function () {
        this.src += '?'
        $('this')[0].src += '?'

    })


    $('.login_btn').on('click',function () {
        $.ajax({
            url:'/login/',
            type:'POST',
            data: $('#form').serialize(),
            success:function (data) {
                if(data.status){
                    location.href = '/index/'
                }else{
                    $('.error').text('验证码错误,请重新输入')
                }

            }

            })

    })
</script>


</body>
</html>

views

前端所采用的为ajax提交验证方法。没有很多亮点,就是前端提交数据可数据库比对。

其中唯一的亮点就是使用PIL模块生成随机验证码,在另一篇文章已经列出,此处不再赘述。

1.在没有的登陆的时候,传输数据里面会有.user(有值) 在没有登陆的时候是匿名用户或者空,登陆成功以后就变成用户名了。(cookie-session的作用)django自动生成的session。在数据库中存有session,用以比对。(使用的auth框架,所以才会有user的默认值)

2.session可以作为服务端和客户端的加密联络方式,使用request.session['key']='value' 等于给了一个字典{‘key’,‘value’},可以用来验证。没有数据沟通的情况下可以用session来实现前后端沟通。

3.在登陆验证的时候,拿前端用户输入的内容和刚才后端传给前端的session值对比,比对成功才有可能登陆成功。

4.其中登陆校验使用的是auth的内部方法 user = auth.authenticate(username=user,password=pwd) 若果返回的对象(实例化成功),则user是一个实例化对象,要不就为空,然后auth.login(request,user) 证明登陆成功,在后台的session注册,保持一段登陆时间。

def login(request):

    if request.is_ajax():
        print(request.POST.get('valid_code'))
        print(request.POST.get('user'))
        print(request.POST.get('pwd'))

        valid_code = request.POST.get('valid_code')
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')

        ret = {'status':False,'message':None}

        valid_str = request.session.get('valid_str')

        if valid_code.lower() == valid_str.lower():
            # 从数据库中拿用户数据
            user = auth.authenticate(username=user,password=pwd)
            if user:
                ret['status'] = True
                auth.login(request,user)
            else:
                ret['message'] = '用户名或密码错误,请重新输入'

        else:
            ret['message'] = '验证码错误,请重新输入'

        return JsonResponse(ret)

    return render(request,'login.html')

 4.index部分

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
    <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script>
    <style>
        .article_item .desc{
            margin-left: -40px;
            font-size: 13px;
            text-align: justify;
        }
        .content{
            margin-top: 5px;
        }
    </style>
</head>



<body>

<nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">博客园</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
        <li><a href="#">Link</a></li>
        <li><a href="#">Link</a></li>
      </ul>

      <ul class="nav navbar-nav navbar-right">
        {% if request.user.username %}
            <li><a href="#">{{ request.user.username }}</a></li>
            <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
          <ul class="dropdown-menu">
{#            <li><a href="#">注销</a></li>#}
            <li><a href="/out/">注销</a></li>
            <li><a href="#">修改密码</a></li>
            <li><a href="#">更换头像</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
          </ul>
        </li>
         {% else %}
              <li><a href="/login/">登录</a></li>
              <li><a href="">注册</a></li>
        {% endif %}


      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>




<div class="container">
    <div class="row">
        <div class="col-md-2">
            <div class="panel panel-primary">
              <div class="panel-heading">Panel heading without title</div>
              <div class="panel-body">
                Panel content
              </div>
            </div>
            <div class="panel panel-info">
              <div class="panel-heading">Panel heading without title</div>
              <div class="panel-body">
                Panel content
              </div>
            </div>
            <div class="panel panel-warning">
              <div class="panel-heading">Panel heading without title</div>
              <div class="panel-body">
                Panel content
              </div>
            </div>
        </div>


        <div class="col-md-7">

            <div class="article_list">
{#            循环展示数据库中的数据#}
               {% for article in article_list %}
                <div class="article_item">
{#                标题属性#}
                   <div><h5><a href="/{{ article.user.username }}/articles/{{ article.nid }}">{{ article.title }}</a></h5></div>
{#                    文章地址格式 username/articles/nid#}
                   <div class="row">
{#                       获取头像属性 注意要配media环境变量 从对象拿出来的只是相对路径#}
{#                       upload_to 的地址外加文件名就是对象获取到的路径#}
                       <div class="col-md-2"><img width="60" height="60" src="/media{{ article.user.avatar }}" alt=""></div>
                       <div class="col-md-9 desc">
{#                           获取描述属性#}
                           <p>{{ article.desc }}</p>
                       </div>
                   </div>

                   <div class="small content">
{#                       获取用户名属性#}
                       <span><a href="">{{ article.user.username }}</a>发布于</span>&nbsp;&nbsp;&nbsp;
{#                       获取时间属性 |date:'Y-m-d' (确定展示方式)#}
                       <span>{{ article.create_time|date:"Y-m-d" }}</span>&nbsp;&nbsp;&nbsp;
                       <span class="glyphicon glyphicon-comment"></span>评论({{ article.comment_count }})&nbsp;&nbsp;&nbsp;
                       <span class="glyphicon glyphicon-thumbs-up"></span>赞({{ article.up_count }})
                   </div>
                    <hr>
                </div>
               {% endfor %}
            </div>

        </div>


        <div class="col-md-3">
                <div class="panel panel-danger">
                  <div class="panel-heading">Panel heading without title</div>
                  <div class="panel-body">
                    Panel content
                  </div>
                </div>
        </div>


    </div>
</div>

</body>
</html>

views

这部分最简单,把所有的文章都取出来发给前端展示就行了。

{{对象.属性}} 就可展示变量数据了。前端展示和后端展示一样,获取了一个对象.属性(正向)对象.表名__set(反向).first(取到一个对象).属性.属性(如果是外联的话)。和后端查询基本一样。

def index(request):
    article_list = Article.objects.all()
    return render(request,"index.html",{"article_list":article_list})

logout views

这里面注销方法实际已经被做成了logout模块,只需要直接引用,然后把request作为参数穿进去就可以了。

特别注意!!!自定义函数名和logout名字不能起的一样,否则会陷入无限递归中。

注销返回登陆页面即可。

def out(request):
    logout(request)
    # 两种都可以
    return redirect('/login/')
    return render(request,'login.html')

5.homesite部分

展示两个部分,一个是文章的分类信息(信息分类)。一个是文章内容(获取信息)。

然后在的分类和标题里面(每个分类实体)给出链接(采用了路径和数据拼接,生成新路径的形式)。点击不同链接返回不同的页面内容。

base模板

 

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    {% load exercise_tyags%}
    <meta name="viewport" content="width=device-width, initial-scale=1">
       <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
       <link rel="stylesheet" href="/static/css/article_detail.css">
        <script src="/static/jquery-3.2.1.min.js"></script>
    <style>
        *{
            margin: 0;
        }
        .header{
            width: 100%;
            height: 50px;
            background-color: #336699;
            line-height: 50px;
            color: white;
            font-size: 16px;

        }
        .header .title{
             margin-left: 20px;
        }
        .comment_tree_item{
            margin-left: 20px;

        }
    </style>
</head>
<body>

<div class="header">
    <p class="title">{{ blog.title }}</p>
</div>

<div class="container">
    <div class="col-md-3">

        {% get_menu username%}

    </div>
    <div class="col-md-8">
       {% block content %}

       {% endblock content%}
    </div>


</div>


</body>
</html>

 

扩展base内容 {% get_menu username%} (查询方法见下面)

from django import template
register = template.Library()
#把旗下的动作作为template.Library里面的方法

from ..models import *

@register.inclusion_tag('menu.html') #方法所需要的文件
def get_menu(username):
    user = UserInfo.objects.filter(username=username).first()
    blog = user.blog
    from django.db.models import Count
    cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article")).values_list("title", "c")
    print(cate_list)
    tag_list = Tag.objects.filter(blog=blog).annotate(c=Count("article")).values_list("title", "c")
    print(tag_list)
    date_list = Article.objects.filter(user=user).extra(
        select={"create_ym": "DATE_FORMAT(create_time,'%%Y-%%m')"}).values("create_ym").annotate(
        c=Count("nid")).values_list("create_ym", "c")
    print(date_list)
    #最后试一下看看能否换成locals()
    return  {"username":username,"cate_list":cate_list,"tag_list":tag_list,"date_list":date_list}
<div class="menu">
      <div class="panel panel-info">
              <div class="panel-heading">我的分类</div>
              <div class="panel-body">
                {% for cate in cate_list %}
                <p><a href="/blog/{{ username }}/cate/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p>
                {% endfor %}

              </div>
        </div>

        <div class="panel panel-success">
              <div class="panel-heading">标签分类</div>
              <div class="panel-body">
                {% for tag in tag_list %}
                <p><a href="/blog/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p>
                {% endfor %}

              </div>
        </div>

       <div class="panel panel-danger">
              <div class="panel-heading">日期归档</div>
              <div class="panel-body">
                {% for date in date_list %}
                <p><a href="/blog/{{ username }}/achrive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p>
                {% endfor %}

              </div>
        </div>
</div>

扩展base内容 {% block content %} {% endblock content %}

def homesite(request,username,**kwargs):
    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return HttpResponse('该页面不存在')
    blog = user.blog
    if not kwargs:
         article_list=Article.objects.filter(user=user)
    else:
        condition = kwargs.get("condition")
        param = kwargs.get("param")
        if condition == "cate":
            article_list = Article.objects.filter(user=user).filter(category__title=param)
        elif condition == "tag":
            article_list = Article.objects.filter(user=user).filter(tags__title=param)
        else:
            year, month = param.split("-")
            article_list = Article.objects.filter(user=user).filter(create_time__year=year, create_time__month=month)
    return render(request, "homesite.html", locals())

 

{% extends "base.html" %}

{% block content %}
 <div class="article_list">
               {% for article in article_list %}
                <div class="article_item">
                   <div><h5><a href="">{{ article.title }}</a></h5></div>
                   <div class="row">
                       <div class="col-md-9 desc">
                           <p>{{ article.desc }}</p>
                       </div>
                   </div>

                   <div class="small">
                       发布于&nbsp;&nbsp;&nbsp;
                       <span>{{ article.create_time|date:"Y-m-d" }}</span>&nbsp;&nbsp;&nbsp;
                       <span class="glyphicon glyphicon-comment"></span>评论({{ article.comment_count }})&nbsp;&nbsp;&nbsp;
                       <span class="glyphicon glyphicon-thumbs-up"></span>赞({{ article.up_count }})
                   </div>
                    <hr>
                </div>
               {% endfor %}
            </div>
{% endblock content %}

具体使用思路:

第一步
在最上面{% load exercise_tyags %}加载自己写好的方法的那个文件名(已经注册到template.Library中)
第二步
{% 函数名 函数返回的变量 %} 用以调用注册方法函数的返回数据
第三步
 {% block content %}
     要填充的内容
 {% endblock content%}
第四步
在要填充的文件最上方填写{% extends "base.html" %} extends+母版文件的带后缀的名称
{% block content %}
     填充的内容
 {% endblock content%}
用这个和base的代码匹配 渲染出整体的效果


整体思路
1 在app文件中新建一个templatetags文件
2里面新建一个py文件 包含封装的一部分views文件
3 引用template.Library 把它做成装饰器 后面含有要渲染的文件名(html文件)
4 装饰完一个函数 可以返回这个函数和接收数据的html文件
-----------------------------------------------------------------
以上部分可以作为一个单独整体直接调用
5 制作一个base文件 存放网页版式的基本框架 内部含有各种接口 可以让其他文件填充进来
含有的两种填充方式
1 {% get_menu username%} 从template.library里自己制作的函数外加html和数据填充过来
2   {% block content %}
    第三方模板 最上写上{% extends "base.html" %} 拼接模板数据生成页面
     {% endblock content%}
-------------------------------------------------------------------

博客的这一部分牵涉到了相关数据的查询(分类-聚合):

ORM查询(如果设有related_name的反向可以按照related_name查询)
尽量多使用数据库的内部方法 比如filter 少使用对象的属性
1 filter方法 后面跟上筛选的对象(数据库key=要匹配对象)
2 使用filter方法返回的是一个queryset对象 使用.first()获取到对象 匹配不上则返回拿到None
3 正向查询使用自己类下的.属性.属性的属性获取到对象 
如果是反向查询则.属性(小写表明)_set.属性的属性获取到对象
4 如果是一对一的查询 正向方法直接.属性 反向查询不用加.set 因为对象唯一
5 如果查询到的对象是一个集合 其中有a,b,c属性 要取的话使用values() (返回一个字典)或 value_list ()(返回一个元组)里面填上要看的属性 
正向查询values or values_list(‘自己表格的属性(外联到外表)_外表的属性值’)(正向用字段)
反向查询values or values_list(‘别人表格的表明(外联到外表)_外表的属性值’)(反向用表名)
5.5 查询自己 自己表名.object.values(‘以谁分组就写谁’).annotate(名字=计算方法(甲酸对象)).values(要看的对象)
6 重要属性annotate 其实质是在之前的对象基础上(xxx=计算方法(count avg)(数据的一个属性))然后给给自己加一个属性xxx=计算结果 后面.values()可以查看这个新设置的属性(其本质是给annotate之前筛选显示筛选结果打上annotate分组计算的标签)
7 重要属性xxx.objects.all().aggregate(自己命名的属性名=计算方法(‘对象的属性’)) 把所有东西聚合 然后根据某一计算方法计算其中某一共有属性的值 返回结果是自己命名的属性名和计算结果
8 注意 在filter查询中 filter(xxx=???)其中xxx要是models中的对象则后面要跟一个对象 如果xxx是数据库表头中的一个量 则???可以是直接的数据(跟数据库中一样就可以查到)
9 使用filter(表名__属性) 不使用filter就反向表名__set(也可以是related name).属性
10 .extra是自主创建一条SQL语句,一般适用于ORM查询语句不能翻译成SQL的情况。查询完以后变成查询结果的一条属性。
6.点赞部分
点赞的原理是前端ajax获取点击的内容,传输到后台,和数据库比对。看是否点赞,然后返回状态信息给前端。最后在指定位置上显示状态信息。
1.|safe是保证文章内容显示如果有html语言的话也正常显示(在存入数据库的时候已经做过安全处理)。
2.点赞的支持和反对内容都放在一个大标签中。支持是一个类,反对是一个类。通过获取其中一个class属性(有是True,没有是False)。这作为一个条件为后边的判断做依据。
3.然后根据True和False对支持和反对数据做修改。(
其中对数据修改需要使用F函数,而且还要用到事件(transaction)-以保证数据库的数据确实被修改过以后才给前端返回。(没有数据,创建数据,对数据操作))
4.出现错误给前端(已经存在这个数据),获取这个错误的支持反对值,然后前端根据这个错误信息来判断是支持过了还是反对过了,给客户显示。
5.前后端信息交互使用Json化的字典。
前端(包含js)部分
<h3 class="text-center">{{ article.title }}</h3>
<div class="content">
    {{ article.articledetail.content|safe }}
</div>

<div class="clearfix">

   <div id="div_digg">

    <div class="diggit digg">
        <span class="diggnum" id="digg_count">{{ article.up_count }}</span>
    </div>

    <div class="buryit digg">
        <span class="burynum" id="bury_count">{{ article.down_count }}</span>
    </div>

</div>

</div>
<div id="digg_word" class="pull-right"></div>


<div id="info"  article_id="{{ article.pk }}"  username="{{ request.user.username }}"></div>
$('#div_digg .digg').click(function(){
{#最妙的就是这块 获取其中一个标签的标签 能获取到就是True 不能就是 False#}
    {#在相同中有不同就是最大的关键点#}
    {#True or False表示了一种状态 同时又可以作为条件语句的判断条件 高!!!#}
    {#获得结果是True or False#}
    var up = $(this).hasClass('diggit')
    var article_id= $('#info').attr('article_id')
    {#alert(123)#}
    $.ajax({
        url: '/poll/',
        type: 'post',
        data: {
            up:up,
            article_id:article_id,
            csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val()},
            success: function(data){
                {#后边应该回过来的数据是:#}
                {#1 这个文章是选中还是没选中#}
                {#选过:#}
                {#选中的是哪个 是点赞还是踩灭(这个需要一个返回的字典中的另一个key value才能实现)#}
                {#没选过:给相应的部分+1 or -1#}
                {#{status:True or False(有没有) up_down:(是什么)}#}
                console.log(123);
                console.log(data);

                if (data.state){
                    if (data.up_down){
                        var val = parseInt($('#digg_count').text()) + 1
                        $('#digg_count').val(val)
                    }
                    else{
                        var val = parseInt($('#bury_count').text()) + 1
                        $('#bury_count').text(val)
                }

                }else{
                    console.log(data.first_operate);
                    if(data.first_operate){
                        $("#digg_word").html("您已经推荐过").css({"color":"red","margin-right":"30px"})
                    }else{
                        $("#digg_word").html("您已经反对过").css({"color":"red","margin-right":"30px"});
                    }
            }
                }


        {#要有文章的id 作者的id 赞True or False#}

    })




})
后端部分
def poll(request):
    print(request.POST)
    is_up=json.loads(request.POST.get("up"))
    print(is_up)
    article_id=request.POST.get("article_id")
    user_id=request.user.pk


    res={"state":True}

    from django.db import transaction
    try:
        with transaction.atomic():
            ArticleUpDown.objects.create(is_up=is_up,article_id=article_id,user_id=user_id)
            if is_up:
                Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1)
            else:
                Article.objects.filter(pk=article_id).update(down_count=F("down_count")+1)


    except Exception as e :
        res["state"]=False
        res["first_operate"]=ArticleUpDown.objects.filter(article_id=article_id,user_id=user_id).first().is_up

    return JsonResponse(res)
7.评论部分
基本(评论楼)
1.注意使用pid(父评论id)根据他可以确定评论的关系。有pid的,在评论渲染的时候可以反映出之前是对谁评论的。
2.不点击回复,pid=空,点击了以后,获取当前的评论id最为pid。同时要@用户名,以显示回复该评论。
3.在提交评论的时候,需要防范用户删除了@用户名,但是还保留pid的情况。验证有否有@用户名,没有则设pid为空。有则不变。
4.注意写入评论的时候还要判断是不是回复的内容,是的话要去除@用户名。
5.后端在创建评论的时候,还要用到事件(transcation)保证评论写入了以后再给文章评论数+1。
6.给前端返回数据的时候不能回复对象,要把对象里面的属性一一拿出来,放在字典里然后序列化以后传给前端(这样才能传输)。
7.文章的楼层直接看<li>标签的长度(看看谁包含着<li>标签,就获取它的长度)。
8.前端文本替换用<标签种类>xxxxxxxxxxx'+内容+'xxxxxxxxxxx<标签种类>,就可以添加或替换原有内容了。
9.把新创建的含有后端数据内容的标签添加到原来评论里(使用append方法),就可以随评随增了。(同时要清pid,要不下次评论等于默认有pid了)
评论输入:
<div class="comment_region">
   <div class="row">
       <div class="col-md-7">
              <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p>
               <p>评论内容:</p>
                <textarea name="" id="comment_text" cols="60" rows="10"></textarea>
               <button class="btn btn-default pull-right comment_btn">提交</button>
       </div>
       </div>
</div>
评论渲染:
   <ul class="list-group comment_list">
      {% for comment in comment_list %}
      <li class="list-group-item comment_item">

            <div>
                <a href="">#{{ forloop.counter }}楼</a>&nbsp;&nbsp;&nbsp;
                <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;
                <a href="">{{ comment.user.username }}</a>
                <a class="pull-right reply"  pk="{{ comment.pk }}"  username="{{ comment.user.username }}">回复</a>
            </div>

            {% if comment.parent_comment_id %}
             <div class="parent_comment_info well">
                 <a href="">@{{ comment.parent_comment.user.username }}</a>&nbsp;&nbsp;
                 <span>{{ comment.parent_comment.content }}</span>
             </div>
            {% endif %}

           <div>
               <p>{{ comment.content }}</p>
           </div>
      </li>
      {% endfor %}


    </ul>
前端JS:
var pid="";
$('.comment_btn').click(function () {if("{{request.user.username}}"){var article_id = $('#info').attr('article_id')
        if ($("#comment_text").val()[0]!=="@"){
                     pid=""
                 }
        if(pid){
            var index = $('#comment_text').val().indexOf('\n');
            var content = $('#comment_text').val().slice(index+1);
        }else{
            var content = $('#comment_text').val();
        }
        $.ajax({
            url: '/comment/',
            type: 'post',
            data:{
                article_id : article_id,
                content:content,
                pid:pid,
                csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val()
            },
            success:function (data) {
                {#返回的只有一个字典res = {"state": True}#}
                {#表明评论写入数据库成功#}
                {#清掉之前的评论#}
                console.log(123);
                console.log(data);

                if (data.state){ // 提交功能
                    {#获取楼层这个比较妙 直接看for循环内容的长度!!!#}
                    {#等于是看 <li></li>的个数 也就是长度#}
                    var floor = $('.comment_list .comment_item').length + 1;
                    var ctime = data.time;
                    var username = $('#info').attr('username');
                    var content = data.content;
                    var s = '<li class="list-group-item comment_item"> <div> <a href="">#'+floor+'楼</a>&nbsp;&nbsp;&nbsp; <span>'+ctime+'</span>&nbsp;&nbsp;<a href="">'+username+'</a></div> <div> <p>'+content+'</p> </div> </li>'

$('.comment_list').append(s) // 清空 $("#comment_text").val(""); pid='' }else{ // 提交失败 {#可以显示提交失败 在评论下方#} } } }) }else{ location.href = '/login/' } }) {#执行什么事件主要是根据点击不同按钮绑定的ajax的事件不同而不同的#} {#点击回复按钮的时间#} {# comment_item 是一个总得标签 reply是其中一个按钮选项#} $(".comment_item .reply").click(function () { $("#comment_text").focus(); var val = '@'+$(this).attr('username')+'\n'; $("#comment_text").val(val); pid = $(this).attr('pk') })
后端:
def comment(request):
    print(request)
    article_id = request.POST.get("article_id")
    content = request.POST.get("content")
    pid = request.POST.get("pid")
    user_id = request.user.pk
    # 其中user_id比较特殊 只用其中pk就可代指user_id


    res = {"state": True}

    with transaction.atomic():
        # 创建完返回的对象是ORM生产的类对象 Jquery查找到的是Queryset对象 所能调用的方法不一样!
        if not pid:  # 提交根评论
            obj = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, )
            print(obj,type(obj))
            print(obj.content)
        else:  # 提交子评论
            obj = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
            print(obj, type(obj))
            print(obj.content)
        Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1)

    #时间是datetime.xxx的类型 要转换
    res['time'] = obj.create_time.strftime('%Y-%m-%d %H:%M')
    # content是str格式
    res['content'] = obj.content
    return JsonResponse(res)
进阶(评论树)
评论树和评论楼的差别就表现在可以不断的繁衍子评论,然后有层级的显示。
前端JS:
{#一个函数被括号括起来了 后面再加一个括号 就是执行这个匿名函数#}
          (function () {
{#向后台的view提交信息有两种方式 #}
{#              1是从url里面提交 拼接使用的参数 (参数) 但是再ajax的提交里面不用使用()括起来#}
{#              2是从data里面传输 这个可以从request.data.get('对应的数据名')#}

              {#可以没有提交方式(默认get)数据 #}
              $.ajax({
                  url:"/tree/"+$("#info").attr("article_id"),
                  success:function (comment_list) {
                         var comment_html="";
                         {#循环展示数据 传过来的数据是一个列表里面套了很多字典#}
                         $.each(comment_list,function (index,comment) {
                             console.log(comment);
                             {#把一个列表里的每一个字典都拿出来 然后把每一个字典里的元素都创建一个变量接收#}
                             var username=comment.user__username;
                             var content=comment.content;
                             var pk=comment.pk;
                             var pid=comment.parent_comment_id;

                             s='<div class="comment_tree_item" id='+pk+'><span>'+username+'</span> <span>'+content+'</span> </div>'

                             if(pid){
                                 $("#"+pid).append(s);
                             }else {
                                 $(".comment_tree").append(s);
                             }


                         })


                  }

              })



          })();

方法 :
1.在渲染每一层级的的标签的时候都加上一个class和id属性(插入评论)。
2.在style中设置含有该class属性margin or padding-left为一个值。
3.判断-只要是有pid的,就在pid的div标签中塞入子标签。
4.因为子标签也有偏移属性,父标签已经偏移,所以子标签等于在父标签的基础上附加了一个偏移。从而有了层级属性。

8.文章内容添加部分
(等待补全)
posted @ 2018-04-24 08:57  大量子  阅读(207)  评论(0编辑  收藏  举报