博客项目——〇二首页设计

首页的设计我们就按照简化的博客园的效果来搞!但是利用了Bootstrap的框架。大概思路是这样的

 最上面是导航条,然后下面是左中右的方式按照2-8-2的布局来分。

导航条

 导航条我们就用Bootstrap里给的基础的样式就可以,

 注意右边的a标签,如果当前状态是已经登录的话就是上面的样式,如果没有登录,就是一个登录和一个注册。

<!-- 导航条开始 -->
<nav class="navbar navbar-default">
    <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>
  

      <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>
        </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">用户中心 <span class="caret"></span></a>
            <ul class="dropdown-menu">
              <li><a href="/blog/{{request.user.username}}">我的博客</a></li>
              <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="/signup/">注册</a></li>
          
        {%endif%}

        </ul>
      </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
  </nav>
  <!-- 导航条结束 -->
导航条部分代码

注意里面的判断的结构。对当前的登录状态进行用户校验。

主页框架

 最上面的那个图中,我们就按照2-8-2的布局进行排列,结构就像下面的代码所示

<!-- 主体开始 -->
<div class="container-fluid">
<div class="row">
    <div class="col-md-2 left-body"></div>
    <div class="col-md-8 main-body"></div>
    <div class="col-md-2 right-body"></div>
</div>
</div>
<!-- 主体结束 -->

左右侧的部分我们可以用来显示一些辅助内容,例如广告、分类什么的。下面就是这一节最主要的部分,模板的填充。

显示内容的主页

和博客园一样,在主页里我们用来显示所有的文章 。

这里先把main-body里的html代码放出来,我们一步步来分析

 1 <!-- 文章列表  开始 -->
 2 <div class="article_list">
 3 {% for article in article_list %}
 4     <div class="article">
 5     <h3><a href="">{{article.title}}</a></h3>
 6     <div class="media">
 7         <div class="media-left">
 8         <a href="#">
 9             <img class="media-object author-img" src="/media/{{article.user.avatar}}" alt="...">
10         </a>
11         </div> 
12         <div class="media-body">
13         {{article.desc}}
14         </div>
15         <div class="article-footer">
16         <span><a href="">{{article.user.username}}</a></span>
17         <span>发布于{{article.create_time|date:'Y-m-d H:i:s'}}</span>
18         <span class="glyphicon glyphicon-comment">评论({{article.comment_count}})</span>
19         <span class="glyphicon glyphicon-thumbs-up">点赞({{article.up_count}})</span>
20         <span class="glyphicon glyphicon-thumbs-down">踩灭({{article.down_count}})</span>
21         </div>
22     </div>
23     </div>
24 {% endfor %}
25 </div>
26 <!-- 文章列表  结束 -->

这个模板对应的视图很简单,只有一行代码就行了

def home(request):
    article_list = models.Article.objects.all()return render(request,'home.html',{'article_list':article_list})

也就是说整个过程就是我们在视图中通过ORM索引到所有的文章列表,然后把这个列表render给主页的模板。先看一下显示出来的效果

 

上面的图就是通过模板渲染出来的效果。下面对几个模板使用的知识点分析一下。

模板使用

这里使用了Bootstrap组件的媒体对象的默认样式,左边一个头像,右边放p标签或者span标签来显示相应内容。

 就是在上面的列表中通过for循环生成的代码段。

<div class="media">
  <div class="media-left">
    <a href="#">
      <img class="media-object" src="..." alt="...">
    </a>
  </div>
  <div class="media-body">
    <h4 class="media-heading">Media heading</h4>
    ...
  </div>
</div>

我们只需要显示出来文章中下面几条内容就可以了

 看一下models里对于文章Article是怎么定义的

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

    # 评论数
    comment_count = models.IntegerField(verbose_name="评论数", default=0)
    # 点赞数
    up_count = models.IntegerField(verbose_name="点赞数", default=0)
    # 踩灭数量
    down_count = models.IntegerField(verbose_name="踩数", default=0)

    category = models.ForeignKey(to="Category", to_field="nid", null=True,on_delete=models.CASCADE)
    user = models.ForeignKey(to="UserInfo", to_field="nid",on_delete=models.CASCADE)
    tags = models.ManyToManyField(  # 中介模型
        to="Tag",
        through="Article2Tag",
        through_fields=("article", "tag"),  # 注意顺序!!!
    )

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "文章"
        verbose_name_plural = verbose_name

