6.2 - BBS + BLOG系统

一、简介

博客系统开发

1.注册,登录,首页
2.个人站点,分组:(分类,标签,归档)
3.文章详细页
4.点赞,踩灭
5.评论楼,评论树
6.后台管理,发布文章,文件上传
7.BeautifulSoup
8.日志

 

数据库          models.py

注册 /reg/
上传头像 request.FILES.get('avatar')

登录 /login/
随机验证码 /get_valid_img/

首页 /index/

个人站点
分类,标签,归档 /blog/egon/

文章详细页 /blog/egon/articles/2/

点赞,踩灭 /blog/poll/
ajax的post 事务

评论楼,评论树 /blog/comment/
根评论,子评论
render显示,ajax显示

后台管理,发布文章 /backend/index/
新建APP
认证装饰器
编辑器(KindEditor)
文件上传 /media/article_imgs/...

防止XSS攻击
BeautifulSoup

二、数据库

知识点:

1.继承AbstractUser
目的:为了使用用户认证组件 auth User
配置:AUTH_USER_MODEL = "blog.UserInfo"

class UserInfo(AbstractUser):
...


2.中介模型
多对多,第三张表自己生成。
class Article(models.Model):
...
tags = models.ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag'))

class Article2Tag(models.Model):
...
class Meta: # 联合唯一
unique_together = [
('article', 'tag'),
]

3.联合唯一
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)

class Meta: # 联合唯一
unique_together = [
('user', 'article'),
]

 

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


class UserInfo(AbstractUser):
    """
    用户信息
    """
    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(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 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):
    """
    文章信息
    comment_count up_count down_count 为了查询时,效率高。
    """
    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='创建时间', auto_now_add=True)

    comment_count = models.IntegerField(default=0)
    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 ArticleDetail(models.Model):
    """
    文章详细表
    """
    nid = models.AutoField(primary_key=True)
    content = models.TextField()
    article = models.OneToOneField(to='Article', to_field='nid', on_delete=models.CASCADE)


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):
        return self.article.title + '--' + self.tag.title


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 = [
            ('user', 'article'),
        ]


class Comment(models.Model):
    """
    评论表
    """
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid', on_delete=models.CASCADE)
    article = models.ForeignKey(verbose_name='评论文章', to='Article', 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
models.py

三、admin

from django.contrib import admin

# Register your models here.

from blog.models import *

admin.site.register(UserInfo)
admin.site.register(Blog)
admin.site.register(Category)
admin.site.register(Tag)
admin.site.register(Article)
admin.site.register(ArticleDetail)
admin.site.register(Article2Tag)
admin.site.register(ArticleUpDown)
admin.site.register(Comment)

  

http://127.0.0.1:8020/admin/

四、注册

 知识点:

1.form组件
class RegForm(forms.Form):pass
局部钩子 全局钩子

2.上传头像 avatar
图像预览
var reader = new FileReader();
上传文件
formdata = new FormData();

3.用户文件配置
avatar = models.FileField(upload_to='avatars/', default='avatars/default.png')
MEDIA_ROOT = os.path.join(BASE_DIR, 'blog', 'media')
MEDIA_URL = '/media/'
re_path(r'media/(?P<path>.*)$',serve,{'document_root':settings.MEDIA_ROOT})


五、登录

 知识点:

1.验证码
随机生成5个字符,0-9 a-z A-Z

pip install pillow
from PIL import Image, ImageDraw, ImageFont
image = Image.new()

在内存中生成图片直接返回
from io import BytesIO
f = BytesIO()

2.request.session['valid_str'] = valid_str
存在session中,为了之后登录,验证是否通过

3.验证码点击刷新:
$('#valid_img').click(function () {
$(this)[0].src += '?'
});

4.认证组件
valid_str = request.session.get('valid_str')
if valid_str.upper() == valid_code.upper():
user = auth.authenticate(username = user, password = pwd)
if user:
auth.login(request, user)

六、首页

 知识点:

1.bootstrap搭建页面

2.导航条
登录: username / 注销
未登录: 登录 / 注册

3.for循环

{% for article in article_list %}
{% endfor %}

七、个人站点

 知识点:

1.文章列表,分类列表,标签列表,日期归档列表
文章列表: /blog/egon/
分类列表: /blog/egon/cate/python
标签列表: /blog/egon/tag/生活
日期归档列表: /blog/egon/archive/2018-06

2.模板继承
{% extends 'base.html' %}

{% block content %}
{% endblock content%}}

3.自定义标签
/blog/templatetags/my_tag.py

@register.inclusion_tag('menu.html')
def get_menu(username):
...
return {} # 去渲染 menu.html

4.分组查询 .annotate() / extra()应用
多表分组
tag_list = Tag.objects.filter(blog=blog).annotate(
count = Count('article')).values_list('title', 'count')

单表分组 / DATE_FORMAT() / extra()
date_list = Article.objects.filter(user=user).extra(
select={"create_ym": "DATE_FORMAT(create_time,'%%Y-%%m')"}).values('create_ym').annotate(
c = Count('nid')).values_list('create_ym', 'c')

5. 时间、区域配置
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False

八、文章详细页

 知识点:

1.模板继承
article = Article.objects.filter(pk=article_id).first()
{% extends 'base.html' %}
{% block content %}
...
{{ article.articledetail.content|safe }}
{% endblock content %}

九、点赞、踩灭

知识点:

1.ajax的post
var csrfmiddlewaretoken = $('input[name="csrfmiddlewaretoken"]').val();

2.事务
try: # article_id 与 user_id 联合唯一 所有使用 try ...
with transaction.atomic():
ArticleUpDown.objects.create(is_up=is_up, article_id=article_id, user_id=user_id)
...
Article.objects.filter(pk=article_id).update(up_count=F('up_count')+1)

