去病率兵十万深入敌后痛击匈奴,经此一役,胡虏元气甚伤,单于伊治斜弃河西平原而迁王庭至漠北。汉乃设酒泉、张掖、敦煌、武威河西四郡。众将凯旋回师,帝召大将军卫青、骠骑将军霍去病、飞将军李广、苏建、公孙敖、公孙贺等将议于宣室殿。众将皆以需涉荒漠为困而欲弃之。然武帝废胡之心愈坚,怒曰:汝皆以为不可,而胡虏亦曰寡人不可为之,然朕必举全国之力誓击之,永绝胡虏之患!
遂点精骑十万,以大将军五万兵出定襄,骠骑将军五万出代郡,辎重步卒五十万,挥师远征漠北!
一、兵马未动,辎重先行。——表结构的设计
(参照抽屉新热榜)
在论坛中文章包含的属性(数据库中的字段)有标题、简介、板块、内容、作者、发布时间、最后一次修改时间、优先级(数字权重)。
model里的字段类型
auto_now和auto_now_add区别:
auto_now:记录字段每次被修改的时间
auto_now_add:记录字段被创建时的时间
若字段被设置为这两种类型的一种,则字段不可再更改。
models.py
#! _*_ coding:utf-8 _*_
from __future__ import unicode_literals from django.db import models from django.contrib.auth.models import User #倒入django-admin自带的User表 from django.core.exceptions import ValidationError #页面提示错误信息 import datetime # Create your models here. #文章 class Article(models.Model): title = models.CharField(max_length=255) #标题 brief = models.CharField(blank=True,null=True,max_length=255)#文章简介,可以为空 category = models.ForeignKey('Category')#板块,需要关联Cotegory类(若要关联尚未定义的model,需要使用引号) content = models.TextField(u'文章内容')#文章内容 author = models.ForeignKey('UserInfo')#作者 pub_date = models.DateTimeField(blank=True,null=True)#发布日期,若是草稿,则没有发布时间 last_modify = models.DateTimeField(auto_now=True)#最后一此修改时间 priority = models.IntegerField(u'优先级',default=1000)#优先级,默认1000
head_img = models.ImageField('文章标题图片',upload_to='uploads')#存储标题图片,图片上传到uploads目录下,该目录在app下会自动创建
status_choices = ( ('draft','草稿'), ('published','已发布'), ('hidden','隐藏'), ) status = models.CharField(choices=status_choices,default='published',max_length=32)#文章状态 #发布时间的判断 def clean(self): if self.status == 'draft' and self.pub_date is not None: raise ValidationError('草稿没有发布时间') #如果已经发布,则发布时间设为当前时间 if self.status == 'published' and self.pub_date is None: self.pub_date = datetime.date.today() def __unicode__(self): return self.title class Meta: verbose_name_plural = '文章' #评论 class Comment(models.Model): article = models.ForeignKey(Article,verbose_name='所属文章')#评论所属文章 parent_comment = models.ForeignKey('self',related_name='my_children',blank=True,null=True)#存储父评论,与自己关联,relate_name获取自己的子评论 comment_choices = ( (1,'评论'), (2,'点赞'), ) comment_type = models.IntegerField(choices=comment_choices,default=1)#选择评论还是点赞,默认是评论 user = models.ForeignKey('UserInfo')#发表评论或点赞的用户 date = models.DateTimeField(auto_now_add=True)#评论时间 comment = models.TextField(blank=True,null=True)#评论内容 #判断评论是否为空 def clean(self): if self.comment_type == 1 and len(self.comment) == 0: raise ValidationError('评论不能为空') def __unicode__(self): return '%s,P:%s,%s' %(self.article,self.parent_comment,self.comment) class Meta: verbose_name_plural = '评论' #板块 class Category(models.Model): name = models.CharField(max_length=64) brief = models.CharField(blank=True,null=True,max_length=255)#板块简介 set_as_top_menu = models.BooleanField(default=False)#是否将该模块添加到顶部菜单栏 position = models.SmallIntegerField()#若在顶部菜单栏显示,则需要设置显示的顺序 admin = models.ManyToManyField('UserInfo',blank=True)#板块管理员 def __unicode__(self): return self.name class Meta: verbose_name_plural = '板块' #用户 class UserInfo(models.Model): user = models.OneToOneField(User)#关联到django-admin自带的user表 name = models.CharField(max_length=32) signature = models.CharField(max_length=255,blank=True,null=True)#个性签名 head_img = models.ImageField(height_field=150,width_field=150,blank=True,null=True)#头像 def __unicode__(self): return self.name class Meta: verbose_name_plural = '用户'
设置settings.py
#注册app INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', ] #设置数据库 DATABASES = { 'default': { #'ENGINE': 'django.db.backends.sqlite3', #'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 'ENGINE': 'django.db.backends.mysql', 'NAME':'blog', 'HOST':'', 'PORT':'', 'USER':'root', 'PASSWORD':'0711' } } #模版路径 'DIRS': [ os.path.join(BASE_DIR,'templates'), ], #静态文件 STATICFILES_DIRS=( os.path.join(BASE_DIR,'statics'), #该定义要求statics目录在project目录下 )
一切前期准备就绪,在数据库中创建blog库(注意字符编码):
mysql> create database blog character set utf8;
同步数据库,创建表:
python manage.py makemigrations
python manage.py migrate
注册admin后台
admin.py
from django.contrib import admin from blog import models # Register your models here. class ArticleAdmin(admin.ModelAdmin): list_display = ('title','category','author','pub_date','last_modify','status') #每个表在页面上展示的字段 class CommentAdmin(admin.ModelAdmin): list_display = ('article','parent_comment','comment_type','comment','user','date') class CategoryAdmin(admin.ModelAdmin): list_display = ('name','set_as_top_menu','position') admin.site.register(models.Article,ArticleAdmin) admin.site.register(models.Category,CategoryAdmin) admin.site.register(models.Comment,CommentAdmin) admin.site.register(models.UserInfo)
二、华丽铠甲,折戟断箭。——前端展示模版的选择
Bootstrap中挑选合适的页面模版,全部下载到本地。
设置模版和静态文件:
在project目录下创建templates和statics目录分别存放模版文件和静态文件
settings.py
#templates 'DIRS': [ os.path.join(BASE_DIR,'templates'), ],
#statics STATIC_URL = '/static/' #该设置对static目录在app目录下有效 STATICFILES_DIRS =( os.path.join(BASE_DIR,'statics'), #该设置对statics目录在project目录下有效 )
在statics目录中添加静态文件:
在templates目录中添加模版文件:
将下载的html文件修改为base.html作为父模版,和下载的目录一起放在templates下创建的blog目录下,然后创建index.html作为项目首页。
index.html:
{% extends 'blog/base.html' %} #继承父模版base.html
将base.html中js和css部分的url修改为本地的文件资源,如:
<!-- Bootstrap core CSS --> <link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this template --> <link href="/static/bootstrap/css/navbar-fixed-top.css" rel="stylesheet">
urls.py
url采用根据不同项目做分发的规则,project目录下的urls.py:
from django.conf.urls import url,include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^blog/$',include('blog.urls')), ]
在app中新创建urls.py,然后设置如下:
from blog import views urlpatterns = [ url(r'^$',views.index), ]
url设置完毕,创建试图函数index,views.py:
def index(request): return render(request,'blog/index.html')
一个简单的页面套用完毕。
三、临阵布局,指挥若定。——动态导航栏及登录
对页面进行改造,将导航栏设为动态,并添加登陆与退出功能。
1、动态导航
根据表结构的设计,在category表中,若set_as_top_menu字段为True,则该板块名称即可显示在导航栏中。即每个现实在导航栏中的category,其set_as_top_menu字段必须为Ture。并且,每个category都有一个position标示,这样就可以根据前端请求的url中的id来现实对应的目录名称。
blog/urls.py:
from blog import views urlpatterns = [ url(r'category/(\d+)',views.category), ]
views中,根据url中的id,查询category表,并将查询到的category名字返回给前端。
views.py:
category_list = models.Category.objects.filter(set_as_top_menu=True).order_by('position') #将其定义为全局变量,便于多个函数引用 def index(request): return render(request,'blog/index.html',{'category_list':category_list}) def category(request,id): category_obj = models.Category.objects.get(id = id) return render(request,'blog/index.html',{'category_list':category_list,'category_obj':category_obj})
前端页面base.html中,循环所有在导航栏中显示的category,并和当前请求的category比较(根据id值),若两者相等,则跳转url并加底色。
base.html:
{% for category in category_list %}
{% if category.id == category_obj.id %} #当前请求id等于导航栏中某一个category的id值
<li class="active"><a href="/blog/category/{{ category.id }}">{{ category.name }}</a></li>
{% else %}
<li class=""><a href="/blog/category/{{ category.id }}">{{ category.name }}</a></li>
{% endif %}
{% endfor %}
这样,若点击导航栏中id为3的category,则url跳转至 http://localhost:8000/blog/category/3
2、登录及退出
若未登录,主页面右上角显示登录/注册按钮,可跳转至登录或注册页面;若已登录,则在该处显示用户名。
全局urls.py的设置:
from blog import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^blog/',include('blog.urls')), url(r'^login/$',views.user_login,name='login'), url(r'^logout/$',views.user_logout,name='logout'), ]
分别对登录和退出添加别名,方便前端页面中调用。
views.py中,引用django框架中的用户登录验证及退出功能。
from django.shortcuts import render,HttpResponseRedirect # Create your views here. from blog import models from django.contrib.auth import login,logout,authenticate from django.contrib.auth.decorators import login_required category_list = models.Category.objects.filter(set_as_top_menu=True).order_by('position') def index(request): return render(request,'blog/index.html',{'category_list':category_list}) def category(request,id): category_obj = models.Category.objects.get(id = id) return render(request,'blog/index.html',{'category_list':category_list,'category_obj':category_obj}) def user_login(request): if request.method == 'POST': user = authenticate(username = request.POST.get('username'),password = request.POST.get('password')) if user is not None: #登录成功 login(request,user) return HttpResponseRedirect('/blog') else: login_error = 'wrong username or password!' return render(request,'blog/login.html',{'login_error':login_error}) return render(request,'blog/login.html') def user_logout(request): logout(request) return HttpResponseRedirect('/blog')
登录页面login.html:
{% extends 'blog/base.html' %} {% block page-container %} <form action="" method="post"> {% csrf_token %} <div> <input type="text" name="username"> </div> <div> <input type="password" name="password"> </div> <div> <input type="submit" value="login"> </div> </form> <div> {% if login_error %} <p style="color: red">{{ login_error }}</p> {% endif %} </div> {% endblock %}
继承base.html 父模版,自定内容主题。
base.html中,在右上角添加登陆/注册按钮
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li class="active"><a href="#">{{ request.user.userinfo.name }}</a></li> #反向查询,查询 UserInfo表中的name字段,这里需要将表名小写
<li class="active"><a href="{% url 'logout' %}">注销</a></li>
{% else %}
<li class=""><a href="{% url 'login' %}">登陆/注册</a></li>
{% endif %}
</ul>
效果:
三、左迂回,右包抄,稳坐中军帐。——内容排版展示
显示文章及图片
主页及各板块下显示相应文章及图片
views.py
category_list = models.Category.objects.filter(set_as_top_menu=True).order_by('position') def index(request): #获取position为1的category,category_obj.id即position为1的categoryid值 category_obj = models.Category.objects.get(position = 1) ''' 返回position为1的category 前端请求主页时,首先从position=1开始循环所有category列表,此时'全部'板块的position为1,将该category加底色 ''' article_list = models.Article.objects.all() #所有文章 return render(request,'blog/index.html',{'category_list':category_list,'article_list':article_list,'category_obj':category_obj}) def category(request,id): category_obj = models.Category.objects.get(id = id) if category_obj.position == 1: #如果是全部,则显示全部文章 article_list = models.Article.objects.all() else: article_list = models.Article.objects.filter(category_id=category_obj.id) #article表中category_id字段 return render(request,'blog/index.html',{'category_list':category_list,'category_obj':category_obj,'article_list':article_list})
前端页面展示设置中关于图片展示的问题:
''' 图片上传到uploads目录,前端请求的url为'/static/uploads/3_DDWq8O3.jpg', uploads目录不在django静态文件的配置中,前端不能访问,因此要在setting中添加该目录, 这样实际图片位置为'/static/3_DDWq8O3.jpg', 自定义模版,将'/static/uploads/3_DDWq8O3.jpg'变为/static/3_DDWq8O3.jpg' 前端中图片src为'<img src="/static/{{ article.head_img }}">', 这样,只需要将'uploads/3_DDWq8O3.jpg'变为'3_DDWq8O3.jpg'即可 '''
settings.py:
STATICFILES_DIRS =( os.path.join(BASE_DIR,'statics'), os.path.join(BASE_DIR,'uploads'), )
自定义django模版:
blog目录下创建templatetags/custom.py文件,并在该目录下创建__init__.py文件。
customer.py:
#! _*_coding:utf-8 _*_ from django import template register = template.Library() @register.filter def truncate_url(img_obj): return img_obj.name.split('/',1)[-1]
#return img_obj.name.split('/')[1]
#截取结果:['uploads','xxx.jpg']
#即将uploads/xxx.jpg 截为xxx.jpg,传给前端之后,就是static/xx.jpg #split('/',1)表示只截取一次,如'1/2/3'截取后为['1','2/3']
index.html页面:
{% extends 'blog/base.html' %} {% load custom %} {% block page-container %} {% for article in article_list %} <div class="article-box"> <div class="article-head-img"> <img src="/static/{{ article.head_img | truncate_url}}"> #自定义模版,将articel.head_img交给truncate_url函数处理
</div> <div class="article-bruff"> {{ article.title }} </div> </div> {% endfor %} {% endblock %}
显示评论及点赞数
(样式参考虎嗅)
statics/bootstrap/css/目录下创建custom.css,用来定义图片及标题显示样式
custom.css
.page-container{ border: 1px dashed darkcyan; padding-right: 150px; padding-left: 150px; } /*去除漂浮*/ .clear-both{ clear: both; } /*左漂浮*/ .wrap-left{ width: 75%; float: left; } /*右漂浮*/ .wrap-right{ width: 25%; float: right; background-color: #9d9d9d; } .footer{ height: 300px; background-color: #ac2925; } /*压缩图片*/ .article-head-img img{ height: 150px; width: 200px; } /*每条新闻间距*/ .article-box{ padding-bottom: 10px; } /*图片和标题之间距离*/ .article-brief{ margin-left: -10px; } /*文章标题*/ .article-title{ font-size: 20px; } /*a标签去掉下划线*/ a:hover{ text-decoration: none; } /*字体样式*/ .body{ color: rgb(51, 51, 51); font-family: Arial, 微软雅黑, "Microsoft yahei", "Hiragino Sans GB", "冬青黑体简体中文 w3", "Microsoft Yahei", "Hiragino Sans GB", "冬青黑体简体中文 w3", STXihei, 华文细黑, SimSun, 宋体, Heiti, 黑体, sans-serif; } /*简介样式*/ .article-brief-text{ margin-top: 8px; color: #999; }