model 设计
from django.contrib.auth.models import AbstractUser
from django.db import models
# Create your models here.
class Userinfo(AbstractUser):
"""
用户信息。关联auth认证
"""
nid = models.AutoField(primary_key=True)
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, on_delete=models.CASCADE)
def __str__(self):
return self.username
class Blog(models.Model):
"""
博客信息
"""
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=32, verbose_name='个人博客标题')
site_name = models.CharField(max_length=32, verbose_name='站点名称')
theme = models.CharField(max_length=32, verbose_name='博客主题')
def __str__(self):
return self.title
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)
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", on_delete=models.CASCADE)
def __str__(self):
return self.title
class Article(models.Model):
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=64, verbose_name="文章标题")
desc = models.CharField(max_length=255, verbose_name="文章描述")
create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
content = models.TextField()
comment_count = models.IntegerField(default=0, verbose_name="评论数")
up_count = models.IntegerField(default=0)
down_count = models.IntegerField(default=0)
user = models.ForeignKey(verbose_name="作者", to="Userinfo", to_field="nid", on_delete=models.CASCADE)
category = models.ForeignKey(to="Category", to_field="nid", null=True, on_delete=models.CASCADE)
tags = models.ManyToManyField(to="Tag", through="Article2Tag", through_fields=("article", "tag"), )
def __str__(self):
return self.title
class Article2Tag(models.Model):
"""文章标签表"""
nid = models.AutoField(primary_key=True)
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"),
]
def __str__(self):
v = self.article.title + "----" + self.tag.title
return v
class ArticleUpDown(models.Model):
"""点赞表"""
nid = models.AutoField(primary_key=True)
user = models.ForeignKey('Userinfo', null=True, on_delete=models.CASCADE)
article = models.ForeignKey("Article", null=True, on_delete=models.CASCADE)
is_up = models.BooleanField(default=True)
class Meta:
unique_together = [
('article', 'user'),
]
class Comment(models.Model):
"""评论表"""
nid = models.AutoField(primary_key=True)
article = models.ForeignKey(verbose_name="评论文章", to="Article", to_field="nid", on_delete=models.CASCADE)
user = models.ForeignKey(verbose_name="评论者", to="Userinfo", to_field="nid", on_delete=models.CASCADE)
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, on_delete=models.CASCADE)
def __str__(self):
return self.content
核心逻辑
import json
import os
import threading
from bs4 import BeautifulSoup
from django.contrib import auth
from django.contrib.auth.decorators import login_required
from django.core.mail import send_mail
from django.db import transaction
from django.db.models import F
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
# Create your views here.
from blog import models
from blog.models import Userinfo
from blog.myforms import UserForms
from blog.utils import validCode
from blogsys import settings
def login(request):
if request.method == "POST":
res_data = {"user": None, "msg": None}
user = request.POST.get('user')
pwd = request.POST.get('pwd')
vaild_code = request.POST.get("valid_code")
valid_code_str = request.session.get("valid_code_str")
if vaild_code.upper() == valid_code_str.upper():
user = auth.authenticate(username=user, password=pwd)
if user:
auth.login(request, user) # request.userobj == request.user
res_data["user"] = user.username
else:
res_data["msg"] = "用户名or密码错误!"
else:
res_data["msg"] = "验证码错误"
return JsonResponse(res_data)
return render(request, "login.html")
@login_required
def index(request):
"""
系统首页
:param request:
:return:
"""
article_list = models.Article.objects.all()
return render(request, "index.html", {'article_list': article_list})
def logout(request):
"""
注销
:param request:
:return:
"""
auth.logout(request)
return redirect("/login/")
def register(request):
if request.method == "POST":
if request.is_ajax():
print(request.POST)
form = UserForms(request.POST)
response_data = {"user": None, "msg": None}
if form.is_valid():
response_data["user"] = form.cleaned_data.get("user")
# 创建一条用户记录
user = form.cleaned_data.get('user')
pwd = form.cleaned_data.get('pwd')
email = form.cleaned_data.get('email')
telephone = form.cleaned_data.get("telephone")
avatar_obj = request.FILES.get("avatar")
# print("avatar_obj", avatar_obj) == 文件名
file_path_name = os.path.join(settings.MEDIA_ROOT, "avatars", avatar_obj.name)
with open(file_path_name, 'wb') as f:
for line in avatar_obj:
f.write(line)
extra = {}
if avatar_obj:
extra["avatar"] = avatar_obj
Userinfo.objects.create_user(username=user, password=pwd, email=email, telephone=telephone, **extra)
else:
print(form.cleaned_data)
print(form.errors)
response_data["msg"] = form.errors
return JsonResponse(response_data)
form = UserForms()
return render(request, "register.html", {"form": form})
def get_validcode_img(request):
"""
基于PIL模块动态生效相应状态码图片
:param request:
:return:
"""
img_data = validCode.get_valid_code_img(request)
# with open('ceshi.png','wb') as f:
# for line in img_data:
# f.write(line)
return HttpResponse(img_data)
@login_required
def home_site(request, username, **kwargs):
# 区分站点页面 or 站点下的跳转页面
print("kwargs", kwargs)
print("username", username)
user = Userinfo.objects.filter(username=username).first()
if not user:
return render(request, "not_found.html")
blog = user.blog
# 当前用户 or 站点对应的文章
article_list = models.Article.objects.filter(user=user)
if kwargs:
condition = kwargs.get("condition")
param = kwargs.get("param") # 2021-05
if condition == "category":
# 登录用户下 指定分类对应的文章
article_list = article_list.filter(category__title=param)
# 登录用户下 指定标签对应的文章
elif condition == "tag":
article_list = article_list.filter(tags__title=param)
'''
SELECT
`blog_article`.`nid`,
`blog_article`.`title`,
`blog_article`.`desc`,
`blog_article`.`create_time`,
`blog_article`.`content`,
`blog_article`.`comment_count`,
`blog_article`.`up_count`,
`blog_article`.`down_count`,
`blog_article`.`user_id`,
`blog_article`.`category_id`
FROM
`blog_article`
INNER JOIN `blog_article2tag` ON ( `blog_article`.`nid` = `blog_article2tag`.`article_id` )
INNER JOIN `blog_tag` ON ( `blog_article2tag`.`tag_id` = `blog_tag`.`nid` )
WHERE
(
`blog_article`.`user_id` = 1
AND `blog_tag`.`title` = 'python'
);
args = ( 1, 'python' )
'''
else:
year, month = param.split("/")
article_list = article_list.filter(create_time__year=year, create_time__month=month)
return render(request, "home_site.html", {"username": username, "blog": blog, "article_list": article_list})
@login_required
def article_detail(request, username, article_id):
"""
文章详情页
:param request:
:param username:
:param article_id:
:return:
"""
user = Userinfo.objects.filter(username=username).first()
blog = user.blog
article_obj = models.Article.objects.filter(pk=article_id).first()
comment_list = models.Comment.objects.filter(article_id=article_id)
return render(request, "article_detail.html", locals())
def get_comment_tree(request):
"""评论树"""
article_id = request.GET.get("article_id")
res_data = list(models.Comment.objects.filter(article_id=article_id).order_by("pk").values("pk", "content",
"parent_comment_id"))
print("res_data", res_data)
# res_data 列表 需要加上safe=False 只能传递字典
return JsonResponse(res_data, safe=False)
@login_required
def cn_backend(request):
"""后台管理"""
article_list = models.Article.objects.filter(user=request.user)
return render(request, "backend/backend.html", locals())
@login_required
def add_article(request):
"""
后台管理中添加内容
:param request:
:return:
"""
if request.method == "POST":
title = request.POST.get("title")
content = request.POST.get("content")
# 防止xss攻击
soup = BeautifulSoup(content, "html.parser")
for tag in soup.find_all():
print(tag.name)
if tag.name == "script":
tag.decompose()
# 构建摘要数据,取标签字符串文本前150个字符
desc = soup.text[0:150] + "..."
models.Article.objects.create(title=title, desc=desc, content=str(soup), user=request.user)
print("content==>", str(soup))
return redirect("/cn_backend/")
return render(request, "backend/add_article.html")
@login_required
def delete_article(request, delete_article_id):
"""后台删除文章"""
models.Article.objects.filter(pk=delete_article_id).delete()
return redirect("/cn_backend/")
@login_required
def edit_article(request, edit_article_id):
"""后台编辑文件"""
edit_article_obj = models.Article.objects.filter(pk=edit_article_id).first()
if request.method == "POST":
title = request.POST.get("title")
content = request.POST.get("content")
models.Article.objects.filter(pk=edit_article_id).update(title=title, content=content)
return redirect("/cn_backend/")
return render(request, "backend/edit_article.html", {"edit_article_obj": edit_article_obj})
def upload(request):
"""
编辑器文件上传
:param request:
:return:
"""
print(request.FILES)
img_obj = request.FILES.get("upload_img")
print(img_obj.name)
path = os.path.join(settings.MEDIA_ROOT, "add_article_img", img_obj.name)
with open(path, "wb") as f:
for line in img_obj:
f.write(line)
# return HttpResponse('ok')
response = {
'error': 0,
'url': '/media/add_article_img/%s' % img_obj.name
}
return HttpResponse(json.dumps(response))
def digg(request):
"""点赞"""
print(request.POST)
article_id = request.POST.get("article_id")
is_up = json.loads(request.POST.get("is_up"))
user_id = request.user.pk
obj = models.ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
res_data = {"status": True}
if not obj:
models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
queryset_id = models.Article.objects.filter(pk=article_id)
if is_up:
queryset_id.update(up_count=F("up_count") + 1)
else:
queryset_id.update(down_count=F("down_count") + 1)
else:
res_data['status'] = False
res_data['handled'] = obj.is_up
return JsonResponse(res_data)
def comment(request):
"""评论"""
print(request.POST)
article_id = request.POST.get("article_id")
pid = request.POST.get("pid")
content = request.POST.get("content")
user_id = request.user.pk
article_obj = models.Article.objects.filter(pk=article_id).first()
# 事务操作
with transaction.atomic():
comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content,
parent_comment_id=pid)
models.Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1)
res_data = {}
res_data["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X")
res_data["username"] = comment_obj.user.username
res_data["content"] = content
# 发送邮件
# send_mail(
# "您的文章%s新增了一条评论内容" % article_obj.title,
# content,
# settings.EMAIL_HOST_USER,
# ["li_xiang111@qq.com"]
# )
t = threading.Thread(target=send_mail, args=("您的文章%s新增了一条评论内容" % article_obj.title,
content,
settings.EMAIL_HOST_USER,
["li_xiang111@qq.com"]))
t.start()
return JsonResponse(res_data)
myform 跟CRUD类似
模板新知识点
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:lixiang
from django import template
from django.db.models import Count
from blog import models
register = template.Library()
@register.simple_tag
def multi_tag(x, y):
return x * y
@register.inclusion_tag("classification.html") # == 闭包传参 inclusion_tag(get_classification_style) 固定用法 渲染模板
def get_classification_style(username):
user = models.Userinfo.objects.filter(username=username).first()
blog = user.blog
# 查询每个博客下面的分类标题的名称以及对应的文章数
cate_list = models.Category.objects.filter(blog=blog).values("pk").annotate(c=Count("article__title")).values_list(
"title", "c")
# 查询对应用户博客的所有标签的标题名称和对应的文章数 不加__title就对article_id进行count,默认关联第三张表的tag.nid == article2tag.tag_id
tag_list = models.Tag.objects.filter(blog=blog).values("pk").annotate(c=Count("article")).values_list("title", "c")
date_list = models.Article.objects.filter(user=user).extra(
select={"y_m_date": "date_format(create_time,'%%Y/%%m')"}).values("y_m_date").annotate(
c=Count("nid")).values_list("y_m_date", "c")
return {"blog": blog, "cate_list": cate_list, "date_list": date_list, "tag_list": tag_list, "username": username}
'''
SELECT
`blog_category`.`title`,
COUNT( `blog_article`.`title` ) AS `c`
FROM
`blog_category`
LEFT OUTER JOIN `blog_article` ON ( `blog_category`.`nid` = `blog_article`.`category_id` )
WHERE
`blog_category`.`blog_id` = 1
GROUP BY
`blog_category`.`nid`
ORDER BY
NULL
LIMIT 21;
SELECT
( date_format( create_time, '%Y/%m' ) ) AS `y_m_date`,
COUNT( `blog_article`.`nid` ) AS `c`
FROM
`blog_article`
WHERE
`blog_article`.`user_id` = 1
GROUP BY
( date_format( create_time, '%Y/%m' ) )
ORDER BY
NULL;
SELECT
`blog_tag`.`title`,
COUNT( `blog_article2tag`.`article_id` ) AS `c`
FROM
`blog_tag`
LEFT OUTER JOIN `blog_article2tag` ON ( `blog_tag`.`nid` = `blog_article2tag`.`tag_id` )
WHERE
`blog_tag`.`blog_id` = 1
GROUP BY
`blog_tag`.`nid`
ORDER BY
NULL;
'''
在对应模板中
<div>
<div class="panel panel-warning">
<div class="panel-heading">我的标签</div>
<div class="panel-body">
{% for tag in tag_list %}
<p><a href="/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">随笔分类</div>
<div class="panel-body">
{% for cate in cate_list %}
<p><a href="/{{ username }}/category/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-success">
<div class="panel-heading">随笔归档</div>
<div class="panel-body">
{% for date in date_list %}
<p><a href="/{{ username }}/archive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p>
{% endfor %}
</div>
</div>
</div>
模板 - 编辑框
{% extends "backend/base.html" %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<div class="add_article">
<div class="alert-success text-center">添加文章</div>
<div class="add_article_region">
<div class="title form-group">
<label for="">标题</label>
<div>
<input type="text" name="title">
</div>
</div>
<div class="content form-group">
<label for="">内容(Kindeditor编辑器,不支持拖放/粘贴上传图片)</label>
<div>
<textarea name="content" id="article_content" cols="30" rows="10"></textarea>
</div>
</div>
<input type="submit" class="btn btn-default">
</div>
</div>
</form>
<script src="/static/js/jquery-3.2.1.min.js"></script>
<script charset="utf-8" src="/static/blog/kindeditor/kindeditor-all.js"></script>
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#article_content', {
width: "800",
height: "600",
resizeType: 0,
uploadJson: "/upload/",
extraFileUploadParams: {
csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
},
filePostName: "upload_img"
});
});
</script>
{% endblock %}
登录
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/blog/bs/css/bootstrap.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form>
{% csrf_token %}
<div class="form-group">
<label for="user">用户名</label>
<input type="text" id="user" class="form-control">
</div>
<div class="form-group">
<label for="pwd">密码</label>
<input type="password" id="pwd" class="form-control">
</div>
<div class="form-group">
<label for="pwd">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control" id="valid_code">
</div>
<div class="col-md-6">
<img width="270" height="36" id="valid_code_img" src="/get_validCode_img/" alt="">
</div>
</div>
</div>
<input type="button" class="btn btn-default login_btn" value="submit"><span class="error"></span>
<a href="/register/" class="btn btn-primary pull-right">注册</a>
</form>
</div>
</div>
</div>
<script src="/static/js/jquery-3.2.1.min.js"></script>
<script>
// 验证码刷新
$("#valid_code_img").click(function () {
//console.log($(this));
$(this)[0].src += "?";
})
//登录验证
$(".login_btn").click(function () {
$.ajax({
url: "",
type: "post",
data: {
user: $("#user").val(),
pwd: $("#pwd").val(),
valid_code: $("#valid_code").val(),
csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
},
success: function (data) {
console.log(data)
if (data.user) {
// 获取url ? 后面的字符串
if (location.search) {
// slice(start,end)已有的数组中返回选定的元素
location.href = location.search.slice(6)
} else {
location.href = "/index/"
}
} else {
$(".error").text(data.msg).css({"color": "red", "margin-left": "10px"});
setTimeout(function () {
$(".error").text("");
}, 2000)
}
}
})
})
</script>
</body>
</html>
注册
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/blog/bs/css/bootstrap.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form id="form">
{% csrf_token %}
{% for filed in form %}
<div class="form-group">
<label for="">{{ filed.label }}</label>
{{ filed }} <span class="error pull-right"></span>
</div>
{% endfor %}
<div class="form-group">
<label for="avatar">
头像
<img src="/static/blog/img/default.png" id="avatar_img" alt="">
</label>
<input type="file" id="avatar" name="avatar">
</div>
<input type="button" class="btn btn-default reg_btn" value="submit"><span class="error"></span>
</form>
</div>
</div>
</div>
<script src="/static/js/jquery-3.2.1.min.js"></script>
<script>
// 头像预览
$("#avatar").change(function () {
// 获取用户选中的文件对象
var file_obj = $(this)[0].files[0];
// 获取文件对象路径
var reader = new FileReader();
reader.readAsDataURL(file_obj);
// 修改img src属性,src=文件对象的路径
reader.onload = function () {
$("#avatar_img").attr("src", reader.result);
};
})
// ajax提交
$(".reg_btn").click(function () {
//获取form数据 [{},{},{}]
var request_data = $("#form").serializeArray();
// 建立提交formdata对象
var formdata = new FormData();
$.each(request_data, function (index, data) {
formdata.append(data.name, data.value)
});
//加入文件对象
formdata.append("avatar", $("#avatar")[0].files[0]);
$.ajax({
url: "",
type: "post",
contentType: false,
processData: false,
data: formdata,
success: function (data) {
console.log(data);
if (data.user) {
//注册成功
window.location.href = "/login/"
} else {
// 注册失败
console.log(data.msg)
// 清空错误信息
$("span.error").html("")
$(".form-group").removeClass("has_error");
//显示错误信息
$.each(data.msg, function (field, error_list) {
//console.log(field, error_list);
if (field == "__all__") {
$("#id_r_pwd").next().html(error_list[0]).parent().addClass("has-error");
} else {
$("#id_" + field).next().html(error_list[0]);
$("#id_" + field).parent().addClass("has-error");
}
})
}
}
})
})
</script>
</body>
</html>