zoukankan      html  css  js  c++  java
  • Django 博客园练习--待完成

    项目流程:

    1. 产品需求

    一. 基于用户认证组件和Ajax实现登陆(图片验证码)

    二. 基于forms组件和Ajax实现用户注册功能

    一和二的数据库表可以放入到用户信息表中

    三.系统首页设计

    四.设计个人站点

    四的个人网站设计了字段为 title(个人网站的名字),site_name(个人网站URL后面的字段,比如 http:xxx/alex),theme(个人网站主题的CSS样式), 然后和用户信息表做一对一关联

    五.个人文章详情页

    六.文章点赞功能

    七.实现评论功能

    ​ --------文章的评论

    ​ --------对评论的评论

    八. 后台管理页面(富文本编辑框)

    2. 设计表结构

    2.1 用户信息表

    使用session功能,利用django自带的auth功能,但是自带的auth表字段很少
    原生的auth_user表中只有id,password等一些基础的字段.无法满足具体业务的认证需求.
    所以我们可以自定义一张表,来写入需要用到的字段,因为原生的User表是继承了AbstractUser 类.
    所以我们也可以继承并扩展这个表

    使用的时候要注意setting.py中要添加 AUTH_USER_MODEL = "app02.UserInfo" app02是应用名,UserInfo是自定义类名.
    否则容易出现报错

    from django.contrib.auth.models import User,AbstractUser
    '''
    这里AbstractUser 父类就已经有了username password等一些列的字段了,
    其他的只是我们扩展的字段而已
    '''
    class UserInfo(AbstractUser):
        nid = models.AutoField(primary_key=True)
        telphone = 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,on_delete=models.CASCADE)
    
        def __str__(self):
            return self.username
    

    2.2 个人主页表

    个人主页表放入了主页的标题,站点名,和网站样式

    class Blog(models.Model):
        nid = models.AutoField(primary_key=True)
        title = models.CharField(verbose_name="个人博客标题", max_length=64)
        sitename = models.CharField(verbose_name="站点名称", max_length=64)
        theme = models.CharField(verbose_name="个人博客主题央视", max_length=32)
    
        def __str__(self):
            return self.title
    

    2.3 分类

    用来定义文章的内容类别 ,比如python,java,mysql, 和个人主页文章表属于一对一关系,也就是一篇文章有一个分类
    同时分类和用户信息表应该是属于一对多关系,一个用户下可以有多个分类
    注意点:因为用户信息表和个人主页表是一对一关系,所以我们的分类表和他们任意一张表建立一对多关系,其他表都是可以通过跨表查询到的

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

    2.4 标签

    用来定义文章的关键字信息 和个人主页文章表属于一对多关系,也就是一篇文章有多个关键字,同时标签和用户信息表应该是属于一对多关系,一个用户下可以有多个标签

    注意点:因为用户信息表和个人主页表是一对一关系,所以我们的标签表和他们任意一张表建立一对多关系,其他表都是可以通过跨表查询到的

    class Tag(models.Model):
        '''个人文章标签表'''
        nid = models.AutoField(primary_key=True)
        title = models.CharField(verbose_name="标签名称", max_length=64)
        blog = models.ForeignKey(to="Blog", verbose_name="所属博客", to_field="nid",on_delete=models.CASCADE)
    
        def __str__(self):
            return self.title
    

    2.5 个人主页文章表

    class Artical(models.Model):
        '''个人文章分类表'''
        nid = models.AutoField(primary_key=True)
        title = models.CharField(verbose_name="文章标题", max_length=64)
        desc = models.CharField(verbose_name="正文描述",max_length=32)
        create_time = models.DateTimeField(verbose_name="文章创建时间", auto_now_add=True)
        content = models.TextField()  # 文章正文
        comment_count = models.IntegerField(verbose_name="文章评论数", default=0)
        up_count = models.IntegerField(verbose_name="点赞数", default=0)
        down_count = models.IntegerField(verbose_name="踩他数", default=0)
        # 文章和用户  一对多关系,字段放在多的这张表
        user = models.ForeignKey(to="UserInfo", to_field="nid", verbose_name="作者", on_delete=models.CASCADE)
        # 文章和分类,可以一对多,也可以多对多,这里设置成一对多
        category = models.ForeignKey(to="Category", to_field="nid", verbose_name="文章分类", on_delete=models.CASCADE)
        # 文章和标签,设置成多对多关系,不用through,就是系统自动创建关联表,用through就是自动创建第三张关联表
        tag = models.ManyToManyField(
            to="Tag",
            through="Artical2Tag",
            through_fields=("article", "tag")
        )
    
        def __str__(self):
            return self.title
    

    文章和标签tag多对多的第三张自定义关系表

    class Artical2Tag(models.Model):
        nid = models.AutoField(primary_key=True)
        article = models.ForeignKey(to="Artical", to_field="nid", on_delete=models.CASCADE, verbose_name="文章")
        tag = models.ForeignKey(to="Tag", to_field="nid", on_delete=models.CASCADE, verbose_name="标签")
        #通过一个内嵌类 "class Meta" 给你的 model 定义元数据
        #这里unique_together 代表 article和tag 他们两者的且关系必须是唯一的
        class Meta:
            unique_together = [
                ("article", "tag")
            ]
    
        def __str__(self):
            ret = self.article.title + "---" +self.tag.title
            return ret
    

    2.6 点赞和踩他

    class ArticleUpDown(models.Model):
        #文章点赞 踩他表
        nid = models.AutoField(primary_key=True)
        article = models.ForeignKey(to="Artical", to_field="nid", on_delete=models.CASCADE, verbose_name="文章",null=True)
        #之所以需要user是因为对文章的点赞 一定是对某个作者的某篇文章进行点赞,而不是单独对某个文章点赞
        user = models.ForeignKey(to="UserInfo", to_field="nid", verbose_name="作者", on_delete=models.CASCADE,null=True)
        is_up = models.BooleanField(default=True)
    
        class Meta:
            unique_together = [
                ("article", "user")
            ]
    

    2.7评论

    class Comment(models.Model):
        # 文章点赞 踩他表
        nid = models.AutoField(primary_key=True)
        article = models.ForeignKey(to="Artical", to_field="nid", on_delete=models.CASCADE, verbose_name="文章", null=True)
        user = models.ForeignKey(to="UserInfo", to_field="nid", verbose_name="作者", on_delete=models.CASCADE,null=True)
        creat_time = models.DateTimeField(verbose_name="评论创建日期",auto_now_add=True)
        content=models.CharField(verbose_name="评论内容",max_length=255)
        # 这个字段主要用于对评论的评论,to=self或者Comment 是一种自关联的写法
        parent_comment = models.ForeignKey(to="self",null=True,on_delete=models.CASCADE)
    
        '''
        对评论的评论逻辑如下:  444和555是对111评论的评论
        111
            444
            555
        222
        333
        
        Comment 表
        parent_comment 为null 表示只是对文章的评论,
        而最后两条分别是对nid为1的评论和nid为4的评论的评论
        nid     article     user    create_time     content     parent_comment
        1          1            1           2021-09-08          111         null
        2           2           1           2021-09-09          222         null
        3           1           1           2021-09-09          333         null
        4           1           1           2021-09-10          444         1
        5           1           1           2021-09-11          555         4
    
        '''
    

    3.对着每个功能进行开发

    功能1 登陆页面

    • 基于form组件渲染登陆页面和注册页面的标签元素

      form组件字段用到的有
      required=False 是否必填 ,

      error_messages={"required": "账号不能为空", "invalid": "账号格式有误"} 自定义错误信息提示

      widget=widgets.PasswordInput(attrs={"class": "form-control"}, )) 内置的widgt插件,设置标签属性等

    • 注册和登陆页面的验证码实现

      简单的文字验证码,用PIL模块,

      先创建实例Image.new(),创建一块画布,

      然后设置ImageFont.truetype字体样式

      再创建画笔实例ImageDraw.Draw(),通过随机生成的大小写和数字进行填写,注意每个字符的间距

      随机的字符串生成后,通过request.session,同步到session会话中,用于后期的验证码校验

      创建BytesIO()实例,把生成的验证码写入进去,这样就不用在硬盘上生成图片调用了,加快代码的执行效率.

      验证图片的刷新可以通过按钮刷新,也可以在图片的URL后添加?实现重复刷新

          $("#verify_code").click(function () {
              $(this)[0].src += "?";
          });
      
    • ajax功能实现用户账号密码POST校验.

      不要忘记添加CSRF字段一起POST,ajax的字段提交常用到的就是url,type,data,和成功回调函数success

    • 后台session进行账号密码校验

      先通过Django自带的auth模块进行校验(auth.authenticate),校验成功后(auth.login)进行注册,返回一个request.user.

      校验失败,则返回一个字典,通过ajax里的success进行渲染到前端网页,进行打印提示.

    • 用户注册功能,也是用form组件进行字段渲染,同时对于用户上传自定义头像图片实现逻辑如下:

      用户头像上传预览的方式1,通过label标签的for和input id一致产生的联动性实现的,点击了label也会出发input的聚焦事件,然后把#user_icon标签进行显示隐藏
      html:
      <div>
      	<label for="user_icon">用户头像 
              <img class="head_pic" src="/static/img/default.jpg"></img> </label>
      	<input type="file" id="user_icon">
      </div>
      
      方式2,通过子绝父相让头像图片(head2_img)和input框(head2_input)重叠,并且大小和长宽设置一样
      然后input(head2_input)透明度设置为透明
      <div class="user_head2_div" style="height: 60px">
          <span>用户头像2</span> <img src="/static/img/default.jpg" class="head2_img">
          <input type="file" id="head2_input">
      </div>
      <div>
          <input type="button" class="btn btn-success" value="确认提交">
      </div>
      
    • 用户头像预览

      前端考虑到功能是上传图片后实现的图片预览,所以选用的是change事件,然后通过files获取用户选中的文件对象,

      然后用js的 FileReader() 类读取文件内容,并返回 class.result()

      修改img的src属性,设置src的值为class.result()文件读取结果的值.

      用onload事件对图片进行加载显示.

    $("#user_icon").change(function () {
            //用change事件获取文件对象
            var file_obj = $(this)[0].files[0];
            //实例化一个FileRead类,用readAsDataURL读取图片内容
            var head_icon = new FileReader();
            head_icon.readAsDataURL(file_obj);
            //用该标签的加载事件进行对图片的前端渲染
            head_icon.onload = function () {
                $(".head_pic").attr("src", head_icon.result)
            };
        })
    
    • ajax中利用js的form表单的进行数据提交

      创建new.FormData()实例,然后用append方法添加键值.头像的值获取方法同上

      利用FormData提交数据时候,AJAX的contentType和processData需要设置为false

      注意csrftoken的键值提交

      ajax数据POST方式1:

      $.ajax({
                  type:"POST",
                  //ajax提交form 情况下 contentType和processData 必须为false
                  contentType:false,
                  processData:false,
                  dataType:"json",
                  data:{
                   	 "user": $("#id_reg_account").val(),
                       'csrfmiddlewaretoken':$("input[name='csrfmiddlewaretoken']").val(),
                       //#todo 怎么在ajax中发送csrf而不是在获取html数值后续研究下
                        "csrfmiddlewaretoken":$.cookie("csrftoken"),
                  },
                  
                  success:function (data) {}
      })
      

      ajax数据POST方式2:

      我们也可以对data中的键值进行用js中的FormData()类进行统一添加,然后赋值给data

      $(".submit").click(function () {
              var post_data = new FormData();
              //js中使用FormData类进行ajax中的data数据提交
              //创建new.FormData()实例,然后用append方法添加键值.头像的值获取方法同上
              //append的key值要注意和html中的name相互对应
              post_data.append("reg_account",$("#id_reg_account").val());
              post_data.append("pwd",$("#id_pwd").val());
              post_data.append("re_pwd",$("#id_re_pwd").val());
              post_data.append("email",$("#id_email").val());
              post_data.append("avatar",$("#user_icon")[0].files[0]);
      
      //利用FormData提交数据时候,AJAX的contentType和processData需要设置为false
              post_data.append("csrfmiddlewaretoken",$("input[name='csrfmiddlewaretoken']").val());
      //注意csrftoken的键值提交
              $.ajax({
                  type:"POST",
                  //ajax提交form 情况下 contentType和processData 必须为false
                  contentType:false,
                  processData:false,
                  dataType:"json",
                  data:post_data,
                  success:function (data) {}
             }
              )
      

      ajax数据POST方式3:

      方式2还可以再优化,注意到添加的键值对除了'#avatar'是一个文件对象,其他都是可以通过遍历进行动态获取form表单中的键值对,利用form对象的serializeArray()方法,这个方法会把里面的键值对组成数组

      var post_data = new FormData();
              //js中使用FormData类进行ajax中的data数据提交
              //创建new.FormData()实例,然后用append方法添加键值.头像的值获取方法同上
              //append的key值要注意和html中的name相互对应
              post_data.append("reg_account",$("#id_reg_account").val());
              post_data.append("pwd",$("#id_pwd").val());
              post_data.append("re_pwd",$("#id_re_pwd").val());
              post_data.append("email",$("#id_email").val());
      
              // 可以获取form表单的对象,用serializeArray()方法,把里面的key和value分别用name和value获取,遍历赋值,图片对象特殊无法用这种方法获取,需要下面手动append
               var request_data = $("#form").serializeArray();
               $(request_data).each(function (index,data) {
                   post_data.append(data.name,data.value)
               });
      
      
    • django的UserForm组件对于前端ajax提交的数据进行清理校验

      form.is_valid()>>>form.cleaned_data和form.errors,对form.errors的错误信息进行对应的显示

      def reg(request):
          """注册账号用户"""
          if request.is_ajax():  #也可以用request.menth=="POST",因为ajax也是一次局部post
              reg_info = reg_form(request.POST)
              response={"user":None,"message":None}
              if reg_info.is_valid():
                  #如果账号密码通过了局部钩子和全局钩子
                  response["user"]=reg_info.get("user")
              else:
                  #未通过钩子校验
                  response["message"] = reg_info.errors
              return JsonResponse(response)
      
          new_regs = reg_form()
          return render(request, "reg.html", {"new_regs": new_regs})
      
    • 前端对返回的错误字典进行前端提示

      success:function (data) {
          
         			//先清空span标签中的错误信息和边框颜色
          		   $(".error_info").text("");
                      $(".form-control").css({"border":"1px solid black"});
                      if(data.user){
                          //获取到user说明POST数据校验成功
                      }else{
                          //否则单元格显示错误信息
                          //Django返回的key是{user:None,message:None}
                          $.each(data.message,function (key,error_list) {
      //通过id_标签名的拼接,定位dom元素,然后在他的span标签中显示错误样式和错误信息                        
                        $("#id_"+key).next(".error_info").text(error_list[0]).css({"color":"red","font-weight":"bolder"});
                              $("#id_"+key).css({"border":"1px solid red"})
                              // $("#id_"+key).parent().addClass("has-error")
                          })
                      }
                  }
      

      先清空span标签中的错误提示和边框标红的效果.然后遍历错误的字典,遍历错误的键值,因为我UserForm组件渲染的标签id命名规则是id_+字段,所以我们也同样构造获取对象$("#id_"+key).next(".error_info"),并利用next方法获取同级别元素中的class=error_info类名进行错误信息的赋值和边框的颜色变更.

      ajax中的success:function(data)中的data返回值如下,message是他的键,值是2个错误信息

    • Django对于用户名,密码和二次密码进行局部钩子和全局钩子验证,通过验证后写入数据库

      class reg_form(forms.Form):
          reg_account = forms.CharField(
              max_length=32, label="注册新账名",
              error_messages={"required": "账号不能为空", "invalid": "账号格式有误"},
              widget=widgets.TextInput(attrs={"class": "form-control"}, ))
      
          pwd = forms.CharField(min_length=3,max_length=32, label="输入密码",
                                error_messages={"required": "密码不能为空", "invalid": "密码请按大小加特殊字符"},
                                widget=widgets.PasswordInput(attrs={"class": "form-control"}, ))
      
          re_pwd = forms.CharField(min_length=3,max_length=32, label="确认密码",
                                   error_messages={"required": "确认密码不能为空", "invalid": "密码请按大小加特殊字符"},
                                   widget=widgets.PasswordInput(attrs={"class": "form-control"}, ))
      
          email = forms.EmailField(max_length=32, required=False, label="邮箱",
                                  widget=widgets.EmailInput(attrs={"class": "form-control"},
                                                            ))
          def clean_reg_account(self):
              """局部钩子,函数命名规则clean_校验字段"""
              user = self.cleaned_data.get("reg_account")
              user_check = UserInfo.objects.filter(username = user)
              if not user_check:
                  return user
              else:
                  raise ValidationError("用户名已注册")
      
          def clean(self):
              # 全局钩子,对几个字段进行额外的校验
              pwd = self.cleaned_data.get("pwd")
              re_pwd = self.cleaned_data.get("re_pwd")
              #密码和二次输入的密码不为空但是2次输入不一致
              #全局钩子的错误key值是__all__开头的,因此需要在前端额外对这个字段进行过滤提取,显示在网页上
              if pwd and re_pwd and  not (pwd == re_pwd):
                  raise ValidationError("2次密码输入不一致")
              # 密码和二次输入的密码不为空且相同
              else:
                  # 按照模块的写法,如果没问题返回cleaned_data
                  return self.cleaned_data
      
      def reg(request):
          """注册账号用户"""
          if request.is_ajax():  #也可以用request.menth=="POST",因为ajax也是一次局部post
              reg_info = reg_form(request.POST)
              response={"user":None,"message":None}
              if reg_info.is_valid():
                  #通过了全局钩子和局部钩子
                  response["user"]=reg_info.cleaned_data.get("reg_account")
                  user = reg_info.cleaned_data.get("reg_account")
                  pwd = reg_info.cleaned_data.get("pwd")
                  email = reg_info.cleaned_data.get("email")
                  avatar = request.FILES.get("avatar")
                  # 如果账号密码通过了局部钩子和全局钩子,就把注册信息写入到数据库中
                  # 要用create_user方法,会进行加密存储
                  UserInfo.objects.create_user(username=user,password=pwd,email=email,avatar=avatar)
              else:
                  #未通过钩子校验,把错误字段放入message中
                  response["message"] = reg_info.errors
              return JsonResponse(response)
          new_regs = reg_form()
          return render(request, "reg.html", {"new_regs": new_regs})
      
      • 全局钩子的错误key值是__all__开头的,因此需要在前端额外对这个字段进行过滤提取,显示在网页上

        success:function (data) {
                        console.log(data);
                        //先把错误信息显示的效果清除
                        $(".error_info").text("");
                         $(".form-control").css({"border":"1px solid black"});
                        if(data.user){
                            //获取到user说明注册成功,跳转到登陆页面
                            location.href="/cnblog/login/"
                        }else{
                            // console.log(data);
                            //否则单元格显示错误信息
                            //先清空span标签中的错误信息和边框颜色
        
                            //Django返回的key是{user:None,message:None}
                            $.each(data.message,function (key,error_list) {
                     //提取全局钩子的错误key值,过滤出来显示前端
                                if(key=="__all__"){
                                    $("#id_re_pwd").next(".error_info").text(error_list[0]).css({"color":"red","font-weight":"bolder"});
                                };
        
                                $("#id_"+key).next(".error_info").text(error_list[0]).css({"color":"red","font-weight":"bolder"});
                                $("#id_"+key).css({"border":"1px solid red"})
                                // $("#id_"+key).parent().addClass("has-error")
                            })
                        }
                    },
        
      • avatar路径设置

        我们在自定义ORM表的时候是这么定义avatar字段的,上传的路径为avatars 文件夹,
        
        在当前项目的根路径下如果有avatars文件夹,则会把文件以`Blogavatars1.jpg`形式存储.
        
        如果没有这个文件夹则会自动创建文件夹
        
        ```python
        class UserInfo(AbstractUser):
            nid = models.AutoField(primary_key=True)
            telphone = 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,on_delete=models.CASCADE)
        #视图文件的代码
        def reg(request):
            """注册账号用户"""
            if request.is_ajax(): 
                reg_info = reg_form(request.POST)
                response={"user":None,"message":None}
                if reg_info.is_valid():
                    response["user"]=reg_info.cleaned_data.get("reg_account")
                    user = reg_info.cleaned_data.get("reg_account")
                    pwd = reg_info.cleaned_data.get("pwd")
                    email = reg_info.cleaned_data.get("email")
                    avatar = request.FILES.get("avatar")
                    # 如果账号密码通过了局部钩子和全局钩子,就把注册信息写入到数据库中
                    if avatar:
                    # 要用create_user方法,会进行加密存储
                    UserInfo.objects.create_user(username=user,password=pwd,email=email,avatar=avatar)
                    else:
                        #考虑到avatar不是必填项,用户如果没有上传头像,则Userinfo表不添加,不添加则会使用字段设置的defalut的内容
                        UserInfo.objects.create_user(username=user, password=pwd, email=email)
                else:
                    response["message"] = reg_info.errors
                return JsonResponse(response)
            new_regs = reg_form()
            return render(request, "reg.html", {"new_regs": new_regs})
        
        #视图文件的代码的部分优化内容
        create_user 函数是可以传字典的,那么我们可以设置空字典,has_avatar={}
        如果有avatar字段,则has_avatar={"avatar":avatar}
        
        avatar = request.FILES.get("avatar")
        has_avatar={}
        if avatar:
        	has_avatar["avatar"]=avatar
        UserInfo.objects.create_user(username=user,password=pwd,email=email,**has_avatar)
        
        ```
        
        • avatar自定义路径配置以及media配置解耦上传路径

          Django有2中静态文件,一个是/static/需要给浏览器下载的 ,另一个是/media/由用户上传的.

          和static一样,我们也需要把avatar上传的文件做下解耦,单独放到一个文件夹去,

          我们可以去setting.py配置MEDIA_ROOT字段来实现这样的目的,通过下面设置,上传的文件路径就变成了cnblogs这个应用的upload_folder文件夹,完整的路径为 cnblogsupload_folderavatars1.jpg

          MEDIA_ROOT = os.path.join(BASE_DIR,"cnblogs/upload_folder/")

        • avatar自定义路径放到每个用户自己的username下

          考虑每个用户上传的文件命名可能重名,最好还是将avatar文件放到用户名下的路径

          在model中自定义一个函数,然后用upload_to去接受这个函数返回值

          在这个自定义函数中也可以对上传的filename进行重命名,防止任意路径访问漏洞

          def user_directory_path(instance,filename):
              print("instance",instance)
              print("filename",filename)
              return os.path.join(instance.username,"avatars",filename)
          
          class UserInfo(AbstractUser):
              # avatar = models.FileField(upload_to="avatars/", default="avatars/default.png")
              avatar = models.FileField(upload_to=user_directory_path, default="avatars/default.png")
          
        • media配置访问url路由

          上一步我们配置了上传的路径,但是里面的客户上传的文件内容或者图片要进行url访问呢?之前的static是django默认配置好了访问路由.那么media我们需要做如下配置

          1.1 setting.py 设置MEDIA_URL = "/media/"

          1.2 路由导入,在urls.py需要导入

          from Blog import settings
          from django.views.static import serve
          

          1.3 设置路由, media是1.1随意命名的,这个设置好后media就代表了绝对路径cnblogs/upload_folder/

          这么设置就表示但凡绝对路径是cnblogs/upload_folder/下的子文件夹,都开通了访问路由.

          re_path("media/(?P<path>.*)/$",serve,{"document_root":settings.MEDIA_ROOT})
          
        • 设置博客导航

          基于bootstrap修改主页样式,用到了导航条,设置了文章类型:随笔 新闻 日记
          右侧增加个人信息
          	如果登陆>>显示用户名字,用户头像,以及下拉菜单(注销,改头像,改密码)
          	如果未登陆>>显示注册,登陆按钮
          
        • 博客body部分,分为左中右,分别列宽占比4-6-2

          用超级账户登陆,先随机导入点数据.刚进去的管理员后台是这样的,看不到之前创建的数据库表

          我们需要在应用名/admin.py把ORM的model表进行注册并导入进来,

          from cnblogs import models
          admin.site.register(models.UserInfo)
          admin.site.register(models.Category)
          admin.site.register(models.Tag)
          admin.site.register(models.ArticleUpDown)
          admin.site.register(models.Artical)
          admin.site.register(models.Artical2Tag)
          admin.site.register(models.Comment)
          admin.site.register(models.Blog)
          

        刷新admin页面就可以看到各个数据库表,接着进行后台数据的自定义添加.
        
        ![](https://img2020.cnblogs.com/blog/1610779/202111/1610779-20211102173402490-604225317.png)
        
        
        添加的时候最好从大到小的顺序添加  比如:
        
        以User表开始,从用户>用户的Bolgs>Article>(Catagory)(Tag)(Artical2Tag)>Comment
        
        还有注意表和表之间的关系,比如创建一个user,然后创建该用户的Bolgs信息.在数据库中,user和Bolgs是一对一的关系.所以创建完毕后在user里要去绑定这个关系,其他的表的关系也是如此
        
        ![](https://img2020.cnblogs.com/blog/1610779/202111/1610779-20211102173459049-1131097736.png)
        
        • ORM获取数据库所有的文章,渲染主页中中间文章的内容

        • 然后做个人主页,配置路由 re_path('^index/(?P<user_home_site>w+)/$', user_home_site),可以先配置404错误网页,如果个人ID未找到显示404页

          注意:判断个人ID是否存在用exists比上面的查询效率高,

          UserInfo.objects.filter(username=user).exists()

          但是如果考虑到user对象是用来查博客的个人站点,基本存在的可能性高于不存在,并且需要根据user对象对博文,分类进行渲染.所以实际还是用这个效率高UserInfo.objects.filter(username=user).first()

        • 个人主页的数据渲染
          做个人主页,大致框架分左右两边,左边 查询当前站点每个分类以及对应的文章数

          artical_num_each_categoryies = Category.objects.filter(blog=user_blog).values("pk").annotate(
                  c=Count("artical__nid")).values("title", "c")
          

          查询当前站点每个标签以及对应的文章数

          artical_num_each_tags = Artical.objects.filter(user=user_check).values("pk").annotate(c=Count("tag__nid")).values(
                  "title", "tag__title", "c")
          

          查询当前站点每一个年月的名称以及对应的文章数

          这里要实现的效果是 2012 6 月(10),根据年月来进行聚合.但是实际上我们存到数据库的时候是

          2021-10-26 16:16:41.691016 这种格式,因为精确到毫秒级别,所以每个时间都是不同的,无法聚合.

          在不对原有的数据库进行修改的前提下,要时间聚合思路有2个,

          一个是根据mysql语句对时间进行格式化

          数据库中可以用date_format对时间的显示做输出格式化.
          mysql> select * from date_time;
          +------+---------------------+----------+------------+
          | id   | dt                  | t        | d          |
          +------+---------------------+----------+------------+
          |    1 | 2021-10-30 10:54:44 | 10:54:44 | 2021-10-30 |
          |    2 | 2021-10-30 10:54:49 | 10:54:49 | 2021-10-30 |
          +------+---------------------+----------+------------+
          2 rows in set (0.00 sec)
          
          mysql> select date_format(dt,"%Y-%m-%d") from date_time;
          +----------------------------+
          | date_format(dt,"%Y-%m-%d") |
          +----------------------------+
          | 2021-10-30                 |
          | 2021-10-30                 |
          +----------------------------+
          

          另一个就是通过orm传输SQL语法进行格式化,这里需要用extra方法 extra(select=None,where=None,params=None,tables=None,order_by=None,select_params=None)

          完整格式query_set对象.extra(select="key: 'sql语句' ")

          Artical.objects.extra(select={"filter_ret":"date_format(create_time,'%%Y-%%m-%%d')"}).values("title","filter_ret")
          
          有时候ORM查询语法无法表达复杂的查询语句,我们可以通过extra指定一个或者多个参数,例如select,where,tables,这些参数都不是必须,但是必须要有一个,
          以select为例,我们在数据库的artical表中有个文章的create_time字段,我们利用extra进行时间过滤
          ret=models.Artical.object.extra(select={"filter_ret":"create_time > '202-10-26' "})
          

          extra的详细案例

          Django里关于时间的筛选有内置的Trunc方法 ,导包和实例如下:

          from django.db.models.functions import TruncDate, TruncDay, TruncHour, TruncMinute, TruncSecond
          
          Experiment.objects.annotate(
          ...     date=TruncDate('start_datetime'),
          ...     day=TruncDay('start_datetime', tzinfo=melb),
          ...     hour=TruncHour('start_datetime', tzinfo=melb),
          ...     minute=TruncMinute('start_datetime'),
          ...     second=TruncSecond('start_datetime'),
          ... ).values('date', 'day', 'hour', 'minute', 'second').get()
          {'date': datetime.date(2014, 6, 15),
           'day': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=<DstTzInfo 'Australia/Melbourne' AEST+10:00:00 STD>),
           'hour': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=<DstTzInfo 'Australia/Melbourne' AEST+10:00:00 STD>),
           'minute': 'minute': datetime.datetime(2014, 6, 15, 14, 30, tzinfo=<UTC>),
           'second': datetime.datetime(2014, 6, 15, 14, 30, 50, tzinfo=<UTC>)
          

          在实际应用中我们对于 2021-06-10 15:30:5657这种datetime时间格式分别用extra和自带的TruncDate进行格式化

          # 第一种,extra格式化 artical_ret = user_articals.extra(select={"filter_ret":"create_time > '2021-10-26 16:20' "})
           date_list=Artical.objects.filter(user=user_check).extra(select={"strift_date":"date_format(create_time,'%%Y-%%m')"}).values("strift_date").annotate(c=Count("nid")).values("strift_date","c")[0]
           print(date_list)  #{'strift_date': '2021-10', 'c': 2}
          
           # 第二种,自带的TruncMonth方法 
          date_list2=Artical.objects.filter(user=user_check).annotate(month=TruncMonth("create_time")).values("month").annotate(c=Count("nid")).values("month","c").first()
          print(date_list2)  #{'month': datetime.datetime(2021, 10, 1, 0, 0), 'c': 1}
          print(datetime.datetime.strftime(date_list2["month"],"%Y-%m"))  #2021-10
          

    4. 功能测试

    5.项目部署

  • 相关阅读:
    【转】Google 的眼光
    【转】不要去SeaWorld
    【转】Tesla Autopilot
    【转】Tesla Model X的车门设计问题
    【转】Tesla Model S的设计失误
    【转】编程的智慧
    【转】智商的圈套
    【转】创造者的思维方式
    【转】恶评《星际穿越》
    【转】谈创新
  • 原文地址:https://www.cnblogs.com/Young-shi/p/15391299.html
Copyright © 2011-2022 走看看