1. 需求分析
1.1 报障功能
1)用户本身
- 提交报障单
- 查看主机的报障记录
2)处理者
- 查看所有人的报障单
- 处理报障单
1.2 博客(知识库)
1)知识库主页
- 展示最新的文章
- 展示最热的文章
- 展示评论最多的文章
- 分类查看
2)个人博客
- 显示个人博客主页
- 显示个人博客文章详细信息:赞、踩、评论
- 显示个人博客分类信息:标签、分类、时间
- 进行个人博客主题自定制:后台修改
3)后台管理
- 个人信息管理
- 个人标签管理
- 个人分类管理
- 个人文章
2. 数据库设计
2.1 关系分析
1)用户表:UserInfo
用户表中存放用户的一些信息如:nid、username、password、email、avatar(头像)、nickname(昵称)以及创建时间create_time;
另外由于要构建互粉的关系,UserInfo需要和自己本身建立起多对多的关系,所以还需要构建一个ManyToMany字段和自己建立起多对多关联;
这个多对多的字段fans(其实是一张多对多的表),保存用户和粉丝的对应关系。这里通过UserFans表构建自定义的ManyToMany关系。
2)互粉关系表:UserFans
互粉关系表就是保存用户和用户之间的互粉关系的,它是一种多对多的关系。
这里构建两个外键字段user和follower,都和UserInfo表的nid建立外键关联;并且user和follower这两个字段需要建立联合唯一索引,以防止条目重复。
3)博客信息表:Blog
博客信息表用来保存某用户的博客信息的,保存nid、title(个人博客标题)、site(个人博客前缀)、theme(博客主题);
此外,个人博客需要和某个用户建立关联,且是一一对应的关系,所以这里需要在Blog表中创建一个user字段和UserInfo表的nid字段建立一对一关联;
访问个人博客时,是通过url获取到博人博客前缀后,查询这里的Blog表,然后拿到对应的user字段,再通过一对一关联去查询UserInfo表获取对应用户的nid,最后再进行后续操作的。
4)博主个人文章分类:Category
文章分类表中只需要定义一个title(分类标题),然后再定义一个外键字段blog和Blog表建立外键关联即可。
因为某个分类标题必须属于某个个人博客,而某个个人博客可以拥有多个分类标题,所以blog字段需要和Blog表的nid建立外键关联。
5)文章详细表:ArticleDetail
文章详细表需要定义content存储文章内容,再定义一个article字段和Article表建立一对一关联。
文章详细表需要和文章表一一对应,所以需要和文章表的nid建立一对一关联。
6)标签表:Tag
标签表和文章分类表类似,需要定义title(标签名称),还需要定义blog字段与Blog表的nid建立外键关联。
7)文章表:Article
文章表中需要包含某篇文章的所有信息,如title(文章标题)、summary(文章简介)、read_count(阅读数)、comment_count(评价数)、up_count(赞数)、down_count(踩数),以及create_time(创建时间);
某篇文章必须是属于某个个人博客的,所以需要定义一个blog字段和Blog表的nid建立外键关联(某个博客下有多篇文章);
某个博客的所有文章类型是保存在Category中的,所以某篇文章需要定义一个category字段和Category表中的nid字段建立外键关联;
此外,关于文章和标签之间的关系:某篇文章可以有多个标签,而某个标签下也可以有多篇文章,所以Article和Tag是多对多的关系;所以在Article表中要定义一个tag字段,通过这个字段来建立Article和Tag的多对多关联,这里使用自定义多对多关联表Article2Tag。
8)文章和标签的关联表:Article2Tag
文章和标签是多对多关系,所以要定义article和tag字段,分别和Article表的nid、Tag表的nid建立外键关联。
并且articel和tag字段需要建立联合唯一索引。
2.2 表结构构建
from django.db import models class UserInfo(models.Model): """用户表""" nid = models.BigAutoField(primary_key=True) username = models.CharField(verbose_name='用户名', max_length=32, unique=True) password = models.CharField(verbose_name='密码', max_length=64) nickname = models.CharField(verbose_name='昵称', max_length=32) email = models.EmailField(verbose_name='邮箱', unique=True) avatar = models.ImageField(verbose_name='头像') create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) fans = models.ManyToManyField( verbose_name='儿子们', to='UserInfo', through='UserFans', related_name='f', through_fields=('user', 'follower') )
class UserFans(models.Model): """互粉关系表""" user = models.ForeignKey(verbose_name='博主', to='UserInfo', to_field='nid', related_name='users', on_delete=models.CASCADE) follower = models.ForeignKey(verbose_name='粉丝', to='UserInfo', to_field='nid', related_name='followers', on_delete=models.CASCADE) class Meta: unique_together = [ ('user', 'follower'), ]
class Blog(models.Model): """博客信息""" nid = models.BigAutoField(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) user = models.OneToOneField(to='UserInfo', to_field='nid', on_delete=models.CASCADE) 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', on_delete=models.CASCADE) class ArticleDetail(models.Model): """文章详细表""" content = models.TextField(verbose_name='文章内容') article = models.OneToOneField(verbose_name='所属文章', to='Article', to_field='nid', on_delete=models.CASCADE) 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', on_delete=models.CASCADE) class Article(models.Model): """文章表""" nid = models.BigAutoField(primary_key=True) title = models.CharField(verbose_name='文章标题', max_length=128) summary = models.CharField(verbose_name='文章简介', max_length=255) read_count = models.IntegerField(default=0) comment_count = models.IntegerField(default=0) up_count = models.IntegerField(default=0) down_count = models.IntegerField(default=0) create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid', on_delete=models.CASCADE) category = models.ForeignKey(verbose_name='文章类型', to='Category', to_field='nid', null=True, on_delete=models.CASCADE) type_choices = [ (1, "Python"), (2, "Linux"), (3, "OpenStack"), (4, "GoLang"), ] article_type_id = models.IntegerField(choices=type_choices, default=None) tags = models.ManyToManyField( to='Tag', through='Article2Tag', through_fields=('article', 'tag'), ) class Article2Tag(models.Model): """文章和标签的关系表""" article = models.ForeignKey(verbose_name='文章', to='Article', to_field='nid', on_delete=models.CASCADE) tag = models.ForeignKey(verbose_name='标签', to='Tag', to_field='nid', on_delete=models.CASCADE) class Meta: unique_together = [ ('article', 'tag'), ]
3. 验证码登录
3.1 程序目录结构
- project
- - APP(repository) - 数据仓库(操作数据Model)
- - APP(backend) - 后台管理
- - APP(web) - 首页,个人博客
- - utils - 工具包(公共模块)
3.2 url路由
from django.conf.urls import url from .views import account from .views import home urlpatterns = [ # 登录 url(r'^login.html$', account.login), # 验证码获取 url(r'^check_code.html$', account.check_code), url(r'^home$', account.home), ]
3.3 构建Form表单
from django.core.exceptions import ValidationError from django import forms as django_forms from django.forms import fields as django_fields from django.forms import widgets as django_widgets from repository import models # 构建一个Form表单的基类,用来存储request对象 # 用以获取session中保存的验证码信息和POST中用户填写的验证码信息 class BaseForm(object): def __init__(self, request, *args, **kwargs): self.request = request super(BaseForm, self).__init__(*args, **kwargs) class LoginForm(BaseForm, django_forms.Form): username = django_fields.CharField() # password = django_fields.RegexField( # '^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[!@#$\%\^\&\*\(\)])[0-9a-zA-Z!@#$\%\^\&\*\(\)]{8,32}$', # min_length=12, # max_length=32, # error_messages={'required': '密码不能为空.', # 'invalid': '密码必须包含数字,字母、特殊字符', # 'min_length': "密码长度不能小于8个字符", # 'max_length': "密码长度不能大于32个字符"} # ) password = django_fields.CharField() # 一个月免登录的勾选框 rmb = django_fields.IntegerField(required=False) check_code = django_fields.CharField( error_messages={'required': '验证码不能为空.'} ) # 自定义验证check_code字段 # 将用户填写的验证码信息和session中保存的验证码信息进行比对 def clean_check_code(self): if self.request.session.get('CheckCode').upper() != self.request.POST.get('check_code').upper(): raise ValidationError(message='验证码错误', code='invalid')
3.4 views视图函数
1)生成验证码
- utils/check_code.py
View Code
#!/usr/bin/env python # -*- coding:utf-8 -*- import random from PIL import Image, ImageDraw, ImageFont, ImageFilter _letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z _upper_cases = _letter_cases.upper() # 大写字母 _numbers = ''.join(map(str, range(3, 10))) # 数字 init_chars = ''.join((_letter_cases, _upper_cases, _numbers)) # PIL def create_validate_code(size=(120, 30), chars=init_chars, img_type="GIF", mode="RGB", bg_color=(255, 255, 255), fg_color=(0, 0, 255), font_size=18, font_type="Monaco.ttf", length=4, draw_lines=True, n_line=(1, 2), draw_points=True, point_chance=2): """ @todo: 生成验证码图片 @param size: 图片的大小,格式(宽,高),默认为(120, 30) @param chars: 允许的字符集合,格式字符串 @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG @param mode: 图片模式,默认为RGB @param bg_color: 背景颜色,默认为白色 @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF @param font_size: 验证码字体大小 @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf @param length: 验证码字符个数 @param draw_lines: 是否划干扰线 @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效 @param draw_points: 是否画干扰点 @param point_chance: 干扰点出现的概率,大小范围[0, 100] @return: [0]: PIL Image实例 @return: [1]: 验证码图片中的字符串 """ width, height = size # 宽高 # 创建图形 img = Image.new(mode, size, bg_color) draw = ImageDraw.Draw(img) # 创建画笔 def get_chars(): """生成给定长度的字符串,返回列表格式""" return random.sample(chars, length) def create_lines(): """绘制干扰线""" line_num = random.randint(*n_line) # 干扰线条数 for i in range(line_num): # 起始点 begin = (random.randint(0, size[0]), random.randint(0, size[1])) # 结束点 end = (random.randint(0, size[0]), random.randint(0, size[1])) draw.line([begin, end], fill=(0, 0, 0)) def create_points(): """绘制干扰点""" chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100] for w in range(width): for h in range(height): tmp = random.randint(0, 100) if tmp > 100 - chance: draw.point((w, h), fill=(0, 0, 0)) def create_strs(): """绘制验证码字符""" c_chars = get_chars() strs = ' %s ' % ' '.join(c_chars) # 每个字符前后以空格隔开 font = ImageFont.truetype(font_type, font_size) font_width, font_height = font.getsize(strs) draw.text(((width - font_width) / 3, (height - font_height) / 3), strs, font=font, fill=fg_color) return ''.join(c_chars) if draw_lines: create_lines() if draw_points: create_points() strs = create_strs() # 图形扭曲参数 params = [1 - float(random.randint(1, 2)) / 100, 0, 0, 0, 1 - float(random.randint(1, 10)) / 100, float(random.randint(1, 2)) / 500, 0.001, float(random.randint(1, 2)) / 500 ] img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲 img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强(阈值更大) return img, strs