Django之博客系统

Django之博客系统

一、设计表结构

from django.db import models
from django.contrib.auth.models import AbstractUser


# Create your models here.




class UserInfo(AbstractUser):
    """
    用户信息
    """
    nid = models.AutoField(primary_key=True)
    nickname = models.CharField(verbose_name='昵称', max_length=32)
    telephone = 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)

    def __str__(self):
        return self.username


class Blog(models.Model):
    """
    博客站点信息
    """
    nid = models.AutoField(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)

    def __str__(self):
        return self.title


class HomeCategory(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')

    def __str__(self):
        return self.title


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')

    def __str__(self):
        return self.title


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

    comment_count = models.IntegerField(default=0)
    up_count = models.IntegerField(default=0)
    down_count = models.IntegerField(default=0)

    homeCategory = models.ForeignKey(to='HomeCategory', to_field='nid', null=True)
    tags = models.ManyToManyField(
        to="Tag",
        through='Article2Tag',
        through_fields=('article', 'tag'),
    )

    user = models.ForeignKey(verbose_name='作者', to='UserInfo', to_field='nid')

    def __str__(self):
        return self.title


class ArticleDetail(models.Model):
    """
    文章详细表
    """
    nid = models.AutoField(primary_key=True)
    content = models.TextField()

    article = models.OneToOneField(to='Article', to_field='nid')


class Comment(models.Model):
    """
    评论表
    """
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid')
    user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid')
    content = models.CharField(verbose_name='评论内容', max_length=255)
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)

    parent_comment = models.ForeignKey('self', null=True)  # 决定是根评论还是子评论

    def __str__(self):
        return self.content


class ArticleUpDown(models.Model):
    """
    点赞表
    """
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey('UserInfo', null=True)
    article = models.ForeignKey("Article", null=True)
    is_up = models.BooleanField(default=True)

    class Meta:
        unique_together = [
            ('article', 'user'),
        ]


class Article2Tag(models.Model):
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid')
    tag = models.ForeignKey(verbose_name='标签', to="Tag", to_field='nid')

    class Meta:
        unique_together = [
            ('article', 'tag'),
        ]

    def __str__(self):
        v = self.article.title + "----" + self.tag.title
        return v
设计表

 

二、注册

用到的知识点:form组件生成标签和校验数据,上传头像,在页面显示头像,基于ajax和form组件提交数据

form组件

from django import forms
from django.forms import widgets
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from . import models
import re