这里就要我们回顾一下ORM里的两种数据查询方法了:

基于对象的查询和基于QuerySet的查询

看一下下面的代码段,我们回顾一下前面的ORM的查询方式

# ORM基于对象的查询和基于QuerySet的查询方法


import os

if __name__ =='__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BLOG.settings")
    import django
    django.setup()


    from blog import models

    #基于对象的查询:SQL里的子查询(select里嵌套select)

    ret = models.Article.objects.first()
    print(ret.user,type(ret.user))   #123 <class 'blog.models.UserInfo'>
    # ret.user是一个对象,就可以直接用对象+.的形式来获取我们需要的内容,像下面一样
    print(ret.user.username)
    print(ret.user.avatar)

    #基于QuerySet的方式查询:SQL里的join查询
    ret2 = models.Article.objects.filter(nid = 1)
    print(ret2,type(ret2))
    # <QuerySet [<Article: 博客项目——〇一需求分析(占位)>]> <class 'django.db.models.query.QuerySet'>
    avatar = ret2.values('user__avatar')
    print(avatar)
ORM基于对象查询和基于QuerySet查询

为了便于我们在模板中获取相应的数据,先通过all()方法获取到一个QuerySet对象(article_list),在通过render函数的时候,我们用for循环把这个拿到具体的article对象,就可以用基于对象的方式获取到所需要的内容

标题
{{article.title}}
简介
{{article.desc}}
头像
{{article.user.avatar}}

头像图片的获取

如果仅仅通过下面的代码是显示不了头像的

<img class="media-object author-img" src="{{article.user.avatar}}" alt="...">

这样图片是无法打开的

检查一下页面上的img标签属性,能发现图片的url是没问题的:http://127.0.0.1:8000/home/avatar/img/hmbb.png,而所有用户上传的头像都放在了根目录下的avatar文件夹中。把这个url复制到地址栏中,f直接访问一下是没有任何效果的

但是如果把这个文件放在static文件夹里,是可以通过url直接访问的(截图太大就不掩饰了,放一个访问css文件的截图)。

因为我们在settings.py里指定了静态文件的路径和URL

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

如果把这个注释掉,就没办法访问了。这样就流出了可以通过URL访问的一些文件的接口,没有暴露的是无法访问的。而静态文件是每次浏览器都要去拿到然后渲染出效果的。这样就保护了我们的代码文件。

在这里我们就做另外一个路径:在Django里,所有用户上传的文件都叫做media,所以我们需要把在settings.py中对media的属性进行设置

#Django 用户上传的文件都叫media文件
MEDIA_URL = '/media/'
#media配置,用户上传的文件都放在这个文件夹下
MEDIA_ROOT = os.path.join(BASE_DIR,'media')

然后新建一个文件夹,把原先的avatar这个文件夹拖进去

 这样就完成了第一步,访问一下这个路径http://127.0.0.1:8000/media/avatars/hmbb.png,发现还是404报错,因为我们这个media的相关路由还没有设置,必须要新指定(STATIC不用手动指定路由是因为STATIC里的静态文件基本上是浏览器在渲染页面的时候必须用的,所有在Django内部就直接设定好了)

记得要先导入serve模块,下面的语法直接记住就行了。

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

url(r'^media/(?P<path>.*)$',serve,{'document_root':settings.MEDIA_ROOT})    #设置静态文件路由

发布时间的显示

发布时间我们用了一个模板中的tag,前面应该有的

{{article.create_time|date:'Y-m-d H:i:s'}}

最后通过一个css文件指定了一下margin-left,避免所有标签挤在一起不美观

最后把整个html文件放下面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/css/home.css">
    <title>BLOG主页</title>
</head>
<body>


