Python入门自学进阶-Web框架——17Django实现评论树形结构Model操作

Posted kaoa000

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门自学进阶-Web框架——17Django实现评论树形结构Model操作相关的知识,希望对你有一定的参考价值。

一、获取浏览器信息

对于不同的设备,后端可以通过request获取设备的相关信息,根据信息的不同返回不同的页面,如手机用的android系统,还是其他等等。

测试工具:user agent switcher for chrome,安装后可以选择模拟的设备进行发送请求

 上面选择的是Chrome on Android Mobile,在谷歌浏览器中查看请求头:

User-Agent显示是Android系统。

在后端,请求的所有信息是封装在request的environ中的:在后端打印print(request.environ.keys())

dict_keys(['ALLUSERSPROFILE', 'APPDATA', 'COMMONPROGRAMFILES', 'COMMONPROGRAMFILES(X86)', 'COMMONPROGRAMW6432', 'COMPUTERNAME', 'COMSPEC', 'CYGWIN', 'DJANGO_SETTINGS_MODULE', 'FP_NO_HOST_CHECK', 'HOMEDRIVE', 'HOMEPATH', 'IDEA_INITIAL_DIRECTORY', 'LOCALAPPDATA', 'LOGONSERVER', 'MOZ_PLUGIN_PATH', 'NUMBER_OF_PROCESSORS', 'OS', 'PATH', 'PATHEXT', 'PROCESSOR_ARCHITECTURE', 'PROCESSOR_IDENTIFIER', 'PROCESSOR_LEVEL', 'PROCESSOR_REVISION', 'PROGRAMDATA', 'PROGRAMFILES', 'PROGRAMFILES(X86)', 'PROGRAMW6432', 'PSMODULEPATH', 'PUBLIC', 'PYCHARM_DISPLAY_PORT', 'PYCHARM_HOSTED', 'PYTHONIOENCODING', 'PYTHONPATH', 'PYTHONUNBUFFERED', 'SESSIONNAME', 'SYSTEMDRIVE', 'SYSTEMROOT', 'TEMP', 'TMP', 'USERDOMAIN', 'USERNAME', 'USERPROFILE', 'VBOX_MSI_INSTALL_PATH', 'WINDIR', 'RUN_MAIN', 'SERVER_NAME', 'GATEWAY_INTERFACE', 'SERVER_PORT', 'REMOTE_HOST', 'CONTENT_LENGTH', 'SCRIPT_NAME', 'SERVER_PROTOCOL', 'SERVER_SOFTWARE', 'REQUEST_METHOD', 'PATH_INFO', 'QUERY_STRING', 'REMOTE_ADDR', 'CONTENT_TYPE', 'HTTP_HOST', 'HTTP_CONNECTION', 'HTTP_CACHE_CONTROL', 'HTTP_SEC_CH_UA', 'HTTP_SEC_CH_UA_MOBILE', 'HTTP_SEC_CH_UA_PLATFORM', 'HTTP_UPGRADE_INSECURE_REQUESTS', 'HTTP_USER_AGENT', 'HTTP_ACCEPT', 'HTTP_SEC_FETCH_SITE', 'HTTP_SEC_FETCH_MODE', 'HTTP_SEC_FETCH_USER', 'HTTP_SEC_FETCH_DEST', 'HTTP_ACCEPT_ENCODING', 'HTTP_ACCEPT_LANGUAGE', 'HTTP_COOKIE', 'wsgi.input', 'wsgi.errors', 'wsgi.version', 'wsgi.run_once', 'wsgi.url_scheme', 'wsgi.multithread', 'wsgi.multiprocess', 'wsgi.file_wrapper', 'CSRF_COOKIE'])

 其中,HTTP_USER_AGENT就保存了前端设备的信息:print(req.environ.get('HTTP_USER_AGENT'))

打印结果:Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36

通过得到的字符串,可以通过正则表达式,查找是否含有Android,以此判断设备类型。

第二点:Django ORM操作,在Django中,models模块中定义的类,其中的字段在admin应用中会用到验证的,如在models中定义Test类:

class Test(models.Model):
    f1 = models.CharField(max_length=32)
    em = models.EmailField()

在admin.py中进行注册:

 在admin中,执行了验证,就是输入的邮箱地址如果不符合邮件格式,会报错。

