开发一个简单的BBS论坛

项目需求:

 整体参考“抽屉新热榜” + “虎嗅网”
实现不同论坛版块
帖子列表展示
帖子评论数、点赞数展示
在线用户展示
允许登录用户发贴、评论、点赞
允许上传文件
帖子可被置顶
可进行多级评论

知识必备:(注:没有必备下面知识的同学,请返回去看会之后再看下面的内容防止蒙了~~!)

 Django
HTML\CSS\JS
BootStrap
Jquery

设计表结构

1、表结构重要性

在开发任何项目的时候,设计到数据库,第一个事情要做的是设计表结构。表结构设计不好就不要写代码,表结构是体现了你业务逻辑关系的。你的数据都要往数据库里存,其实表结构你要理清了你的架构也就出来了!

2、设计表

#!/usr/bin/env python
#-*- coding:utf-8 -*- from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User # Create your models here. class Article(models.Model):
'''
帖子表
'''
#标题最大长度255,不能重名
title = models.CharField(u'文章标题',max_length=255,unique=True)
#发布办款-使用外键关联Category
category = models.ForeignKey("Category",verbose_name='板块名称')
'''
这里在admin中,title默认是显示英文的,我们可以在他的最前面加要给字段,在admin中就可以显示中文,他和verbose_name一样,什么时候必须使用
verbose_name呢?比如上面的{category = models.ForeignKey("Category",verbose_name='板块名称')} 这个字段第一个字段是关联的类,这里
就必须使用verbose_name
'''
#上传文件
head_img = models.ImageField(upload_to="uploads")
#文章内容(文章内容可能有很多,所以我们就不用"CharField"来写了,我们用TextField,不用规定他多长了,为可扩展长度)
content = models.TextField(u"内容")
#文章作者
author = models.ForeignKey("UserProfile",verbose_name="作者")
#发布日期
publish_date = models.DateTimeField(auto_now=True,verbose_name="发布日期")
#是否隐藏
hidden = models.BooleanField(default=False,verbose_name="是否隐藏")
#帖子的优先级
priority = models.IntegerField(default=1000,verbose_name="优先级") def __unicode__(self):
return "<%s,author:%s>" % (self.title,self.author) class Comment(models.Model):
'''
评论表
'''
#评论是基于文章的,并且一条评论只属于一个文章!对多的关系
#一个文章可以有多个评论,一个评论只属于一个文章
#评论文章
article = models.ForeignKey("Article")
#评论用户
user = models.ForeignKey("UserProfile")
#评论内容
comment = models.TextField(max_length=1000)
#评论时间
date = models.DateTimeField(auto_now=True) #多级评论,是不是评论评论的当前的表(自己表),所以就得和自己做一个关联!
#这里在关联自己的时候必须设置一个related_name否则会报错冲突
#这里parent_comment,必须设置为可以为空,因为如果他是第一评论他是没有父ID的
parent_comment = models.ForeignKey("self",related_name='p_comment',blank=True,null=True)
'''
prent self
Null 1
1 2
1 3
2 4
通过上面的这种方法来记录,评论的级别关系!
'''
def __unicode__(self):
return "<user:%s>" %(self.user)
class ThumbUp(models.Model):
'''
点赞
'''
#给那个文章点的
article = models.ForeignKey('Article')
#用户名
user = models.ForeignKey('UserProfile')
#时间
date = models.DateTimeField(auto_now=True) class Category(models.Model):
'''
板块表
'''
#板块名称
name = models.CharField(max_length=64,unique=True,verbose_name="板块名称")
#板块管理员
admin = models.ManyToManyField("UserProfile",verbose_name="模块管理员")
def __unicode__(self):
return self.name class UserProfile(models.Model):
'''
用户表
'''
#使用Django提供的用户表,直接继承就可以了.在原生的User表里扩展!(原生的User表里就有用户名和密码)
#一定要使用OneToOne,如果是正常的ForeignKey的话就表示User中的记录可以对应UserProfile中的多条记录!
#并且OneToOne的实现不是在SQL级别实现的而是在代码基本实现的!
user = models.OneToOneField(User)
#名字
name = models.CharField(max_length=32)
#属组
groups = models.ManyToManyField("UserGroup") def __unicode__(self):
return self.name class UserGroup(models.Model):
'''
用户组表
'''
name = models.CharField(max_length=64,unique=True)
def __unicode__(self):
return self.name

