翻譯不完整。請協助 翻譯此英文文件

本文介紹如何為 LocalLibrary 網站定義模型。它解釋了模型是什麼、聲明的方式以及一些主要字段類型。它還簡要展示了您可以訪問模型數據的幾個主要方法。

前提: Django 教學 2: 創建骨架網站。
目標: 能夠設計和創建自己的模型,選擇適當的欄位。

概覽

Django Web 應用程序通過被稱為模型的 Python 對象,訪問和管理數據。模型定義儲存數據的結構,包括欄位類型、以及可能還有最大大小,默認值,選擇列表選項,幫助文檔,表單的標籤文本等。模型的定義與底層數據庫無關 — 你可以選擇其中一個,作為項目設置的一部分。一旦你選擇了要使用的數據庫,你就不需要直接與之交談 — 只需編寫模型結構和其他代碼,Django 可以處理與數據庫通信的所有繁瑣工作。

本教程將介紹如何定義和訪問 LocalLibrary 範例網站的模型。

設計LocalLibrary模型

在你投身開始編寫模型之前,花幾分鐘時間考慮我們需要存放的數據、以及不同對象之間的關係。

我們知道,我們需要存放書籍的信息(標題,摘要,作者,語言,類別,ISBN),並且我們可能有多個副本(具有全球唯一的ID,可用狀態等)。我們可以存放更多關於作者的信息,而不僅僅是他的名字,或多個作者的相同或相似的名稱。我們希望能根據書名,作者名,語言和類別對信息進行排序。

在設計模型時,為每個“對象”(相關信息分組)分別設置模型是有意義的。在這種情況下,明顯的對象是書籍,書本實例和作者。

你可能想要使用模型,來表示選擇列表選項(例如:選擇下拉列表),而不是硬編碼,將選項編寫進網站—這是當所有選項面臨未知、或改變時候的建議。在本網站,模型的明顯之處,包括書籍類型(例如:科幻小說,法國詩歌等)和語言(英語,法語,日語)。

一旦我們已經決定了我們的模型和字段,我們需要考慮它們的關聯性。Django允許你來定義一對一的關聯(OneToOneField),一對多(ForeignKey)和多對多(ManyToManyField)。

思考一下,在網站中,我們將定義模型展示在下面UML關聯圖中(下圖)。如以上,我們創建了書的模型(書的通用細節),書本實例(系統中特定物理副本的書籍狀態),和作者。我們也決定了個類型模型,以便通過管理界面創建/選擇值。我們決定沒有一個模型BookInstance:status —我們硬編碼了值(LOAN_STATUS),因為我們不希望這改變。在每個框中,你可以看到模型名稱,字段名稱和類型,以及方法和返回類型。

該圖顯示模型之間的關係,包括它們的多重性。多重性是圖中的數字,顯示可能存在於關係中的每個模型的數量(最大值和最小值)。例如,盒子之間的連接線,顯示書和類型相關。書模型中數字表明,一本書必須有一個或多個流派(盡可能多),而類型旁邊的線的另一端的數字,表明它可以有零個或更多的關聯書本。

LocalLibrary Model UML

注意 :下一節提供一個基本解釋模型的定義與使用,當你讀到,考慮如何構建上圖中的每個模型。

模型入門

本節簡要概述了模型定義,和一些重要的字段、和字段參數。

模型定義

模型通常在models.py  中定義。它們是繼承自  django.db.models.Model的子类, 可以包括字段,方法和元数据。下面的代碼片段,展示了一个 “典型” 模型,名為 MyModelName:

from django.db import models

class MyModelName(models.Model):
    """A typical class defining a model, derived from the Model class."""

    # Fields
    my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
    ...

    # Metadata
    class Meta: 
        ordering = ['-my_field_name']

    # Methods
    def get_absolute_url(self):
         """Returns the url to access a particular instance of MyModelName."""
         return reverse('model-detail-view', args=[str(self.id)])
    
    def __str__(self):
        """String for representing the MyModelName object (in Admin site etc.)."""
        return self.field_name

在下面章節中,我們將細緻解釋模型的每個功能。

字段

模型可以有任意數量的字段、任何類型的字段 — 每個字段都表示我們要存放在我們的一個資料庫中的一列數據。每個資料庫記錄(行)將由每個字段值之一組成。我們來看看上面看到的例子。

 

my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')

Our above example has a single field called my_field_name, of type models.CharField — which means that this field will contain strings of alphanumeric characters. The field types are assigned using specific classes, which determine the type of record that is used to store the data in the database, along with validation criteria to be used when values are received from an HTML form (i.e. what constitutes a valid value). The field types can also take arguments that further specify how the field is stored or can be used. In this case we are giving our field two arguments:

上面例子中,單個字段叫my_field_name,類型為models.CharField  —這意味著這個字段將會包含字母、數字字符串。使用特定的類別分配字段類型,這些類別,決定了用於將數據存放在資料庫中的記錄的類型,以及從HTML表單接收到值(即構成有效值)時使用的驗證標準。字段類型還可以獲取參數,進一步指定字段如何存放或可以使用。在這裡的情況下,我們給了字段兩個參數:

  • max_length=20 — 表示此字段中值的最大長度為20個字符的狀態。
  • help_text="Enter field documentation" — 提供一個幫助用戶的文本標籤,讓用戶知道當前通過HTML表單輸入時要提供什麼值。

字段名稱用於在查詢和模版中引用它。字段還有一個標籤,它被指定一個參數(verbose_name),或者通過大寫字段的變量名的第一個字母,並用空格替換下劃線(例如my_field_name 的默認標籤為 My field name )。

如果模型在表單中呈現(例如:在管理站點中),則聲明該字段的順序,將影響其默認順序,但可能會被覆蓋。

共同字段參數

當聲明很多/大多數不同的字段類型時,可以使用以下共同參數:

  • help_text :提供HTML表單文本標籤(eg i在管理站點中),如上所述。
  • verbose_name :字段標籤中可讀性名稱,如果沒有指定性,Django將從字段名稱推斷默認的詳細名稱。
  • default :該字段的默認值。這可以是值或可調用對象,在這種情況下,每次創建新紀錄時都將調用該對象。
  • null :如果是True,Django將NULL在數據庫中存儲適合的字段(一個CharField將代替一個空字符串)的空值。默認是False
  • blank :如果True,表單中的字段被允許為空白。默認是False,這意味著Django的表單驗證將強制你輸入一個值。這通常用於NULL=True,因為如果要允許空值,你還希望數據庫能夠適當地表示它們。
  • choices :這是一組字段選項。如果提供這一項,默認對應的表單部件是選擇字段的盒子,而不是標准文本字段。
  • primary_key :如果是True,將當前字段設置為模型的主鍵(主鍵是指定唯一標識所有不同表記錄的特殊數據庫列)。如果沒有指定字段作為主鍵,則Django將自動為此添加一個字段。

還有許多其他選項 — 你可以在這裡看到完整的字段選項

共同字段類型

 

以下列表描述了一些更常用的字段類型。

  • CharField是用來定義短到中等長度的字段字符串。你必須指定max_length要存儲的數據。
  • TextField 用於大型任意長度的字符串。你可以max_length為該字段指定一個字段,但僅當該字段以表單顯示時才會使用(不會在數據庫級別強制執行)。
  • IntegerField是一個用於存儲整數(整數)值的字段,用於在表單中驗證輸入的值為整數。
  • DateFieldDateTimeField用於存儲/表示日期和日期/時間信息(分別是Python.datetime.date和datetime.datetime對象。這些字段可以另外表明(互斥)參數auto_now=Ture (在每次保存模型時將該字段設置為當前日期),auto_now_add(僅設置模型首次創建時的日期)和default(設置默認日期,可以被用戶覆蓋)。
  • EmailField用於存儲和驗證電子郵件地址。
  • FileFieldImageField分別用於上傳文件和圖像(ImageField只需添加上傳的文件是圖像的附加驗證)。這些參數用於定義上傳文件的存儲方式和位置。
  • AutoField是一種IntegerField自動遞增的特殊類型。如果你沒有明確指定一個主鍵,則此類型的主鍵將自動添加到模型中。
  • ForeignKey用於指定與另一個數據庫模型的一對多關係(例如,汽車有一個製造商,但製造商可以製作許多汽車)。關係的“一”側是包含密鑰的模型。
  • ManyToManyField用於指定多對多關係(例如,一本書可以有幾種類型,每種類型可以包含幾本書)。在我們的圖書館應用程序中,我們將非常類似地使用它們ForeignKeys,但是可以用更複雜的方式來描述組之間的關係。這些具有參數on_delete來定義關聯記錄被刪除時會發生什麼(例如,值models.SET_NULL將簡單地設置為值NULL)。

還有許多其他類型的字段,包括不同類型數字的字段(大整數,小整數,浮點數),布爾值,URL,唯一Ids和其他“時間相關”信息(持續時間,時間等)。你可以查閱完整列表 .

元數據

你可以通過聲明class Meta 聲明模型級別的元數據,如圖所示。You can declare model-level metadata for your Model by declaring class Meta, as shown.

class Meta:
    ordering = ['-my_field_name']

此元數據的最有用功能之一是控制在查詢模型類型時返回的記錄的默認排序。你可以通過在ordering 屬性的字段名稱列表中指定匹配順序來執行此操作,如上所示。排序將依賴字段的類型(字符串字段按字母順序排序,而日期字段按時間順序排序)。如上所示,你可以使用減號(-)對字段名稱進行前綴,以反轉排序順序。

例如,如果我們選擇默認排列這樣的書單:

ordering = ['title', '-pubdate']

書單通過標題依據-字母排序-排列,從A到Z,還有每個標題的出版日期,從最新到最舊。

另一個常見的屬性是verbose_name ,一個verbose_name 說明單數和複數形式的類別。

verbose_name = 'BetterName'

其他有用的屬性允許你為模型創建和應用新的“訪問權限”(自動應用默認權限),允許基於其他的字段排序,或聲明該類是”抽象的“(你無法創建的記錄基類,並將由其他型號派生)。

許多其他元數據選項控制模型中必須使用哪些數據庫以及數據的存儲方式。(如果你需要模型映射一個現有數據庫,這會有用)。

完整有用的元數據選項在這裡Model metadata options (Django docs).

 

方法

一個模型也可以有方法。

最起碼,在每個模型中,你應該定義標準的Python 類方法__str__() 來為每個對象返回一個人類可讀的字符串此字符用於表示管理站點的各個記錄(以及你需要引用模型實例的任何其他位置)。通常這將返回模型中的標題或名稱字段。

def __str__(self):
    return self.field_name

Django方法中另一個常用方法是 get_absolute_url(),這函數返回一個在網站上顯示個人模型記錄的URL(如果你定義了該方法,那麼Django 將自動在“管理站點”中添加“在站點中查看“按鈕在模型的記錄編輯欄)。get_absolute_url()的典型示例如下:

def get_absolute_url(self):
    """Returns the url to access a particular instance of the model."""
    return reverse('model-detail-view', args=[str(self.id)])

注意 :假設你將使用URL/myapplication/mymodelname/2 來顯示模型的單個記錄(其中“2”是id特定記錄),則需要創建一個URL映射器來將響應和id傳遞給“模型詳細視圖” (這將做出顯示記錄所需的工作)。以上示例中,reverse()函數可以“反轉”你的url映射器(在上訴命名為“model-detail-view”的案例中,以創建正確格式的URL。

當然要做這個工作,你還是要寫URL映射,查看和模版!

 

你可以定義一些你喜歡的其他方法,並從你的代碼或模版調用它們(只要它們不帶任何參數)。

模型管理

一旦你定義了模型類,你可以使用它們來創建,更新或刪除記錄,並運行查詢獲取所有記錄或特定的記錄子集。當我們定義我們的視圖,我們將展示給你在這個教程如何去做。

創建和修改記錄

要創建一個記錄,你可以定義一個模型實例,然後調用save()

# Create a new record using the model's constructor.
record = MyModelName(my_field_name="Instance #1")

# Save the object into the database.
record.save()

Note: If you haven't declared any field as a primary_key, the new record will be given one automatically, with the field name id. You could query this field after saving the above record, and it would have a value of 1.

You can access the fields in this new record using the dot syntax, and change the values. You have to call save() to store modified values to the database.

# Access model field values using Python attributes.
print(record.id) #should return 1 for the first record. 
print(record.my_field_name) # should print 'Instance #1'

# Change record by modifying the fields, then calling save().
record.my_field_name = "New Instance Name"
record.save()

Searching for records

You can search for records that match a certain criteria using the model's objects attribute (provided by the base class).

Note: Explaining how to search for records using "abstract" model and field names can be a little confusing. In the discussion below we'll refer to a Book model with title and genre fields, where genre is also a model with a single field name.

We can get all records for a model as a QuerySet, using objects.all(). The QuerySet is an iterable object, meaning that it contains a number of objects that we can iterate/loop through.

all_books = Book.objects.all()

Django's filter() method allows us to filter the returned QuerySet to match a specified text or numeric field against a particular criteria. For example, to filter for books that contain "wild" in the title and then count them, we could do the following.

wild_books = Book.objects.filter(title__contains='wild')
number_wild_books = Book.objects.filter(title__contains='wild').count()

The fields to match and the type of match are defined in the filter parameter name, using the format: field_name__match_type (note the double underscore between title and contains above). Above we're filtering title with a case-sensitive match. There are many other types of matches you can do: icontains (case insensitive), iexact (case-insensitive exact match), exact (case-sensitive exact match) and in, gt (greater than), startswith, etc. The full list is here.

In some cases you'll need to filter on a field that defines a one-to-many relationship to another model (e.g. a ForeignKey). In this case you can "index" to fields within the related model with additional double underscores. So for example to filter for books with a specific genre pattern, you will have to index to the name through the genre field, as shown below:

# Will match on: Fiction, Science fiction, non-fiction etc.
books_containing_genre = Book.objects.filter(genre__name__icontains='fiction')

Note: You can use underscores (__) to navigate as many levels of relationships (ForeignKey/ManyToManyField) as you like. For example, a Book that had different types, defined using a further "cover" relationship might have a parameter name: type__cover__name__exact='hard'.

There is a lot more you can do with queries, including backwards searches from related models, chaining filters, returning a smaller set of values etc. For more information see Making queries (Django Docs).

Defining the LocalLibrary Models

In this section we will start defining the models for the library. Open models.py (in /locallibrary/catalog/). The boilerplate at the top of the page imports the models module, which contains the model base class models.Model that our models will inherit from.

from django.db import models

# Create your models here.

Genre model

Copy the Genre model code shown below and paste it into the bottom of your models.py file. This model is used to store information about the book category — for example whether it is fiction or non-fiction, romance or military history, etc. As mentioned above, we've created the Genre as a model rather than as free text or a selection list so that the possible values can be managed through the database rather than being hard coded.

class Genre(models.Model):
    """Model representing a book genre."""
    name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)')
    
    def __str__(self):
        """String for representing the Model object."""
        return self.name

The model has a single CharField field (name), which is used to describe the genre (this is limited to 200 characters and has some help_text. At the end of the model we declare a __str__() method, which simply returns the name of the genre defined by a particular record. No verbose name has been defined, so the field will be called Name in forms.

Book model

Copy the Book model below and again paste it into the bottom of your file. The book model represents all information about an available book in a general sense, but not a particular physical "instance" or "copy" available for loan. The model uses a CharField to represent the book's title and isbn (note how the isbn specifies its label as "ISBN" using the first unnamed parameter because the default label would otherwise be "Isbn"). The model uses TextField for the summary, because this text may need to be quite long.

from django.urls import reverse #Used to generate URLs by reversing the URL patterns

class Book(models.Model):
    """Model representing a book (but not a specific copy of a book)."""
    title = models.CharField(max_length=200)
    author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
    
    # Foreign Key used because book can only have one author, but authors can have multiple books
    # Author as a string rather than object because it hasn't been declared yet in the file.
    summary = models.TextField(max_length=1000, help_text='Enter a brief description of the book')
    isbn = models.CharField('ISBN', max_length=13, help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>')
    
    # ManyToManyField used because genre can contain many books. Books can cover many genres.
    # Genre class has already been defined so we can specify the object above.
    genre = models.ManyToManyField(Genre, help_text='Select a genre for this book')
    
    def __str__(self):
        """String for representing the Model object."""
        return self.title
    
    def get_absolute_url(self):
        """Returns the url to access a detail record for this book."""
        return reverse('book-detail', args=[str(self.id)])

The genre is a ManyToManyField, so that a book can have multiple genres and a genre can have many books. The author is declared as ForeignKey, so each book will only have one author, but an author may have many books (in practice a book might have multiple authors, but not in this implementation!)

In both field types the related model class is declared as the first unnamed parameter using either the model class or a string containing the name of the related model. You must use the name of the model as a string if the associated class has not yet been defined in this file before it is referenced! The other parameters of interest in the author field are null=True, which allows the database to store a Null value if no author is selected, and on_delete=models.SET_NULL, which will set the value of the author to Null if the associated author record is deleted.

The model also defines __str__() , using the book's title field to represent a Book record. The final method, get_absolute_url() returns a URL that can be used to access a detail record for this model (for this to work we will have to define a URL mapping that has the name book-detail, and define an associated view and template).

BookInstance model

Next, copy the BookInstance model (shown below) under the other models. The BookInstance represents a specific copy of a book that someone might borrow, and includes information about whether the copy is available or on what date it is expected back, "imprint" or version details, and a unique id for the book in the library.

Some of the fields and methods will now be familiar. The model uses

  • ForeignKey to identify the associated Book (each book can have many copies, but a copy can only have one Book).
  • CharField to represent the imprint (specific release) of the book.
import uuid # Required for unique book instances

class BookInstance(models.Model):
    """Model representing a specific copy of a book (i.e. that can be borrowed from the library)."""
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular book across whole library')
    book = models.ForeignKey('Book', on_delete=models.SET_NULL, null=True) 
    imprint = models.CharField(max_length=200)
    due_back = models.DateField(null=True, blank=True)

    LOAN_STATUS = (
        ('m', 'Maintenance'),
        ('o', 'On loan'),
        ('a', 'Available'),
        ('r', 'Reserved'),
    )

    status = models.CharField(
        max_length=1,
        choices=LOAN_STATUS,
        blank=True,
        default='m',
        help_text='Book availability',
    )

    class Meta:
        ordering = ['due_back']

    def __str__(self):
        """String for representing the Model object."""
        return f'{self.id} ({self.book.title})'

We additionally declare a few new types of field:

  • UUIDField is used for the id field to set it as the primary_key for this model. This type of field allocates a globally unique value for each instance (one for every book you can find in the library).
  • DateField is used for the due_back date (at which the book is expected to come available after being borrowed or in maintenance). This value can be blank or null (needed for when the book is available). The model metadata (Class Meta) uses this field to order records when they are returned in a query.
  • status is a CharField that defines a choice/selection list. As you can see, we define a tuple containing tuples of key-value pairs and pass it to the choices argument. The value in a key/value pair is a display value that a user can select, while the keys are the values that are actually saved if the option is selected. We've also set a default value of 'm' (maintenance) as books will initially be created unavailable before they are stocked on the shelves.

The model __str__() represents the BookInstance object using a combination of its unique id and the associated Book's title.

Note: A little Python:

  • Starting with Python 3.6, you can use the string interpolation syntax (also known as f-strings): f'{self.id} ({self.book.title})'.
  • In older versions of this tutorial, we were using a formatted string syntax, which is also a valid way of formatting strings in Python (e.g. '{0} ({1})'.format(self.id,self.book.title)).

Author model

Copy the Author model (shown below) underneath the existing code in models.py.

All of the fields/methods should now be familiar. The model defines an author as having a first name, last name, date of birth, and (optional) date of death. It specifies that by default the __str__() returns the name in last name, firstname order. The get_absolute_url() method reverses the author-detail URL mapping to get the URL for displaying an individual author.

class Author(models.Model):
    """Model representing an author."""
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    date_of_birth = models.DateField(null=True, blank=True)
    date_of_death = models.DateField('Died', null=True, blank=True)

    class Meta:
        ordering = ['last_name', 'first_name']
    
    def get_absolute_url(self):
        """Returns the url to access a particular author instance."""
        return reverse('author-detail', args=[str(self.id)])

    def __str__(self):
        """String for representing the Model object."""
        return f'{self.last_name}, {self.first_name}'

Re-run the database migrations

All your models have now been created. Now re-run your database migrations to add them to your database.

python3 manage.py makemigrations
python3 manage.py migrate

Language model — challenge

Imagine a local benefactor donates a number of new books written in another language (say, Farsi). The challenge is to work out how these would be best represented in our library website, and then to add them to the models.

Some things to consider:

  • Should "language" be associated with a Book, BookInstance, or some other object?
  • Should the different languages be represented using model, a free text field, or a hard-coded selection list?

After you've decided, add the field. You can see what we decided on Github here.

Summary

In this article we've learned how models are defined, and then used this information to design and implement appropriate models for the LocalLibrary website.

At this point we'll divert briefly from creating the site, and check out the Django Administration site. This site will allow us to add some data to the library, which we can then display using our (yet to be created) views and templates.

See also

 

In this module

 

文件標籤與貢獻者

此頁面的貢獻者: edgar-chen
最近更新: edgar-chen,