在一般的应用中,可以使用:
obj = Test(f1='fsafafsd',em='fdsafsaf')
obj.clean_fields()  这一句,就执行了验证,这里的验证,都是Django的默认规则验证
obj.save()验证通过了,就可以保存,相应的记录就保存到数据库中。

以前是form组件验证,model组件操作数据

还有一种:ModelForm,ModelForm组件验证,用户model中的字段

class UserInfoModelForm(forms.ModelForm):
    class Meta:
        model = Test
        fields = "__all__"

第三点:实现评论的数据结构(类似多级菜单格式)

对一条新闻的评论,从评论数据库中查询对应的评论,最终形成如下的列表
['评论的自增id':id,‘评论的内容content’:‘xxx’,‘评论的用户名user’:‘xxx’,‘评论的父级idparent_id’:‘’,,。。。],如下模拟数据:

comment_list=[
    'id': 1, 'content': 'Python是最好的编程语言', 'user':'张三',  'parent_id': None,
    'id': 2, 'content': 'Java是第二好的编程语言', 'user': '张三', 'parent_id': None,
    'id': 3, 'content': 'php最垃圾', 'user': '张三', 'parent_id': None,
    'id': 4, 'content': 'Python很垃圾', 'user': '李四', 'parent_id': 1,
    'id': 5, 'content': 'Python可不是最好的编程语言', 'user': '王五', 'parent_id': 1,
    'id': 6, 'content': 'C是最好的编程语言', 'user': '赵六', 'parent_id': 4,
    'id': 7, 'content': 'JAVA是最好的编程语言', 'user': '钱七', 'parent_id': 2,
    'id': 8, 'content': 'PHP是最好的编程语言', 'user': '王八', 'parent_id': 3,
    'id': 9, 'content': '同意同意', 'user': '张三', 'parent_id': 6,
    'id': 10, 'content': '全都垃圾', 'user': '孙九', 'parent_id': None,
]

最终要形成的是如下的显示格式

 在数据结构中就是形成一个列表,列表的每项是以根评论为根的树状结构,一层一层包含下级子评论。

def test(req):
    """
    模拟数据,对于一条新闻的评论,通过查询数据库,最终形成以下结构:
    ['评论的自增id':id,‘评论的内容content’:‘xxx’,‘评论的用户名user’:‘xxx’,‘评论的父级idparent_id’:‘’,,。。。]
    parent_id为None时,表示是根评论
    :param req:
    :return:
    """
    comment_list=[
        'id': 1, 'content': 'Python是最好的编程语言', 'user':'张三',  'parent_id': None,
        'id': 2, 'content': 'Java是第二好的编程语言', 'user': '张三', 'parent_id': None,
        'id': 3, 'content': 'PHP最垃圾', 'user': '张三', 'parent_id': None,
        'id': 4, 'content': 'Python很垃圾', 'user': '李四', 'parent_id': 1,
        'id': 5, 'content': 'Python可不是最好的编程语言', 'user': '王五', 'parent_id': 1,
        'id': 6, 'content': 'C是最好的编程语言', 'user': '赵六', 'parent_id': 4,
        'id': 7, 'content': 'JAVA是最好的编程语言', 'user': '钱七', 'parent_id': 2,
        'id': 8, 'content': 'PHP是最好的编程语言', 'user': '王八', 'parent_id': 3,
        'id': 9, 'content': '同意同意', 'user': '张三', 'parent_id': 6,
        'id': 10, 'content': '全都垃圾', 'user': '孙九', 'parent_id': None,
    ]
    ret = []
    comment_list_dict = 

    for row in comment_list:
        row.update('children':[])
        comment_list_dict[row['id']] = row
    print(comment_list_dict)
    for item in comment_list:
        parent_row = comment_list_dict.get(item['parent_id'])
        if not parent_row:
            ret.append(item)
        else:
            parent_row['children'].append(item)
    print(ret)
    return render(req,'test.html','tree':ret)

最终ret形成的是以根评论为节点的树形结构,以children来存储下一级的评论。

这里的要点是理解列表、字典是引用类型,如id为1的根评论加入ret后,后续循环将id为4的评论加入id为1的评论的children中,实际上也就加入了ret中,同理id为6的评论加入了id为4的评论的children中,也就加入了id为1的评论中,也就加入了ret中。

