在实际web开发过程中你需要掌握一些模型的高级技巧,比如灵活定义Meta选项,动态定义文件上传路径,使用Manager方法,重写save方法,才能充分发挥Django的灵活优势。
一个最基本的django模型
我们来先看下一个新闻博客的Article模型。这个模型是最基本的django模型,里面包括了各个字段(fields),重写了显示文章对象名字的__str__方法(python内置的),并在Meta选项里给模型命名(verbose name)。我们建议每个django模型至少包括字段,重写的__str__方法和Meta选项。
# Create your models here.
from django.db import models
from Myaccount.models import User
from django.urls import reverse
from django.utils.timezone import now
class Article(models.Model):
STATUS_CHOICES = (
('d', '草稿'),
('p', '发表'),
)
title = models.CharField('标题', max_length=200, unique=True)
slug = models.SlugField('slug', max_length=60)
body = models.TextField('正文')
pub_date = models.DateTimeField('发布时间', default= now, null=True)
create_date = models.DateTimeField('创建时间', auto_now_add=True)
mod_date = models.DateTimeField('修改时间', auto_now=True)
status = models.CharField('文章状态', max_length=1, choices=STATUS_CHOICES, default='p')
views = models.PositiveIntegerField('浏览量', default=0)
author = models.ForeignKey(User, verbose_name='作者', on_delete=models.CASCADE)
# category = models.ForeignKey('Category', verbose_name='分类', on_delete=models.CASCADE, blank=False, null=False)
# tags = models.ManyToManyField('Tag', verbose_name='标签集合', blank=True)
def __str__(self):
return self.title
class Meta:
ordering = ['-pub_date']
verbose_name = "文章"
verbose_name_plural = verbose_name
get_latest_by = 'create_date'
# 获取文章相对路径
def get_absolute_url(self):
return reverse('blog:article_detail', args=[str(self.id)])
# 统计文章访问次数
def viewed(self):
self.views += 1
self.save(update_fields=['views'])
基础模型很多时候并不能满足我们的需求
django的基础模型很多时候并不能满足我们的需求。试想我们打算使用django自带的通用视图创建文章,由于通用视图在完成对象创建后需要跳转到文章的absolute_url, 这时我们需要在模型里加入自定义的get_absolute_url方法。由于我们希望统计每篇文章浏览次数,我们还需自定义一个使浏览量自增1的viewed方法,并更新数据表。
# 获取文章相对路径
def get_absolute_url(self):
return reverse('blog:article_detail', args=[str(self.id)])
# 统计文章访问次数
def viewed(self):
self.views += 1
self.save(update_fields=['views'])模型中自定义图片和文件上传路径
Django模型中的ImageField和FileField的upload_to选项是必填项,其存储路径是相对于MEIDA_ROOT而来的。然而我们可能希望动态定义上传路径,比如把文件上传到每个用户名下的文件夹里,并对上传文件重命名
#扩展Django自带的User模型字
class User(AbstractUser):
nickname = models.CharField(max_length=30, blank=True, default='', verbose_name='昵称')
# 扩展用户个人网站字段
link = models.URLField('个人网址', blank=True, help_text='提示:网址必须填写以http开头的完整形式', default='')
# 扩展用户头像字段,upload_to后必须是相对路径,上传路径已设置为media,因此upto不需要media/avatar,数据库中avatar/...,前端用avatar.url为media/avatar/...
avatar = ProcessedImageField(upload_to='avatar',default='avatar/default.png',verbose_name='头像',
processors=[ResizeToFill(100, 100)], # 处理后的图像大小
format='JPEG', # 处理后的图片格式
options={'quality': 95} # 处理后的图片质量
)
#重写User的save()方法保存上传的头像目录
def save(self, *args, **kwargs):
# 当用户更改头像的时候,avatar.name = '文件名',其他情况下avatar.name = 'upload_to/文件名'
if len(self.avatar.name.split('/')) == 1:
self.avatar.name = self.username + '/' + self.avatar.name
#调用父类的save()方法后,avatar.name就变成了'upload_to/用户名/文件名'
super(User, self).save()Django模型的Manager方法
使用模型进行查询的时候,都会调用objects,那么这个objects到底是什么呢?
默认情况下,Django 为每个Django模型类添加一个模型管理类Manager的对象为objects。如果想要将这个对象修改为其他名称,那么可以用models.Manager()来自定义创建对象,如下:
class Article(models.Model):
"""
这里有一个默认的
objects = models.Manager()
"""
wenzhang = models.Manager()使用自定义的模型管理类对象后就不能再用默认的objects,进行查询如下:

