Django2 Web實戰01-啓動項目-model 擴展

做者:Hubery 時間:2018.8.31html

接上文: Django2 web實戰01-啓動項目java

給項目添加Person和model關係

咱們將給項目添加model間的關係。movie中的人物關係能夠構成一個很複雜的數據模型。 同一我的,能夠是演員actor,編劇writer,導演director等角色。即便脫離劇組和創做團隊,簡化一點, 數據模型也將包括 經過ForiengKey字段造成的一對多的關係, 經過nyToManyField字段造成的多對多的關係; 經過在ManyToManyField中使用through類來造成的多對多的關係,用來新增額外信息;python

順勢,web

  • 建立一個Person模型
  • 添加一個ForeignKey字段,從Movie到Person,追蹤導演director
  • 添加一個ManyToMany字段,從Movie到Person,追蹤編劇writers
  • 添加一個ManyToMany字段,經過一個through類(演員Actor)來追蹤,誰在電影中演出以及什麼角色。
  • 改動model以後,進行數據庫遷移
  • 往電影movie詳情模版中添加導演director,編劇writer,演員actors
  • 添加一個PersonDetail視圖,顯示出電影是誰導演的,誰編劇的,以及誰出演的。

添加一個關係模型

咱們須要一個Person類,來描述和存儲電影中的人物person 編輯core/models.py數據庫

from django.db import models

class Movie(models.Model):
    NOT_RATED = 0
    RATED_G = 1
    RATED_PG = 2
    RATED_R = 3
   
    RATINGS = (
        (NOT_RATED, 'NR - 沒有評分'),
        (RATED_G, 'G - 普通觀衆'),
        (RATED_PG, 'PG - 父母的引導和規範'),
        (RATED_R, 'R - 限制級'),
    )

    title = models.CharField(max_length=140)
    plot = models.TextField()
    year = models.PositiveIntegerField()
    rating = models.IntegerField(
        choices=RATINGS,
        default=NOT_RATED
    )
    runtime = models.PositiveIntegerField()
    website = models.URLField(blank=True)

    class Meta:
        ordering = ('-year', 'title')

    def __str__(self):
        return '{} ({})'.format(self.title, self.year)

class Person(models.Model):
    first_name = models.CharField(max_length=140)
    last_name = models.CharField(max_length=140)
    born = models.DateField()
    died = models.DateField(null=True, blank=True)

    class Meta:
        ordering = (
            'last_name', 'first_name'
        )

    def __str__(self):
        if self.died:
            return '{}, {} ({}-{})'.format(
                self.last_name,
                self.first_name,
                self.born,
                self.died)
        return '{}, {} ({})'.format(
            self.last_name,
            self.first_name,
            self.born)
複製代碼

Person中,有DataField字段,用適當的數據庫列類型來記錄時間類型數據。 全部字段都支持null參數,表示列字段是否應該支持NULL類型的值。 died字段設置了null=True,代表咱們能夠記錄此person是否died。 __str__方法相似java的toString(),方便輸出對象內容。django

如今,Person與Movies 有着各類各樣的關係。bash

不一樣類型的關係字段

Django的ORM支持映射模型之間關係的字段,包括一對多多對多,以及包含內部模型的多對多。 當兩個model間是一對多關係, 用ForeignKey字段,該字段能夠生成一個兩數據庫表之間的約束。 若model中沒有ForeignKey字段,Django會自動添加一個RelatedManager對象,做爲屬性實例。 RelatedManager類使得查詢關係對象更簡單。post

當兩個model間是多對多關係,二者均可以使用ManyToManyField();Django會在另外一方給你建立一個RelatedManager。 你知道,關係型數據庫在兩表之間不能有多對多的關係。關係型數據庫須要一個擁有外鍵的bridging橋接表,來訪問相關的表。假設咱們不想添加任何屬性來描述這個關係,Django會自動建立和管理這個橋接表。fetch

有時候咱們想用額外的字段來描述一段多對多的關係,咱們能夠用包含through模型的字段:ManyToManyField(在UML中稱爲association聯繫)。這個模型有一個ForeignKey,能夠到達關係的每一邊,以及獲取任何想獲取的額外字段。ui

模型關係 對應字段
一對多 ForeignKey
多對多 ManyToManyField
有內部模型的多對多 ManyToManyField中使用through字段 藉助其餘模型描述模型關係

那,能夠開始建立模型了,仔細體驗下之間的關係。

Director - ForeignKey

模型中,每一個movie都有一個director導演,但每一個導演能夠拍過不少做品。那麼,用ForeignKey字段來爲movie添加一個導演director; 編輯core/models.py Movie類中新增這一段