<!-- 导航条开始 -->
<nav class="navbar navbar-default">
    <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>
  

      <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>
        </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">用户中心 <span class="caret"></span></a>
            <ul class="dropdown-menu">
              <li><a href="/blog/{{request.user.username}}">我的博客</a></li>
              <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="/signup/">注册</a></li>
          
        {%endif%}

        </ul>
      </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
  </nav>
  <!-- 导航条结束 -->





  <!-- 主体开始 -->
  <div class="container-fluid">
    <div class="row">
        <div class="col-md-2">
            <div class="panel panel-default">
                <div class="panel-heading">
                  <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body">
                  Panel content
                </div>
              </div>
        </div>
        <div class="col-md-8">
          <!-- 文章列表  开始 -->
          <div class="article_list">
            {% for article in article_list %}
              <div class="article">
                <h3><a href="">{{article.title}}</a></h3>
                <div class="media">
                  <div class="media-left">
                    <a href="#">
                      <img class="media-object author-img" src="/media/{{article.user.avatar}}" alt="...">
                    </a>
                  </div> 
                  <div class="media-body">
                    {{article.desc}}
                  </div>
                  <div class="article-footer">
                    <span><a href="">{{article.user.username}}</a></span>
                    <span>发布于{{article.create_time|date:'Y-m-d H:i:s'}}</span>
                    <span class="glyphicon glyphicon-comment">评论({{article.comment_count}})</span>
                    <span class="glyphicon glyphicon-thumbs-up">点赞({{article.up_count}})</span>
                    <span class="glyphicon glyphicon-thumbs-down">踩灭({{article.down_count}})</span>
                  </div>
                </div>
              </div>
            {% endfor %}
          </div>
          <!-- 文章列表  结束 -->

        </div>
        <div class="col-md-2">

            <div class="panel panel-default">
                <div class="panel-heading">
                  <h3 class="panel-title">Panel title</h3>
                </div>
                <div class="panel-body">
                  Panel content
                </div>
              </div>

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


  <script src="/static/jquery-3.2.1.min.js"></script>
  <script src="/static/bootstrap/js/bootstrap.min.js"></script>

</body>
</html>
home.html
数据的添加

 在前面讲数据可视性操作的时候讲过了怎么添加数据,因为整个数据结构互相牵扯的比较多,简易还是从admin页面里添加新的数据,但是要记得修改admin文件,把所有的model都注册了

from django.contrib import admin
from blog import models
# Register your models here.
admin.site.register(models.UserInfo)
admin.site.register(models.Article)
admin.site.register(models.Blog)
admin.site.register(models.Tag)
admin.site.register(models.Category)
admin.site.register(models.Comment)
admin.site.register(models.ArticleUpDown)
admin.site.register(models.ArticleDetail)
admin.site.register(models.Article2Tag)

对Article新加几条数据,就能在主页上显示出来

用户管理页面

 由于在导航栏里有用户管理功能,用户的注册和登录方法我们在前面已经讲过了,这里就把代码直接放出来了,不在过多解释。

FORM组件

在注册的页面里我只用了一部分form组件,但是还是把一些以后可能会用到的字段放出来了,便于以后的使用,登录的页面未使用这个form组件

"""
用到的form类
"""
from django import forms
from django.core.exceptions import ValidationError

from . import models

class RegForm(forms.Form):
    username = forms.CharField(
        max_length=16,
        label='用户名',
        error_messages={
            'max_length': '用户名最长为16位!',
            'required': '用户名不能为空!'
        },
        widget=forms.widgets.TextInput(
            attrs={
                'class': 'form-control'
            }
        )
    )

    password = forms.CharField(
        min_length=6,
        label='密    码',
        widget=forms.widgets.PasswordInput(
            attrs={
                'class': 'form-control'
            }
        ),
        error_messages={
            'min_length': '密码不能少于6位!',
            'required': '密码不能为空!'
        }
    )

    re_password = forms.CharField(
        max_length=32,
        label='确认密码',
        widget=forms.widgets.PasswordInput(
            attrs={
                'class': 'form-control'
            }
        ),
        error_messages={
            'max_length': '用户名最长为16位!',
            'required': '用户名不能为空!'
        }
    )

    email = forms.EmailField(
        label='邮箱',
        widget=forms.widgets.EmailInput(
            attrs={
                'class': 'form-control'
            }
        ),
        error_messages={
            'invalid': '邮箱格式不正确',
            'required': '邮箱不能为空'
        }
    )
