尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Django 模板复用、ORM 查询与多对多关系

Django 模板复用、ORM 查询与多对多关系 Django 模板复用、ORM 查询与多对多关系本章包括两类高频能力一是通过 inclusion tag 和模板继承复用页面片段与整体布局二是通过 ORM 查询、字段查找和关系模型组织数据。它们分别解决“页面不要重复写”和“数据不要重复存”的问题。一、自定义 inclusion tag复用局部模板当多个页面共享一小块结构但显示的数据不同例如公告栏、推荐商品、分页器或广告位可以使用 inclusion tag。它由 Python 函数准备上下文再渲染一个指定的局部模板比复制 HTML 更易维护。创建模板标签库模板标签模块必须位于已安装 App 的templatetags/目录中且目录需要包含__init__.py以便 Django 将它识别为 Python 包。shop/ ├── templatetags/ │ ├── __init__.py │ └── shop_tags.py └── templates/ └── shop/ └── includes/ └── product_cards.html# shop/templatetags/shop_tags.pyfromdjangoimporttemplate registertemplate.Library()register.inclusion_tag(shop/includes/product_cards.html)defproduct_cards(products,title推荐商品):返回局部模板渲染所需的上下文。return{products:products,title:title,}!-- shop/templates/shop/includes/product_cards.html --sectionclassproduct-cardsh2{{ title }}/h2ul{% for product in products %}li{{ product.name }}{{ product.price }}/li{% empty %}li暂无商品/li{% endfor %}/ul/section代码说明register.inclusion_tag()参数是局部模板的名称。标签函数返回字典该字典就是局部模板上下文不要返回locals()以免把无关变量暴露给模板。在页面中使用 inclusion tag{% load shop_tags %}mainh1首页/h1{% product_cards featured_products 本周精选 %}/main代码说明{% load shop_tags %}加载的是 Python 模块名不含.py后缀。修改templatetags目录结构或新增标签模块后开发服务器有时需要重启才能重新发现标签库。二、模板继承复用整体页面布局inclusion tag 用于复用局部片段模板继承用于复用页面骨架例如 HTML 文档结构、导航、页脚和公共静态资源。父模板用{% block %}留出可替换区域子模板用{% extends %}继承父模板并重写对应块。!-- templates/base.html --{% load static %}!doctypehtmlhtmllangzh-CNheadmetacharsetutf-8title{% block title %}站点首页{% endblock %}/titlelinkrelstylesheethref{% static css/site.css %}{% block extra_css %}{% endblock %}/headbodyheader公共导航/headermain{% block content %}p默认页面内容/p{% endblock %}/mainfooter公共页脚/footer{% block extra_js %}{% endblock %}/body/html!-- shop/templates/shop/product_list.html --{% extends base.html %} {% block title %}商品列表{% endblock %} {% block content %}h1商品列表/h1{% for product in products %}article{{ product.name }}/article{% empty %}p暂无商品。/p{% endfor %} {% endblock %}代码说明{% extends %}通常应放在子模板第一行。块名需要在父子模板中一致子模板只替换声明的块未重写部分继续使用父模板内容。保留父模板块内容当子模板需要在父模板已有内容基础上追加而非完全替换时可使用{{ block.super }}。{% block extra_js %} {{ block.super }}scriptsrc/static/js/product-list.js/script{% endblock %}代码说明公共资源最好仍使用{% static %}生成路径。block.super只在继承块内部可用表示父模板中同名块的原始内容。三、模型设计与时间字段模型应位于各 App 的models.py并通过迁移同步到数据库。字段类型要符合数据含义金额使用DecimalField而不是CharField或浮点数创建时间和更新时间的自动行为也不同。# library/models.pyfromdjango.dbimportmodelsclassUser(models.Model):namemodels.CharField(max_length32,verbose_name姓名)agemodels.PositiveIntegerField(verbose_name年龄)created_atmodels.DateTimeField(auto_now_addTrue,verbose_name注册时间)updated_atmodels.DateTimeField(auto_nowTrue,verbose_name更新时间)classMeta:db_tableuserordering[-created_at]def__str__(self):returnself.name代码说明auto_now_addTrue仅在对象第一次创建时自动写入时间auto_nowTrue会在每次调用实例save()时更新。批量QuerySet.update()不会触发auto_now因为它不会调用每个实例的save()方法。数据库配置原则数据库密码不应直接写入或提交到代码仓库。以下是 MySQL 配置结构示例实际密码应通过环境变量或密钥管理服务读取。# settings.pyimportos DATABASES{default:{ENGINE:django.db.backends.mysql,NAME:library_db,USER:os.environ.get(DB_USER,library_user),PASSWORD:os.environ[DB_PASSWORD],HOST:127.0.0.1,PORT:3306,OPTIONS:{charset:utf8mb4},}}四、QuerySet 常用操作与返回类型objects管理器生成的查询对象叫QuerySet。它具有惰性构造查询时通常不会立即访问数据库遍历、转换为list()、调用first()、count()等操作时才会执行 SQL。fromlibrary.modelsimportUser# 创建记录userUser.objects.create(namedream,age18)# 查询都返回 QuerySetall_usersUser.objects.all()adultsUser.objects.filter(age__gte18)non_dream_usersUser.objects.exclude(namedream)# 单条结果或 Nonefirst_adultadults.first()last_adultadults.last()# get() 期望恰好一条记录否则抛出异常one_userUser.objects.get(pkuser.pk)代码说明all()、filter()、exclude()返回QuerySet不是 Python 列表可以继续链式追加筛选和排序。get()找不到时抛出DoesNotExist多条匹配时抛出MultipleObjectsReturned因此仅用于结果唯一的场景。更新、删除与统计# 批量更新返回受影响行数User.objects.filter(namedream).update(age19)# 实例更新可触发 save() 相关逻辑userUser.objects.get(pk1)user.age20user.save(update_fields[age])# 删除所有匹配记录User.objects.filter(nameobsolete).delete()# 统计和存在性判断totalUser.objects.count()has_dreamUser.objects.filter(namedream).exists()代码说明只需要判断是否存在时使用exists()比查询全部记录再判断效率更高。update()和delete()都可能影响多条数据执行前应确认筛选条件。生产系统的敏感删除通常还需要权限、审计和软删除策略。values()、values_list() 与排序当只需要部分字段而不需要模型实例时使用values()或values_list()能减少对象构造开销。distinct()去重的行为受数据库和order_by()影响应在目标数据库中验证最终 SQL。# 字典形式的 QuerySetnames_and_agesUser.objects.values(name,age)# 元组形式的 QuerySetflatTrue 只适用于单字段namesUser.objects.values_list(name,flatTrue)# 排序升序、降序和反转已排序结果young_firstUser.objects.order_by(age)old_firstUser.objects.order_by(-age)reversed_usersUser.objects.order_by(age).reverse()# 对选定字段去重unique_namesUser.objects.values(name).distinct()print(User.objects.filter(age__gte18).query)代码说明values()的每项是字典values_list()的每项默认是元组。.query适合开发调试时查看 ORM 生成的 SQL 表达式但不要依赖其字符串格式实现业务逻辑。五、双下划线字段查找Django 使用字段名__查找类型表达查询条件。双下划线既可以表示比较方式也可跨越关联关系。以下示例以User.age和User.name为例。fromdatetimeimportdatefromlibrary.modelsimportUser# 数值比较User.objects.filter(age__gt18)User.objects.filter(age__gte18)User.objects.filter(age__lt60)User.objects.filter(age__lte60)# 多值与范围range 包含两个边界User.objects.filter(age__in[18,20,22])User.objects.filter(age__range(18,30))# 文本匹配。大小写敏感性也会受具体数据库排序规则影响。User.objects.filter(name__containsdream)User.objects.filter(name__icontainsDREAM)User.objects.filter(name__startswithd)User.objects.filter(name__endswithm)# 日期字段的组成部分与范围查询User.objects.filter(created_at__year2026)User.objects.filter(created_at__datedate(2026,7,24))User.objects.filter(created_at__date__range(date(2026,7,1),date(2026,7,31)))代码说明range是包含边界的。日期时间字段按__day、__month等单独查询可能让数据库难以利用索引在数据量较大时优先使用明确的日期或时间范围查询。查询含有用户输入的文本时ORM 会参数化处理不要拼接原始 SQL。六、关系模型一对一、一对多与多对多关系字段避免重复存储关联数据。Django 中常用OneToOneField、ForeignKey和ManyToManyField表达三类关系关系Django 字段示例一对一OneToOneField用户与扩展资料一对多ForeignKey部门与员工、出版社与图书多对多ManyToManyField图书与作者、学生与课程多对多方案一只显式定义中间模型当关系本身需要额外数据例如作者在一本书中的署名、排序、加入时间时应显式定义中间模型。这个方案最灵活但不会自动得到book.authors.add()这类多对多管理器。fromdjango.dbimportmodelsclassBook(models.Model):titlemodels.CharField(max_length255)pricemodels.DecimalField(max_digits10,decimal_places2)classAuthor(models.Model):namemodels.CharField(max_length32)classBookAuthor(models.Model):bookmodels.ForeignKey(Book,on_deletemodels.CASCADE)authormodels.ForeignKey(Author,on_deletemodels.CASCADE)pen_namemodels.CharField(max_length64,blankTrue)positionmodels.PositiveSmallIntegerField(default1)classMeta:constraints[models.UniqueConstraint(fields[book,author],nameunique_book_author,),]代码说明中间模型是正常的数据表能保存关系的额外属性。UniqueConstraint防止同一作者被重复关联到同一本书。on_deletemodels.CASCADE表示删除图书或作者时相关中间记录也会删除这不是“级联更新”。多对多方案二显式中间模型 through若既需要中间表字段又希望从Book或Author方便地访问关联对象可在ManyToManyField中通过through指定中间模型。classBook(models.Model):titlemodels.CharField(max_length255)authorsmodels.ManyToManyField(Author,throughBookAuthor,related_namebooks,)classAuthor(models.Model):namemodels.CharField(max_length32)classBookAuthor(models.Model):bookmodels.ForeignKey(Book,on_deletemodels.CASCADE)authormodels.ForeignKey(Author,on_deletemodels.CASCADE)rolemodels.CharField(max_length32,defaultauthor)bookBook.objects.get(pk1)authorAuthor.objects.get(pk1)# through 模型含有额外字段时直接创建中间记录最明确BookAuthor.objects.create(bookbook,authorauthor,roletranslator)print(book.authors.all())print(author.books.all())代码说明指定through后不应假设book.authors.add(author)一定可用当中间模型有额外必填字段时应显式创建BookAuthor或在满足条件时传入through_defaults。若中间模型含有多个指向同一模型的外键需要再使用through_fields消除歧义。多对多方案三让 Django 自动创建中间表若关系本身没有额外字段最简单的方式是直接声明ManyToManyFieldDjango 会自动创建中间表和管理器方法。classCourse(models.Model):namemodels.CharField(max_length100)classStudent(models.Model):namemodels.CharField(max_length32)coursesmodels.ManyToManyField(Course,related_namestudents)studentStudent.objects.get(pk1)courseCourse.objects.get(pk1)student.courses.add(course)student.courses.remove(course)student.courses.set([course])student.courses.clear()代码说明自动中间表适合纯关联关系使用最简单。关系管理器的add()、remove()、set()和clear()会直接维护中间表关联的两端对象必须已经保存到数据库。七、实践要点局部页面片段使用 inclusion tag整体布局使用模板继承。模型的金额字段使用DecimalField时间字段要区分auto_now_add和auto_now。values()返回字典形式的QuerySetvalues_list()返回元组形式两者仍是惰性查询对象。双下划线查询是 ORM 的统一表达方式数据量大时优先考虑索引和范围查询。没有额外属性的多对多关系让 Django 自动建表关系需要保存额外信息时建立显式中间模型并使用through。完成模型修改后始终运行python manage.py makemigrations与python manage.py migrate并将生成的迁移文件提交到版本控制。
返回列表