except Exception as e:
...

3.F查询:
Article.objects.filter(pk=article_id).update(up_count=F('up_count')+1)

十、评论楼、评论树

 知识点:

1.提交根评论
2.显示根评论
--- render显示
--- ajax显示
3.提交子评论
4.显示子评论
--- render显示
--- ajax显示
评论楼
评论树
1.ajax提交评论
post (csrfmiddlewaretoken)
pid = "" 根评论
pid = value 子评论

2.回复事件
@alex
val ="@" + $(this).attr('username')+ '\n';

3.事务
with transaction.atomic():
...
多个orm sql操作!

4.F查询,更新
Article.objects.filter(pk=article_id).update(comment_count=F("comment_count")+1)

 评论树:

5.js匿名函数
(function(){})()

6.ajax get方式获取comment_list
$.each(comment_list,function(index,comment)){
...
s = '...'
if(pid){ //子评论
$('#'+pid).append(s)
}else{ //根评论
$('.comment_tree').append(s)
}
}

7.JsonResponse() 返回 non-dict objects 需要 safe=False
def get_comment_tree(request, article_id):
ret = list(Comment.objects.filter(article_id=article_id).values(
'pk', 'content', 'parent_comment_id', 'user__username').order_by('nid'))

return JsonResponse(ret, safe=False)

十一、后台管理、KindEditor、BeautifulSoup

 知识点:

1.新建APP(backend)
settings:
INSTALLED_APPS = [..., 'backend.apps.BackendConfig',]

2.url分配
re_path(r'backend/', include(('backend.urls', 'backend'))),

3.认证装饰器
@login_required
settings:
LOGIN_URL = '/login/'

4.static配置
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'blog', 'static'),
os.path.join(BASE_DIR, 'backend', 'static'),
]

5.编辑器(KindEditor)
    <textarea name="article_con" id="article_box" cols="30" rows="10"></textarea>
    <script src="/static/kindeditor/kindeditor-all.js"></script>
KindEditor.ready(function (k) {
window.editor = k.create('#article_box', {
...
...
uploadJson: 'upload_img/',
extraFileUploadParams: {"csrfmiddlewaretoken":$('input[name="csrfmiddlewaretoken"]').val()},
filePostName: 'img'
})
})

6.文件上传
用户文件存在 /media/article_imgs/...
media_path = settings.MEDIA_ROOT
path = os.path.join(media_path, 'article_imgs', img_obj.name)
返回json
img_obj = request.FILES.get('img')
res = {
"url": "/media/article_imgs/"+img_obj.name,
"error": 0
}
return HttpResponse(json.dumps(res))

7.发布文章
防止XSS攻击 BeautifulSoup,对网页,解析数据

article_con = request.POST.get('article_con')
soup = BeautifulSoup(article_con, 'html.parser')

# 过滤script, 删除了所有的script标签
for tag in soup.find_all():
if tag.name == 'script':
tag.decompose()

# soup.prettify() == str(soup)
return redirect(reverse('backend:index'))

十二、code

https://github.com/alice-bj/cnblog

 

结构:

cnblog
├── backend
│   ├── static
│   │   ├── css
│   │   │   ├── backend.css
│   │   ├── js
│   │   │   ├── add_article.js
│   │   │   ├── jquery-3.2.1.js
│   │   │   └── jquery-3.2.1.min.js
│   │   └── kindeditor
│   │   ├── kindeditor-all.js
│   │   ├── kindeditor-all-min.js
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── blog
│   ├── admin.py
│   ├── apps.py
│   ├── media
│   │   ├── article_imgs
│   │   │   ├── girl.jpg
│   │   │   ├── jiqimao.gif
│   │   │   ├── jiqimao.jpg
│   │   │   └── lufei.jpg
│   │   ├── avatars
│   │   │   ├── girl.jpg
│   │   │   ├── lufei.jpg
│   ├── models.py
│   ├── myforms.py
│   ├── settings.py
│   ├── static
│   │   ├── bootstrap-3.3.7
│   │   │   ├── css
│   │   │   │   ├── bootstrap.css
│   │   │   ├── fonts
│   │   │   └── js
│   │   │   ├── bootstrap.js
│   │   │   ├── bootstrap.min.js
│   │   ├── css
│   │   │   ├── article_detail.css
│   │   │   ├── login.css
│   │   │   └── reg.css
│   │   ├── font
│   │   │   └── kumo.ttf
│   │   ├── img
│   │   │   ├── default.png
│   │   │   ├── downdown.gif
│   │   │   ├── icon_form.gif
│   │   │   └── upup.gif
│   │   ├── js
│   │   │   ├── article_detail.js
│   │   │   ├── jquery-3.2.1.js
│   │   │   ├── jquery-3.2.1.min.js
│   │   │   ├── login.js
│   │   │   └── reg.js
│   │   └── theme
│   │   ├── egon.css
│   │   └── yuan.css
│   ├── templatetags
│   │   ├── my_tags.py
│   ├── tests.py
│   ├── urls.py
│   ├── valid_img.py
│   └── views.py
├── cnblog
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── log
│   ├── cnblog_collect.log
│   ├── cnblog_err.log
│   ├── cnblog_info.log
├── manage.py
└── templates
├── add_article.html
├── article_detail.html
├── backend.html
├── base.html
├── homesite.html
├── index.html
├── login.html
├── menu.html
└── reg.html

账号:

egon egon1234 / yuan yuan1234 / alex alex1234
posted @ 2018-06-09 17:49  Alice的小屋  阅读(576)  评论(2编辑  收藏  举报