30-项目实战BBS(仿抽屉论坛)

创建如上的关系:

方法1

m2m 会自动创建联合唯一索引(但是数据表只能3列数据:id,user,news)

通过对象操作第三张表

方法2

自己创建第三张表,但是需要手工创建联合唯一索引【unique_together】(可以指定多列数据)

通过操作第三张表

方法3.

m2m通过to关联m2m的另一张表,还可以through关联另一张表(通过through_fields指定字段),这种方式不需要创建第三张表

通过对象操作第三张表(只能查询,不能增加,修改,是因为这种方式定义的第三张表还可能有其他的列) + 通过操作第三张表

备注:through_fields 接收一个二元组('field1', 'field2'),其中field1 为指向定义ManyToManyField 字段的模型的外键名称(本例中为group),field2 为指向目标模型的外键的名称(本例中为person). 更多参考  Django多表查询

 

from django.db import models

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

class NewsType(models.Model):
    caption = models.CharField(max_length=16)

class News(models.Model):
    title = models.CharField(verbose_name='标题',max_length=32)
    url = models.CharField(verbose_name='URL',max_length=255)
    avatar = models.CharField(verbose_name='头像',max_length=255)
    summary = models.CharField(verbose_name='简介',max_length=255)
    new_type = models.ForeignKey(verbose_name='新闻类型',to="NewsType")
    user = models.ForeignKey(verbose_name='发布者',to='UserInfo',related_name='c')
    ctime = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)
    # 点赞和评论时,记着更新like_count,comment_count。自增1: F 实现
    like_count = models.IntegerField(default=0)
    comment_count = models.IntegerField(default=0)
    # 第一种
    # like = models.ManyToManyField(to='UserInfo')

    # 查询功能才能使用
    # likes = models.ManyToManyField(to='UserInfo',through="Like",through_fields=('uuser','nnew'))
# new_list = News.objects.all()
# for obj in new_list:
#     obj.title,

class Comment(models.Model):
    content = models.CharField(verbose_name='评论内容',max_length=255)
    new = models.ForeignKey(verbose_name='评论的新闻ID',to='News')
    user = models.ForeignKey(verbose_name='评论者',to='UserInfo')
    ctime = models.DateTimeField(verbose_name='评论时间', auto_now_add=True)

# 第二种
# class Like(models.Model):
#     user = models.ForeignKey(to='UserInfo')
#     news = models.ForeignKey(to='News')
#     ctime = models.DateTimeField(verbose_name='点赞时间', auto_now_add=True)
#
#     class Meta:
#         unique_together = [
#             ('user','news'),
#         ]

class Like(models.Model):
    uuser = models.ForeignKey(to='UserInfo',related_name='a')
    nnew = models.ForeignKey(to='News',related_name='b')
    ctime = models.DateTimeField(verbose_name='点赞时间', auto_now_add=True)

    class Meta:
        unique_together = [
            ('uuser','nnew'),
        ]

 

1. Bug解决
    related_name,用于定义反向关联时候,使用的字段名称
        related_query_name
    
        
        class A:
            title = models.CharField()
        obj = models.A.objects.get(id=1)
        obj.b_set.all()
        obj.xxxxxx_set.all() # related_query_name='xxxxxx'
        obj.uuuu.all()       # related_name='uuuu'
        
        obj.x
        obj.u
        
        class B:
            xx ..xx
            fk1 = models.ForignKey(related_name='x')
            fk2 = models.ManyToMany(related_name='u')
        
        models.B.objects.filter(fk__title='xx')
    
    through_fields = (在关系表中与当前表建立FK关系的字段名称,在关系表中与目标表建立的FK关系字段名称)

 

 

 

chunks 类似有readlines

related_name的作用:反查。不用表名加set了 

    - BBS项目练习:
        地址:http://dig.chouti.com/ 
        要求:
            表结构设计
            功能开发:
                页面样式和布局
                文章列表(分页)
                点赞:思路,发送ajax请求:
                            - 判断:
                                已经存在: like表中删除一条记录,new中like_count,自减1
                                不存在: like表中天剑一条记录,new中like_count,自加1
                            - 登录之后才能点赞
                
                发布文章(上传图片)
                    1. 根据URL,自动获取标题和摘要
                       pip3 install beautifulsoup4
                       pip3 install requests
                       点击:
                            发送Ajax请求,将 http://music.163.com/#/song?id=188057 发送到后台
                            def get_title_summary(request):
                                url = request.POST.get('url')
                                import requests
                                from bs4 import BeautifulSoup


                                response = requests.get('http://music.163.com/#/song?id=188057')
                                soup = BeautifulSoup(response.text,'html.parser')
                                title = soup.find('title').text
                                desc = soup.find('meta',attrs={'name': 'description'}).get('content')
                                print(title)
                                print(desc)
                                data = {'title':title,'desc':decc}
                                return HttpResponse(json.dumps(data))
                    2. 基于Ajax实现图片上传    
                        隐藏的Input框,放置头像路径
                    
                评论

 

 

 

 

 

 

 

#####################################

点击不同的标签分类(跳转不同的URL展示不同的内容)

urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^all/(?P<new_type_id>\d+)/', views.index),  # 将文章类型的id传递给url   
    url(r'^', views.index),  # 不要写在上面,会劫胡
]

views.py

from django.shortcuts import render, HttpResponse
from app01 import models
import os