看如下例题,理解引用:

list1 = []
dic1 = 1:[]
dic2 = 2:[]
dic3 = 3:[]
list1.append(dic1)
print(list1)
dic1[1] = dic2
print(list1)
dic2[2] = dic3
print(list1)
dic3[3] = 'aaaaaa'
print(list1)

结果为:

 以上是后台的数据结构,然后在前端形成树形评论结构

第四点:前端解析后端数据形成树形评论

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .comment-box
            margin-left: 20px;
        
    </style>
</head>
<body>
    <div class="item">
        <a news_id="18" class="com">评论</a>
        <div class="comment-list">
            <div class="comment-box">
                <span>Python最好</span>
                <div class="comment-box">
                    <span>同意</span>
                    <div class="comment-box">
                        <span>同意同意</span>
                    </div>
                </div>
            </div>
            <div class="comment-box">
                <span>Java最好</span>
                <div class="comment-box">
                    <span>dsafsdafsdafs</span>
                </div>
                <div class="comment-box">
                    <span>wowowow撒发撒</span>
                </div>
            </div>
        </div>
    </div>
    <div class="item">
        <a news_id="17" class="com">评论1</a>
    </div>
    <div class="item">
        <a news_id="16" class="com">评论2</a>
    </div>
<script src="/static/jquery-3.6.0.js"></script>
<script>
    $(function () 
        bindCommentEvent();
    );
    function bindCommentEvent() 
        $('.com').click(function () 
            var news_id = $(this).attr('news_id');
            var _this = $(this);
            console.log(_this);
            $.ajax(
                url: '/comment/',
                type: 'GET',
                data: news_id:news_id,
                dataType: 'json',
                success: function (arg) 
                    create_tree(arg,_this);
                
            );
        );
        function diGui(children_list) 
            var html = "";
            $.each(children_list,function (ck,cv) 
                var b = '<div class="comment-box"><span>';
                b += cv.content + "</span>";
                b += diGui(cv.children);
                b += "</div>";
                html += b;
            );
            return html;
        
        function create_tree(data,$this) 
            var html = '<div class="comment-list">';
                 $.each(data,function (k,v) 
                    var a = '<div class="comment-box"><span>';
                     a+= v.content + "</span>";
                     a += diGui(v.children);
                     a+= "</div>";
                     html += a;
                 );
            html += "</div>";
            $this.after(html);
        

    
</script>
</body>
</html>
def comment(req):
    """
    模拟数据,对于一条新闻的评论,通过查询数据库,最终形成以下结构:
    ['评论的自增id':id,‘评论的内容content’:‘xxx’,‘评论的用户名user’:‘xxx’,‘评论的父级idparent_id’:‘’,,。。。]
    parent_id为None时,表示是根评论
    :param req:
    :return:
    """
    news_id = req.GET.get('news_id')
    print(news_id)
    comment_list=[
        'id': 1, 'content': 'Python是最好的编程语言', 'user':'张三',  'parent_id': None,
        'id': 2, 'content': 'Java是第二好的编程语言', 'user': '张三', 'parent_id': None,
        'id': 3, 'content': 'PHP最垃圾', 'user': '张三', 'parent_id': None,
        'id': 4, 'content': 'Python很垃圾', 'user': '李四', 'parent_id': 1,
        'id': 5, 'content': 'Python可不是最好的编程语言', 'user': '王五', 'parent_id': 1,
        'id': 6, 'content': 'C是最好的编程语言', 'user': '赵六', 'parent_id': 4,
        'id': 7, 'content': 'JAVA是最好的编程语言', 'user': '钱七', 'parent_id': 2,
        'id': 8, 'content': 'PHP是最好的编程语言', 'user': '王八', 'parent_id': 3,
        'id': 9, 'content': '同意同意', 'user': '张三', 'parent_id': 6,
        'id': 10, 'content': '全都垃圾', 'user': '孙九', 'parent_id': None,
    ]
    comment_tree = []
    comment_list_dict = 

    for row in comment_list:
        row.update('children':[])
        comment_list_dict[row['id']] = row

    for item in comment_list:
        parent_row = comment_list_dict.get(item['parent_id'])
        if not parent_row:
            comment_tree.append(item)
        else:
            parent_row['children'].append(item)
    return HttpResponse(json.dumps(comment_tree))