# #重构局部的钩子对username字段进行校验
    def clean_username(self):
        username = self.cleaned_data.get('username')
        user = models.UserInfo.objects.filter(username=username)
        if user:
            self.add_error('username',ValidationError('用户名已存在!'))

        return username #一定记得加这个返回值,否则在视图中cleaned_data里返回值为空

#重写全局钩子,对确认密码进行校验
    def clean(self):
        username = self.cleaned_data.get('username')
        pwd = self.cleaned_data.get('password')
        re_pwd = self.cleaned_data.get('re_password')


        if re_pwd and re_pwd != pwd:
            self.add_error('re_password',ValidationError("两次密码不一致"))

        else:
            return self.cleaned_data
 
form.py

form里的局部钩子一定一定要记得加返回值,在调试的过程中忘记加返回值了,拿到cleaned_data的时候一直显示username为null,搞了快一天才高清是怎么回事。

用户管理视图

我把所有用户管理方面的视图都放在了一个py文件里,便于后期的维护

# user_manage.py
#用户管理视图


from django.shortcuts import render,redirect,HttpResponse
from django.contrib import auth
from django.http import JsonResponse
from . import models,forms

from django.views import View


#注册
def signup(request):
    if request.method == 'POST':
        print(request.POST)
        
        ret = {'status':0,'msg':''}

        reg_FORM_obj = forms.RegForm(request.POST)
        if reg_FORM_obj.is_valid():
            reg_FORM_obj.cleaned_data.pop('re_password')
            avatar_img = request.FILES.get('avatar')
            models.UserInfo.objects.create_user(**reg_FORM_obj.cleaned_data,avatar=avatar_img)
            ret['msg'] = '/home/'
            
        else:
            ret['status'] =1
            ret['msg'] = reg_FORM_obj.errors
        return JsonResponse(ret)        
    user_form = forms.RegForm()
    
    return render(request,'signup.html',{'reg_form':user_form})

#校验用户名是否存在
def name_check(request):
    ret = {}
    if request.method == 'POST':
        username = request.POST.get('username')
        user = models.UserInfo.objects.filter(username = username)
        if  user:
            ret['state'] = 1
            ret['error'] = '用户名已存在'

        
        return JsonResponse(ret)    



#登录
def login(request):
    message={}
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')

        if models.UserInfo.objects.filter(username = username):
        
            user = auth.authenticate(username=username,password=password)

            if user:
                auth.login(request,user)
                return redirect('/home/')
            else:
                message['error'] = '密码错误'

        else:
            message['error'] = '用户名不存在'

    return render(request,'login.html',{'message':message})
        
    

#注销
def logout(request):
    auth.logout(request)
    return redirect('/home/')
user_manage.py

对应的用户管理的路由没什么要注意的,但是注册('/signup/')和用户名校验('/signup/namechk')

模板文件

用户的注册是通过了FORM组件完成的,但是登录功能因为只有两个input标签就直接用form标签完成了。数据的提交都是通过AJAX完成。