可以看到已经没有objects属性了
自定义管理
您可以Manager通过扩展基Manager类并Manager在模型中实例化您的自定义来在特定模型中 使用自定义。
有可能是您想自定义两个原因Manager:添加额外的 Manager方法,和/或修改最初QuerySet的Manager 返回。
增加额外的manager方法
添加额外的Manager方法是向模型添加“表级”功能的首选方法。(对于“行级”功能-即作用于模型对象的单个实例的函数-使用Model方法,而不是自定义Manager方法。)
自定义Manager方法可以返回您想要的任何内容。它不必返回QuerySet。
例如,此定制Manager提供了一个方法with_counts(),该方法返回所有OpinionPoll对象的列表,每个对象都具有一个额外的 num_responses属性,该属性是聚合查询的结果:
from django.db import models
class PollManager(models.Manager):
def with_counts(self):
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("""
SELECT p.id, p.question, p.poll_date, COUNT(*)
FROM polls_opinionpoll p, polls_response r
WHERE p.id = r.poll_id
GROUP BY p.id, p.question, p.poll_date
ORDER BY p.poll_date DESC""")
result_list = []
for row in cursor.fetchall():
p = self.model(id=row[0], question=row[1], poll_date=row[2])
p.num_responses = row[3]
result_list.append(p)
return result_list
class OpinionPoll(models.Model):
question = models.CharField(max_length=200)
poll_date = models.DateField()
objects = PollManager()
class Response(models.Model):
poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
person_name = models.CharField(max_length=50)
response = models.TextField()
在此示例中,您将使用OpinionPoll.objects.with_counts()返回OpinionPoll带有num_responses属性的对象列表。
关于此示例的另一件事要注意的是,Manager方法可以访问self.model以获取它们所附加的模型类。
修改Manager的姓名首字母QuerySet
管理器的基查询集返回系统中的所有对象。例如,使用此模型:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
...该语句Book.objects.all()将返回数据库中的所有书籍。
您可以通过覆盖 方法来覆盖Manager的基数。应该返回 带有所需属性的。QuerySetManager.get_queryset()get_queryset()QuerySet
例如,以下模型有两个 管理器-一个返回所有对象,一个仅返回Roald Dahl的书:
# First, define the Manager subclass.
class DahlBookManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(author='Roald Dahl')
# Then hook it into the Book model explicitly.
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
objects = models.Manager() # The default manager.
dahl_objects = DahlBookManager() # The Dahl-specific manager.
使用此示例模型,Book.objects.all()将返回数据库中的所有书籍,但Book.dahl_objects.all()仅返回Roald Dahl编写的书籍。
当然,由于get_queryset()返回一个QuerySet对象,因此您可以使用filter(),exclude()以及该对象上的所有其他QuerySet方法。因此,这些声明都是合法的:
Book.dahl_objects.all() Book.dahl_objects.filter(title='Matilda') Book.dahl_objects.count()
该示例还指出了另一种有趣的技术:在同一模型上使用多个管理器。您可以根据需要将尽可能多的Manager()实例附加到模型。这是为模型定义通用“过滤器”的简便方法。
例如:
class AuthorManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(role='A')
class EditorManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(role='E')
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
people = models.Manager()
authors = AuthorManager()
editors = EditorManager()
这个例子可以让你请求Person.authors.all(),Person.editors.all()和Person.people.all(),产生可预见的结果。
默认管理器
Model._default_manager
如果使用自定义Manager对象,请注意,第一个Manager Django遇到(按在模型中定义的顺序)处于特殊状态。Django将Manager类中定义的第一个解释为“默认” Manager,并且Django的多个部分(包括 dumpdata)将Manager仅将其用于该模型。因此,最好谨慎选择默认管理器,以免出现覆盖get_queryset()导致无法检索要使用的对象的情况。
您可以使用指定自定义默认管理器Meta.default_manager_name。
如果您正在编写一些必须处理未知模型的代码,例如,在实现通用视图的第三方应用程序中,请使用此管理器(或 _base_manager),而不要假设模型具有objects 管理器。
根管理器
Model._base_manager
使用管理器进行相关对象访问
默认情况下,DjangoModel._base_manager在访问相关对象(即choice.question)而不是 相关对象上时使用manager类的实例_default_manager。这是因为Django需要能够检索相关对象,即使默认管理器将其过滤掉(因此无法访问)也是如此。
如果常规基本管理器类(django.db.models.Manager)不适合您的情况,则可以通过设置告诉Django使用哪个类Meta.base_manager_name。
在相关模型上查询时,不使用基本管理器。例如,如果教程中的 Question模型具有一个 字段和一个基本管理器,可使用过滤出实例,则类似的查询集 将包括与已删除问题相关的选择。deleteddeleted=TrueChoice.objects.filter(question__name__startswith='What')
不要过滤掉这种类型的管理器子类中的任何结果
该管理器用于访问与其他模型相关的对象。在这种情况下,Django必须能够查看其正在获取的模型的所有对象,以便可以检索所引用的任何内容。
如果您覆盖该get_queryset()方法并过滤出任何行,则Django将返回错误的结果。不要那样做 过滤结果的管理器get_queryset()不适合用作基本管理器。
调用自定义QuerySet从管理方法
尽管QuerySet可以直接通过来访问标准中的大多数方法Manager,但是只有在自定义上定义的其他方法QuerySet也要在以下方法上实现时,才是这种情况Manager:
class PersonQuerySet(models.QuerySet):
def authors(self):
return self.filter(role='A')
def editors(self):
return self.filter(role='E')
class PersonManager(models.Manager):
def get_queryset(self):
return PersonQuerySet(self.model, using=self._db)
def authors(self):
return self.get_queryset().authors()
def editors(self):
return self.get_queryset().editors()
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
people = PersonManager()
此示例使您既可以直接调用经理authors(),也可以editors()直接调用经理Person.people。
一个完美的Django高级模型结构
一个完美的django高级模型结构如下所示,可以满足绝大部分应用场景,希望对你有所帮助。
from django.db import models
from django.urls import reverse
# 自定义Manager方法
class HighRatingManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(rating='1')
class Product(models.Model):
# CHOICES选项
RATING_CHOICES = (
("1", 'Very good'),
("2", 'Good'),
("3", 'Bad'),
)
# 数据表字段
name = models.CharField('name', max_length=30)
rating = models.CharField(max_length=1, choices=RATING_CHOICES)
# MANAGERS方法
objects = models.Manager()
high_rating_products =HighRatingManager()
# META类选项
class Meta:
verbose_name = 'product'
verbose_name_plural = 'products'
# __str__方法
def __str__(self):
return self.name
# 重写save方法
def save(self, *args, **kwargs):
do_something()
super().save(*args, **kwargs)
do_something_else()
# 定义绝对路径
def get_absolute_url(self):
return reverse('product_details', kwargs={'pk': self.id})
# 定义其它方法
def do_something(self):参考:https://docs.djangoproject.com/zh-hans/2.1/topics/db/managers/





Comments | NOTHING