后端实现方法:使用自定义标签:

前端的tree.html中的函数修改一下:

function bindCommentEvent() 
        $('.com').click(function () 
            var news_id = $(this).attr('news_id');
            var _this = $(this);
            console.log(_this);
            $.ajax(
                url: '/comment/',
                type: 'GET',
                data: news_id:news_id,
                // dataType: 'json',   //前端时使用
                dataType : 'html',     //后端直接形成页面代码时,使用这个配置
                success: function (arg) 
                //    create_tree(arg,_this);  //前端时使用
                    _this.after(arg);   //后端直接形成页面代码,然后添加上
                
            );
        );

后端views函数:

def comment(req):
    """
    模拟数据,对于一条新闻的评论,通过查询数据库,最终形成以下结构:
    ['评论的自增id':id,‘评论的内容content’:‘xxx’,‘评论的用户名user’:‘xxx’,‘评论的父级idparent_id’:‘’,,。。。]
    parent_id为None时,表示是根评论
    :param req:
    :return:
    """
    news_id = req.GET.get('news_id')
    print(news_id)
    comment_list=[
        'id': 1, 'content': 'Python是最好的编程语言', 'user':'张三',  'parent_id': None,
        'id': 2, 'content': 'Java是第二好的编程语言', 'user': '张三', 'parent_id': None,
        'id': 3, 'content': 'PHP最垃圾', 'user': '张三', 'parent_id': None,
        'id': 4, 'content': 'Python很垃圾', 'user': '李四', 'parent_id': 1,
        'id': 5, 'content': 'Python可不是最好的编程语言', 'user': '王五', 'parent_id': 1,
        'id': 6, 'content': 'C是最好的编程语言', 'user': '赵六', 'parent_id': 4,
        'id': 7, 'content': 'JAVA是最好的编程语言', 'user': '钱七', 'parent_id': 2,
        'id': 8, 'content': 'PHP是最好的编程语言', 'user': '王八', 'parent_id': 3,
        'id': 9, 'content': '同意同意', 'user': '张三', 'parent_id': 6,
        'id': 10, 'content': '全都垃圾', 'user': '孙九', 'parent_id': None,
    ]
    comment_tree = []
    comment_list_dict = 

    for row in comment_list:
        row.update('children':[])
        comment_list_dict[row['id']] = row

    for item in comment_list:
        parent_row = comment_list_dict.get(item['parent_id'])
        if not parent_row:
            comment_tree.append(item)
        else:
            parent_row['children'].append(item)

    # return HttpResponse(json.dumps(comment_tree))   # 前端生成评论结构时使用
    return render(req,'comment.html','comment_tree':comment_tree)

此时返回给前端的是comment.html页面

在应用的目录下建立子目录templatetags:

在此目录下建立自己的标签文件mytags.py:

from django import template
from django.utils.safestring import mark_safe
register = template.Library()

def diGui(children_list):
    html = ""
    for cv in children_list:
        b = '<div class="comment-box"><span>'
        b += cv['content'] + "</span>"
        b += diGui(cv['children'])
        b += "</div>"
        html += b
    return html

@register.simple_tag
def create_tree(comment_list):
    html = '<div class="comment-list">'
    for row in comment_list:
        a = '<div class="comment-box"><span>'
        a += row['content'] + "</span>"
        a += diGui(row['children'])
        a += "</div>"
        html += a
    return mark_safe(html)

在comment.html模板中使用自定义的标签,生成相应的页面代码串:

% load mytags %
% create_tree comment_tree %

 第五点:Model 、Form 、ModelForm

Model:用于数据库操作

Form:用于用户请求的验证

ModelForm:能做以上两项的部分操作。

Model操作:
1、对数据表操作:创建类,根据类生成表(代码优先,Code First),
                               创建表,根据表生成类(DB First)

创建表时,有如下的关系,一对一,一对多、多对多

一对多是使用ForeignKey字段,一对一也是ForeignKey,只是多了一个参数配置,即unique=True,而多对多是使用了两个ForeignKey。

class News(models.Model):
            title = models.CharField(max_length=10)
            favor = models.ManyToManyField('User',through="Favor",through_fields=("new_obj", 'user_obj'))

class User(models.Model):
        name = models.CharField(max_length=10)
        email = models.EmailField(max_length=10)
        user_type = models.ForeignKey('UserType')  # 一对多
        # user_profile = models.ForeignKey('UserDetail',unique=True)
        user_profile = models.OneToOneField('UserDetail    ')

class UserDetail(models.Model):
        pwd = models.CharField(max_length=32)

class Favor(models.Model):
        new_obj = models.ForeignKey('News',related_name="n")
        user_obj = models.ForeignKey('User',related_name='u')
        name = models.CharField(max_length=64,null=True,blank=True)

class UserType(models.Model):
        name = models.CharField(max_length=10)

一对一使用OneToOneField(‘关联表名’),等价于ForeignKey(‘关联表名’,unique=True)

多对多使用ManyToManyField(‘关联表名’),这时会自动生成第三张表,也可以不使用ManyToManyField,而是自己建立第三张表,在第三张表中使用两个ForeignKey,如上面的Favor类。也可以在自定义第三张表时,同时使用ManyToManyField,这时要增加参数配置,如ManyToManyField('User',through="Favor",through_fields=("new_obj", 'user_obj')),through=配置对应的第三张自定义表名,through_fields=是一个元组,定义第三张表的字段,第三张表的字段定义时要配置related_name=,即反向查找时的别名。

在Django的Admin管理中,一对一,一对多,多对多会给出一个下拉选择框,对应单选和多选下拉框。

对于model中定义的字段,主要有:
        数字
        字符串(带正则的字段)
        时间
        文件
        关系(一对一,一对多,多对多)

这些定义的字段是有验证规则的,但是只有Django的admin用到。

字段的参数,主要有如下几种:

null                数据库中字段是否可以为空
db_column           数据库中字段的列名
default             数据库中字段的默认值
primary_key         数据库中字段是否为主键
db_index            数据库中字段是否可以建立索引
unique              数据库中字段是否可以建立唯一索引
unique_for_date     数据库中字段【日期】部分是否可以建立唯一索引
unique_for_month    数据库中字段【月】部分是否可以建立唯一索引
unique_for_year     数据库中字段【年】部分是否可以建立唯一索引

verbose_name        Admin中显示的字段名称
blank               Admin中是否允许用户输入为空
editable            Admin中是否可以编辑
help_text           Admin中该字段的提示信息
choices             Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
                    如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1)

error_messages      自定义错误信息(字典类型),从而定制想要显示的错误信息;
                        字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date
                        如:'null': "不能为空.", 'invalid': '格式错误'

validators          自定义错误验证(列表类型),从而定制想要的验证规则
                        from django.core.validators import RegexValidator
                        from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\\
                        MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
                        如:
                            test = models.CharField(
                                max_length=32,
                                error_messages=
                                    'c1': '优先错信息1',
                                    'c2': '优先错信息2',
                                    'c3': '优先错信息3',
                                ,
                                validators=[
                                    RegexValidator(regex='root_\\d+', message='错误了', code='c1'),
                                    RegexValidator(regex='root_112233\\d+', message='又错误了', code='c2'),
                                    EmailValidator(message='又错误了', code='c3'), ]
                            )

多表关联以及参数:

ForeignKey(ForeignObject)     
    to,                         # 要进行关联的表名
    to_field=None,              # 要关联的表中的字段名称
    on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为
                                    - models.CASCADE,删除关联数据,与之关联也删除
                                    - models.DO_NOTHING,删除关联数据,引发错误IntegrityError
                                    - models.PROTECT,删除关联数据,引发错误ProtectedError
                                    - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
                                    - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
                                    - models.SET,删除关联数据,
                                                  a. 与之关联的值设置为指定值,设置:models.SET(值)
                                                  b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

                                                    def func():
                                                        return 10

                                                    class MyModel(models.Model):
                                                        user = models.ForeignKey(
                                                            to="User",
                                                            to_field="id"
                                                            on_delete=models.SET(func),)
    related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
    related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
    limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                # 如:
                                        - limit_choices_to='nid__gt': 5
                                        - limit_choices_to=lambda : 'nid__gt': 5

                                        from django.db.models import Q
                                        - limit_choices_to=Q(nid__gt=10)
                                        - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                        - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
    db_constraint=True          # 是否在数据库中创建外键约束
    parent_link=False           # 在Admin中是否显示关联数据