def index(request, *args, **kwargs):
    """
    首页:
        - 显示所有的新闻类型
        - 显示所有新闻列表
    :param request:
    :param args:
    :param kwargs:
    :return:
    """
    # current_new_type_id的值可能是:None, "1"  "2" ......
    current_new_type_id = kwargs.get('new_type_id')
    if current_new_type_id:
        current_new_type_id = int(current_new_type_id)  # 获取当前选中的新闻类型
    new_type_list = models.NewsType.objects.all()  # 获取所有新闻类型分类

    new_list = models.News.objects.filter(**kwargs)  # 获取当前选中的新闻类型下的所有文章

    return render(request,
                  'index.html',
                  {'new_type_list': new_type_list,
                   'new_list': new_list,
                   'current_new_type_id': current_new_type_id})

models.py

from django.db import models


class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)


class NewsType(models.Model):
    caption = models.CharField(max_length=16)


class News(models.Model):
    title = models.CharField(verbose_name='标题', max_length=32)
    url = models.CharField(verbose_name='URL', max_length=255)
    avatar = models.CharField(verbose_name='头像', max_length=255)
    summary = models.CharField(verbose_name='简介', max_length=255)
    new_type = models.ForeignKey(verbose_name='新闻类型', to="NewsType")
    user = models.ForeignKey(
        verbose_name='发布者',
        to='UserInfo',
        related_name='c')
    # related_name  反向查找的时候不在用【小写表名_set】(如果B表与A表有两个FK、m2m的时候很有用)
    # 如果B中关联了两个A的字段,可以在一个FK或m2m中定义一个related_name
    # 那么A的对象使用的时候,一个使用related_name,另外一个则用默认的(a.b_set)
    ctime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True),
    # 点赞和评论时,记着更新like_count,comment_count。自增1: F 实现
    # 字段定义结尾不能加逗号,否则前端页面显示(<django.db.models.fields.IntegerField>,)
    like_count = models.IntegerField(default=0)
    comment_count = models.IntegerField(default=0)

    likes = models.ManyToManyField(
        to='UserInfo',
        through="Like",
        through_fields=(
            'nnew',
            'uuser'))


class Comment(models.Model):
    content = models.CharField(verbose_name='评论内容', max_length=255)
    new = models.ForeignKey(verbose_name='评论的新闻ID', to='News')
    user = models.ForeignKey(verbose_name='评论者', to='UserInfo')
    ctime = models.DateTimeField(verbose_name='评论时间', auto_now_add=True)


class Like(models.Model):
    nnew = models.ForeignKey(to='News', related_name='b')
    uuser = models.ForeignKey(to='UserInfo', related_name='a')
    ctime = models.DateTimeField(verbose_name='点赞时间', auto_now_add=True)

    class Meta:
        unique_together = [
            ('uuser', 'nnew'),
        ]

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
    <h1>分类列表</h1>
    <ul>
        {% if current_new_type_id %}
            <li><a href="/">全部</a></li>
        {% else %}
            <li><a style="color:green" href="/">全部</a></li>
        {% endif %}

        {% for row in new_type_list %}
            {% if row.id == current_new_type_id %}
                <li><a style="color:green" href="/all/{{ row.id }}/">{{ row.caption }}</a></li>
            {% else %}
                <li><a href="/all/{{ row.id }}/">{{ row.caption }}</a></li>
            {% endif %}
        {% endfor %}
    </ul>
    <h2>新闻列表</h2>
    <div>
        {% for row in new_list %}
            <div>
                <div>{{ row.title }}</div>
                <div>{{ row.user.username }}  评论:{{ row.comment_count }}
                    <div style="display: inline-block;position: relative;">
                        赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a>
                    </div>
                </div>
            </div>
            <div>
                <div>{{ row.title }}</div>
                <div>{{ row.user.username }}  评论:{{ row.comment_count }}  赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div>
            </div>
            <div>
                <div>{{ row.title }}</div>
                <div>{{ row.user.username }}  评论:{{ row.comment_count }}  赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div>
            </div>
        {% endfor %}
    </div>
</body>
</html>

实现的效果:

上传图片两种方法(FormData和伪Ajax的方法)

1. FormData实现图片上传

urls.py

from django.conf.urls import url
from django.contrib import admin
from app01.views import index,upload,upload_img
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^all/(?P<new_type_id>\d+)/', index),
    url(r'^upload/', views.upload),
    url(r'^upload_img/', views.upload_img),
    url(r'^', index),  # 不要写在上面,会劫胡
]

views.py

from django.shortcuts import render, HttpResponse
from app01 import models
import os

def upload(request):
return render(request, 'upload.html')


def upload_img(request):
obj = request.FILES.get('a1') # 从请求中获取图片
img_path = os.path.join('static', 'img', obj.name) # 拼接图片路径
with open(img_path, mode='wb') as f:
for chunk in obj.chunks():
f.write(chunk)
data = {
'status': True,
'path': img_path # 图片保存后,并将图片路径返回给前端
}
import json
return HttpResponse(json.dumps(data))

