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">上传头像 <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 }} - 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 @ {{ article.create_time|date:"Y-m-d H:i" }} {{ article.user.nickname }}
评论({{ article.comment_count }}) 点赞({{ article.up_count }}) <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())