用户注册的模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户注册</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/mystyle.css">
</head>
<body>

    <div class="container">
        <div class="row">
            <div class="col-md-6 col-md-offset-3">
                <form action="{{RegURL}}" method="POST"  novalidate class="form-horizontal reg-form" enctype="multipart/form-data">

                    {% csrf_token %}
                    <div class="form-group"><h3>用户注册</h3></div>
                    

                    <div class="form-group">
                        <label for="{{reg_form.username.id_for_label}}" class="col-sm-2 control-label">{{reg_form.username.label}}</label>
                        <div class="col-sm-10">
                            {{reg_form.username}}
                            <span class="help-block">{{reg_form.username.errors.0}}</span>
                        </div>
                    </div>

                    <div class="form-group">
                        <label for="{{reg_form.password.id_for_label}}" class="col-sm-2 control-label">{{reg_form.password.label}}</label>
                        <div class="col-sm-10">
                            {{reg_form.password}}
                            <span class="help-block">{{reg_form.password.errors.0}}</span>
                        </div>
                    </div>



                    <div class="form-group">
                        <label for="{{reg_form.re_password.id_for_label}}" class="col-sm-2 control-label">{{reg_form.re_password.label}}</label>
                        <div class="col-sm-10">
                            {{reg_form.re_password}}
                            <span class="help-block">{{reg_form.re_password.errors.0}}</span>
                        </div>
                    </div>


                    <div class="form-group">
                        <label for="{{reg_form.email.id_for_label}}" class="col-sm-2 control-label">{{reg_form.email.label}}</label>
                        <div class="col-sm-10">
                            {{reg_form.email}}
                            <span class="help-block"></span>
                        </div>
                    </div>


                    <div class="form-group">
                        <label for="" 
                               class="col-sm-2 control-label">头像</label>
                        <div class="col-sm-10">
                            <label for="id_avatar"><img src="/static/img/default.png" alt="" id="avatar-img"></label>
                            <input type="file"  name="avatar" id="id_avatar" style="display: none;">
                        </div>
                    </div>

                </form>
                <div class="form-group">
                    <div class="col-sm-offset-2 col-sm-10">
                    <button class="btn btn-success" id="btn">AJAX提交</button>
                    </div>
                </div>
            </div>
        </div>

    </div>


<script src="/static/jquery-3.2.1.min.js"></script>
<script src="/static/bootstrap/js/bootstrap.js"></script>
<script>

// 上传图像事件
$('#id_avatar').change(function(){
    var filePath = this.files[0];
    var fileReader = new FileReader();
    fileReader.readAsDataURL(filePath);
    fileReader.onload = function(){
        $('#avatar-img').attr('src',fileReader.result);
    };
});


// 登录按钮事件
$('#btn').click(function(){
    //点击按钮 ,触发AJAX请求事件
    var formData = new FormData()

    formData.append('username',$('#id_username').val());
    formData.append('password',$('#id_password').val());
    formData.append('re_password',$('#id_re_password').val());
    formData.append('email',$('#id_email').val());
    formData.append('avatar',$('#id_avatar')[0].files[0]);
    formData.append('csrfmiddlewaretoken','{{csrf_token}}');

    $.ajax({
        url:'/signup/',
        type:'post',
        processData:false,
        contentType:false,
        data:formData,
        success:function(data){
            console.log(data);
            if (data.status){
                //有错误状态,错误为data.msg
                //将错误填写至标签内
                $.each(data.msg,function(k,v){
                    $('#id_'+k).next('span').text(v[0])
                })

            }
            else{//没有错误跳转到指定页面
                location.href = data.msg;
            }
        }
    })
})



//将所有的input框绑定的获取焦点信息
$('input').focus(function(){
     $(this).next('span').text('');
})
</script>
</body>
</html>
signup.html

还有用户登录的模板

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="../../favicon.ico">

    <title>Signin Template for Bootstrap</title>

    <!-- Bootstrap core CSS -->
    <link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">


    <!-- Custom styles for this template -->
    <link href="/static/css/login.css" rel="stylesheet">
  </head>

  <body>

    <div class="container">

      <form class="form-signin" method="POST" action="/login/">
        {%csrf_token%}
        <h2 class="form-signin-heading">请登录</h2>
        <label for="inputEmail" class="sr-only">用户名</label>
        <input type="text" id="inputusername" class="form-control" name="username" placeholder="用户名" required autofocus>
        <label for="inputPassword" class="sr-only">Password</label>
        <input type="password" id="inputPassword" class="form-control" name="password" placeholder="Password" required>
        <div class="checkbox">
          <label>
            <input type="checkbox" value="remember-me"> Remember me
          </label>
        </div>
        <div class="error_message">{{message.error}}</div>
        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
      </form>

    </div> <!-- /container -->

  </body>
</html>
login.html

但是本章节最后的三个html文件(home,login和signup)还有许多细节需要完善,比如有些a标签的链接地址都还没有定义,图片也没有做只是预留了个位置,在后面我会慢慢补齐!

原文地址:https://www.cnblogs.com/yinsedeyinse/p/13111345.html