class RegisterForm(forms.Form):
    username = forms.CharField(min_length=4, max_length=12,
                               error_messages={
                                   'min_length': '用户名长度不能少于4位',
                                   'required': '必填',
                               },
                               widget=widgets.TextInput(attrs={'class': 'form-control col-sm-4'}))
    nickname = forms.CharField(min_length=4, max_length=12,
                               error_messages={
                                   'min_length': '昵称长度不能少于4位',
                                   'required': '必填',
                               },
                               widget=widgets.TextInput(attrs={'class': 'form-control col-sm-4'}))
    password = forms.CharField(min_length=6, max_length=15,
                               error_messages={
                                   'min_length': '密码长度不能少于6位',
                                   'required': '必填',
                               },
                               widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    repeat_pwd = forms.CharField(min_length=6, max_length=15,
                                 error_messages={
                                     'min_length': '密码长度不能少于6位',
                                     'required': '必填',
                                 },
                                 widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    email = forms.EmailField(
        error_messages={
            'required': '必填',
            'invalid': '邮箱格式错误'
        },
        widget=widgets.EmailInput(attrs={'class': 'form-control'}))
    telephone = forms.CharField(
        required=False,
        error_messages={
            'required': '必填',
        },
        widget=widgets.TextInput(attrs={'class': 'form-control'}))
    avatar = forms.CharField(
        required=False,
        error_messages={
            'required': '必填',
        },
        widget=widgets.FileInput(attrs={'class': 'form-control hide'}))

    def clean_username(self):
        user = self.cleaned_data.get('username')
        ret = models.UserInfo.objects.filter(username=user)
        if ret:
            raise ValidationError('该用户已注册')
        else:
            return user

    def clean_nickname(self):
        nickname = self.cleaned_data.get('nickname')
        ret = models.UserInfo.objects.filter(nickname=nickname)
        if ret:
            raise ValidationError('该昵称已经被使用')
        else:
            return nickname

    def clean_telephone(self):
        telephone = self.cleaned_data.get('telephone')
        if telephone:
            ret = re.match('1[345678]\d{9}', telephone)
            if ret:
                return telephone
            else:
                raise ValidationError('手机号码格式不正确')

    def clean(self):
        pwd = self.cleaned_data.get('password')
        repeat_pwd = self.cleaned_data.get('repeat_pwd')
        if pwd == repeat_pwd:
            return self.cleaned_data
        else:
            raise ValidationError('两次密码不一致')

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>博客系统—注册</title>
    <link rel="stylesheet" href="/static/plugins/bootstrap-3.3.7/css/bootstrap.min.css">
    <script src="/static/js/jquery-3.2.1.min.js"></script>
    <style>
        span {
            color: red;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="col-md-10 col-md-offset-1">
        <div class="page-header">
            <h1>新用户注册
                <small>register</small>
            </h1>
        </div>
        <div class="panel panel-primary">
            <div class="panel-heading">Welcome To Blog!</div>
            <div class="panel-body">
                <form class="form-horizontal" novalidate>
                    {% csrf_token %}
                    <div class="form-group">
                        <label for="" class="col-sm-4 control-label"><span>*</span>用户名</label>
                        <div class="col-sm-4">
                            {{ register_obj.username }}
                        </div>
                        <span></span>
                    </div>
                    <div class="form-group">
                        <label for="" class="col-sm-4 control-label"><span>*</span>昵称</label>
                        <div class="col-sm-4">
                            {{ register_obj.nickname }}
                        </div>
                        <span></span>
                    </div>
                    <div class="form-group">
                        <label for="" class="col-sm-4 control-label"><span>*</span>密码</label>
                        <div class="col-sm-4">
                            {{ register_obj.password }}
                        </div>
                        <span></span>
                    </div>
                    <div class="form-group">
                        <label for="" class="col-sm-4 control-label"><span>*</span>确认密码</label>
                        <div class="col-sm-4">
                            {{ register_obj.repeat_pwd }}
                        </div>
                        <span></span>
                    </div>
                    <div class="form-group">
                        <label for="" class="col-sm-4 control-label"><span>*</span>邮箱</label>
                        <div class="col-sm-4">
                            {{ register_obj.email }}
                        </div>
                        <span></span>
                    </div>
                    <div class="form-group">
                        <label for="" class="col-sm-4 control-label">手机号码</label>
                        <div class="col-sm-4">
                            {{ register_obj.telephone }}
                        </div>
                        <span></span>
                    </div>
                    <div class="form-group">
                        <label for="id_avatar" class="col-sm-6 control-label">上传头像&ensp;<img id="img_avatar"
                                                                                             style="margin:0 21px 0 28px"
                                                                                             width="100" height="100"
                                                                                             src="/static/img/default.png"
                                                                                             alt=""></label>
                        <div class="col-sm-4">
                            {{ register_obj.avatar }}
                        </div>
                        <span></span>
                    </div>
                </form>
                <div class="form-group">
                    <div class="col-sm-offset-4 col-sm-10">
                        <button id="b1" class="btn btn-success">提交</button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>


<script>
    $("#id_avatar").change(function () {
        var choose_file = $(this)[0].files[0];
        var reader = new FileReader();
        reader.readAsDataURL(choose_file);
        reader.onload = function () {
            $("#img_avatar").attr("src", this.result)
        }
    });
    $('#b1').click(function () {
        var formdata = new FormData();
        formdata.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
        formdata.append("username", $("#id_username").val());
        formdata.append("nickname", $("#id_nickname").val());
        formdata.append("password", $("#id_password").val());
        formdata.append("repeat_pwd", $("#id_repeat_pwd").val());
        formdata.append("email", $("#id_email").val());
        formdata.append("telephone", $("#id_telephone").val());
        formdata.append("avatar", $("#id_avatar")[0].files[0]);
        $.ajax({
            url: '{% url "register" %}',
            type: 'post',
            data: formdata,
            contentType: false,
            processData: false,
            success: function (data) {
                var data = JSON.parse(data);
                if (data.is_reg) {
                    $(".col-sm-4+span").html("");
                    $(".form-group div").removeClass("has-error");
                    $.each(data.errors, function (field, error_info) {
                        var $inputEle = $("#id_" + field);
                        $inputEle.parent().next().html(error_info[0]).css('color', 'red');
                        $inputEle.parent().addClass("has-error");
                        if (field === "__all__") {
                            var $repeatPwdEle = $("#id_repeat_pwd");
                            $repeatPwdEle.parent().next().html(error_info);
                            $repeatPwdEle.parent().addClass("has-error");
                        }
                    });
                } else {
                    location.href = "{% url 'login' %}"
                }
            }
        })
    })
</script>
</body>
</html>

views

def register(request):
    if request.method == 'POST':
        register_obj = forms.RegisterForm(request.POST)
        d = {'is_reg': True, 'errors': None}
        if register_obj.is_valid():
            d['is_reg'] = False
            username = register_obj.cleaned_data.get('username')
            nickname = register_obj.cleaned_data.get('nickname')
            password = register_obj.cleaned_data.get('password')
            email = register_obj.cleaned_data.get('email')
            telephone = register_obj.cleaned_data.get('telephone')
            avatar_obj = request.FILES.get('avatar')
            if avatar_obj:
                user = models.UserInfo.objects.create_user(username=username, nickname=nickname, password=password,
                                                           email=email,
                                                           telephone=telephone, avatar=avatar_obj)
            else:
                user = models.UserInfo.objects.create_user(username=username, nickname=nickname, password=password,
                                                           email=email,
                                                           telephone=telephone)
        else:
            d['errors'] = register_obj.errors
        return HttpResponse(json.dumps(d))
    register_obj = forms.RegisterForm()
    return render(request, 'register.html', {'register_obj': register_obj})

 

三、登陆

用到的知识点:随机生成验证码

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>博客系统-登陆</title>
    <link rel="stylesheet" href="/static/plugins/bootstrap-3.3.7/css/bootstrap.min.css">
    <script src="/static/js/jquery-3.2.1.min.js"></script>
    <style>
        #p1 {
            color: red
        }

        span {
            color: red
        }
    </style>
</head>
<body>
<div class="container">
    <div class="col-md-10 col-md-offset-1">
        <div class="page-header">
            <h1>用户登陆
                <small>login</small>
            </h1>
        </div>
        <div class="panel panel-success">
            <div class="panel-heading"><strong>Welcome User!</strong></div>
            <div class="panel-body">
                <form class="form-horizontal" novalidate>
                    {% csrf_token %}
                    <div class="form-group">
                        <label for="inputUser" class="col-sm-4 control-label"><span>*</span>用户名</label>
                        <div class="col-sm-4">
                            <input type="text" class="form-control" id="inputUser" placeholder="用户名">
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="inputPassword" class="col-sm-4 control-label"><span>*</span>密码</label>
                        <div class="col-sm-4">
                            <input type="password" class="form-control" id="inputPassword" placeholder="Password">
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="valid_code" class="col-sm-4 control-label"><span>*</span>验证码</label>
                        <div class="col-sm-2">
                            <input type="text" class="form-control" id="valid_code"
                                   placeholder="验证码">
                        </div>
                        <img id="valid_img" style="margin-left: 3px" src="{% url 'validate' %}" alt="">
                    </div>
                    <div class="col-sm-4 col-sm-offset-4">
                        <p id="p1"></p>
                    </div>
                    <div class="form-group">
                        <div class="col-sm-offset-4 col-sm-10">
                            <input type="button" class="btn btn-success" value="提交" id="b1">
                        </div>
                    </div>
                </form>

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

<script>
    $('#b1').click(function () {
        $.ajax({
            url: '{% url "login" %}',
            type: 'post',
            data: {
                "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(),
                'user': $("#inputUser").val(),
                'pwd': $("#inputPassword").val(),
                'valid_code': $("#valid_code").val()
            },
            success: function (data) {
                var data = JSON.parse(data);
                console.log(data);
                if (data.is_exist) {
                    location.href = "{% url 'index' %}"
                } else {
                    $('#p1').html(data.error_msg)
                }
            }
        })
    })
    
    $("#valid_img").click(function () {
        $(this)[0].src += "?"
    })
</script>
</body>
</html>'

views、随机生成验证码:

from django.shortcuts import render, HttpResponse, redirect
from django.contrib import auth
from django.urls import reverse
from . import forms,models
import json,random,os,datetime
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
from django.db.models import F
from django.db import transaction
from django.http import JsonResponse
from blog import settings

def log_in(request): if request.method == 'POST': user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') code_str = request.session.get('valid_code') d = {'is_exist': False, 'error_msg': None} if valid_code.upper() == code_str.upper(): user_obj = auth.authenticate(username=user, password=pwd) if user_obj: auth.login(request, user_obj) d['is_exist'] = True else: d['error_msg'] = '用户名或密码错误' else: d['error_msg'] = '验证码错误' return HttpResponse(json.dumps(d)) return render(request, 'login.html') def get_valid_img(request): def get_random_color(): return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) image = Image.new(mode='RGB', size=(135, 35), color=get_random_color()) draw = ImageDraw.Draw(image) font = ImageFont.truetype('app01/utils/kumo.ttf', size=28) random_code = '' for i in range(5): random_char = random.choice([str(random.randint(0, 9)), chr(random.randint(65, 90)), chr(random.randint(97, 122))]) random_code += random_char draw.text((29, 3), random_code, get_random_color(), font=font) width = 135 height = 35 for i in range(30): draw.point((random.randint(0, width), random.randint(0, height)), fill=get_random_color()) # for i in range(10): # x1 = random.randint(0, width) # x2 = random.randint(0, width) # y1 = random.randint(0, height) # y2 = random.randint(0, height) # draw.line((x1, y1, x2, y2), fill=get_random_color()) for i in range(40): draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random_color()) f = BytesIO() image.save(f, 'png') data = f.getvalue() request.session['valid_code'] = random_code return HttpResponse(data)

 

四、个人站点

用到知识的:跨表查询,母版继承,自定义标签,日期格式化

自定义标签

archive.html

<div class="panel panel-primary">
    <div class="panel-heading">公告</div>
    <div class="panel-body">
        <div>昵称:{{ user.nickname }}</div>
        <div>加入日期:{{ user.create_time|date:"Y-m-d" }}</div>
        <div>粉丝:0</div>
        <div>关注:0</div>
    </div>
</div>
<div class="panel panel-success">
    <div class="panel-heading">我的标签</div>
    <div class="panel-body">
        {% for tag in tag_list %}
            <a href="/{{ user.username }}/tag/{{ tag.0 }}/"><p>{{ tag.0 }}({{ tag.1 }})</p></a>
        {% endfor %}
    </div>
</div>
<div class="panel panel-info">
    <div class="panel-heading">随笔分类</div>
    <div class="panel-body">
        {% for category in category_list %}
            <a href="/{{ user.username }}/category/{{ category.0 }}">
                <p>{{ category.0 }}({{ category.1 }})</p></a>
        {% endfor %}
    </div>
</div>
<div class="panel panel-danger">
    <div class="panel-heading">随笔档案</div>
    <div class="panel-body">
        {% for date in date_list %}
            <a href="/{{ user.username }}/archive/{{ date.0 }}"><p>{{ date.0 }}({{ date.1 }})</p></a>
        {% endfor %}
    </div>
</div>

my_tags.py

from django import template
from django.utils.safestring import mark_safe
from app01 import models

from django.db.models import Count

register = template.Library()


@register.inclusion_tag("blog/archive.html")
def get_archive_style(username):
    user = models.UserInfo.objects.filter(username=username).first()
    blog = user.blog
    tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article__nid')).values_list('title', 'c')
    category_list = models.HomeCategory.objects.filter(blog=blog).annotate(c=Count('article__nid')).values_list(
        'title', 'c')
    date_list = models.Article.objects.filter(user=user).extra(
        select={"date_ym": "date_format(create_time,'%%Y/%%m')"}). \
        values_list('date_ym').annotate(c=Count('nid')).values_list('date_ym', 'c').order_by('date_ym')
    return {"tag_list": tag_list, "category_list": category_list, "date_list": date_list,"user":user}

home_base.html

{% load my_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/plugins/bootstrap-3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/css/article_detail.css">
    <script src="/static/js/jquery-3.2.1.min.js"></script>
    <title>{{ user.username }}&nbsp;-&nbsp;Blog</title>
    <style>
        .header {
            background-color: rgb(120, 23, 22);
            color: white;
        }

        .nick {
            margin: 10px 0 10px 30px;
        }

        .article-title {
            font-size: 16px;
            color: black;
        }

        .a1 {
            color: white;
        }

        .a1:hover {
            color: white;
        }
    </style>
</head>
<body>
<div class="jumbotron header" id="m1">
    <h1 class="nick"><a class="a1" href="/{{ user.username }}/">{{ user.username }}</a></h1>
</div>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-2">
            {% get_archive_style user.username %}
        </div>
        <div class="col-md-10 table-bordered">
            <ul class="nav nav-tabs">
                <li role="presentation"><a href="#">博客园</a></li>
                <li role="presentation"><a href="#">首页</a></li>
                <li role="presentation"><a href="#">新随笔</a></li>
                <li role="presentation"><a href="#">联系</a></li>
                <li role="presentation"><a href="/{{ request.user.username }}/backend/">管理</a></li>
                <li role="presentation"><a href="#">订阅</a></li>
            </ul>
            <p></p>
            {% block content %}
                {% for article in article_list %}
                    <p><a class="article-title"
                          href="/{{ username }}/articles/{{ article.pk }}.html"><strong>{{ article.title }}</strong></a>
                    </p>
                    <p>{{ article.desc }}</p>
                    <div class="small text-right">
                    <span>
                    post&nbsp;@&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}&nbsp;&nbsp;{{ article.user.nickname }}&nbsp;
                    评论({{ article.comment_count }})&nbsp;&nbsp;点赞({{ article.up_count }})&nbsp;&nbsp;<a href="">编辑</a>
                    </span>
                    </div>
                    <hr>
                {% endfor %}
            {% endblock %}
        </div>
        <div class="col-md-1"></div>
    </div>
</div>
</body>
</html>

homesite.html

{% extends "blog/home_base.html" %}

views

def homesite(request, username, **kwargs):
    user = models.UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'blog/not_found.html')
    blog = user.blog
    sect = kwargs.get('sect')
    param = kwargs.get('param')
    art_list = models.Article.objects.filter(user=user).order_by('create_time')
    if not sect:
        article_list = art_list
    else:
        if sect == 'tag':
            article_list = art_list.filter(tags__title=param)
        elif sect == 'category':
            article_list = art_list.filter(homeCategory__title=param)
        elif sect == 'archive':
            year, month = param.split('/')
            article_list = art_list.filter(create_time__year=year).extra(where=['date_format(create_time,"%%m")=%s'],
                                                                         params=[month])
    return render(request, 'blog/homesite.html', locals())

 

posted @ 2018-03-01 15:06  Coufusion  阅读(80)  评论(0)    收藏  举报