配置Django Admin

配置admin注册model,不要忘记创建Django 管理员用户

from django.contrib import admin
import models
# Register your models here. admin.site.register(models.Article)
admin.site.register(models.Category)
admin.site.register(models.Comment)
admin.site.register(models.ThumbUp)
admin.site.register(models.UserProfile)
admin.site.register(models.UserGroup)

我创建了几个板块,我在板块中查看的时候。只能看到下面简单的信息:

这里我想看到板块中的ID或其他信息怎么办?

#!/usr/bin/env python
#-*- coding:utf-8 -*- from django.contrib import admin
import models
# Register your models here. #给某个表专门的定制的类
class CategoryAdmin(admin.ModelAdmin):
list_display = ('id','name') class ArticleAdmin(admin.ModelAdmin):
list_display = ('id','title','author','hidden','publish_date') admin.site.register(models.Article,ArticleAdmin) #把自定义的类绑定到注册的类中
admin.site.register(models.Category,CategoryAdmin) #把自定义的类绑定到注册的类中
admin.site.register(models.Comment)
admin.site.register(models.ThumbUp)
admin.site.register(models.UserProfile)
admin.site.register(models.UserGroup)

效果如下:

前端页面&URLorViews配置

1、url别名使用

url里配置别名

url(r'^category/(\d+)/$',views.category,name='category'),

html里配置的时候就只认那个别名了

          <li role="presentation"><a href="{% url 'category' 1 %}">欧美专区</a></li>
<li role="presentation"><a href="{% url 'category' 2 %}">日韩专区</a></li>
<li role="presentation"><a href="{% url 'category' 3 %}">印度专区</a></li>

别名的好处:如果说那天想修改url里的这个url名称了,是不是所有前端都得修改!并且在有好几层的时候怎么改使用别名就会非常方便了!

2、前端页面写完之后发现图片无法正常显示

出现这个问题的原因:他能找到uploads这个目录吗?他能直接访问这个目录吗?他不能直接访问不了!

  • 一个是在Linux环境下做一个软连接连接过去

如果在settings里加入uploads这个目录,但是这个方法还是有问题!他会去找/static/uploads/uploads目录,看下面的图!

但是通过下面的方式就可以访问(原因就是因为:他去/static/uploads/uploads目录找了)

2.2、我们自己写上传的方法

定义form表单认证

#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Tim Luo LuoTianShuai from django import forms class ArticleForm(forms.Form):
title = forms.CharField(max_length=255,min_length=5)
summary = forms.CharField(max_length=255,min_length=5)
head_img = forms.ImageField()
content = forms.CharField(min_length=10)
category_id = forms.IntegerField()

定义上传方法

#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Tim Luo LuoTianShuai
import os def handle_upload_file(f,request): #f这里获取到文件句柄
base_img_upload_path = 'static/Uploads'
user_path = "%s/%s" % (base_img_upload_path,request.user.userprofile.id)
if not os.path.exists(user_path):
os.mkdir(user_path)
with open('%s/%s'% (user_path,f.name),'wb+') as destinations:
for chunk in f.chunks():
destinations.write(chunk)
#为了防止用户传送图片进行冲突,我们为每个用户进行创建用户 return "/static/Uploads/%s/%s" % (request.user.userprofile.id,f.name)

定义views

def new_article(request):
category_list = models.Category.objects.all()
if request.method == 'POST':
form = ArticleForm(request.POST,request.FILES)
if form.is_valid():
form_data = form.cleaned_data
form_data['author_id'] = request.user.userprofile.id #自定义图片上传
new_img_path = handle_upload_file(request.FILES['head_img'],request)
#但是在views也保存了一份,我们给他改掉改成我们自己的就行了
form_data['head_img'] = new_img_path #create只能返回成功失败,我想在创建完成之后返回文章的ID,直接下面那么写就可以
print form_data
new_article_obj = models.Article(**form_data)
new_article_obj.save()#这个对象就直接返回了
return render(request,'new_article.html',{'new_article_obj':new_article_obj}) #如果没有这个变量说明是创建新文章呢
else:
print form.errors return render(request,'new_article.html',{'category_list':category_list})