templates/upload.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>上传图片</h1>
    <input id="fafafa" type="file" name="fafafa" />
    <input type="button" value="点我点我" onclick="uploadImg()" />
    <input type="text" id="avatar" name="avatar" /> <!--这个可以对用户隐藏-->
    <div id="container"></div>

    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        function uploadImg() {
            // 获取文件
            // 上传文件
            // 预览
            var formData = new FormData(); //创建一个FormData空对象
            formData.append('a1',$('#fafafa')[0].files[0]); //然后使用append方法添加key/value
            console.log(formData);
            $.ajax({
                url: '/upload_img/',
                type: 'POST',
                data: formData,
                processData: false,  // tell jQuery not to process the data
                contentType: false,  // tell jQuery not to set contentType
                success:function (arg) {
                    arg = JSON.parse(arg);
                    if (arg.status){
                        var tag = document.createElement('img');
                        tag.src = "/" + arg.path;
                        $('#container').empty();
                        $('#container').append(tag); //预览的效果即在页面生成一个img标签
                        $('#avatar').val(arg.path);

                    }
                }
            })
        }
    </script>
</body>
</html>

实现效果:

 

2. 伪Ajax实现图片上传(form + iframe)

iframe学习

urls.py

url(r'^upload2/', views.upload2),

views.py

def upload2(request):

    return render(request, 'upload2.html')

templates/upload2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form>
        <input type="text" />

        <input type="submit" value="提交" />
    </form>

    <input type="text"/>
    <input type="text" placeholder="请输入关键字" id="key" />
    <input type="button" value="搜索" onclick="searchSB();" />  
    <div class="container">
        <iframe id="im1" src="" style="width: 500px;height: 1000px;"></iframe>
        <iframe id="im2" src="" style="width: 500px;height: 1000px;"></iframe>
    </div>
    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        function searchSB() {
            var val = $('#key').val();
            $('#im1').attr("src","https://www.baidu.com/s?wd="+val);
            $('#im2').attr("src","https://www.sogou.com/web?query="+val);
        }
    </script>
</body>
</html>

效果

 

上传文件总结

    
4. 上传文件
    - 基于FormData
        - 缺点,兼容性不好
        - 优点,Ajax直接发送
    
    - 伪Ajax,兼容性更好
        - iframe,天生局部刷新
        - form,天生整个页面刷新
    
    
    普通POST请求:
        - Ajax(*- 伪Ajax 
    上传内文件:
        - Ajax, FormData
        - 伪Ajax (*)

 

点赞方法实现

urls.py

from django.conf.urls import url
from django.contrib import admin
from app01.views import index,upload,upload_img
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^all/(?P<new_type_id>\d+)/', views.index),
    url(r'^do_like.html$', views.do_like),
    url(r'^', views.index),  # 不要写在上面,会劫胡
]

views.py


from django.shortcuts import render, HttpResponse
from app01 import models
import os
import json
from django.db.models import F
from django.db import transaction
from utils.response import LikeResponse #用了自定义的LikeResponse


def index(request, *args, **kwargs):
"""
首页:
- 显示所有的新闻类型
- 显示所有新闻列表
:param request:
:param args:
:param kwargs:
:return:
"""
# current_new_type_id的值可能是:None, "1" "2" ......
current_new_type_id = kwargs.get('new_type_id')
if current_new_type_id:
current_new_type_id = int(current_new_type_id) # 获取当前选中的新闻类型
new_type_list = models.NewsType.objects.all() # 获取所有新闻类型分类

new_list = models.News.objects.filter(**kwargs) # 获取当前选中的新闻类型下的所有文章

return render(request,
'index.html',
{'new_type_list': new_type_list,
'new_list': new_list,
'current_new_type_id': current_new_type_id})

def do_like(request):
"""
点赞
:param request:
:return:
"""
response = LikeResponse()
try:
new_id = request.POST.get('newId')
# 当前登录用户ID
# uid = request.session.get('uid')
uid = 1
# 判断当前用户是否已经点赞
exist_like = models.Like.objects.filter(
nnew_id=new_id, uuser_id=uid).count()
with transaction.atomic(): # 开启django的事务,但是只能处理事务,不能处理异常,所以外面加上异常判断
if exist_like:
# 已点赞,则取消
models.Like.objects.filter(
nnew_id=new_id, uuser_id=uid).delete()
models.News.objects.filter(
id=new_id).update(
like_count=F('like_count') - 1)
response.code = 666 # 返回封装的状态码
else:
# 未点赞,则加1
models.Like.objects.create(nnew_id=new_id, uuser_id=uid)
models.News.objects.filter(
id=new_id).update(
like_count=F('like_count') + 1)
response.code = 999 # 返回封装的状态码
except Exception as e:
response.msg = str(e) # 返回封装的错误信息
else:
response.status = True # 返回封装的状态值(True | False)
# json不能dumps对象,可以通过__dict__ 把对象换成dict
return HttpResponse(json.dumps(response.get_dict()))

models.py(同上)

from django.db import models


class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)


class NewsType(models.Model):
    caption = models.CharField(max_length=16)


class News(models.Model):
    title = models.CharField(verbose_name='标题', max_length=32)
    url = models.CharField(verbose_name='URL', max_length=255)
    avatar = models.CharField(verbose_name='头像', max_length=255)
    summary = models.CharField(verbose_name='简介', max_length=255)
    new_type = models.ForeignKey(verbose_name='新闻类型', to="NewsType")
    user = models.ForeignKey(
        verbose_name='发布者',
        to='UserInfo',
        related_name='c')
    # related_name  反向查找的时候不在用【小写表名_set】(如果B表与A表有两个FK、m2m的时候很有用)
    # 如果B中关联了两个A的字段,可以在一个FK或m2m中定义一个related_name
    # 那么A的对象使用的时候,一个使用related_name,另外一个则用默认的(a.b_set)
    ctime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True),
    # 点赞和评论时,记着更新like_count,comment_count。自增1: F 实现
    # 字段定义结尾不能加逗号,否则前端页面显示(<django.db.models.fields.IntegerField>,)
    like_count = models.IntegerField(default=0)
    comment_count = models.IntegerField(default=0)

    likes = models.ManyToManyField(
        to='UserInfo',
        through="Like",
        through_fields=(
            'nnew',
            'uuser'))