director = models.ForeignKey(
    to='Person',
    related_name='directed',
    on_delete=models.SET_NULL,
    null=True,
    blank=True
)
複製代碼
  • to='Person', Django的全部關係字段,均可以使用字符串引用以及相關模型的引用。這個參數是必須的。
  • on_delete=models.SET_NULL,當刪除引用的模型(實例/行)時,Django須要知道接下來要執行什麼指令。SET_NULL,將會設置全部Movie實例當刪除Person時,director字段設置爲NULL。若是想級聯刪除,那麼用這個對象: models.CASCADE。
  • related_name='directed', 可選參數,代表這是其餘model的RelatedManager實例的名字。這個表示:讓咱們查詢這我的所拍過的全部Movie實例。 若是沒有這個參數,那麼Person會有個叫movie_set的屬性。咱們將會得到多個不一樣的關係,Movie和Person(writer/director/actors),因此movie_set會變得隱晦不清,所以咱們必須提供一個related_name

這是首次向已經存在的model中添加字段。咱們必須設置null=True或者提供一個默認參數。若是沒有,Django在執行migration的時候會強制咱們這麼作。由於,當咱們作數據庫遷移的時候,Django必須確保這個實例在數據庫表中存在。當數據庫添加這個字段後,須要知道插入已經存在的行rows的數據是啥。上面代碼中的director字段,咱們能夠接受NULL值。

咱們已經向Movie模型中添加了一個字段director,向Person實例中添加了一個叫directed的屬性。這個directed是RelatedManager類型。

RelatedManager是個頗有用的類,相似於模型的默認Manager,objects,能夠跨表自動處理之間的關係。至關於一個模型持有了另外一個模型的引用/句柄,能夠操做另外一個模型。

對比一下:

person.directed.create()
複製代碼
Movie.objects.create()
複製代碼

這倆方法都會建立一個Movie,但person.directed.create()會確保新Movie做爲director.RelatedManager,同時還提供了addremove方法,以便咱們能夠經過調用person.directed.add(movie)將一個Movie添加到一個directed的Person集合。 相同的,remove()方法差很少意思,會從關係中移除一個model。

Writers - ManyToManyField

兩個模型之間可能存在多對多的關係。好比,一我的能夠寫多個movies,一樣一個movie能夠有多我的來寫完。 向Movie模型中添加一個writers字段,處理多對多: core/models.py

writers = models.ManyToManyField(
    to='Person',
    related_name='writing_credits',
    blank=True
)
複製代碼

一個ManyToManyField建立了一個多對多的關係,充當了RelatedManager角色,保證了用戶查詢和建立model。 再次用到了related_name,避免給Person一個movie_set屬性,直接給賦值一個writing_credits屬性充當一個RelatedManager,不然可能會形成混亂。 上面代碼中,兩端都有RelatedManager,因此這倆操做等效:

person.writing_credits.add(movie)
複製代碼
movie.writers.add(person)
複製代碼

Role - ManyToManyField 用一個through類

當咱們想用內部模型來描述兩個具備多對多關係的模型之間的關係時,這種類型就派上用場了。 Django容許咱們經過建立一個模型來實現這一點,該模型描述了多對多關係中兩個模型之間的鏈接表join table。 新增一個Role中間類,與Movie平級; Movie中新增一個actors

actors = models.ManyToManyField(
    to='Person',
    through='Role',
    related_name='acting_credits',
    blank=True
)
複製代碼
class Role(models.Model):
    movie = models.ForeignKey(Movie, on_delete=models.DO_NOTHING)
    person = models.ForeignKey(Person, on_delete=models.DO_NOTHING)
    name = models.CharField(max_length=140)

    class Meta:
        unique_together = ('movie', 'person', 'name')

    def __str__(self):
        return "{} {} {}".format(self.movie_id, self.person_id, self.name)
複製代碼

這看起來很像以前的ManyToManyField,除了咱們同時設置了to和through參數。 Role模型看起來很是像是要涉及一個鏈接表,join table;有着與每個表的多對多關係。同時還有個name字段用來描述Role。

Role有一個獨一無二的約束。須要movie/person/name一塊兒組成,在Role的內部類Meta上設置unique_together屬性。

此時,ManyToManyField會建立4個新的RelatedManager實例;

  • movie.actors 是與Person相關的manager
  • person.acting_credits 是與Movie相關的manager
  • movie.role_set 是與Role相關的manager
  • person.role_set 是與Role相關的manager

你能夠用上面任意一個manager來查詢model,不過只有role_set managers才能建立model或者修改模型關係,由於role_set有內部類。若是你嘗試着運行movie.actors.add(person),Django會拋IntegrityError異常,由於沒有任何方式能夠給Role.name字段賦值。然而,你能夠這樣:movie.role_set.add(person=person, name='hubery')。

添加數據庫遷移

python manage.py makemigrations 
python manage.py migrates
複製代碼

接下來,咱們就可讓movie頁和movie中的人物相關聯。

建立PersonView 更新MovieList

新增一個PersonDetail視圖,使得movie_detail.html能夠連接到。 爲了建立該視圖,能夠用4步來完成:

  • 建立一個manager,限制數據庫查詢次數
  • 建立view
  • 建立template
  • 建立URL來關聯view

建立一個自定義manager - PersonManager

PersonDetail視圖會列出 一我的在表演,寫做,或導演過的全部電影。在template中,咱們會打印出每一個演員表中的每部電影的名稱。 爲了不數據庫出現大量查詢,給models建立新的managers,返回QuerySets。 Django中,每次從一個關係中訪問一個屬性,Django都會經過查詢數據庫來獲取相關的數據項。 在一個Person出如今N個movies中的狀況下,會出現N次數據庫查詢。咱們能夠經過prefetch_related()方法來避免這種狀況。經過prefetch_related()方法,Django將會經過一個額外的查詢來獲取單個關係中的相關數據。 這其中有個問題,若是咱們最終沒有使用預處理的數據,那麼此次查詢會浪費時間和內存。

接下來,建立一個PersonManager,有個新方法all_with_prefetch_movies(),讓PersonManager成爲Person的默認manager: core/models.py

class PersonManager(models.Manager):
    def all_with_prefetch_movies(self):
        qs = self.get_queryset()
        return qs.prefetch_related(
            'directed',
            'writing_credits',
            'role_set__movie')

class Person(models.Model):   	objects = PersonManager()
複製代碼

PersonManager提供了默認manager一樣的方法,應爲其繼承自models.Manager。定義了一個新方法,用get_queryset()來獲取QuerySet,通知它來預獲取相關的model。 QuerySets是惰性的,在評估查詢集以前,不會與數據庫進行交互。

DetailView經過PK調用get()方法獲取model以前不會評估查詢。

prefetch_related()方法須要一次或屢次查詢,查詢初始化後,會自動查詢相關models。當你從相關的QuerySet中查詢model時,Django不會去查詢它,由於它已經在QuerySet中。

一次查詢是一個Django的QuerySet用來表述模型中的字段或RelatedManager。甚至能夠經過將關係字段或RelatedManager的名稱與相關模型字段分割爲兩個下劃線,實現跨越關係:

Movie.objects.all().filter(actors__last_name='Freeman', 
actors__first_name='Morgan')
複製代碼

這個調用會返回一個QuerySet,包含演員Morgan Freeman的全部Movie模型實例。

建立一個PersonDetail視圖和template

如今寫一個很是簡單的視圖, core/views.py

class PersonDetail(DetailView):
    queryset = Person.objects.all_with_prefetch_movies()
複製代碼

DetailView比較特殊,沒有提供mode屬性。 相反,咱們給他傳入一個PersonManager類的QuerySet對象。當DetailView用filter()方法和get()方法來獲取model實例時,DetailView會從model的實例類名中,派生出模版template的名稱,就像咱們在模型類中提供了模型類做爲屬性同樣。

那, 建立一個template: core/template/core/person_detail.html

{% extends 'base.html' %}

{% block title %}
    {{ object.first_name }}
    {{ object.last_name }}
{% endblock %}

{% block main %}
    <h1>{{ object }}</h1>
    <h2>Actor</h2>
    <ul>
        {% for role in object.role_set.all %}
            <li>
                <a href="{% url 'core:MovieDetail' role.movie.id %}">
                    {{ role.movie }}
                </a>
                {{ role.name }}
            </li>
        {% endfor %}
    </ul>

    <h2>Writer</h2>
    <ul>
        {% for movie in object.writing_credits.all %}
            <li>
                <a href="{% url 'core:MovieDetail' movie.id %}">
                    {{ movie }}
                </a>
            </li>
        {% endfor %}
    </ul>

    <h2>Director</h2>
    <ul>
        {% for movie in object.directed.all %}
            <li>
                <a href="{% url 'core:MovieDetail' movie.id %}">
                    {{ movie }}
                </a>
            </li>
        {% endfor %}
    </ul>
    
{% endblock %}
複製代碼

template 不須要特地作啥就能用預處理數據。

建立MovieManager

class MovieManager(models.Manager):
    def all_with_related_persons(self):
        qs = self.get_queryset()
        qs = qs.select_related(
            'director')
        qs.prefetch_related(
            'writers', 'actors')
        return qs
複製代碼

設置默認manager

class Movie(models.Model):
	objects = MovieManager()
複製代碼

MovieManager介紹了另一個方法:select_related。與prefetch_related()方法很像,但只有一個關係模型存在時採用select_related ,好比只有一個ForeignKey字段。

QuerySet方法 說明
prefetch_related() 當關系可能涉及多個模型時採用,如只有一個ForeignKEy
select_related() 只有一個關係模型存在時採用,若有MaynToMany或RelatedManager

如今,能夠直接使用查詢結果來更新MovieDetail,而不是直接使用model:

class MovieDetail(DetailView):
	queryset = ( 
		Movie.objects.all_with_related_persons())
複製代碼

小結

建立Person模型,並在Movie和Person之間創建了許多種關係。 ForeignKey 創建一對多的關係; ManyToManyField 創建多對多關係; ManyToManyField 中經過提供through模型來建立多對多關係,用中介類來給多對多關係提供額外的信息;

建立一個PersonDetail視圖來顯示Person模型實例;用一個自定義模型manager來控制數據庫的查詢次數。

Django2 Web實戰02-用戶註冊登陸退出

天星技術團QQ:557247785

歡迎來擾
相關文章
相關標籤/搜索