多级评论实现

用户可以直接对贴子进行评论,其它用户也可以对别的用户的评论再进行评论,也就是所谓的垒楼,如下图:

所有的评论都存在一张表中, 评论与评论之前又有从属关系,如何在前端 页面上把这种层级关系体现出来?

首先咱们在存储数据的时候是怎么来实现记录层级关系的呢?(下面的图是经过简化的把其他列隐藏了)

我们在上面创建数据库表结构的时候,就定义了一个外键为他们自己(parent_comment_id),如果他没有父级别的ID说明他们是第一层,如果有说明他包含在一个评论之内!(仔细看上面的表结构)

先把评论简化成一个这样的模型:

data = [
(None,'A'),
('A','A1'),
('A','A1-1'),
('A1','A2'),
('A1-1','A2-3'),
('A2-3','A3-4'),
('A1','A2-2'),
('A2','A3'),
('A2-2','A3-3'),
('A3','A4'),
(None,'B'),
('B','B1'),
('B1','B2'),
('B1','B2-2'),
('B2','B3'),
(None,'C'),
('C','C1'), ]

转换为字典之后:

data_dic = {
'A': {
'A1': {
'A2':{
'A3':{
'A4':{}
}
},
'A2-2':{
'A3-3':{}
}
}
},
'B':{
'B1':{
'B2':{
'B3':{}
},
'B2-2':{}
}
},
'C':{
'C1':{}
} }

看上面的字典,我们能通过for循来获取他有多少层吗?当然不行,我们不知道他有多少层就没有办法进行找,或者通过while循环,最好是用递归进行一层一层的查找!

我们在前端展示的时候需要知道,那条数据是那一层的,不可能是垒下去的!因为他们是有层级关系的!

我们用后端来实现:咱们给前端返回一个字典这样是不行的,咱们在后端把层级关系建立起来~返回的时候直接返回一个完整的HTML

转换为字典之后就有层级关系了我们可以通过递归来实现了!上面再没有转换为字典的时候层级关系就不是很明确了!

在循环的过程中不断的创建字典,先建立最顶级的,然后在一层一层的建立

先通过一个简单的例子看下:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Tim Luo LuoTianShuai data = [
(None,'A'),
('A','A1'),
('A','A1-1'),
('A1','A2'),
('A1-1','A2-3'),
('A2-3','A3-4'),
('A1','A2-2'),
('A2','A3'),
('A2-2','A3-3'),
('A3','A4'),
(None,'B'),
('B','B1'),
('B1','B2'),
('B1','B2-2'),
('B2','B3'),
(None,'C'),
('C','C1'), ] def tree_search(d_dic,parent,son):
#一层一层找,先拨第一层,一层一层往下找
for k,v in d_dic.items():
#举例来说我先遇到A,我就把A来个深度查询,A没有了在找B
if k == parent:#如果等于就找到了parent,就吧son加入到他下面
d_dic[k][son] = {} #son下面可能还有儿子
#这里找到就直接return了,你找到就直接退出就行了
return
else:
#如果没有找到,有可能还有更深的地方,的需要剥掉一层
tree_search(d_dic[k],parent,son) data_dic = {} for item in data:
# 每一个item代表两个值一个父亲一个儿子
parent,son = item
#先判断parent是否为空,如果为空他就是顶级的,直接吧他加到data_dic
if parent is None:
data_dic[son] = {} #这里如果为空,那么key就是他自己,他儿子就是一个空字典
else:
'''
如果不为空他是谁的儿子呢?举例来说A3他是A2的儿子,但是你能直接判断A3的父亲是A2你能直接判断他是否在A里面吗?你只能到第一层.key
所以咱们就得一层一层的找,我们知道A3他爹肯定在字典里了,所以就得一层一层的找,但是不能循环找,因为你不知道他有多少层,所以通过递归去找
直到找到位置
'''
tree_search(data_dic,parent,son) #因为你要一层一层找,你的把data_dic传进去,还的把parent和son传进去 for k,v in data_dic.items():
print(k,v)