class Comment(models.Model):
    content = models.CharField(verbose_name='评论内容', max_length=255)
    new = models.ForeignKey(verbose_name='评论的新闻ID', to='News')
    user = models.ForeignKey(verbose_name='评论者', to='UserInfo')
    ctime = models.DateTimeField(verbose_name='评论时间', auto_now_add=True)


class Like(models.Model):
    nnew = models.ForeignKey(to='News', related_name='b')
    uuser = models.ForeignKey(to='UserInfo', related_name='a')
    ctime = models.DateTimeField(verbose_name='点赞时间', auto_now_add=True)

    class Meta:
        unique_together = [
            ('uuser', 'nnew'),
        ]

utils/response.py

class BaseResponse(object):  # 定义BaseResponse
    def __init__(self):
        self.status = False
        self.data = None
        self.msg = None

    def get_dict(self):
        return self.__dict__  # json不能dumps对象,可以通过__dict__ 把对象换成dict,这里将其定义为方法


class LikeResponse(BaseResponse):
    def __init__(self):
        self.code = 0
        super(LikeResponse,self).__init__()

# obj = LikeResponse()
# print(obj.__dict__)

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
    <h1>分类列表</h1>
    <ul>
        {% if current_new_type_id %}
            <li><a href="/">全部</a></li>
        {% else %}
            <li><a style="color:green" href="/">全部</a></li>
        {% endif %}

        {% for row in new_type_list %}
            {% if row.id == current_new_type_id %}
                <li><a style="color:green" href="/all/{{ row.id }}/">{{ row.caption }}</a></li>
            {% else %}
                <li><a href="/all/{{ row.id }}/">{{ row.caption }}</a></li>
            {% endif %}
        {% endfor %}
    </ul>
    <h2>新闻列表</h2>
    <div>
        {% for row in new_list %}
            <div>
                <div>{{ row.title }}</div>
                <div>{{ row.user.username }}  评论:{{ row.comment_count }}
                    <div style="display: inline-block;position: relative;">
                        赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a>
                    </div>
                </div>
            </div>
            <div>
                <div>{{ row.title }}</div>
                <div>{{ row.user.username }}  评论:{{ row.comment_count }}  赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div>
            </div>
            <div>
                <div>{{ row.title }}</div>
                <div>{{ row.user.username }}  评论:{{ row.comment_count }}  赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div>
            </div>
        {% endfor %}
    </div>

    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        $(function () {
            bindLikeEvent();
        });

        function bindLikeEvent() {
            $('.new-like').click(function () {
                // 获取当前新闻ID
                var newId = $(this).attr('new-id');
                var $this = $(this);  //ajax里面不能用$(this),###可以提前赋值###,当前函数没有,没有会找上面的
                $.ajax({
                    url: '/do_like.html',
                    type: "POST",
                    data: {'newId': newId},
                    dataType: 'JSON',
                    success:function (arg) {
                        if(arg.status){
                            var origin = $this.text();
                            var count = parseInt(origin);
                            if(arg.code == 666){
                                $this.text(count - 1 );  // 这个$this就是提前赋值的
                                showLikeCount($this,'-1');

                            }else if(arg.code == 999){
                                $this.text(count + 1 );
                                showLikeCount($this,'+1');

                            }
                        }else{
                            alert(arg.msg);
                        }
                    }

                })
            })
        }

        function showLikeCount($this,text) {
            var fontSize = 5;
            var top = 0;
            var right = 0;
            var opacity = 1;  // 定义字体的透明度

            var tag = document.createElement('span');
            // var tag = document.getElementById()
            tag.innerText = text;
            tag.style.position = "absolute";  //父级div用relative,这里用absolute
            // 默认大小
            tag.style.fontSize = fontSize + "px";
            tag.style.top = top + 'px';
            tag.style.right = right + 'px';
            tag.style.opacity = opacity;
            // 已上都是设置tag标签的属性
            $this.after(tag);

            // 设置点赞数字的动画效果
            // 定时器,没0.5s执行一次
            var obj = setInterval(function () {
                fontSize += 5;
                top -= 5;
                right -= 5;
                opacity -= 0.1;

                tag.style.fontSize = fontSize + "px"; // tag是引用上面的,不用重新再取了
                tag.style.top = top + 'px';
                tag.style.right = right + 'px';
                tag.style.opacity = opacity;
                if(opacity <= 0){
                    clearInterval(obj); //一个函数里面没有这个obj变量,在往上一层找,闭包>>>>>>
                    tag.remove();
                }
            },100);

        }
    </script>
</body>
</html>

实现效果