OneToOneField(ForeignKey)
    to,                         # 要进行关联的表名
    to_field=None               # 要关联的表中的字段名称
    on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为

                                ###### 对于一对一 ######
                                # 1. 一对一其实就是 一对多 + 唯一索引
                                # 2.当两个类之间有继承关系时,默认会创建一个一对一字段
                                # 如下会在A表中额外增加一个c_ptr_id列且唯一:
                                        class C(models.Model):
                                            nid = models.AutoField(primary_key=True)
                                            part = models.CharField(max_length=12)

                                        class A(C):
                                            id = models.AutoField(primary_key=True)
                                            code = models.CharField(max_length=1)

ManyToManyField(RelatedField)
    to,                         # 要进行关联的表名
    related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
    related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
    limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                # 如:
                                        - limit_choices_to='nid__gt': 5
                                        - limit_choices_to=lambda : 'nid__gt': 5

                                        from django.db.models import Q
                                        - limit_choices_to=Q(nid__gt=10)
                                        - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                        - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
    symmetrical=None,           # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
                                # 做如下操作时,不同的symmetrical会有不同的可选字段
                                    models.BB.objects.filter(...)

                                    # 可选字段有:code, id, m1
                                        class BB(models.Model):

                                        code = models.CharField(max_length=12)
                                        m1 = models.ManyToManyField('self',symmetrical=True)

                                    # 可选字段有: bb, code, id, m1
                                        class BB(models.Model):

                                        code = models.CharField(max_length=12)
                                        m1 = models.ManyToManyField('self',symmetrical=False)

    through=None,               # 自定义第三张表时,使用字段用于指定关系表
    through_fields=None,        # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
                                    from django.db import models

                                    class Person(models.Model):
                                        name = models.CharField(max_length=50)

                                    class Group(models.Model):
                                        name = models.CharField(max_length=128)
                                        members = models.ManyToManyField(
                                            Person,
                                            through='Membership',
                                            through_fields=('group', 'person'),
                                        )

                                    class Membership(models.Model):
                                        group = models.ForeignKey(Group, on_delete=models.CASCADE)
                                        person = models.ForeignKey(Person, on_delete=models.CASCADE)
                                        inviter = models.ForeignKey(
                                            Person,
                                            on_delete=models.CASCADE,
                                            related_name="membership_invites",
                                        )
                                        invite_reason = models.CharField(max_length=64)
    db_constraint=True,         # 是否在数据库中创建外键约束
    db_table=None,              # 默认创建第三张表时,数据库中表的名称




 

联表操作:

def select_related(self, *fields)
     性能相关:表之间进行join连表操作,一次性获取关联的数据。
     model.tb.objects.all().select_related()
     model.tb.objects.all().select_related('外键字段')
     model.tb.objects.all().select_related('外键字段__外键字段')

def prefetch_related(self, *lookups)
    性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。
            # 获取所有用户表
            # 获取用户类型表where id in (用户表中的查到的所有用户ID)
            models.UserInfo.objects.prefetch_related('外键字段')



            from django.db.models import Count, Case, When, IntegerField
            Article.objects.annotate(
                numviews=Count(Case(
                    When(readership__what_time__lt=treshold, then=1),
                    output_field=CharField(),
                ))
            )

            students = Student.objects.all().annotate(num_excused_absences=models.Sum(
                models.Case(
                    models.When(absence__type='Excused', then=1),
                default=0,
                output_field=models.IntegerField()
            )))

以上是关于Python入门自学进阶-Web框架——17Django实现评论树形结构Model操作的主要内容,如果未能解决你的问题,请参考以下文章

Python入门自学进阶-Web框架——18FormModelForm

Python入门自学进阶-Web框架——18FormModelForm

Python入门自学进阶-Web框架——20Django其他相关知识2

Python入门自学进阶-Web框架——2Django初识

Python入门自学进阶-Web框架——3Django的URL配置

Python入门自学进阶-Web框架——21DjangoAdmin项目应用