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