2. 点赞
    - 事务
        from django.db import transaction
        with transaction.atomic():
            models.Like.objects.create(nnew_id=new_id,uuser_id=uid)
            models.News.objects.filter(id=new_id).update(like_count=F('like_count') + 1)
            response.code = 999
    - 返回值:封装对象
        class BaseResponse(object):
            def __init__(self):
                self.status = False
                self.data = None
                self.msg = None

            def get_dict(self):
                return self.__dict__


        class LikeResponse(BaseResponse):
            def __init__(self):
                self.code = 0
                super(LikeResponse,self).__init__()
        

        json.dumps(对象.__dict__)
        json.dumps(对象.get_dict())

    - 在Ajax操作时候,回调函数中的 $(this)已经不是原来的$(this)
    
    - css: 
           position:fixed,absolute,relative
        setInterval:定时器

 

评论方法实现(扩展:动态菜单的实现)

评论

1. 评论展示

urls.py

from django.conf.urls import url
from app01 import views
urlpatterns = [
    url(r'^comment_list.html$', views.comment_list),  # ajax 这块必须加$,不加的话不会执行do_like函数
]

views.py

def build_comment_data(li):
    dic = {}
    for item in li:
        item['children'] = []
        dic[item['id']] = item

    result = []

    for item in li:
        pid = item['parent_id']
        if pid:
            dic[pid]['children'].append(item)
        else:
            result.append(item)
    return result


def build_comment_tree(com_list):
    tpl = """
    <div class="item">
        <div class="title">{0}:{1}</div>
        <div class="body">{2}</div>
    </div>
    """
    html = ''
    for item in com_list:
        if not item['children']:
            html += tpl.format(item['user'], item['content'], '')
        else:
            html += tpl.format(item['user'],
                               item['content'],
                               build_comment_tree(item['children']))
    return html


def comment_list(request):
    li = [
        {'id': 1, 'user': '银秋良', 'content': '灌我鸟事', 'parent_id': None},
        {'id': 2, 'user': '银秋良', 'content': '管我鸟事', 'parent_id': None},
        {'id': 3, 'user': '型谱', 'content': '你个文盲', 'parent_id': 1},
        {'id': 4, 'user': '详解', 'content': '好羡慕你们这些没脸的人呀', 'parent_id': 2},
        {'id': 5, 'user': '银秋良', 'content': '你是流氓', 'parent_id': 3},
        {'id': 6, 'user': '银秋良', 'content': '你冷库无情', 'parent_id': 5},
        {'id': 7, 'user': '银秋良', 'content': '你才冷酷无情', 'parent_id': 4},
        {'id': 8, 'user': '银秋良', 'content': '你无理取闹', 'parent_id': 4},
    ]

    com_list = build_comment_data(li)

    html = build_comment_tree(com_list)

    return render(request, 'comment_list.html', {'comment_html': html})

templates/comment_list.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</title>
        <style>
        .hide{
            display: none;
        }
        .item .body{
            margin-left: 30px;
        }
        </style>
</head>
<body>
    <h1>评论</h1>
    {{ comment_html|safe }}

</body>
</html>

效果

2. 评论和click事件结合

urls.py

from django.conf.urls import url
from django.contrib import admin
from app01.views import index
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^all/(?P<new_type_id>\d+)/', index),
    url(r'^fetch_comment.html$', views.fetch_comment),
    url(r'^', index),
]

views.py


from django.shortcuts import render,HttpResponse
from app01 import models
import os
import json
from django.db.models import F
from django.db import transaction
from utils.response import LikeResponse

def index(request,*args,**kwargs):
"""
首页:
- 显示所有的新闻类型
- 显示所有新闻列表
:param request:
:param args:
:param kwargs:
:return:
"""
# None, "1" "2"
current_new_type_id = kwargs.get('new_type_id')
if current_new_type_id:
current_new_type_id = int(current_new_type_id)
new_type_list = models.NewsType.objects.all()

new_list = models.News.objects.filter(**kwargs)

return render(request,'index.html',{'new_type_list':new_type_list,'new_list':new_list,'current_new_type_id':current_new_type_id})

def build_comment_data(li):
    dic = {}
    for item in li:
        item['children'] = []
        dic[item['id']] = item

    result = []
    for item in li:
        pid = item['parent_id']
        if pid:
            dic[pid]['children'].append(item)
        else:
            result.append(item)

    return result


def build_comment_tree(com_list):
    """
    {'user': '银秋良1', 'children': [],content:'xxx'],
    {'user': '银秋良2', 'children': [{'user': '型谱', 'children': [{'user': '银秋良', 'children': [], 'parent_id': 3, 'content': '你是流氓', 'id': 5}], 'parent_id': 1, 'content': '你个文盲', 'id': 3}], 'parent_id': None, 'content': '灌我鸟事', 'id': 1}
    {'user': '银秋良3', 'children': [{'user': '详解', 'children': [], 'parent_id': 2, 'content': '好羡慕你们这些没脸的人呀', 'id': 4}], 'parent_id': None, 'content': '管我鸟事', 'id': 2}

    [
        {'user': '银秋良', 'children': [], 'parent_id': 3, 'content': '你是流氓', 'id': 5}
    ]

    """
    tpl = """
    <div class="item">
        <div class="title">{0}:{1}</div>
        <div class="body">{2}</div>
    </div>
    """
    html = ""
    for item in com_list:
        if not item['children']:
            html +=tpl.format(item['user'],item['content'],"")
        else:
            html +=tpl.format(item['user'], item['content'], build_comment_tree(item['children']))
    return html


