外观
处理数据关系
一对一
一对一关系类型的定义如下:
class OneToOneField(to, on_delete, parent_link=False, **options)[source]一对一关系非常类似于关系数据库的unique字段,但是反向对象关联只有一个。这种关系多数用于一个对象从另一个对象扩展而来。如Django自带auth模块的User用户表,若想在自己的项目里创建用户模型,又想方便地使用Django的认证功能,就要可以在用户模型内使用一对一关系,添加一个与auth模块User模型的关联字段。
下面通过餐厅和地址的例子介绍一对一模型。
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __str__(self):
return f'{self.name} the place'
class Restaurant(models.Model):
place = models.OneToOneField(Place, on_delete=models.CASCADE, primary_key=True)
serves_hot_dogs = models.BooleanField(default=False)
serves_pizzas = models.BooleanField(default=False)
def __str__(self):
return f'{self.place.name} thr restaurant'上述代码中,在餐厅模型中定义了models.OneToOneField()方法,第一个参数表示关联的模型,第二个参数on_delete表示删除关系,models.CASCADE表示级联删除;第三个参数表示设置主键。
下面使用Shell命令指定一对一操作。
首先创建一组Place模型数据。
>>> p1 = Place(name='肯德基', address='人民广场88号')
>>> p1.save()
>>> p2 = Place(name='麦当劳', address='人民广场99号')
>>> p2.save()创建一组Restaurant模型数据,传递parent作为这个对象的主键。
>>>r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizzas=False)
>>>r.save()一个Restaurant对象可以获取它的地点。
>>>r.place
<Place: 肯德基 the place>一个Place对象可以获取它的餐厅,示例代码如下:
>>> p1.restaurant
<Restaurant: 肯德基 the restaurant>现在p2还没有和Restaurant关联,所以使用try...except语句检测异常,示例代码如下:
>>> from django.core.exceptions import ObjectDoesNotExist
>>> try:
>>> p2.restaurant
>>> except ObjectDoesNotExist:
>>> print("there is no restaurant here.")
There is no restaurant here.也可以使用hasattr属性来避免捕获异常,示例代码如下:
>>> hasattr(p2,'restaurant')
False使用分配符号设置地点。由于地点是餐厅的主键,因此保存将创建一个新餐厅,示例代码如下:
>>> r.place = p2
>>> r.save(
>>> p2.restaurant>
<Restaurant: 麦当劳the restaurant>
>>> r.place
<Place: 麦当劳 the place>反向设置Place,示例代码如下:
>>> p1.restaurant = r
>>> p1.restaurant
<Restaurant: 肯德基 the restaurant>注意,必须先保存一个对象,然后才能将其分配给一对一关系。例如,创建一个未保存位置的餐厅会引发ValueError,示例代码如下:
>>> p3 = Place(name='Demon Dogs', address='944 W.Fullerton")
>>> Restaurant.objects.create(place=p3, serves_hot_dogs=True, serves_pizza=False)
Traceback (most recent calllast):
...
ValueError: save() prohibited to prevent data loss due to unsaved related object 'place'.Restaurant.objects.all()返回餐厅,而不是地点。示例代码如下:
>>> Restaurant.objects.all()
<QuerySet [<Restaurant: 肯德基 the restaurant>, <Restaurant: 麦当劳 the restaurant>]Place.objects.all()返回所有Places,无论它们是否有Restaurants。示例代码如下:
>>> Place.obiects.all('name')
<QuerySet [<Place: 肯德基 the place>, <Place: 麦当劳 the place>]>也可以使用跨关系的查询来查询模型,示例代码如下:
>>> Restaurant.objects.get(place=p1)
<Restaurant: Demon Dogs the restaurants>
>>> Restaurant.objects.get(place pk=1)
<Restaurant: Demon Dogs the restaurant>
>>>Restaurant.objects.filter(place__name__startswith="Demon")
<QuerySet [<Restaurant: Demon Dogs the restaurant>]>
>>> Restaurant.objects.exclude(place__address__contains="Ashland")
<QuerySet [<Restaurant: Demon Dogs the restaurant>]>反向也同样适用,示例代码如下:
>>> Place.objects.get(pk=1)
<Place: Demon Dogs the place>
>>> Place.objects.get(restaurant_place=p1)
<Place: Demon Dogs the place>
>>> Place.objects.get(restaurant=r)
<Place: Demon Dogs the place>
>>>Place.objects.get(restaurant__place__name__startswith="Demon")
<Place: Demon Dogs the place>多对一
多对一和一对多是相同的模型,只是表述不同。以班主任和学生为例,班主任和学生的关系是一够的关系,而学生和班主任的关系就是多对一的关系。
多对一的关系,通常被称为外键。外键字段类的定义如下:
class ForeignKey(to, on_delete, **options)[source]外键需要两个位置参数:一个是关联的模型,另一个是on_delete选项。外键要定义在“多”的一方下面以新闻报道的文章和记者为例,一篇文章(Article)有一个记者(Reporte),而一个记者可以发布多篇文章,所以文章和作者之间的关系就是多对一的关系。模型的定义如下:
from django.db import models
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
def __str__(self):
return "%s %s"%(self.first_name, self.last_name)
class Article(models.Model)
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
def str_(self):
return self.headline
class Meta:
ordering =['headline']上述代码中,在“多”的一侧(Article)定义ForeignKey(),关联Reporter下面使用Shell命令执行多对一操作。创建一组Reporter对象,示例代码如下:
>>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com')
>>> r.save()
>>> r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com')
>>> r2.save()创建一组文章对象,示例代码如下:
>>> from datetime import date
>>> a = Article(id=None, headline="This is a test", pub_date=date(2005,7,27), reporter=n)
>>> a.save()
>>> a.reporter.id
1
>>> a.reporter
<Reporter: John Smith>请注意,必须先保存一个对象,然后才能将其分配给外键关系。例如,使用未保存的Reporter创建文章会引发ValueError,示例代码如下:
>>> r3 = Reporter(first_name='John', last_name='Smith', email='john@example.com')
>>> Article.objects.create(headline="This is a test", pub_date=date(2005,7.27), reporter=r3)
Traceback (most recent call last):
ValueError: save() prohibited to prevent data loss due to unsaved related object 'reporter'.文章对象可以访问其相关的Reporter对象,示例代码如下:
>>> r= a.reporter通过Reporter对象创建文章,示例代码如下:
>>> new_article = r.article_set.create(headline="John's second story", pub_date=date(2005,7,29))
>>> new_article
<Article: John's second story>
>>> new_article.reporter
<Reporter: John Smith>
>>> new article.reporter.id
1创建新文章,示例代码如下:
>>> new_article2 = Article.objects.create(headline="Paul's story"pub_date=date(2006,1,17), reporter=r)
>>> new_article2.reporter
<Reporter: John Smith>
>>> new article2.reporter.id
1
>>> r.article_set.all()
<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]>将同一篇文章添加到其他文章集中,并检查其是否移动,示例代码如下:
>>> r2.article_set.add(new_article2)
>>> new_article2.reporter.id
2
>>> new_article2.reporter
<Reporter: Paul Jones>添加错误类型的对象会引发TypeError,示例代码如下:
>>> r.article_set.add(r2)
Traceback (most recent call last):
...
TypeError: 'Article' instance expected, got <Reporter: Paul Jones>
>>> r.article_set.all()
<QuerySet [<Article: Join's second story>, Article: This is a test>]>
>>> r2.article_set.all()
<QuerySet [<Article: Paul's story>]>
>>> r.article_set.count()
2
>>> r2.article_set.count()
1请注意,在最后一个示例中,文章已从John转到Paul。相关管理人员也支持字段查找,API会相据需要自动遵循关系。通常使用双下画线分隔关系,例如要查找headline字段,可以使用headline作为过滤条件,示例代码如下:
>>> r.article_set.filter(headline__startswith='This')
<QuervSet [<Article: This is a test>]>
# 查找所有名字为"John"的记者的所有文章
>>> Article.obiects.filter(reporter__first__name='John')
<QuerySet [<Article: John's second storvy>, <Article: This is a test>]>也可以使用完全匹配,示例代码如下:
>>> Article.objects.filter(reporter__first name='John')
<QuerySet [<Article: John's second story>, <Article: This is a test>]>也可以查询多个条件,这将转换为WHERE子句中的AND条件,示例代码如下:
>>> Article.objects.filter(reporter__first__name='John', reporter__last__name='Smith')
<QuerySet [<Article: John's second story>, <Article: This is a test>]>对于相关查找,可以提供主键值或显式传递相关对象,示例代码如下:
>>> Article.objects.filter(reporter_pk=1)
<QuerySet [<Article: John's second story>,<Article: This is a test>]>
>>> Article.objects.filter(reporter=1)
<QuerySet [<Article: John's second story>, <Article: This is a test>]>
>>> Article.objects.filter(reporter=r)
<QuerySet [<Article: John's second story>, <Article: This is a test>]>
>>>Article.objects.filter(reporter__in=[1 ,2]).distinct()
<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]>
>>> Article.objects.filter(reporter__in=[r, r2]).distinct()
<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]>还可以使用查询集代替实例的列表,示例代码如下:
>>> Article.objects.filter(reporter__in=Reporter.obiects.filter(first__name='John')).distinct()
<QuerySet [<Article: John's second story>, <Article: This is a test>]>也支持反向查询,示例代码如下:
>>> Reporter.obiects.filter(article_pk=1)
<QuerySet [<Reporter: John Smith>]>
>>> Reporter.objects.filter(article=1)
<QuerySet [<Reporter: John Smith>]>
>>> Reporter.objects.filter(article=a)
<QuerySet [<Reporter: John Smith>]
>>> Reporter.objects.filter(article__headline__startswith='This")
<QuerySet [<Reporter: John Smith>, <Reporter: John Smith>,<Reporter: John Smith>]>
>>> Reporter.objects.filter(article__headline__startswith='This').distinct()
<QuerySet [<Reporter: John Smith>]>反向计数可以与distinct()结合使用,示例如下:
>>> Reporter.objects.filter(article_headline_startswith='This').count(3)
>>> Reporter.objects.filter(article_headline_startswith='This').distinct().count(1)查询可以转向自身,示例如下:
>>> Reporter.objects.filter(article__reporter__first__name__startswith='John")
<QuerySet [<Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John Smith>]>
>>> Reporter.objects.filter(article__reporter__first__name_startswith='John').distinct()
<QuerySet [<Reporter: John Smith>]>
>>> Reporter.obiects.filter(article__reporter=r).distinct()
<QuerySet [<Reporter: John Smith>]>如果删除记者,则他的文章将被删除(假设ForeignKey是在django.db.models.ForeignKey.on_delete没置为CASCADE的情况下定义的,这是默认设置),示例代码如下:
>>> Article.objects.all()
<QuerySet [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]>
>>>Reporter.objects.order_by('first_name')
<QuerySet [<Reporter: John Smith>, <Reporter: Paul Jones>]>
>>> r2.delete()
>>> Article.objects.all()
<QuerySet [<Article: John's second story>, <Article: This is a test>]>
>>> Reporter.objects.order_by('first_name')
<QuerySet [<Reporter: John Smith>]>也可以在查询中使用JOIN删除,示例代码如下:
>>> Reporter.objects.filter(article_headline_startswith='This').delete()
>>> Reporter.objects.all()
<QuerySet []>
>>> Article.objects.all()
<QuerySet []>多对多
多对多关系在数据库中也是非常常见的关系类型。比如一本书可以有好几个作者,一个作者也可写多本书。多对多的字段可以定义在任何一方,一般定义在符合人们思维习惯的一方,且不要同义。
定义多对多关系,需要使用ManyToManyField,语法格式如下:
class ManyToManyField(to, **options)[source]多关系需要一个位置参数——关联的对象模型,它的用法和外键多对一基本类似。下面通过文章和出版模型为例,说明如何使用多对多模型。
一篇文章(Article)可以在多个出版对象(Publication)中发布,一个出版对象可以具有多个文章对象。它们之间是多对多的关系,模型的定义如下:
from diango.db import models
class Publication(models.Model):
title = models CharField(max_length=30)
class Meta:
ordering =['title']
def __str__(self):
return self.title
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication)
class Meta:
ordering = ['headline']
def __str__(self):
return self.headline上述代码中,在Article模型中使用了ManyToManyField定义多对多关系。下面使用Shell命令执行多对多操作。创建一组Publication对象,示例代码如下:
>>> p1=Publication(title='The Python Joumal')
>>> p1.save()
>>> p2=Publication(title='Science News')
>>> p2.save()
>>> p3=Publication(title='Science Weekly')
>>> p3.save()创建Article对象,示例代码如下:
>>> a1 = Article(headline='Django lets you build Web apps easily')在将其保存之前,无法将其与Publication对象相关联,示例代码如下:
>>> a1.publications.add(p1)
Traceback (most recent call last):
...
ValueEror: "<Article: Django lets you build Web apps easily>" needs to have a value for field "id" before this many-to-many relationship can be used.保存对象,示例代码如下:
>>> a1.save()管理Arcticle对象和Publication对象,示例代码如下:
>>> a1.publications.add(p1)创建另一个Article对象,并将其设置为出现在Publications中,示例代码如下:
>>> a2 = Article(headline='NASA uses Python')
>>> a2.save()
>>> a2.publications.add(p1, p2)
>>> a2.publications.add(p3)再次添加是可以的,它不会重复该关系,示例代码如下:
>>> a2.publications.add(p3)添加错误类型的对象会引发TypeError,示例代码如下:
>>> a2.publications.add(a1)
Traceback (most recent call last):
TypeError: 'Publication' instance expected使用create()创建出版物并将其添加到文章,示例代码如下:
>>> new publication = a2.publications.create(title='Highlights for Children')Article对象可以访问其相关的Publication对象,示例代码如下:
>>> a1.publications.all()
<QuerySet [<Publication: The Python Journal>]>
>>> a2.publications.all()
<QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>Publication对象可以访问其相关的Article对象,示例代码如下:
>>> p2.article_set.all()
QuerySet [<Article: NASA uses Python>]>
>>> p1.article_set.all()
<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
>>>Publication.objects.get(id=4).article_set.all()
<QuerySet [<Article: NASA uses Python>]>可以使用跨关系的查询来查询多对多关系,示例代码如下;
>>> Article.objects.filter(publications_id=1)
<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
>>> Article.objects.filter(publications_pk=1)
<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
>>> Article.objects.filter(publications=1)
KQuerySet [<Article: Diango lets you build Web apps easily>, <Article: NASA uses Python>]>
>>> Article.objects.filter(publications=p1)
<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
>>> Article.objects.filter(publications_title_startswith="Science")
<QuerySet [<Article: NASA uses Python>, <Article: NASA uses Python>]>
>>> Article.objects.filter(publications_title_startswith="Science").distinct()
<QuerySet [<Article: NASA uses Python>]>count()函数也支持distinct()函数,示例代码如下:
>>> Article.objects.filter(publications_title_startswith="Science").count()
>>> Article.objects.filter(publications titlestartswith="Science").distinct().count()
>>> Article.objects.filter(publications_in=[1,2]).distinct()
<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
>>> Article.objects.filter(publications_in=[p1,p2]).distinct()
<QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>如果删除Publication对象,则其Article将无法访问它,示例代码如下:
>>> p1.delete()
>>> Publication.objects.all()
<QuerySet [<Publication: Highlights for Children>,<Publication: Science News>,<Publication: SciencWeekly>]>
>>> a1 = Article.obiects.aet(pk=1)
>>> a1.publications.all()
<QuerySet []>如果删除Article,则其Publication也将无法访问,示例代码如下:
>>> a2.delete()
>>> Article.objects.all()
<QuerySet [<Article: Django lets you build Web apps easily>]>
>>> p2.article_set.all()
<QuerySet []>ModelAdmin
如果知识在admin中简单地展示和管理模型,那么在admin.py中使用admin.site.register注册即可,示例如下:
from django.contrib import admin
from myproject.myapp.models import Author
admin.site.register(Author)但是,很多时候为了满足业务需求,需要对admin进行深度定制。这时就可以使用Django为我们提供的ModelAdmin类了。ModelAdmin有很多内置属性,可以很方便地定制我们想要的功能。如:
from django.contrib import admin
class AuthorAdmin(admin.ModelAdmin):
date_hierarchy = 'pub_date'