执行结果:(完美)

('A', {'A1': {'A2': {'A3': {'A4': {}}}, 'A2-2': {'A3-3': {}}}, 'A1-1': {'A2-3': {'A3-4': {}}}})
('C', {'C1': {}})
('B', {'B1': {'B2-2': {}, 'B2': {'B3': {}}}})

2、前端返回

当咱们把这个字典往前端返回的时候,前端模板里是没有一个语法递归的功能的,虽然咱们的层级关系已经出来了!所以咱们需要自定义一个模板语言然后拼成一个html然后返回给前端展示!

2.1、配置前端吧数据传给simple_tag

{% load custom_tags %}

{% build_comment_tree article_obj.comment_set.select_related %}

2.2、simple_tag获取数据然后把用户穿过来的数据进行转换为字典

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from django import template
from django.utils.safestring import mark_safe register = template.Library() def tree_search(d_dic,comment_obj):#这里不用传附近和儿子了因为他是一个对象,可以直接找到父亲和儿子
for k,v_dic in d_dic.items():
if k == comment_obj.parent_comment:#如果找到了
d_dic[k][comment_obj] = {} #如果找到父亲了,你的把自己存放在父亲下面,并把自己当做key,value为一个空字典
return
else:#如果找不到递归查找
tree_search(d_dic[k],comment_obj) @register.simple_tag
def build_comment_tree(comment_list):
'''
把评论传过来只是一个列表格式(如下),要把列别转换为字典,在把字典拼接为html
[<Comment: <A,user:罗天帅>>, <Comment: <A2-1,user:罗天帅>>, <Comment: <A3-1,user:罗天帅>>, <Comment: <A2-2,user:罗天帅>>,
<Comment: <A4-1,user:罗天帅>>, <Comment: <A4-2,user:罗天帅>>, <Comment: <A5-1,user:罗天帅>>, <Comment: <A3-2,user:罗天帅>>,
<Comment: <B2,user:罗天帅>>, <Comment: <B2-1,user:罗天帅>>]
:param comment_list:
:return:
'''
comment_dic = {}
#print(comment_list)
for comment_obj in comment_list: #每一个元素都是一个对象
if comment_obj.parent_comment is None: #如果没有父亲
comment_dic[comment_obj] = {}
else:
#通过递归找
tree_search(comment_dic,comment_obj) # #测试:
# for k,v in comment_dic.items():
# print(k,v) # 上面完成之后开始递归拼接字符串

2、3生成html标签

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from django import template
from django.utils.safestring import mark_safe register = template.Library() def tree_search(d_dic,comment_obj):#这里不用传附近和儿子了因为他是一个对象,可以直接找到父亲和儿子
for k,v_dic in d_dic.items():
if k == comment_obj.parent_comment:#如果找到了
d_dic[k][comment_obj] = {} #如果找到父亲了,你的把自己存放在父亲下面,并把自己当做key,value为一个空字典
return
else:#如果找不到递归查找
tree_search(d_dic[k],comment_obj) def generate_comment_html(sub_comment_dic):
#先创建一个html默认为空
html = ""
for k,v_dic in sub_comment_dic.items():#循环穿过来的字典
html += "<div class='comment-node'>" + k.comment + "</div>"
#上面的只是把第一层加了他可能还有儿子,所以通过递归继续加
if v_dic:
html += generate_comment_html(v_dic)
return html @register.simple_tag
def build_comment_tree(comment_list):
'''
把评论传过来只是一个列表格式(如下),要把列别转换为字典,在把字典拼接为html
[<Comment: <A,user:罗天帅>>, <Comment: <A2-1,user:罗天帅>>, <Comment: <A3-1,user:罗天帅>>, <Comment: <A2-2,user:罗天帅>>,
<Comment: <A4-1,user:罗天帅>>, <Comment: <A4-2,user:罗天帅>>, <Comment: <A5-1,user:罗天帅>>, <Comment: <A3-2,user:罗天帅>>,
<Comment: <B2,user:罗天帅>>, <Comment: <B2-1,user:罗天帅>>]
:param comment_list:
:return:
'''
comment_dic = {}
#print(comment_list)
for comment_obj in comment_list: #每一个元素都是一个对象
if comment_obj.parent_comment is None: #如果没有父亲
comment_dic[comment_obj] = {}
else:
#通过递归找
tree_search(comment_dic,comment_obj) # #测试:
# for k,v in comment_dic.items():
# print(k,v) # 上面完成之后开始递归拼接字符串 #div框架
html = "<div class='comment-box'>"
margin_left = 0
for k,v in comment_dic.items():
#第一层的html
html += "<div class='comment-node'>" + k.comment + "</div>"
#通过递归把他儿子加上
html += generate_comment_html(v)
html += "</div>"
return mark_safe(html)