def fetch_comment(request):
    li = [
        {'id': 1, 'user': '银秋良', 'content': '灌我鸟事', 'parent_id': None},
        {'id': 2, 'user': '银秋良', 'content': '管我鸟事', 'parent_id': None},
        {'id': 3, 'user': '型谱', 'content': '你个文盲', 'parent_id': 1},
        {'id': 4, 'user': '详解', 'content': '好羡慕你们这些没脸的人呀', 'parent_id': 2},
        {'id': 5, 'user': '银秋良', 'content': '你是流氓', 'parent_id': 3},
        {'id': 6, 'user': '银秋良', 'content': '你冷库无情', 'parent_id': 5},
        {'id': 7, 'user': '银秋良', 'content': '你才冷酷无情', 'parent_id': 4},
        {'id': 8, 'user': '银秋良', 'content': '你无理取闹', 'parent_id': 4},
    ]
    com_list = build_comment_data(li)
    # 第一种选择
    html = build_comment_tree(com_list)
    return HttpResponse(html)
    # 第二种选择,前端递归去生成
    # return HttpResponse(json.dumps(com_list))

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .body a{
            display: block;
        }
        .body{
            margin-left: 30px;
        }
    </style>
</head>
<body>
    <h1>分类列表</h1>
    <ul>
        {% if current_new_type_id %}
            <li><a href="/">全部</a></li>
        {% else %}
            <li><a style="color:green" href="/">全部</a></li>
        {% endif %}

        {% for row in new_type_list %}
            {% if row.id == current_new_type_id %}
                <li><a style="color:green" href="/all/{{ row.id }}/">{{ row.caption }}</a></li>
            {% else %}
                <li><a href="/all/{{ row.id }}/">{{ row.caption }}</a></li>
            {% endif %}
        {% endfor %}
    </ul>
    <h2>新闻列表</h2>
    <div>
        {% for row in new_list %}
            <div>
                <div>{{ row.title }}</div>
                <div>
                    {{ row.user.username }}
                     <div class="new-comment" style="display: inline-block;"  new-id="{{ row.id }}">
                         评论:{{ row.comment_count }}
                     </div>
                    <div style="display: inline-block;position: relative;">
                        赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a>
                    </div>
                </div>
                <div class="comment-area"></div>
            </div>
            <div>
                <div>{{ row.title }}</div>
                <div>{{ row.user.username }}  评论:{{ row.comment_count }}  赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div>
            </div>
            <div>
                <div>{{ row.title }}</div>
                <div>{{ row.user.username }}  评论:{{ row.comment_count }}  赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div>
            </div>
        {% endfor %}
    </div>

    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        $(function () {
            bindLikeEvent();
            bindCommentEvent();  // 点击评论数字,则显示评论
        });
        function bindCommentEvent() {
            $('.new-comment').click(function () {
                var newId = $(this).attr('new-id');
                var $this = $(this);
                $.ajax({
                    url: '/fetch_comment.html',
                    type: "GET",
                    data: {'newId': newId},
                    success:function (arg) {
                        console.log(arg);
                        $this.parent().next().html(arg);
                    }
                })


            })
        }
        
        function bindLikeEvent() {
            $('.new-like').click(function () {
                // 获取当前新闻ID
                var newId = $(this).attr('new-id');
                var $this = $(this);
                $.ajax({
                    url: '/do_like.html',
                    type: "POST",
                    data: {'newId': newId},
                    dataType: 'JSON',
                    success:function (arg) {
                        if(arg.status){
                            var origin = $this.text();
                            var count = parseInt(origin);
                            if(arg.code == 666){
                                $this.text(count - 1 );
                                showLikeCount($this,'-1');

                            }else if(arg.code == 999){
                                $this.text(count + 1 );
                                showLikeCount($this,'+1');

                            }
                        }else{
                            alert(arg.msg);
                        }
                    }

                })
            })
        }
        
        function showLikeCount($this,text) {
            var fontSize = 5;
            var top = 0;
            var right = 0;
            var opacity = 1;

            var tag = document.createElement('span');
            // var tag = document.getElementById()
            tag.innerText = text;
            tag.style.position = "absolute";
            // 默认大小
            tag.style.fontSize = fontSize + "px";
            tag.style.top = top + 'px';
            tag.style.right = right + 'px';
            tag.style.opacity = opacity;
            $this.after(tag);

            // 定时器,没0.5s执行一次
            var obj = setInterval(function () {
                fontSize += 5;
                top -= 5;
                right -= 5;
                opacity -= 0.1;

                tag.style.fontSize = fontSize + "px";
                tag.style.top = top + 'px';
                tag.style.right = right + 'px';
                tag.style.opacity = opacity;
                if(opacity <= 0){
                    clearInterval(obj);
                    tag.remove();
                }
            },100);

        }
    </script>
</body>
</html>

效果(点击数字,会展开评论)

 

    3. 评论
        - 字典,列表,通过引用赋值,一个修改全部都改
        - 递归
            
        作业:
            评论:0 绑定点击事件
                - 发送Ajax请求: 新闻ID
                
                - 查询当前新闻ID对应的所有评论 models.xxxx.values()
                    li = [
                        {'id': 1, 'user': '银秋良', 'content': '灌我鸟事', 'parent_id': None},
                        {'id': 2, 'user': '银秋良', 'content': '管我鸟事', 'parent_id': None},
                        {'id': 3, 'user': '型谱', 'content': '你个文盲', 'parent_id': 1},
                        {'id': 4, 'user': '详解', 'content': '好羡慕你们这些没脸的人呀', 'parent_id': 2},
                        {'id': 5, 'user': '银秋良', 'content': '你是流氓', 'parent_id': 3},
                        {'id': 6, 'user': '银秋良', 'content': '你冷库无情', 'parent_id': 5},
                        {'id': 7, 'user': '银秋良', 'content': '你才冷酷无情', 'parent_id': 4},
                        {'id': 8, 'user': '银秋良', 'content': '你无理取闹', 'parent_id': 4},
                    ]
                选择:
                    - 数据进行操作并且渲染,返回给用户HTML
                    - 返回给前端JavaScrip *****
        
        应用:菜单

 

