Django的模型和数据库
模型(module)
模型是有关数据的唯一确定的信息源。它包含要存储数据的基本字段和行为。通常,每个模型都映射到单个数据库表。基础:
- 每个模型都是一个子类的Python类django.db.models.Model。
- 模型的每个属性代表一个数据库字段
- Django提供了一个自动生成的数据库访问API
示例
例模型定义了一个Person,其中包含first_name和 last_name:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
first_name和last_name是模型的字段。每个字段都指定为类属性,并且每个属性都映射到数据库列。
上面的Person模型将创建一个数据库表,如下所示:
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
说明:
- 表的名称myapp_person是自动从某些模型元数据派生的,但可以覆盖。
- id将自动添加一个字段,但是可以覆盖此行为
使用模型
定义模型后,您需要告诉Django您将使用 这些模型。为此,请编辑设置文件并更改 INSTALLED_APPS设置以添加包含您的的模块的名称models.py。
例如,如果应用程序的模型位于模块 myweb.models(脚本为应用程序创建的包结构)中, 则应部分读取:manage.py startappINSTALLED_APPS
INSTALLED_APPS = [
#...
'myapp',
#...
]
字段
模型最重要的部分,也是模型唯一需要的部分,是它定义的数据库字段的列表。字段由类属性指定。要小心,不要选择字段名称与冲突 模型API一样clean,save或 delete。
例:
from django.db import models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
字段类型
模型中的每个字段都应是相应Field类的实例 。Django使用字段类类型来确定一些事情:
- 列类型,它告诉数据库该列的数据用什么类型存储(例如:INTEGER,VARCHAR,TEXT)
- 默认的HTML空间在呈现一个表单字段时使用(例如:)
- 最低验证要求,用于Django的管理员和自动生成的形式
Django附带了数十种内置字段类型。除了内置字段还可以编写自定义的模型字段
字段参数
每个字段采用一组特定于字段的参数,例如, CharField(及其子类)需要一个max_length参数,该 参数指定VARCHAR用于存储数据的数据库字段的大小。
还有一组通用参数可用于所有字段类型。所有都是可选的。
-
Null
如果为True,则Django将NULL在数据库中存储空值。默认值为False。 -
blank
如果为True,则该字段允许为空白。默认值为False。
请注意,它与null有所不同,null 与数据库相关,而blank 与验证相关,如果字段包含 blank=True,则表单验证将允许输入一个空值。如果字段包含blank=False,则将需要该字段。 -
choices
一个可迭代的2元组,用作该字段的选择。如果指定了此选项,则默认表单窗口小部件将是一个选择框,而不是标准文本字段,并将选择限制为给定的选择。
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
每个元组中的第一个元素是将存储在数据库中的值。第二个元素将通过默认表单窗口小部件或来显示ModelChoiceField。给定一个模型实例,可以使用该get_FOO_display()方法访问选择字段的显示值 。例如:
from django.db import models
class Person(models.Model):
SHIRT_SIZES = (
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
-
default
字段的默认值。这可以是值或可调用对象。如果可以调用,则每次创建新对象时都会调用它。 -
help_text
额外的“帮助”文本将与表单窗口小部件一起显示。即使您的字段未在表单上使用,它对于文档记录也很有用。 -
primary_key
如果为True,则此字段是模型的主键。
如果您未primary_key=True在模型中指定任何字段,则Django会自动添加一个 IntegerField来保留主键,因此您无需primary_key=True在任何字段上进行设置 ,除非您要覆盖默认的主键行为。
主键字段是只读的。如果更改现有对象上的主键的值然后保存,则将在旧对象的旁边创建一个新对象。例如:
from django.db import models
class Fruit(models.Model):
name = models.CharField(max_length=100, primary_key=True)
>>> fruit = Fruit.objects.create(name='Apple')
>>> fruit.name = 'Pear'
>>> fruit.save()
>>> Fruit.objects.values_list('name', flat=True)
<QuerySet ['Apple', 'Pear']>
- unique
如果为True,则该字段在整个表格中必须是唯一的。
自增字段
默认情况下,Django为每个模型提供以下字段:
id = models.AutoField(primary_key=True
这是一个自动递增的主键。
如果您想指定自定义主键,只需primary_key=True在您的一个字段中指定即可 。如果Django看到您已明确设置Field.primary_key,则不会添加自动 id列
每个模型仅需要一个字段primary_key=True(显式声明或自动添加)。
详细字段名称
每个字段类型,除了ForeignKey, ManyToManyField并且 OneToOneField,有一个可选的第一位置参数-一个详细名称。如果未提供详细名称,则Django将使用字段的属性名称自动创建,将下划线转换为空格。
在此示例中,详细名称是:“person’s first name”
first_name = models.CharField("person's first name", max_length=30)
在此示例中,详细名称是:“first name”
first_name = models.CharField(max_length=30)
ForeignKey, ManyToManyField并且 OneToOneField要求第一个参数为模型类,因此请使用verbose_name关键字参数:
poll = models.ForeignKey(
Poll,
on_delete=models.CASCADE,
verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
verbose_name="related place",
)
惯例是不要用大写的第一个字母大写 verbose_name。Django会在需要的地方自动将首字母大写。
数据表的关系
关系数据库的功能在于将表相互关联。Django提供了定义三种最常见的数据库关系类型的方法:多对一,多对多和一对一。
- 多对一关系
要定义多对一关系,请使用django.db.models.ForeignKey。您可以像使用其他任何Field类型一样使用它:将其包含在模型的类属性中。
ForeignKey 需要位置参数:与模型相关的类。
例如,如果一个Car模型有一个Manufacturer-即, Manufacturer制造多辆汽车,但每辆Car只有一个 Manufacturer,则使用以下定义:
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...
建议(但并非必须)ForeignKey字段名称 (manufacturer在上面的示例中)为模型名称(小写)
- 多对多关系
要定义多对多关系,请使用 ManyToManyField。您可以像使用其他任何Field类型一样使用它 :将其包含在模型的类属性中。
ManyToManyField 需要位置参数:与模型相关的类
例如,如果一个Pizza具有多个Topping对象(即,一个 Topping可以在多个比萨饼上并且每个Pizza都有多个浇头),这就是您的表示方式:
from django.db import models
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
建议(但不是必需)将a 的名称 ManyToManyField(toppings在上面的示例中)描述为一组相关模型对象的复数形式。
在使用多对多的关系时,将ManyToManyField 放在哪个模型中都可以,也就是是说,您既可以将ManyToManyField放着上面例子中的Pizza模型中,也可以放在Topping模型中,但是不要同时放在两个模型中
通常,ManyToManyField实例应该放在要在表单上进行编辑的对象中。
ManyToManyField字段还接受许多额外的参数
- 多对多关系中的额外字段
有时您可能需要将数据与两个模型之间的关系相关联。
例如,考虑一个应用程序跟踪音乐家所属的音乐团体的情况。一个人与其所属的组之间存在多对多关系,因此您可以使用a ManyToManyField表示这种关系。但是,关于您可能希望收集的成员资格,有很多详细信息,例如该人加入该组的日期。
对于这些情况,Django允许您指定将用于管理多对多关系的模型。然后,您可以在中间模型上放置额外的字段。中间模型与ManyToManyField使用 through参数关联 以指向将充当中间模型的模型。对于我们的音乐家示例,代码看起来像这样:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
设置中介模型时,您需要为多对多关系中涉及的模型明确指定外键。该显式声明定义了两个模型之间的关系。
中间模型有一些限制:
-
您的中间模型必须包含一个到源模型的唯一外键(这Group在我们的示例中),或者您必须使用显式指定Django应该用于关系的外键ManyToManyField.through_fields。如果您有多个外键through_fields且未指定,则将引发验证错误。类似的限制适用于目标模型的外键(这Person在我们的示例中)。
-
对于通过中介模型与其自身具有多对多关系的模型,允许使用同一模型的两个外键,但是它们将被视为多对多关系的两个(不同)方面。如果有更多的比两个外键虽然,你还必须指定through_fields如上,或验证错误将得到提升。
-
在使用中介模型定义模型与模型之间的多对多关系时,必须使用 symmetrical=False。
既然您已经设置好ManyToManyField使用中介模型(Membership在本例中为),就可以开始创建一些多对多关系了。您可以通过创建中间模型的实例来做到这一点:
>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
... date_joined=date(1962, 8, 16),
... invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>
不同于一般的多到很多领域,你不能使用add(),create()或者set()创建关系:
>>> # The following statements will not work
>>> beatles.members.add(john)
>>> beatles.members.create(name="George Harrison")
>>> beatles.members.set([john, paul, ringo, george])
为什么?您不能只在一个 Person和一个 Group 之间建立关系Group -您需要为Membership模型所需的关系指定所有详细信息 。add和create是简单的分配和调用,不提供一种方式来指定这个额外的细节。结果,对于使用中间模型的多对多关系禁用了它们。创建这种类型的关系的唯一方法是创建中间模型的实例。
由于remove()类似的原因,该方法被禁用。例如,如果中间模型定义的定制通过表未在该对上强制执行唯一性,则 调用将无法提供足够的信息来确定应删除哪个中间模型实例:(model1, model2)remove()
>>> Membership.objects.create(person=ringo, group=beatles,
... date_joined=date(1968, 9, 4),
... invite_reason="You've been gone for a month and we miss you.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This will not work because it cannot tell which membership to remove
>>> beatles.members.remove(ringo)
但是,该clear() 方法可用于删除实例的所有多对多关系:
>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
<QuerySet []>
通过创建中间模型的实例建立多对多关系后,就可以发出查询。就像普通的多对多关系一样,您可以使用多对多相关模型的属性进行查询:
# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith='Paul')
<QuerySet [<Group: The Beatles>]>
使用中间模型时,还可以查询其属性:
# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
... group__name='The Beatles',
... membership__date_joined__gt=date(1961,1,1))
<QuerySet [<Person: Ringo Starr]>
如果您需要访问会员的信息,可以通过直接查询Membership模型来进行:
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
访问同一信息的另一种方式是通过查询 许多一对多相反的关系从一个 Person对象:
>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
- 一对一关系
要定义一对一关系,请使用 OneToOneField。您可以像使用其他任何Field类型一样使用它 :将其包含在模型的类属性中。
当某个对象以某种方式“扩展”另一个对象时,这在该对象的主键上最有用。
OneToOneField 需要位置参数:与模型相关的类。
例如,如果您要建立“地点”数据库,则会在数据库中建立相当标准的内容,例如地址,电话号码等。然后,如果你想建立的地方顶部的餐馆数据库,而不是重复自己,在复制这些领域Restaurant模型,你可以把Restaurant有OneToOneField到Place
例如,如果您要建立“地点”数据库,则会在数据库中建立相当标准的内容,例如地址,电话号码等。然后,如果你想建立的地方顶部的餐馆数据库,而不是重复自己,在复制这些领域Restaurant模型,你可以把Restaurant有OneToOneField到Place(因为餐厅“是”的地方;事实上,为了处理通常您会使用 继承,这涉及隐式一对一关系)。
例如
在此示例中,一个 Place可以选择是Restaurant:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __str__(self): # __unicode__ on Python 2
return "%s the place" % self.name
class Restaurant(models.Model):
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
primary_key=True,
)
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
def __str__(self): # __unicode__ on Python 2
return "%s the restaurant" % self.place.name
class Waiter(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
def __str__(self): # __unicode__ on Python 2
return "%s the waiter at %s" % (self.name, self.restaurant)
以下是可以使用Python API工具执行的操作示例。
创建几个地方:
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
>>> p1.save()
>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
>>> p2.save()
创建一家餐厅。传递“父”对象的ID作为该对象的ID:
>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
>>> r.save()
跨文件模型
将模型与另一个应用程序相关联是完全可以的。为此,请在定义模型的文件顶部导入相关模型。然后,仅在需要时引用其他模型类。例如:
from django.db import models
from geography.models import ZipCode
class Restaurant(models.Model):
# ...
zip_code = models.ForeignKey(
ZipCode,
on_delete=models.SET_NULL,
blank=True,
null=True,
)
字段名称的限制
- Django对模型字段名称仅设置了两个限制:
字段名称不能是Python保留字,因为这会导致Python语法错误。例如:
class Example(models.Model):
pass = models.IntegerField() # 'pass' is a reserved word!
- 由于Django查询查询语法的工作方式,一个字段名称不能连续包含多个下划线。例如:
class Example(models.Model):
foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
但是,可以解决这些限制,因为您的字段名称不一定必须与数据库列名称匹配
SQL保留字,如join,where或select,被允许作为模型字段的名字,因为Django的转义每个底层的SQL查询的所有数据库表名和列名。它使用特定数据库引擎的引用语法。
自定义字段类型
如果现有模型字段之一不能用于满足您的目的,或者您希望利用一些不太常见的数据库列类型,则可以创建自定义的字段类型
模型属性
objects
模型最重要的属性是 Manager。它是用于向Django模型提供数据库查询操作的接口,用于 从数据库中检索实例。如果Manager未定义自定义,则默认名称为 objects。只能通过模型类而非模型实例访问管理器。
模型方法
在模型上定义自定义方法,以向对象添加自定义“行级”功能。尽管Manager方法旨在执行“表范围内的事情”,但模型方法应作用于特定的模型实例。
这是将业务逻辑放在一个位置(模型)中的宝贵技术。
例如,此模型具有一些自定义方法:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
@property
def full_name(self):
"Returns the person's full name."
return '%s %s' % (self.first_name, self.last_name)
此示例中的最后一个方法是property。
该模型实例参考具有的完整列表,自动给每个模型的方法。您可以覆盖其中的大多数方法(请参见下面的覆盖预定义的模型方法),但是几乎总是需要定义几个方法:
str() (Python 3)
一个Python“魔术方法”,它返回任何对象的unicode“表示形式”。这是Python和Django在需要强制模型实例并将其显示为纯字符串时将使用的内容。最值得注意的是,当您在交互式控制台或管理员中显示对象时,会发生这种情况。
您将始终要定义此方法;默认值不是很有帮助。
unicode() (Python 2)
相当于Python 2 str()。
get_absolute_url()
这告诉Django如何计算对象的URL。Django在其管理界面中以及需要查找对象URL的任何时间都使用它。
具有唯一标识其URL的URL的任何对象都应定义此方法。
重写预定义的模型方法
还有另一组模型方法封装了您要自定义的一系列数据库行为。特别是,您经常需要更改方式save()和 delete()工作方式。
您可以随意覆盖这些方法(和任何其他模型方法)来更改行为。
覆盖内置方法的经典用例是,如果您希望在保存对象时发生某些事情。:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs):
do_something()
super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
do_something_else()
您还可以防止保存:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs):
if self.name == "Yoko Ono's blog":
return # Yoko shall never have her own blog!
else:
super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
重要的是要记住要调用超类方法(就是这样),以确保仍然将对象保存到数据库中。如果您忘记调用超类方法,则默认行为不会发生,并且数据库也不会受到影响。super(Blog, self).save(*args, **kwargs)
传递可以传递给模型方法的参数也很重要-这就是位的作用。Django会不时地扩展内置模型方法的功能,并添加新的参数。如果在方法定义中使用,则可以确保代码添加后将自动支持这些参数。args, **kwargsargs, **kwargs
- 执行自定义的SQL
另一个常见的模式是在模型方法和模块级方法中编写自定义SQL语句
当模型查询API不够完善时,您可以退回编写原始SQL。Django为您提供了两种执行原始SQL查询的方式:您可以Manager.raw()用来执行原始查询并返回模型实例,或者可以完全避开模型层并 直接执行自定义SQL。
一. 执行原查询
raw()管理方法可以用于执行原始SQL查询该回报模型实例:
Manager.raw(raw_query,params = None,translations = None)
此方法接受原始SQL查询,执行该查询,然后返回一个 django.db.models.query.RawQuerySet实例。RawQuerySet可以像普通方法一样迭代此实例 QuerySet以提供对象实例。
最好用一个例子说明。假设您具有以下模型:
class Person(models.Model):
first_name = models.CharField(...)
last_name = models.CharField(...)
birth_date = models.DateField(...)
然后,您可以像这样执行自定义SQL:
>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
... print(p)
John Smith
Jane Jones
当然,该示例不是很令人兴奋-它与running完全相同Person.objects.all()。但是,raw()还有很多其他选择使其功能非常强大。
- 将查询字段映射到模型字段
raw() 自动将查询中的字段映射到模型上的字段。
查询中字段的顺序无关紧要。换句话说,以下两个查询的工作方式相同:
>>> Person.objects.raw('SELECT id, first_name, last_name, birth_date FROM myapp_person')
...
>>> Person.objects.raw('SELECT last_name, birth_date, first_name, id FROM myapp_person')
...
匹配按名称完成。这意味着您可以使用SQL的AS子句将查询中的字段映射到模型字段。因此,如果您还有其他包含Person数据的表,则可以轻松地将其映射到Person实例中:
>>> Person.objects.raw('''SELECT first AS first_name,
... last AS last_name,
... bd AS birth_date,
... pk AS id,
... FROM some_other_table''')
只要名称匹配,就可以正确创建模型实例。
或者,您可以使用的translations参数将查询中的字段映射到模型字段 raw()。这是一个字典,将查询中的字段名称映射到模型上的字段名称。例如,上面的查询也可以写成:
>>> name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
>>> Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
- 索引查找
raw() 支持索引,因此如果只需要第一个结果,则可以编写:
>>> first_person = Person.objects.raw('SELECT * FROM myapp_person')[0]
但是,索引和切片不是在数据库级别执行的。如果Person数据库中有大量对象,则将查询限制在SQL级别会更有效:
>>> first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0]
推迟例子
字段也可能被忽略:
>>> people = Person.objects.raw('SELECT id, first_name FROM myapp_person')
该Person查询返回的对象将是延迟的模型实例。这意味着查询中省略的字段将按需加载。例如:
>>> for p in Person.objects.raw('SELECT id, first_name FROM myapp_person'):
... print(p.first_name, # This will be retrieved by the original query
... p.last_name) # This will be retrieved on demand
...
John Smith
Jane Jones
从外观上看,查询看起来已检索到名字和姓氏。但是,此示例实际上发出了3个查询。raw()查询仅检索名字-印刷时都按需检索名字。
只有一个字段您不能忽略-主键字段。Django使用主键来标识模型实例,因此必须始终将其包含在原始查询中。一InvalidQuery,如果你忘了,包括主键,将引发异常。
- 添加注释
您还可以执行包含模型未定义的字段的查询。例如,我们可以使用PostgreSQL的age()函数来获取由数据库计算其年龄的人员列表:
>>> people = Person.objects.raw('SELECT *, age(birth_date) AS age FROM myapp_person')
>>> for p in people:
... print("%s is %s." % (p.first_name, p.age))
John is 37.
Jane is 42.
...
- 将参数传递给raw()
如果您需要执行参数化查询,则可以使用params 参数raw():
>>> lname = 'Doe'
>>> Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])
params是参数的列表或字典。无论数据库引擎是什么,都将%s 在查询字符串的占位符中使用列表,或%(key)s 在字典中使用占位符(key当然,其中用字典键替换)。此类占位符将替换为自params 变量中的参数。
二.直接执行自定义
有时甚至Manager.raw()是不太够:您可能需要执行不干净映射到模型,或直接执行查询 UPDATE,INSERT或DELETE查询。
在这些情况下,您始终可以直接访问数据库,并在模型层中完全路由。
该对象django.db.connection代表默认的数据库连接。要使用数据库连接,请调用connection.cursor()以获取游标对象。然后,调用以执行SQL和或返回结果行。cursor.execute(sql, [params])cursor.fetchone()cursor.fetchall()
例如:
from django.db import connection
def my_custom_sql(self):
with connection.cursor() as cursor:
cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])
cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
row = cursor.fetchone()
return row
请注意,如果要在查询中包括文字百分号,则在传递参数的情况下必须将它们加倍:
cursor.execute("SELECT foo FROM bar WHERE baz = '30%'")
cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])
如果您使用多个数据库,则可以用于django.db.connections获取特定数据库的连接(和游标)。django.db.connections是一个类似于字典的对象,它允许您使用其别名检索特定的连接:
from django.db import connections
cursor = connections['my_db_alias'].cursor()
# Your code here...
默认情况下,Python DB API将返回不包含字段名称的结果,这意味着您最终得到的list是值,而不是dict。以较小的性能和内存成本,您可以使用以下方法将结果作为返回dict:
def dictfetchall(cursor):
"Return all rows from a cursor as a dict"
columns = [col[0] for col in cursor.description]
return [
dict(zip(columns, row))
for row in cursor.fetchall()
]
另一个选择是collections.namedtuple()从Python标准库中使用。A namedtuple是一个类似元组的对象,具有可通过属性查找访问的字段;它也是可索引和可迭代的。结果是不可变的,可以通过字段名称或索引访问,这可能很有用:
from collections import namedtuple
def namedtuplefetchall(cursor):
"Return all rows from a cursor as a namedtuple"
desc = cursor.description
nt_result = namedtuple('Result', [col[0] for col in desc])
return [nt_result(*row) for row in cursor.fetchall()]
这是三个之间的区别的示例:
>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> cursor.fetchall()
((54360982, None), (54360880, None))
>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> dictfetchall(cursor)
[{'parent_id': None, 'id': 54360982}, {'parent_id': None, 'id': 54360880}]
>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> results = namedtuplefetchall(cursor)
>>> results
[Result(id=54360982, parent_id=None), Result(id=54360880, parent_id=None)]
>>> results[0].id
54360982
>>> results[0][0]
54360982
连接和游标
connection并cursor主要实现了中所述的标准Python DB-APIPEP 249-涉及交易处理时除外。
如果您不熟悉Python DB-API,请注意其中的SQL语句 cursor.execute()使用占位符"%s",而不是直接在SQL中添加参数。如果使用此技术,则基础数据库库将根据需要自动转义参数。
还要注意,Django需要使用"%s"占位符,而不是"?" 由SQLite Python绑定使用的占位符。这是为了保持一致性和理智。
使用游标作为上下文管理器:
with connection.cursor() as c:
c.execute(...)
等效于:
c = connection.cursor()
try:
c.execute(...)
finally:
c.close()
调用存储过程
CursorWrapper.callproc(procname,params = None)
使用给定名称和可选的输入参数序列调用数据库存储过程。
例如,给定此存储过程在Oracle数据库中:
CREATE PROCEDURE "TEST_PROCEDURE"(v_i INTEGER, v_text NVARCHAR2(10)) AS
p_i INTEGER;
p_text NVARCHAR2(10);
BEGIN
p_i := v_i;
p_text := v_text;
...
END;
这将称为:
with connection.cursor() as cursor:
cursor.callproc('test_procedure', [1, 'test'])
抽象基类
当您要将一些公共信息放入许多其他模型时,抽象基类很有用。你写你的基类,并把abstract=True在元 类。这样,该模型将不会用于创建任何数据库表。相反,当将其用作其他模型的基类时,会将其字段添加到子类的字段中。
一个例子:
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
class Student(CommonInfo):
home_group = models.CharField(max_length=5)
该Student模型将有三个领域:name,age和 home_group。该CommonInfo模型不能用作普通的Django模型,因为它是抽象的基类。它不生成数据库表或没有管理器,并且不能实例化或直接保存。
对于许多用途,这种类型的模型继承将正是您想要的。它提供了一种在Python级别上排除常见信息的方法,同时仍然在数据库级别为每个子模型仅创建一个数据库表。
Meta 继承
创建抽象基类后,Django 会将您在基类中声明的任何Meta内部类用作属性。如果子类没有声明自己的Meta 类,它将继承父类的Meta。如果孩子想扩展父母的Meta类,则可以对其进行子类化。例如:
from django.db import models
class CommonInfo(models.Model):
# ...
class Meta:
abstract = True
ordering = ['name']
class Student(CommonInfo):
# ...
class Meta(CommonInfo.Meta):
db_table = 'student_info'
Django确实对抽象基类的Meta类进行了一种调整:在安装Meta属性之前,它先进行了设置abstract=False。这意味着抽象基类的子代本身不会自动成为抽象类。当然,您可以创建一个从另一个抽象基类继承的抽象基类。您只需要记住abstract=True每次都明确设置即可。
某些属性没有必要包含在抽象基类的Meta类中。例如,包括db_table将意味着所有子类(未指定自己的Meta的子类)将使用相同的数据库表,这几乎肯定不是您想要的。
多表继承
Django支持的第二种类型的模型继承是层次结构中的每个模型本身都是一个模型时。每个模型都对应于其自己的数据库表,并且可以单独查询和创建。继承关系在子模型及其每个父模型之间引入了链接(通过自动创建的OneToOneField)。例如:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
的所有字段Place也将在中提供Restaurant,尽管数据将驻留在不同的数据库表中。所以这两种可能:
>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")
如果您的Place也是Restaurant,则可以使用模型名称的小写形式从一个 Place对象到另一个对象Restaurant:
>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>
然而,如果p在上述示例中是不一个Restaurant(它已被直接创建为Place对象,或者在一些其它类的父),指的是p.restaurant将提高一个Restaurant.DoesNotExist 例外。
在上自动创建的OneToOneField将 Restaurant其链接到,Place如下所示:
place_ptr = models.OneToOneField(
Place, on_delete=models.CASCADE,
parent_link=True,
)
您可以通过声明自己重写场 OneToOneField与parent_link=True上Restaurant。
Meta和多表继承
在多表继承的情况下,子类从其父级的Meta类继承是没有意义的。所有元选项都已经应用于父类,并且再次应用它们通常只会导致矛盾的行为(这与抽象基类情况相反,在抽象基类情况下,基类本身不存在)。
因此,子模型无法访问其父模型的Meta类。但是,在少数情况下,子代会从父代继承行为:如果子代未指定 ordering属性或 get_latest_by属性,它将从其父代继承这些行为。
如果父母有命令,并且您不希望孩子有任何自然命令,则可以显式禁用它:
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
继承和反向关系
由于多表继承使用隐式 OneToOneField链接子级和父级,因此可以从父级向下移动到子级,如上例所示。但是,这将使用related_name作为ForeignKeyand ManyToManyField关系的默认值 的名称。如果要将这些类型的关系放在父模型的子类上,则 必须related_name 在每个此类字段上指定属性。如果您忘记了,Django将引发验证错误。
例如,Place再次使用上面的类,让我们创建另一个带有的子类ManyToManyField:
class Supplier(Place):
customers = models.ManyToManyField(Place)
这导致错误:
Reverse query name for 'Supplier.customers' clashes with reverse query
name for 'Supplier.place_ptr'.
HINT: Add or change a related_name argument to the definition for
'Supplier.customers' or 'Supplier.place_ptr'.
按如下所示添加related_name到customers字段中将解决错误:。models.ManyToManyField(Place, related_name=‘provider’)
指定父链接字段
如前所述,Django将自动创建一个 OneToOneField将您的子类链接回任何非抽象父模型的链接。如果要控制链接回父类的属性的名称,则可以创建自己的属性OneToOneField并进行设置, parent_link=True 以指示您的字段是指向父类的链接。
代理模型管理器
如果您未在代理模型上指定任何模型管理器,它将从其模型父级继承这些管理器。如果在代理模型上定义管理器,尽管在父类上定义的任何管理器仍然可用,它将成为默认模型。
从上面继续我们的示例,您可以更改查询Person模型时使用的默认管理器,如下所示:
from django.db import models
class NewManager(models.Manager):
# ...
pass
class MyPerson(Person):
objects = NewManager()
class Meta:
proxy = True
如果要向代理添加新的管理器而不替换现有的默认管理器,则可以使用定制管理器文档中描述的技术:创建一个包含新管理器的基类,并在主基类之后继承该基类:
# Create an abstract class for the new manager.
class ExtraManagers(models.Model):
secondary = NewManager()
class Meta:
abstract = True
class MyPerson(Person, ExtraManagers):
class Meta:
proxy = True
您可能不需要经常执行此操作,但是当您执行此操作时,这是可能的。
代理继承和非托管模式之间的差异
使用managed模型Meta类的属性,代理模型继承可能看起来与创建非托管模型非常相似。
精心设置后,Meta.db_table您可以创建一个不受管理的模型,该模型可以遮盖现有模型并向其中添加Python方法。但是,这将是非常重复且脆弱的,因为如果进行任何更改,则需要使两个副本保持同步。
另一方面,代理模型的行为应与它们要代理的模型完全相同。它们始终与父模型保持同步,因为它们直接继承其字段和管理器。
一般规则是:
- 如果要镜像现有模型或数据库表,并且不希望所有原始数据库表列,请使用Meta.managed=False。通常,该选项对于不受Django控制的数据库视图和表建模很有用。
- 如果您想更改模型的仅Python行为,但保留与原始模型相同的所有字段,请使用Meta.proxy=True。这样就可以进行设置,以便在保存数据时,代理模型可以完全复制原始模型的存储结构。
多重继承
就像Python的子类化一样,Django模型可以从多个父模型继承。请记住,通常的Python名称解析规则适用。出现特定名称(例如Meta)的第一个基类将是使用的基类。例如,这意味着如果多个父类包含一个Meta类,则仅将使用第一个,而其他所有将被忽略。
通常,您不需要从多个父母那里继承。有用的主要用例是“混入”类:向继承混入的每个类添加特定的额外字段或方法。尝试使继承层次结构尽可能简单明了,这样您就不必费力找出特定信息的来源。
请注意,从具有公共id主键字段的多个模型继承将引发错误。要正确使用多重继承,可以AutoField在基本模型中使用显式:
class Article(models.Model):
article_id = models.AutoField(primary_key=True)
...
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
...
class BookReview(Book, Article):
pass
或使用共同的祖先来持有AutoField。这要求使用OneToOneField从每个父模型到公共祖先的显式方法,以避免子对象自动生成和继承的字段之间发生冲突:
class Piece(models.Model):
pass
class Article(Piece):
article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
...
class Book(Piece):
book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
...
class BookReview(Book, Article):
pass