效果如下:

2.4、上面的看起来不是很好看怎么办?给他增加一个margin-left让他来显示层级效果,每次进行递归的时候给他加一个值!

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from django import template
from django.utils.safestring import mark_safe register = template.Library() def tree_search(d_dic,comment_obj):#这里不用传附近和儿子了因为他是一个对象,可以直接找到父亲和儿子
for k,v_dic in d_dic.items():
if k == comment_obj.parent_comment:#如果找到了
d_dic[k][comment_obj] = {} #如果找到父亲了,你的把自己存放在父亲下面,并把自己当做key,value为一个空字典
return
else:#如果找不到递归查找
tree_search(d_dic[k],comment_obj) def generate_comment_html(sub_comment_dic,margin_left_val):
#先创建一个html默认为空
html = ""
for k,v_dic in sub_comment_dic.items():#循环穿过来的字典
html += "<div style='margin-left:%spx' class='comment-node'>" % margin_left_val + k.comment + "</div>"
#上面的只是把第一层加了他可能还有儿子,所以通过递归继续加
if v_dic:
html += generate_comment_html(v_dic,margin_left_val+15)
return html @register.simple_tag
def build_comment_tree(comment_list):
'''
把评论传过来只是一个列表格式(如下),要把列别转换为字典,在把字典拼接为html
[<Comment: <A,user:罗天帅>>, <Comment: <A2-1,user:罗天帅>>, <Comment: <A3-1,user:罗天帅>>, <Comment: <A2-2,user:罗天帅>>,
<Comment: <A4-1,user:罗天帅>>, <Comment: <A4-2,user:罗天帅>>, <Comment: <A5-1,user:罗天帅>>, <Comment: <A3-2,user:罗天帅>>,
<Comment: <B2,user:罗天帅>>, <Comment: <B2-1,user:罗天帅>>]
:param comment_list:
:return:
'''
comment_dic = {}
#print(comment_list)
for comment_obj in comment_list: #每一个元素都是一个对象
if comment_obj.parent_comment is None: #如果没有父亲
comment_dic[comment_obj] = {}
else:
#通过递归找
tree_search(comment_dic,comment_obj) # #测试:
# for k,v in comment_dic.items():
# print(k,v) # 上面完成之后开始递归拼接字符串 #div框架
html = "<div class='comment-box'>"
margin_left = 0
for k,v in comment_dic.items():
#第一层的html
html += "<div class='comment-node'>" + k.comment + "</div>"
#通过递归把他儿子加上
html += generate_comment_html(v,margin_left+15)
html += "</div>"
return mark_safe(html)

效果如下:

Python之路【第十八篇】Django小项目简单BBS论坛部分内容知识点的更多相关文章

  1. Django小项目简单BBS论坛

    开发一个简单的BBS论坛 项目需求: 1 整体参考"抽屉新热榜" + "虎嗅网" 2 实现不同论坛版块 3 帖子列表展示 4 帖子评论数.点赞数展示 5 在线用 ...

  2. Python之路(第二十八篇) 面向对象进阶:类的装饰器、元类

    一.类的装饰器 类作为一个对象,也可以被装饰. 例子 def wrap(obj): print("装饰器-----") obj.x = 1 obj.y = 3 obj.z = 5 ...

  3. Python之路(第十八篇)shutil 模块、zipfile模块、configparser模块

    一.shutil 模块 1.shutil.copyfileobj(fsrc, fdst[, length]) 将文件内容拷贝到另一个文件中,需要打开文件 import shutil shutil.co ...

  4. Python之路【第八篇】:堡垒机实例以及数据库操作

    Python之路[第八篇]:堡垒机实例以及数据库操作   堡垒机前戏 开发堡垒机之前,先来学习Python的paramiko模块,该模块机遇SSH用于连接远程服务器并执行相关操作 SSHClient ...

  5. Python开发【第十八篇】Web框架之Django【基础篇】

    一.简介 Python下有许多款不同的 Web 框架,Django 是重量级选手中最有代表性的一位,许多成功的网站和APP都基于 Django. Django 是一个开放源代码的Web应用框架,由 P ...

  6. Python自动化 【第十八篇】:JavaScript 正则表达式及Django初识

    本节内容 JavaScript 正则表达式 Django初识 正则表达式 1.定义正则表达式 /.../  用于定义正则表达式 /.../g 表示全局匹配 /.../i 表示不区分大小写 /.../m ...

  7. Python之路【第八篇】:Python模块

    阅读目录 一.模块和包 模块(module)的概念: 在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码会越来越长,越来越不容易维护. 为了编写可维护的代码,我们把很多函数分组,分别放到 ...

  8. Python之路【第八篇】:面向对象的程序设计

    阅读目录 一 面向对象的程序设计的由来二 什么是面向对象的程序设计及为什么要有它三 类和对象3.1 什么是对象,什么是类3.2 类相关知识3.3 对象相关知识3.4 对象之间的交互3.5 类名称空间与 ...

  9. 【Python之路】第二十三篇--Django【进阶篇】

    文件配置 1.模版Templates文件配置: TEMPLATE_DIRS = ( os.path.join(BASE_DIR,'templates'), ) 2.静态文件static配置: STAT ...

随机推荐

  1. 服务器运行环境(LNMP)安装说明

    服务器运行环境(LNMP)安装说明 因为公司需要一套流程标准,所以写了如下步骤. 先下载文件environment.tar,将文件上传到服务器. 使用命令解压文件,tar xvf environmen ...

  2. Python学习day3作业

    days3作业 作业需求 HAproxy配置文件操作 根据用户输入,输出对应的backend下的server信息 可添加backend 和sever信息 可修改backend 和sever信息 可删除 ...

  3. 实验楼 linux 学习

    实验楼 linux 学习     一.Linux 用户管理 1.查看用户 who am i // who mom likes whoami   ====--------====== 输入的第一列表示打 ...

  4. 【2016-11-2】【坚持学习】【Day17】【微软 推出的SQLHelper】

    从网络上找到 微软原版本的SQLHelper,很多行代码.认真看了,学习了.  代码:  using System; using System.Data; using System.Xml; usin ...

  5. BZOJ 3531: [Sdoi2014]旅行 [树链剖分]

    3531: [Sdoi2014]旅行 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1685  Solved: 751[Submit][Status] ...

  6. 代理延迟加载中proxy和弄no-proxy区别

    Child   <-   many-to-one   ->Parent         class   Child   {         private   Parent   paren ...

  7. JSON 字符串中的中括号和大括号区别详解

    json 变量有两种可能, 可能是一个对象, (类似 类的实例), 也可能是一个数组!! 主要是要 从 ""语义" 上来分析, 到底该用 大括号还是用中括号: 如果从语义 ...

  8. [No000076]用Eclipse编写python(配置PyDev插件)

    下载,安装python解释器 地址:https://www.python.org/downloads/ 下载,安装java jdk 地址:http://www.oracle.com/technetwo ...

  9. C#.NET 大型企业信息化系统集成快速开发平台 4.2 版本 - 检查版本升级、检查登录超时

    当用户都在一个公司内,甚至一个办公室时,喉一下或者跑过去亲自更新一下程序,就可以了,问题就很简单也没多少复杂性,也不怎么考验技术能力.当面对全国10万以上客户端时,问题就来了. 1:有的用户打开系统一 ...

  10. 记录android显示流程

    mtk平台: displayclient->deque->数据放入显存->surfaceflinger->分发数据到surfacetexture(OnFrameAvailabl ...