菜单

1. 普通格式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .comment{
            margin-left: 20px;
        }</style>
</head>
<body><h1>菜单</h1>

    <hr/>
    <div>
        <h3>普通格式</h3>
        <div class="comment">
            <div class="content">第一个评论</div>
            <div class="content">第二个评论</div>
            <div class="comment">
                <div class="content">对第二条评论恢复</div>
                <div class="comment">
                    <div class="content">对第二条评论恢复</div>
                </div>
                <div class="content">对第二条评论恢复</div>
            </div>
            <div class="content">第三个评论</div>
        </div>
    </div>

</body>
</html>

效果

2.带标题格式(菜单)

urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^comment_list.html$', views.comment_list),
]

views.py

from django.shortcuts import render,HttpResponse
from app01 import models
import os
import json
from django.db.models import F
from django.db import transaction
from utils.response import LikeResponse


def build_comment_data(li):
    dic = {}
    for item in li:
        item['children'] = []
        dic[item['id']] = item

    result = []
    for item in li:
        pid = item['parent_id']
        if pid:
            dic[pid]['children'].append(item)
        else:
            result.append(item)

    return result


def build_comment_tree(com_list):
    """
    {'user': '银秋良1', 'children': [],content:'xxx'],
    {'user': '银秋良2', 'children': [{'user': '型谱', 'children': [{'user': '银秋良', 'children': [], 'parent_id': 3, 'content': '你是流氓', 'id': 5}], 'parent_id': 1, 'content': '你个文盲', 'id': 3}], 'parent_id': None, 'content': '灌我鸟事', 'id': 1}
    {'user': '银秋良3', 'children': [{'user': '详解', 'children': [], 'parent_id': 2, 'content': '好羡慕你们这些没脸的人呀', 'id': 4}], 'parent_id': None, 'content': '管我鸟事', 'id': 2}

    [
        {'user': '银秋良', 'children': [], 'parent_id': 3, 'content': '你是流氓', 'id': 5}
    ]

    """
    tpl = """
    <div class="item">
        <div class="title">{0}:{1}</div>
        <div class="body">{2}</div>
    </div>
    """
    html = ""
    for item in com_list:
        if not item['children']:
            html +=tpl.format(item['user'],item['content'],"")
        else:
            html +=tpl.format(item['user'], item['content'], build_comment_tree(item['children']))
    return html


def comment_list(request):
    """
    获取多级评论列表
    :param request:
    :return:
    """
    li = [
        {'id': 1, 'user': '银秋良', 'content': '灌我鸟事', 'parent_id': None},
        {'id': 2, 'user': '银秋良', 'content': '管我鸟事', 'parent_id': None},
        {'id': 3, 'user': '型谱', 'content': '你个文盲', 'parent_id': 1},
        {'id': 4, 'user': '详解', 'content': '好羡慕你们这些没脸的人呀', 'parent_id': 2},
        {'id': 5, 'user': '银秋良', 'content': '你是流氓', 'parent_id': 3},
        {'id': 6, 'user': '银秋良', 'content': '你冷库无情', 'parent_id': 5},
        {'id': 7, 'user': '银秋良', 'content': '你才冷酷无情', 'parent_id': 4},
        {'id': 8, 'user': '银秋良', 'content': '你无理取闹', 'parent_id': 4},
    ]
    com_list = build_comment_data(li)
    """
    {'user': '银秋良', 'children': [{'user': '型谱', 'children': [{'user': '银秋良', 'children': [], 'parent_id': 3, 'content': '你是流氓', 'id': 5}], 'parent_id': 1, 'content': '你个文盲', 'id': 3}], 'parent_id': None, 'content': '灌我鸟事', 'id': 1}
    {'user': '银秋良', 'children': [{'user': '详解', 'children': [], 'parent_id': 2, 'content': '好羡慕你们这些没脸的人呀', 'id': 4}], 'parent_id': None, 'content': '管我鸟事', 'id': 2}
    """

    html = build_comment_tree(com_list)

    return render(request,'comment_list.html',{'comment_html':html})

tempaltes/comment_list.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .comment{
            margin-left: 20px;
        }
        .body a{
            display: block;
        }
        .body{
            margin-left: 30px;
        }
        .hide{
            display: none;
        }

    </style>
</head>
<body>

    <h1>菜单</h1>

    <hr/>
    <div>
        <h3>普通格式</h3>
        <div class="comment">
            <div class="content">第一个评论</div>
            <div class="content">第二个评论</div>
            <div class="comment">
                <div class="content">对第二条评论恢复</div>
                <div class="comment">
                    <div class="content">对第二条评论恢复</div>
                </div>
                <div class="content">对第二条评论恢复</div>
            </div>
            <div class="content">第三个评论</div>
        </div>

        <h3>带标题格式(菜单)</h3>
        <div class="item">
            <div class="title">用户管理</div>
            <div class="body">
                <div class="item">
                    <div class="title">权限管理</div>
                    <div class="body">
                        <a href="#">用户授权</a>
                        <a href="#">权限变更</a>
                    </div>
                </div>

                <div class="item">
                    <div class="title">权限管理</div>
                    <div class="body">
                        <a href="#">用户授权</a>
                        <a href="#">权限变更</a>
                    </div>
                </div>
            </div>
        </div>

        <div class="item">
            <div class="title">订单管理</div>
            <div class="body">
                <a href="#">创建叮当</a>
                <a href="#">取消订单</a>
            </div>
        </div>
        
    </div>

    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        $(function () {
            $('.title').click(function () {
                if ($(this).next().hasClass('hide')){
                    $(this).next().removeClass('hide')
                }else{
                    $(this).next().addClass('hide')
                }
            })
        })
    </script>

</body>
</html>

效果

3. NBMENU(通过templatetags实现)

目录结构如下:

settings.py【将目录信息放在了配置文件中,缺点增删目录结构的时候需要重启应用,最好的方式是放在数据库中

.....
.....
STATIC_URL = '/static/' STATICFILES_DIRS = ( os.path.join(BASE_DIR,'static'), ) MENU_LIST = [ {'id': 1, 'caption': '用户管理', 'url': None, 'parent_id': None}, {'id': 2, 'caption': '订单管理', 'url': None, 'parent_id': None}, {'id': 3, 'caption': '用户列表', 'url': '/users.html', 'parent_id': 1}, {'id': 4, 'caption': '添加用户', 'url': '/add_user.html', 'parent_id': 1}, {'id': 5, 'caption': '订单列表', 'url': '/orders.html', 'parent_id': 2}, {'id': 6, 'caption': '取消订单', 'url': '/xxxxx.html', 'parent_id': 2}, {'id': 7, 'caption': '退换管理', 'url': None, 'parent_id': 2}, {'id': 8, 'caption': '退货', 'url': '/aaa.html', 'parent_id': 7}, {'id': 9, 'caption': '换货', 'url': '/bbb.html', 'parent_id': 7}, ]

 

urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index.html', views.index),
]

views.py【优点:将菜单展示逻辑和view的逻辑分开

from django.shortcuts import render


def index(request):
    return render(request,'index.html')

templates/index.html

{% load menu %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .body a{
            display: block;
        }
        .body{
            margin-left: 30px;  <!--margin-left 是相对于父标签的 -->
        }
        .hide{
            display: none;
        }
    </style>
</head>
<body>
    <div>
        <h2>原来方式已经注释</h2>
        <!-- {{ menu_html|safe }} -->
        <h2>sample_tag的方式</h2>
        {% menu_html %}

    </div>
        <script src="/static/jquery-3.2.1.js"></script>
    <script>
        $(function () {
            $('.title').click(function () {
                if ($(this).next().hasClass('hide')){
                    $(this).next().removeClass('hide')
                }else{
                    $(this).next().addClass('hide')
                }
            })
        })
    </script>

</body>
</html>

app01/templatetags/menu.py

from django.template import Library
from django.utils.safestring import mark_safe
from django.conf import settings
# 备注:
# 1,放在配置文件中( from django.conf import serrints 如果settingsh中有定义配置项,通过这种方式导入更全),修改得重启 ,放在数据库中是动态的,
# 2,settings中的内容必须大写,不大写读取不到!!!!

register = Library()

def build_menu_data(li):
    dic = {}
    """
    创建一个字典,保存原来的列表内容,没有开辟新的内存空间,而是引用(指向的都是内存中的同一块数据)
    """
    for item in li:
        item['children'] = []
        dic[item['id']] = item

    result = []
    for item in li:
        pid = item['parent_id']
        if pid:
            dic[pid]['children'].append(item)
        else:
            result.append(item)

    return result

def build_menu_tree(mn_list):
    tpl1 = """
    <div class="item">
        <div class="title">{0}</div>
        <div class="body">{1}</div>
    </div>
    """
    tpl2 = "<a href={0}>{1}</a>"

    html = ""
    for item in mn_list: # 判断:如果有url就生成a标签,没有url就不生成div(item title body)
        if item['url']:
            html += tpl2.format(item['url'],item['caption'])
        else:
            if not item['children']:
                html +=tpl1.format(item['caption'],"")
            else:
                html +=tpl1.format(item['caption'],  build_menu_tree(item['children']))
    return html


@register.simple_tag
def menu_html():
    result = build_menu_data(settings.MENU_LIST)
    html = build_menu_tree(result)
    return mark_safe(html)  # 前端不用再使用 safe

备注:

如果要将目录信息放到数据库中,models.py如下

from django.db import models

class Menu(models.Model):
    caption = models.CharField(max_length=32)
    # is_menu = models.BooleanField()
    url = models.CharField(max_length=128,null=True,blank=True)
    parent = models.ForeignKey('Menu',related_name='xxxx',null=True,blank=True)  # 自关联的时候要加realted_name

 

 

 

 

 

 

 

 

 

 

 

 

 

作业参考

https://github.com/zengchunyun/news

https://github.com/uge3/BBS

 

posted @ 2017-09-17 22:05  番茄土豆西红柿  阅读(543)  评论(0编辑  收藏  举报
TOP