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
    #!/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
    View Code

相关文章: