项目需求:html
1 总体参考“抽屉新热榜” + “虎嗅网” 2 实现不一样论坛版块 3 帖子列表展现 4 帖子评论数、点赞数展现 5 在线用户展现 6 容许登陆用户发贴、评论、点赞 7 容许上传文件 8 帖子可被置顶 9 可进行多级评论
知识必备:(注:没有必备下面知识的同窗,请返回去看会以后再看下面的内容防止蒙了~~!)前端
1 Django 2 HTML\CSS\JS 3 BootStrap 4 Jquery
一、表结构重要性node
在开发任何项目的时候,设计到数据库,第一个事情要作的是设计表结构。表结构设计很差就不要写代码,表结构是体现了你业务逻辑关系的。你的数据都要往数据库里存,其实表结构你要理清了你的架构也就出来了!python
二、设计表数据库
#!/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
配置admin注册model,不要忘记建立Django 管理员用户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)
效果以下:框架
一、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名称了,是否是全部前端都得修改!而且在有好几层的时候怎么改使用别名就会很是方便了!
二、前端页面写完以后发现图片没法正常显示
出现这个问题的缘由:他能找到uploads这个目录吗?他能直接访问这个目录吗?他不能直接访问不了!
若是在settings里加入uploads这个目录,可是这个方法仍是有问题!他会去找/static/uploads/uploads目录,看下面的图!
可是经过下面的方式就能够访问(缘由就是由于:他去/static/uploads/uploads目录找了)
2.二、咱们本身写上传的方法
定义form表单认证
#!/usr/bin/env python #-*- coding:utf-8 -*- # Tim Luo LuoTianShuaifrom 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 LuoTianShuaiimport 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没有了在找Bif k == parent:#若是等于就找到了parent,就吧son加入到他下面 d_dic[k][son] = {} #son下面可能还有儿子#这里找到就直接return了,你找到就直接退出就好了returnelse: #若是没有找到,有可能还有更深的地方,的须要剥掉一层 tree_search(d_dic[k],parent,son) data_dic = {} for item in data: # 每个item表明两个值一个父亲一个儿子 parent,son = item #先判断parent是否为空,若是为空他就是顶级的,直接吧他加到data_dicif 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': {}}}})
二、前端返回
当我们把这个字典往前端返回的时候,前端模板里是没有一个语法递归的功能的,虽然我们的层级关系已经出来了!因此我们须要自定义一个模板语言而后拼成一个html而后返回给前端展现!
2.一、配置前端吧数据传给simple_tag
{% load custom_tags %}
{% build_comment_tree article_obj.comment_set.select_related %}
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为一个空字典returnelse:#若是找不到递归查找 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)# 上面完成以后开始递归拼接字符串
二、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为一个空字典returnelse:#若是找不到递归查找 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.四、上面的看起来不是很好看怎么办?给他增长一个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为一个空字典returnelse:#若是找不到递归查找 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)
效果以下: