Django------ Advanced Models 模型高级进阶 2012.09.02

714558923
Accessing Foreign Key Values When you access a field that’s a ForeignKey, you’ll
get the related model object. For example: >>> b = Book.objects.get(id=50) >>> b.publisher <Publisher: Apress Publishing> >>> b.publisher.website u'http://www.apress.com/' With ForeignKey fields, it works the other way, too, but it’s slightly different due to the non-symmetrical nature of the relationship. To get a list of books for a given publisher, use publisher.book_set.all(), like this:1 >>> p = Publisher.objects.get(name='Apress Publishing') >>> p.book_set.all() [<Book: The Django Book>, <Book: Dive Into Python>, ...] Behind the scenes, book_set is just a QuerySet (as covered in Chapter 5), and it can be filtered and sliced like any other QuerySet. For example:2 >>> p = Publisher.objects.get(name='Apress Publishing') >>> p.book_set.filter(name__icontains='django') [<Book: The Django Book>, <Book: Pro Django>] The attribute name book_set is generated by appending the lower case model name to _set.
访问多对多值(Many-to-Many Values)

多对多和外键工作方式相同,只不过我们处理的是QuerySet而不是模型实例。 例如,这里是如何查看书籍的作者:

>>> b = Book.objects.get(id=50)
>>> b.authors.all()
[<Author: Adrian Holovaty>, <Author: Jacob Kaplan-Moss>]
>>> b.authors.filter(first_name='Adrian')
[<Author: Adrian Holovaty>]
>>> b.authors.filter(first_name='Adam')
[]
反向查询也可以。 要查看一个作者的所有书籍,使用author.book_set ,就如这样:

>>> a = Author.objects.get(first_name='Adrian', last_name='Holovaty')
>>> a.book_set.all()
[<Book: The Django Book>, <Book: Adrian's Other Book>]
这里,就像使用 ForeignKey字段一样,属性名book_set是在数据模型(model)名后追加_set。

Database Schema 更改数据库模式

    When dealing with schema changes, it is important to keep a few things in mind about how Django's database layer works:

  • Django will complatin loudly if a model contains a field that has not yet been creater in the database table. This will cause an error the first time you use the Django database API to query the given table(it will happen at code execution time, not at compilation time).
  • Django does not care if a database table contains columns that are not defined in the model.
  • Django does not care if a database contains a table that is not represented by a model.

    Make schema changes is a matter of changing the various pieces---the Python code and the database itself---in the right order.(更改模型的模式架构意味着需要按照顺序更改python代码和数据库)

Adding Fields 

    When adding a field to a table/model in a production setting, the trick is to take advantage of the fact that Django does not care if a table coantains columns that are not defined inthe model.

the strategy is to add the column in the database, and then update the Django model to include the new field.

    However, there's a bit of a chicken-and-egg problem. because in order to know how the new database column should be expressed in SQL,you need to look at the output of Django's manage.py sqlall command.

First, take these steps in the development environment (i.e., not on the production server):

Add the field to your model.
Run manage.py sqlall [yourapp] to see the new CREATE TABLE statement for the model. Note the column definition for the new field.1
Start your database’s interactive shell (e.g., psql or mysql, or you can use manage.py dbshell). Execute an ALTER TABLE statement that adds your new column.2
Launch the Python interactive shell with manage.py shell and verify that the new field was added properly by importing the model and selecting from the table (e.g., MyModel.objects.all()[:5]). If you updated the database correctly, the statement should work without errors.
Then on the production server perform these steps:

Start your database’s interactive shell.
Execute the ALTER TABLE statement you used in step 3 of the development environment steps.
Add the field to your model. If you’re using source-code revision control and you checked in your change in development environment step 1, now is the time to update the code (e.g., svn update, with Subversion) on the production server.
Restart the Web server for the code changes to take effect.

    For example. let's walk through what we'd do if we added a num_pages field to the Book model . First we'd alter the model in our development environment to look like this:

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()
    num_pages = models.IntegerField(blank=True, null=True)

    def __unicode__(self):
        return self.title

    Then we'd run the command manage.py sqlall books to see the CREATE TABLE statement, Depending on your database backend, it would look something like this:

CREATE TABLE "books_book" (
    "id" serial NOT NULL PRIMARY KEY,
    "title" varchar(100) NOT NULL,
    "publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id"),
    "publication_date" date NOT NULL,
    "num_pages" integer NULL
);
The new column is represented like this:

"num_pages" integer NULL
Next, we’d start the database’s interactive shell for our development database by typing psql (for PostgreSQL), and we’d execute the following statements:

ALTER TABLE books_book ADD COLUMN num_pages integer;
After the ALTER TABLE statement, we’d verify that the change worked properly by starting the Python shell and running this code:

>>> from mysite.books.models import Book
>>> Book.objects.all()[:5]
If that code didn’t cause errors, we’d switch to our production server and execute the ALTER TABLE statement on the production database. Then, we’d update the model in the production environment and restart the Web server.

Removing Fields
Removing a field from a model is a lot easier than adding one. To remove a field, just follow these steps:2

Remove the field from your model and restart the Web server.

Remove the column from your database, using a command like this:

ALTER TABLE books_book DROP COLUMN num_pages;
Make sure to do it in this order. If you remove the column from your database first, Django will immediately begin raising errors.

Removing Many-to-Many Fields1
Because many-to-many fields are different than normal fields, the removal process is different:

Remove the ManyToManyField from your model and restart the Web server.

Remove the many-to-many table from your database, using a command like this:

DROP TABLE books_book_authors;
As in the previous section, make sure to do it in this order.

Removing Models
Removing a model entirely is as easy as removing a field. To remove a model, just follow these steps:

Remove the model from your models.py file and restart the Web server.

Remove the table from your database, using a command like this:1

DROP TABLE books_book;
Note that you might need to remove any dependent tables from your database first — e.g., any tables that have foreign keys to books_book.

As in the previous sections, make sure to do it in this order.

-------------------------------------------------------------------------------------------------------------------------

Managers:

    In the statement Book.objects.all(),objects is a special attribute through which you query your database.Now it isNo time to dive a bit deeper into what managers are and how you can use them.

    In short, a model's manager is an object through which django models perform database queries, Each Django model has at least one manager, and you can create custom manager in order to customize database access.

    There are two reasons you might want to create a custom manager:to add extra manager methods, and to modify the initial QuerySet the manager returns.

Adding extra manager methods:

    Adding extra manager methods is the preferred way to add "table-level" functionality to your models.(For "row-lever" functionality--i.e. functions that act on a single instance of a model object--use model methods, which are explained later in the chapter.)

    Let's give our Book model a manager method title_count() that takes a keyword and returns the number of books that have a title containing that keyword.

# models.py

from django.db import models

# ... Author and Publisher models here ...

class BookManager(models.Manager):
    def title_count(self, keyword):
        return self.filter(title__icontains=keyword).count()
class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()
    num_pages = models.IntegerField(blank=True, null=True)
    **objects = BookManager()**

    def __unicode__(self):
        return self.title

解释:我们定义了一个BookManager类,它继承了django.db.models.Manager.这个类只有一个方法,title_count()方法,用来做统计,这个方法使用的self.filter,此处的self指的是manager本身。我们把BookManager()赋值给了模型的objects属性,它将取代模型的默认manager,如果我们没有特别定义,它将会被能够自动创建,我们命名为objects,这个是为了与自动创建的manager保持一致。

修改初始的Manager QuerySets:

我们可以通过覆盖Manager.get_query_set()方法来重写manager的基本QuerySet.get_query_set()按照你的要求返回一个QuerySet。

from django.db import models

**# First, define the Manager subclass.**
**class DahlBookManager(models.Manager):**
    **def get_query_set(self):**
        **return super(DahlBookManager, self).get_query_set().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()只返回了一本. 注意我们明确地将objects设置成manager的实例,因为如果我们不这么做,那么唯一可用的manager就将是dah1_objects。

当然,由于get_query_set()返回的是一个QuerySet对象,所以我们可以使用filter(),exclude()和其他一切QuerySet的方法。 像这些语法都是正确的:

Book.dahl_objects.all()
Book.dahl_objects.filter(title='Matilda')
Book.dahl_objects.count()

这个例子也指出了其他有趣的技术: 在同一个模型中使用多个manager。 只要你愿意,你可以为你的模型添加多个manager()实例。 这是一个为模型添加通用滤器的简单方法。

class MaleManager(models.Manager):
    def get_query_set(self):
        return super(MaleManager, self).get_query_set().filter(sex='M')

class FemaleManager(models.Manager):
    def get_query_set(self):
        return super(FemaleManager, self).get_query_set().filter(sex='F')

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    sex = models.CharField(max_length=1, choices=(('M', 'Male'), ('F', 'Female')))
    people = models.Manager()
    men = MaleManager()
    women = FemaleManager()
这个例子允许你执行`` Person.men.all()`` ,`` Person.women.all()`` ,`` Person.people.all()`` 查询,生成你想要的结果。

如果你使用自定义的Manager对象,请注意,Django遇到的第一个Manager(以它在模型中被定义的位置为准)会有一个特殊状态。 Django将会把第一个Manager 定义为默认Manager ,Django的许多部分(但是不包括admin应用)将会明确地为模型使用这个manager。 结论是,你应该小心地选择你的默认manager。因为覆盖get_query_set()了,你可能接受到一个无用的返回对像,你必须避免这种情况。

模型方法:

为了给你的对像添加一个行级功能,那就定义一个自定义方法。 有鉴于manager经常被用来用一些整表操作(table-wide),模型方法应该只对特殊模型实例起作用。

这是一项在模型的一个地方集中业务逻辑的技术。

最好用例子来解释一下。 这个模型有一些自定义方法:

from django.contrib.localflavor.us.models import USStateField
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()
    address = models.CharField(max_length=100)
    city = models.CharField(max_length=50)
    state = USStateField() # Yes, this is U.S.-centric...

    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime
        if datetime.date(1945, 8, 1) <= self.birth_date <= datetime.date(1964, 12, 31):
            return "Baby boomer"
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        return "Post-boomer"

    def is_midwestern(self):
        "Returns True if this person is from the Midwest."
        return self.state in ('IL', 'WI', 'MI', 'IN', 'OH', 'IA', 'MO')

    def _get_full_name(self):
        "Returns the person's full name."
        return u'%s %s' % (self.first_name, self.last_name)
    full_name = property(_get_full_name)

这是用法的实例:

>>> p = Person.objects.get(first_name='Barack', last_name='Obama')
>>> p.birth_date
datetime.date(1961, 8, 4)
>>> p.baby_boomer_status()
'Baby boomer'
>>> p.is_midwestern()
True
>>> p.full_name  # Note this isn't a method -- it's treated as an attribute
u'Barack Obama'

执行原始SQL查询:

有时候你会发现Django数据库API带给你的也只有这么多,那你可以为你的数据库写一些自定义SQL查询。 你可以通过导入django.db.connection对像来轻松实现,它代表当前数据库连接。 要使用它,需要通过connection.cursor()得到一个游标对像。 然后,使用cursor.execute(sql, [params])来执行SQL语句,使用cursor.fetchone()或者cursor.fetchall()来返回记录集。 例如:

>>> from django.db import connection
>>> cursor = connection.cursor()
>>> cursor.execute("""
...    SELECT DISTINCT first_name
...    FROM people_person
...    WHERE last_name = %s""", ['Lennon'])
>>> row = cursor.fetchone()
>>> print row
['John']

connectioncursor几乎实现了标准Python DB-API,你可以访问` http://www.python.org/peps/pep-0249.html<http://www.python.org/peps/pep-0249.html>`__来获取更多信息。 如果你对Python DB-API不熟悉,请注意在cursor.execute() 的SQL语句中使用`` “%s”`` ,而不要在SQL内直接添加参数。 如果你使用这项技术,数据库基础库将会自动添加引号,同时在必要的情况下转意你的参数。

不要把你的视图代码和django.db.connection语句混杂在一起,把它们放在自定义模型或者自定义manager方法中是个不错的主意。 比如,上面的例子可以被整合成一个自定义manager方法,就像这样:

from django.db import connection, models

class PersonManager(models.Manager):
    def first_names(self, last_name):
        cursor = connection.cursor()
        cursor.execute("""
            SELECT DISTINCT first_name
            FROM people_person
            WHERE last_name = %s""", [last_name])
        return [row[0] for row in cursor.fetchone()]

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    objects = PersonManager()
然后这样使用:

>>> Person.objects.first_names('Lennon')
['John', 'Cynthia']

 

 

 

 

 

posted @ 2012-09-02 17:01  事件轮询,回不到过去  阅读(515)  评论(0编辑  收藏  举报