It's time for Django

本節內容

 

Django流程介紹

Django url

Django view

Django models

Django template

Django form

Django admin 

 

Django流程介紹

 

 

Django URL

 

Example

Here’s a sample URLconf:

1
2
3
4
5
6
7
8
9
10
from django.conf.urls import url
 
from import views
 
urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/([0-9]{4})/$', views.year_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]

  

Notes:

  • To capture a value from the URL, just put parenthesis around it.
  • There’s no need to add a leading slash, because every URL has that. For example, it’s ^articles, not ^/articles.
  • The 'r' in front of each regular expression string is optional but recommended. It tells Python that a string is “raw” – that nothing in the string should be escaped. See Dive Into Python’s explanation.

Example requests:

  • A request to /articles/2005/03/ would match the third entry in the list. Django would call the functionviews.month_archive(request, '2005', '03').
  • /articles/2005/3/ would not match any URL patterns, because the third entry in the list requires two digits for the month.
  • /articles/2003/ would match the first pattern in the list, not the second one, because the patterns are tested in order, and the first one is the first test to pass. Feel free to exploit the ordering to insert special cases like this. Here, Django would call the function views.special_case_2003(request)
  • /articles/2003 would not match any of these patterns, because each pattern requires that the URL end with a slash.
  • /articles/2003/03/03/ would match the final pattern. Django would call the functionviews.article_detail(request, '2003', '03', '03').

Named groups

The above example used simple, non-named regular-expression groups (via parenthesis) to capture bits of the URL and pass them as positional arguments to a view. In more advanced usage, it’s possible to use named regular-expression groups to capture URL bits and pass them as keyword arguments to a view.

In Python regular expressions, the syntax for named regular-expression groups is (?P<name>pattern), where name is the name of the group and pattern is some pattern to match.

Here’s the above example URLconf, rewritten to use named groups:

1
2
3
4
5
6
7
8
9
10
from django.conf.urls import url
 
from import views
 
urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]

  

This accomplishes exactly the same thing as the previous example, with one subtle difference: The captured values are passed to view functions as keyword arguments rather than positional arguments. For example:

  • A request to /articles/2005/03/ would call the function views.month_archive(request, year='2005',month='03'), instead of views.month_archive(request, '2005', '03').
  • A request to /articles/2003/03/03/ would call the function views.article_detail(request, year='2003',month='03', day='03').

In practice, this means your URLconfs are slightly more explicit and less prone to argument-order bugs – and you can reorder the arguments in your views’ function definitions. Of course, these benefits come at the cost of brevity; some developers find the named-group syntax ugly and too verbose.

 

 

What the URLconf searches against

The URLconf searches against the requested URL, as a normal Python string. This does not include GET or POST parameters, or the domain name.

For example, in a request to https://www.example.com/myapp/, the URLconf will look for myapp/.

In a request to https://www.example.com/myapp/?page=3, the URLconf will look for myapp/.

The URLconf doesn’t look at the request method. In other words, all request methods – POSTGETHEAD, etc. – will be routed to the same function for the same URL.

Captured arguments are always strings

Each captured argument is sent to the view as a plain Python string, regardless of what sort of match the regular expression makes. For example, in this URLconf line:

1
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
...the year argument passed to views.year_archive() will be a string,
not an integer, even though the [0-9]{4} will only match integer strings.

 

Including other URLconfs

 

At any point, your urlpatterns can “include” other URLconf modules. This essentially “roots” a set of URLs below other ones.

For example, here’s an excerpt of the URLconf for the Django website itself. It includes a number of other URLconfs:

1
2
3
4
5
6
7
8
from django.conf.urls import include, url
 
urlpatterns = [
    # ... snip ...
    url(r'^community/', include('django_website.aggregator.urls')),
    url(r'^contact/', include('django_website.contact.urls')),
    # ... snip ...
]

  

Note that the regular expressions in this example don’t have a $ (end-of-string match character) but do include a trailing slash. Whenever Django encounters include() (django.conf.urls.include()), it chops off whatever part of the URL matched up to that point and sends the remaining string to the included URLconf for further processing.

Another possibility is to include additional URL patterns by using a list of url() instances. For example, consider this URLconf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.conf.urls import include, url
 
from apps.main import views as main_views
from credit import views as credit_views
 
extra_patterns = [
    url(r'^reports/$', credit_views.report),
    url(r'^reports/(?P<id>[0-9]+)/$', credit_views.report),
    url(r'^charge/$', credit_views.charge),
]
 
urlpatterns = [
    url(r'^$', main_views.homepage),
    url(r'^help/', include('apps.help.urls')),
    url(r'^credit/', include(extra_patterns)),
]

  

In this example, the /credit/reports/ URL will be handled by the credit_views.report() Django view.

This can be used to remove redundancy from URLconfs where a single pattern prefix is used repeatedly. For example, consider this URLconf:

1
2
3
4
5
6
7
8
9
from django.conf.urls import url
from import views
 
urlpatterns = [
    url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/history/$', views.history),
    url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/edit/$', views.edit),
    url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/discuss/$', views.discuss),
    url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/permissions/$', views.permissions),
]

  

We can improve this by stating the common path prefix only once and grouping the suffixes that differ:

1
2
3
4
5
6
7
8
9
10
11
from django.conf.urls import include, url
from import views
 
urlpatterns = [
    url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/', include([
        url(r'^history/$', views.history),
        url(r'^edit/$', views.edit),
        url(r'^discuss/$', views.discuss),
        url(r'^permissions/$', views.permissions),
    ])),
]

Passing extra options to view functions

URLconfs have a hook that lets you pass extra arguments to your view functions, as a Python dictionary.

The django.conf.urls.url() function can take an optional third argument which should be a dictionary of extra keyword arguments to pass to the view function.

For example:

1
2
3
4
5
6
from django.conf.urls import url
from import views
 
urlpatterns = [
    url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo''bar'}),
]

  

In this example, for a request to /blog/2005/, Django will call views.year_archive(request, year='2005',foo='bar').

This technique is used in the syndication framework to pass metadata and options to views.

Dealing with conflicts

It’s possible to have a URL pattern which captures named keyword arguments, and also passes arguments with the same names in its dictionary of extra arguments. When this happens, the arguments in the dictionary will be used instead of the arguments captured in the URL.

Passing extra options to include()

Similarly, you can pass extra options to include(). When you pass extra options to include()each line in the included URLconf will be passed the extra options.

For example, these two URLconf sets are functionally identical:

Set one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# main.py
from django.conf.urls import include, url
 
urlpatterns = [
    url(r'^blog/', include('inner'), {'blogid'3}),
]
 
# inner.py
from django.conf.urls import url
from mysite import views
 
urlpatterns = [
    url(r'^archive/$', views.archive),
    url(r'^about/$', views.about),
]

  

Set two:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# main.py
from django.conf.urls import include, url
from mysite import views
 
urlpatterns = [
    url(r'^blog/', include('inner')),
]
 
# inner.py
from django.conf.urls import url
 
urlpatterns = [
    url(r'^archive/$', views.archive, {'blogid'3}),
    url(r'^about/$', views.about, {'blogid'3}),
]

  

Note that extra options will always be passed to every line in the included URLconf, regardless of whether the line’s view actually accepts those options as valid. For this reason, this technique is only useful if you’re certain that every view in the included URLconf accepts the extra options you’re passing.

 

 

Django Views

最簡單的返回一個字符串形式的view

1
2
3
4
5
6
from django.http import HttpResponse
 
def my_view(request):
    if request.method == 'GET':
        # <view logic>
        return HttpResponse('result')

如果想直接返回一個html文檔

1
2
3
4
5
6
7
from django.shortcuts import render,HttpResponse
 
# Create your views here.
 
 
def test_view(request):
    return render(request,'index.html')

  

  

Django Models  

django 本身提供了非常強大易使用的ORM組件,並且支持多種數據庫,如sqllite,mysql,progressSql,Oracle等,當然最常用的搭配還是mysql,要啓用orm,先要配置好連接數據 的信息

在你創建的project目錄下編輯settings.py 

1
2
3
4
5
6
7
8
9
10
DATABASES = {
    'default': {
        'ENGINE''django.db.backends.mysql',
        'NAME''OldboyWebsite'#確保此數據庫已存在
        'HOST':'',
        'PORT':'',
        'USER':'root',
        'PASSWORD':''
    }
}

下面要開始學習Django ORM語法了,爲了更好的理解,我們來做一個基本的 書籍/作者/出版商 數據庫結構。 我們這樣做是因爲 這是一個衆所周知的例子,很多SQL有關的書籍也常用這個舉例。

我們來假定下面的這些概念、字段和關係:

  • 一個作者有姓,有名及email地址。

  • 出版商有名稱,地址,所在城市、省,國家,網站。

  • 書籍有書名和出版日期。 它有一個或多個作者(和作者是多對多的關聯關係[many-to-many]), 只有一個出版商(和出版商是一對多的關聯關係[one-to-many],也被稱作外鍵[foreign key])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.db import models
 
class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()
 
class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()
 
class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()

更多models field 字段:https://docs.djangoproject.com/en/1.9/ref/models/fields/ 

每個模型相當於單個數據庫表,每個屬性也是這個表中的一個字段。 屬性名就是字段名,它的類型(例如CharField )相當於數據庫的字段類型 (例如 varchar )。例如, Publisher 模塊等同於下面這張表(用PostgreSQL的 CREATE TABLE 語法描述):

1
2
3
4
5
6
7
8
9
CREATE TABLE "books_publisher" (
    "id" serial NOT NULL PRIMARY KEY,
    "name" varchar(30) NOT NULL,
    "address" varchar(50) NOT NULL,
    "city" varchar(60) NOT NULL,
    "state_province" varchar(30) NOT NULL,
    "country" varchar(50) NOT NULL,
    "website" varchar(200) NOT NULL
);

模型安裝

完成這些代碼之後,現在讓我們來在數據庫中創建這些表。 要完成該項工作,第一步是在 Django 項目中 激活這些模型。 將上面的模型所在的app (此例子app是books) 添加到配置文件的已安裝應用列表中即可完成此步驟。

再次編輯 settings.py 文件, 找到 INSTALLED_APPS 設置。 INSTALLED_APPS 告訴 Django 項目哪些 app 處於激活狀態。 缺省情況下如下所示:

1
2
3
4
5
6
7
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
   'books',
)

使數據庫按照模型的配置生成表結構

1
2
3
4
5
6
7
8
9
10
11
12
13
Jies-MacBook-Air:s12django jieli$ python manage.py makemigrations  #生成同步紀錄
Migrations for 'app01':
  0001_initial.py:
    - Create model Author
    - Create model Book
    - Create model Publisher
    - Add field publisher to book
Jies-MacBook-Air:s12django jieli$ python manage.py migrate #開始同步
Operations to perform:
  Apply all migrations: admin, contenttypes, app01, auth, sessions
Running migrations:
  Rendering model states... DONE
  Applying app01.0001_initial... OK

基本數據訪問

一旦你創建了模型,Django自動爲這些模型提供了高級的Python API。 運行 python manage.py shell 並輸入下面的內容試試看:

1
2
3
4
5
6
7
8
9
10
11
12
>>> from books.models import Publisher
>>> p1 = Publisher(name='Apress', address='2855 Telegraph Avenue',
...     city='Berkeley', state_province='CA', country='U.S.A.',
...     website='http://www.apress.com/')
>>> p1.save()
>>> p2 = Publisher(name="O'Reilly", address='10 Fawcett St.',
...     city='Cambridge', state_province='MA', country='U.S.A.',
...     website='http://www.oreilly.com/')
>>> p2.save()
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
[<Publisher: Publisher object>, <Publisher: Publisher object>]

這裏有一個值得注意的地方,在這個例子可能並未清晰地展示。 當你使用Django modle API創建對象時Django並未將對象保存至數據庫內,除非你調用`` save()`` 方法:

1
2
3
4
p1 = Publisher(...)
# At this point, p1 is not saved to the database yet!
p1.save()
# Now it is.  

如果需要一步完成對象的創建與存儲至數據庫,就使用`` objects.create()`` 方法。 下面的例子與之前的例子等價:

1
2
3
4
5
6
7
8
9
10
>>> p1 = Publisher.objects.create(name='Apress',
...     address='2855 Telegraph Avenue',
...     city='Berkeley', state_province='CA', country='U.S.A.',
...     website='http://www.apress.com/')
>>> p2 = Publisher.objects.create(name="O'Reilly",
...     address='10 Fawcett St.', city='Cambridge',
...     state_province='MA', country='U.S.A.',
...     website='http://www.oreilly.com/')
>>> publisher_list = Publisher.objects.all()
>>> publisher_list

  

 __unicode__()方法是個什麼鬼?  

當我們打印整個publisher列表時,我們沒有得到想要的有用信息,無法把````對象區分開來:

1
[<Publisher: Publisher object>, <Publisher: Publisher object>]

我們可以簡單解決這個問題,只需要爲Publisher 對象添加一個方法 __unicode__() 。 __unicode__() 方法告訴Python如何將對象以unicode的方式顯示出來。 爲以上三個模型添加__unicode__()方法後,就可以看到效果了。

1
2
def __unicode__(self):
      return self.name

對__unicode__()的唯一要求就是它要返回一個unicode對象 如果`` __unicode__()`` 方法未返回一個Unicode對象,而返回比如說一個整型數字,那麼Python將拋出一個`` TypeError`` 錯誤,並提示:”coercing to Unicode: need string or buffer, int found” 。  


 

插入和更新數據

你已經知道怎麼做了: 先使用一些關鍵參數創建對象實例,如下:

1
2
3
4
5
6
>>> p = Publisher(name='Apress',
...         address='2855 Telegraph Ave.',
...         city='Berkeley',
...         state_province='CA',
...         country='U.S.A.',
...         website='http://www.apress.com/')

這個對象實例並 沒有 對數據庫做修改。 在調用`` save()`` 方法之前,記錄並沒有保存至數據庫,像這樣:

1
>>> p.save()

在SQL裏,這大致可以轉換成這樣:

1
2
3
4
5
INSERT INTO books_publisher
    (name, address, city, state_province, country, website)
VALUES
    ('Apress''2855 Telegraph Ave.''Berkeley''CA',
     'U.S.A.''http://www.apress.com/');

因爲 Publisher 模型有一個自動增加的主鍵 id ,所以第一次調用 save() 還多做了一件事: 計算這個主鍵的值並把它賦值給這個對象實例:

1
2
>>> p.id
52    # this will differ based on your own data

接下來再調用 save() 將不會創建新的記錄,而只是修改記錄內容(也就是 執行 UPDATE SQL語句,而不是 INSERT語句):

1
2
>>> p.name = 'Apress Publishing'
>>> p.save()

前面執行的 save() 相當於下面的SQL語句:

1
2
3
4
5
6
7
8
UPDATE books_publisher SET
    name 'Apress Publishing',
    address = '2855 Telegraph Ave.',
    city = 'Berkeley',
    state_province = 'CA',
    country = 'U.S.A.',
    website = 'http://www.apress.com'
WHERE id = 52;

注意,並不是只更新修改過的那個字段,所有的字段都會被更新。

查找對象

當然,創建新的數據庫,並更新之中的數據是必要的,但是,對於 Web 應用程序來說,更多的時候是在檢索查詢數據庫。 我們已經知道如何從一個給定的模型中取出所有記錄:

1
2
>>> Publisher.objects.all()
[<Publisher: Apress>, <Publisher: O'Reilly>]

這相當於這個SQL語句:

1
2
SELECT id, name, address, city, state_province, country, website
FROM books_publisher;

注意

注意到Django在選擇所有數據時並沒有使用 SELECT* ,而是顯式列出了所有字段。 設計的時候就是這樣:SELECT* 會更慢,而且最重要的是列出所有字段遵循了Python 界的一個信條: 明言勝於暗示。

數據過濾

我們很少會一次性從數據庫中取出所有的數據;通常都只針對一部分數據進行操作。 在Django API中,我們可以使用`` filter()`` 方法對數據進行過濾:

1
2
>>> Publisher.objects.filter(name='Apress')
[<Publisher: Apress>]

filter() 根據關鍵字參數來轉換成 WHERE SQL語句。 前面這個例子 相當於這樣:

1
2
3
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name 'Apress';

你可以傳遞多個參數到 filter() 來縮小選取範圍:

1
2
>>> Publisher.objects.filter(country="U.S.A.", state_province="CA")
[<Publisher: Apress>]

多個參數會被轉換成 AND SQL從句, 因此上面的代碼可以轉化成這樣:

1
2
3
4
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = 'U.S.A.'
AND state_province = 'CA';

注意,SQL缺省的 = 操作符是精確匹配的, 其他類型的查找也可以使用:

1
2
>>> Publisher.objects.filter(name__contains="press")
[<Publisher: Apress>]

在 name 和 contains 之間有雙下劃線。和Python一樣,Django也使用雙下劃線來表明會進行一些魔術般的操作。這裏,contains部分會被Django翻譯成LIKE語句:

1
2
3
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name LIKE '%press%';

其他的一些查找類型有:icontains(大小寫不敏感的LIKE),startswithendswith, 還有range

 

獲取單個對象

上面的例子中`` filter()`` 函數返回一個記錄集,這個記錄集是一個列表。 相對列表來說,有些時候我們更需要獲取單個的對象, `` get()`` 方法就是在此時使用的:

1
2
>>> Publisher.objects.get(name="Apress")
<Publisher: Apress>

這樣,就返回了單個對象,而不是列表(更準確的說,QuerySet)。 所以,如果結果是多個對象,會導致拋出異常:

1
2
3
4
5
>>> Publisher.objects.get(country="U.S.A.")
Traceback (most recent call last):
    ...
MultipleObjectsReturned: get() returned more than one Publisher --
    it returned 2! Lookup parameters were {'country''U.S.A.'}

如果查詢沒有返回結果也會拋出異常:

1
2
3
4
>>> Publisher.objects.get(name="Penguin")
Traceback (most recent call last):
    ...
DoesNotExist: Publisher matching query does not exist.

這個 DoesNotExist 異常 是 Publisher 這個 model 類的一個屬性,即 Publisher.DoesNotExist。在你的應用中,你可以捕獲並處理這個異常,像這樣:

1
2
3
4
5
6
try:
    = Publisher.objects.get(name='Apress')
except Publisher.DoesNotExist:
    print "Apress isn't in the database yet."
else:
    print "Apress is in the database."

數據排序

在運行前面的例子中,你可能已經注意到返回的結果是無序的。 我們還沒有告訴數據庫 怎樣對結果進行排序,所以我們返回的結果是無序的。

在你的 Django 應用中,你或許希望根據某字段的值對檢索結果排序,比如說,按字母順序。 那麼,使用order_by() 這個方法就可以搞定了。

1
2
>>> Publisher.objects.order_by("name")
[<Publisher: Apress>, <Publisher: O'Reilly>]

跟以前的 all() 例子差不多,SQL語句裏多了指定排序的部分:

1
2
3
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name;
我們可以對任意字段進行排序:
1
2
3
4
5
>>> Publisher.objects.order_by("address")
[<Publisher: O'Reilly>, <Publisher: Apress>]
 
>>> Publisher.objects.order_by("state_province")
[<Publisher: Apress>, <Publisher: O'Reilly>] 

如果需要以多個字段爲標準進行排序(第二個字段會在第一個字段的值相同的情況下被使用到),使用多個參數就可以了,如下:

1
2
>>> Publisher.objects.order_by("state_province""address")
 [<Publisher: Apress>, <Publisher: O'Reilly>]

我們還可以指定逆向排序,在前面加一個減號 - 前綴:

1
2
>>> Publisher.objects.order_by("-name")
[<Publisher: O'Reilly>, <Publisher: Apress>]

儘管很靈活,但是每次都要用 order_by() 顯得有點囉嗦。 大多數時間你通常只會對某些 字段進行排序。 在這種情況下,Django讓你可以指定模型的缺省排序方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()
 
    def __unicode__(self):
        return self.name
 
    class Meta:
        ordering = ['name']

現在,讓我們來接觸一個新的概念。 class Meta,內嵌於 Publisher 這個類的定義中(如果 class Publisher 是頂格的,那麼 class Meta 在它之下要縮進4個空格--按 Python 的傳統 )。你可以在任意一個 模型 類中使用Meta 類,來設置一些與特定模型相關的選項。Meta 還可設置很多其它選項,現在,我們關注ordering 這個選項就夠了。 如果你設置了這個選項,那麼除非你檢索時特意額外地使用了 order_by(),否則,當你使用 Django 的數據庫 API 去檢索時,Publisher對象的相關返回值默認地都會按 name 字段排序。

連鎖查詢

我們已經知道如何對數據進行過濾和排序。 當然,通常我們需要同時進行過濾和排序查詢的操作。 因此,你可以簡單地寫成這種“鏈式”的形式:

1
2
>>> Publisher.objects.filter(country="U.S.A.").order_by("-name")
[<Publisher: O'Reilly>, <Publisher: Apress>]

你應該沒猜錯,轉換成SQL查詢就是 WHERE 和 ORDER BY 的組合:

1
2
3
4
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = 'U.S.A'
ORDER BY name DESC;

限制返回的數據

另一個常用的需求就是取出固定數目的記錄。 想象一下你有成千上萬的出版商在你的數據庫裏, 但是你只想顯示第一個。 你可以使用標準的Python列表裁剪語句:

1
2
>>> Publisher.objects.order_by('name')[0]
<Publisher: Apress>

這相當於:

1
2
3
4
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
LIMIT 1;

類似的,你可以用Python的range-slicing語法來取出數據的特定子集:

1
>>> Publisher.objects.order_by('name')[0:2]
這個例子返回兩個對象,等同於以下的SQL語句:
1
2
3
4
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
OFFSET 0 LIMIT 2;

注意,不支持Python的負索引(negative slicing):

1
2
3
4
>>> Publisher.objects.order_by('name')[-1]
Traceback (most recent call last):
  ...
AssertionError: Negative indexing is not supported.

雖然不支持負索引,但是我們可以使用其他的方法。 比如,稍微修改 order_by() 語句來實現:

1
>>> Publisher.objects.order_by('-name')[0]

更新多個對象

在“插入和更新數據”小節中,我們有提到模型的save()方法,這個方法會更新一行裏的所有列。 而某些情況下,我們只需要更新行裏的某幾列。

例如說我們現在想要將Apress Publisher的名稱由原來的”Apress”更改爲”Apress Publishing”。若使用save()方法,如:

1
2
3
>>> p = Publisher.objects.get(name='Apress')
>>> p.name = 'Apress Publishing'
>>> p.save()

這等同於如下SQL語句:

1
2
3
4
5
6
7
8
9
10
11
12
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name 'Apress';
 
UPDATE books_publisher SET
    name 'Apress Publishing',
    address = '2855 Telegraph Ave.',
    city = 'Berkeley',
    state_province = 'CA',
    country = 'U.S.A.',
    website = 'http://www.apress.com'
WHERE id = 52;

(注意在這裏我們假設Apress的ID爲52)

在這個例子裏我們可以看到Django的save()方法更新了不僅僅是name列的值,還有更新了所有的列。 若name以外的列有可能會被其他的進程所改動的情況下,只更改name列顯然是更加明智的。 更改某一指定的列,我們可以調用結果集(QuerySet)對象的update()方法: 示例如下:

1
>>> Publisher.objects.filter(id=52).update(name='Apress Publishing')
與之等同的SQL語句變得更高效,並且不會引起競態條件。
1
2
3
UPDATE books_publisher
SET name 'Apress Publishing'
WHERE id = 52;

update()方法對於任何結果集(QuerySet)均有效,這意味着你可以同時更新多條記錄。 以下示例演示如何將所有Publisher的country字段值由’U.S.A’更改爲’USA’:

1
2
>>> Publisher.objects.all().update(country='USA')
2

update()方法會返回一個整型數值,表示受影響的記錄條數。 在上面的例子中,這個值是2。

刪除對象

刪除數據庫中的對象只需調用該對象的delete()方法即可:

1
2
3
4
>>> p = Publisher.objects.get(name="O'Reilly")
>>> p.delete()
>>> Publisher.objects.all()
[<Publisher: Apress Publishing>]

同樣我們可以在結果集上調用delete()方法同時刪除多條記錄。這一點與我們上一小節提到的update()方法相似:

1
2
3
4
>>> Publisher.objects.filter(country='USA').delete()
>>> Publisher.objects.all().delete()
>>> Publisher.objects.all()
[]

刪除數據時要謹慎! 爲了預防誤刪除掉某一個表內的所有數據,Django要求在刪除表內所有數據時顯示使用all()。 比如,下面的操作將會出錯:

1
2
3
4
>>> Publisher.objects.delete()
Traceback (most recent call last):
  File "<console>", line 1in <module>
AttributeError: 'Manager' object has no attribute 'delete' 

而一旦使用all()方法,所有數據將會被刪除:

1
>>> Publisher.objects.all().delete()
如果只需要刪除部分的數據,就不需要調用all()方法。再看一下之前的例子:
1
>>> Publisher.objects.filter(country='USA').delete()
 

Django Template

你可能已經注意到我們在例子視圖中返回文本的方式有點特別。 也就是說,HTML被直接硬編碼在 Python 代碼之中。

1
2
3
4
def current_datetime(request):
    now = datetime.datetime.now()
    html = "<html><body>It is now %s.</body></html>" % now
    return HttpResponse(html)

儘管這種技術便於解釋視圖是如何工作的,但直接將HTML硬編碼到你的視圖裏卻並不是一個好主意。 讓我們來看一下爲什麼:

  • 對頁面設計進行的任何改變都必須對 Python 代碼進行相應的修改。 站點設計的修改往往比底層 Python 代碼的修改要頻繁得多,因此如果可以在不進行 Python 代碼修改的情況下變更設計,那將會方便得多。

  • Python 代碼編寫和 HTML 設計是兩項不同的工作,大多數專業的網站開發環境都將他們分配給不同的人員(甚至不同部門)來完成。 設計者和HTML/CSS的編碼人員不應該被要求去編輯Python的代碼來完成他們的工作。

  • 程序員編寫 Python代碼和設計人員製作模板兩項工作同時進行的效率是最高的,遠勝於讓一個人等待另一個人完成對某個既包含 Python又包含 HTML 的文件的編輯工作。

基於這些原因,將頁面的設計和Python的代碼分離開會更乾淨簡潔更容易維護。 我們可以使用 Django的 模板系統 (Template System)來實現這種模式,這就是本章要具體討論的問題。

Django 模版基本語法

1
2
3
4
5
>>> from django.template import Context, Template
>>> t = Template('My name is {{ name }}.')
>>> c = Context({'name''Stephane'})
>>> t.render(c)
u'My name is Stephane.'

同一模板,多個上下文

一旦有了 模板 對象,你就可以通過它渲染多個context, 例如:

1
2
3
4
5
6
7
8
>>> from django.template import Template, Context
>>> t = Template('Hello, {{ name }}')
>>> print t.render(Context({'name''John'}))
Hello, John
>>> print t.render(Context({'name''Julie'}))
Hello, Julie
>>> print t.render(Context({'name''Pat'}))
Hello, Pat

無論何時我們都可以像這樣使用同一模板源渲染多個context,只進行 一次模板創建然後多次調用render()方法渲染會更爲高效:

1
2
3
4
5
6
7
8
9
# Bad
for name in ('John''Julie''Pat'):
    = Template('Hello, {{ name }}')
    print t.render(Context({'name': name}))
 
# Good
= Template('Hello, {{ name }}')
for name in ('John''Julie''Pat'):
    print t.render(Context({'name': name}))

Django 模板解析非常快捷。 大部分的解析工作都是在後臺通過對簡短正則表達式一次性調用來完成。 這和基於 XML 的模板引擎形成鮮明對比,那些引擎承擔了 XML 解析器的開銷,且往往比 Django 模板渲染引擎要慢上幾個數量級。

深度變量的查找

在到目前爲止的例子中,我們通過 context 傳遞的簡單參數值主要是字符串,然而,模板系統能夠非常簡潔地處理更加複雜的數據結構,例如list、dictionary和自定義的對象。

在 Django 模板中遍歷複雜數據結構的關鍵是句點字符 (.)。

最好是用幾個例子來說明一下。 比如,假設你要向模板傳遞一個 Python 字典。 要通過字典鍵訪問該字典的值,可使用一個句點:

1
2
3
4
5
6
>>> from django.template import Template, Context
>>> person = {'name''Sally''age''43'}
>>> t = Template('{{ person.name }} is {{ person.age }} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
u'Sally is 43 years old.'

同樣,也可以通過句點來訪問對象的屬性。 比方說, Python 的 datetime.date 對象有 year 、 month 和 day 幾個屬性,你同樣可以在模板中使用句點來訪問這些屬性:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> from django.template import Template, Context
>>> import datetime
>>> d = datetime.date(199352)
>>> d.year
1993
>>> d.month
5
>>> d.day
2
>>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.')
>>> c = Context({'date': d})
>>> t.render(c)
u'The month is 5 and the year is 1993.'

這個例子使用了一個自定義的類,演示了通過實例變量加一點(dots)來訪問它的屬性,這個方法適用於任意的對象。

1
2
3
4
5
6
7
8
>>> from django.template import Template, Context
>>> class Person(object):
...     def __init__(self, first_name, last_name):
...         self.first_name, self.last_name = first_name, last_name
>>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.')
>>> c = Context({'person': Person('John''Smith')})
>>> t.render(c)
u'Hello, John Smith.'

點語法也可以用來引用對象的* 方法*。 例如,每個 Python 字符串都有 upper() 和 isdigit() 方法,你在模板中可以使用同樣的句點語法來調用它們:

1
2
3
4
5
6
>>> from django.template import Template, Context
>>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}')
>>> t.render(Context({'var''hello'}))
u'hello -- HELLO -- False'
>>> t.render(Context({'var''123'}))
u'123 -- 123 -- True'

注意這裏調用方法時並* 沒有* 使用圓括號 而且也無法給該方法傳遞參數;你只能調用不需參數的方法。 (我們將在本章稍後部分解釋該設計觀。)

最後,句點也可用於訪問列表索引,例如:

1
2
3
4
5
>>> from django.template import Template, Context
>>> t = Template('Item 2 is {{ items.2 }}.')
>>> c = Context({'items': ['apples''bananas''carrots']})
>>> t.render(c)
u'Item 2 is carrots.'

  

include 模板標籤

在講解了模板加載機制之後,我們再介紹一個利用該機制的內建模板標籤: {% include %} 。該標籤允許在(模板中)包含其它的模板的內容。 標籤的參數是所要包含的模板名稱,可以是一個變量,也可以是用單/雙引號硬編碼的字符串。 每當在多個模板中出現相同的代碼時,就應該考慮是否要使用 {% include %} 來減少重複。

下面這兩個例子都包含了 nav.html 模板。這兩個例子是等價的,它們證明單/雙引號都是允許的。

{% include 'nav.html' %}
{% include "nav.html" %}

下面的例子包含了 includes/nav.html 模板的內容:

{% include 'includes/nav.html' %}

模板繼承

到目前爲止,我們的模板範例都只是些零星的 HTML 片段,但在實際應用中,你將用 Django 模板系統來創建整個 HTML 頁面。 這就帶來一個常見的 Web 開發問題: 在整個網站中,如何減少共用頁面區域(比如站點導航)所引起的重複和冗餘代碼?

解決該問題的傳統做法是使用 服務器端的 includes ,你可以在 HTML 頁面中使用該指令將一個網頁嵌入到另一箇中。 事實上, Django 通過剛纔講述的 {% include %} 支持了這種方法。 但是用 Django 解決此類問題的首選方法是使用更加優雅的策略—— 模板繼承 。

本質上來說,模板繼承就是先構造一個基礎框架模板,而後在其子模板中對它所包含站點公用部分和定義塊進行重載。

讓我們通過修改 current_datetime.html 文件,爲 current_datetime 創建一個更加完整的模板來體會一下這種做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>The current time</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    <p>It is now {{ current_date }}.</p>
 
    <hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

這看起來很棒,但如果我們要爲第三章的 hours_ahead 視圖創建另一個模板會發生什麼事情呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>Future time</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
 
    <hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

很明顯,我們剛纔重複了大量的 HTML 代碼。 想象一下,如果有一個更典型的網站,它有導航條、樣式表,可能還有一些 JavaScript 代碼,事情必將以向每個模板填充各種冗餘的 HTML 而告終。

解決這個問題的服務器端 include 方案是找出兩個模板中的共同部分,將其保存爲不同的模板片段,然後在每個模板中進行 include。 也許你會把模板頭部的一些代碼保存爲 header.html 文件:

1
2
3
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>

你可能會把底部保存到文件 footer.html :

1
2
3
4
  <hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

對基於 include 的策略,頭部和底部的包含很簡單。 麻煩的是中間部分。 在此範例中,每個頁面都有一個<h1>My helpful timestamp site</h1> 標題,但是這個標題不能放在 header.html 中,因爲每個頁面的 <title> 是不同的。 如果我們將 <h1> 包含在頭部,我們就不得不包含 <title> ,但這樣又不允許在每個頁面對它進行定製。 何去何從呢?

Django 的模板繼承系統解決了這些問題。 你可以將其視爲服務器端 include 的逆向思維版本。 你可以對那些不同 的代碼段進行定義,而不是 共同 代碼段。

第一步是定義 基礎模板 , 該框架之後將由 子模板 所繼承。 以下是我們目前所講述範例的基礎模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    {% block content %}{% endblock %}
    {% block footer %}
    <hr>
    <p>Thanks for visiting my site.</p>
    {% endblock %}
</body>
</html>

這個叫做 base.html 的模板定義了一個簡單的 HTML 框架文檔,我們將在本站點的所有頁面中使用。 子模板的作用就是重載、添加或保留那些塊的內容。 (如果你一直按順序學習到這裏,保存這個文件到你的template目錄下,命名爲 base.html .)

我們使用一個以前已經見過的模板標籤: {% block %} 。 所有的 {% block %} 標籤告訴模板引擎,子模板可以重載這些部分。 每個{% block %}標籤所要做的是告訴模板引擎,該模板下的這一塊內容將有可能被子模板覆蓋。

現在我們已經有了一個基本模板,我們可以修改 current_datetime.html 模板來 使用它:

1
2
3
4
5
6
7
{% extends "base.html" %}
 
{% block title %}The current time{% endblock %}
 
{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}

再爲 hours_ahead 視圖創建一個模板,看起來是這樣的:

1
2
3
4
5
6
7
{% extends "base.html" %}
 
{% block title %}Future time{% endblock %}
 
{% block content %}
<p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
{% endblock %}

看起來很漂亮是不是? 每個模板只包含對自己而言 獨一無二 的代碼。 無需多餘的部分。 如果想進行站點級的設計修改,僅需修改 base.html ,所有其它模板會立即反映出所作修改。

以下是其工作方式。 在加載 current_datetime.html 模板時,模板引擎發現了 {% extends %} 標籤, 注意到該模板是一個子模板。 模板引擎立即裝載其父模板,即本例中的 base.html 。

此時,模板引擎注意到 base.html 中的三個 {% block %} 標籤,並用子模板的內容替換這些 block 。因此,引擎將會使用我們在 block title %} 中定義的標題,對 {% block content %} 也是如此。 所以,網頁標題一塊將由{% block title %}替換,同樣地,網頁的內容一塊將由 {% block content %}替換。

注意由於子模板並沒有定義 footer 塊,模板系統將使用在父模板中定義的值。 父模板 {% block %} 標籤中的內容總是被當作一條退路。

繼承並不會影響到模板的上下文。 換句話說,任何處在繼承樹上的模板都可以訪問到你傳到模板中的每一個模板變量。

你可以根據需要使用任意多的繼承次數。 使用繼承的一種常見方式是下面的三層法:

  1. 創建 base.html 模板,在其中定義站點的主要外觀感受。 這些都是不常修改甚至從不修改的部分。

  1. 爲網站的每個區域創建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。這些模板對base.html 進行拓展,幷包含區域特定的風格與設計。

  1. 爲每種類型的頁面創建獨立的模板,例如論壇頁面或者圖片庫。 這些模板拓展相應的區域模板。

這個方法可最大限度地重用代碼,並使得向公共區域(如區域級的導航)添加內容成爲一件輕鬆的工作。

以下是使用模板繼承的一些訣竅:

  • 如果在模板中使用 {% extends %} ,必須保證其爲模板中的第一個模板標記。 否則,模板繼承將不起作用。

  • 一般來說,基礎模板中的 {% block %} 標籤越多越好。 記住,子模板不必定義父模板中所有的代碼塊,因此你可以用合理的缺省值對一些代碼塊進行填充,然後只對子模板所需的代碼塊進行(重)定義。 俗話說,鉤子越多越好。

  • 如果發覺自己在多個模板之間拷貝代碼,你應該考慮將該代碼段放置到父模板的某個 {% block %} 中。

  • 如果你需要訪問父模板中的塊的內容,使用 {{ block.super }}這個標籤吧,這一個魔法變量將會表現出父模板中的內容。 如果只想在上級代碼塊基礎上添加內容,而不是全部重載,該變量就顯得非常有用了。

  • 不允許在同一個模板中定義多個同名的 {% block %} 。 存在這樣的限制是因爲block 標籤的工作方式是雙向的。 也就是說,block 標籤不僅挖了一個要填的坑,也定義了在模板中這個坑所填充的內容。如果模板中出現了兩個相同名稱的 {% block %} 標籤,父模板將無從得知要使用哪個塊的內容。

Django Form表單

django中的Form一般有兩種功能:

  • 輸入html
  • 驗證用戶輸入
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import re
    from django import forms
    from django.core.exceptions import ValidationError
    
    
    def mobile_validate(value):
        mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
        if not mobile_re.match(value):
            raise ValidationError('手機號碼格式錯誤')
    
    
    class PublishForm(forms.Form):
    
        user_type_choice = (
            (0, u'普通用戶'),
            (1, u'高級用戶'),
        )
    
        user_type = forms.IntegerField(widget=forms.widgets.Select(choices=user_type_choice,
                                                                      attrs={'class': "form-control"}))
    
        title = forms.CharField(max_length=20,
                                min_length=5,
                                error_messages={'required': u'標題不能爲空',
                                                'min_length': u'標題最少爲5個字符',
                                                'max_length': u'標題最多爲20個字符'},
                                widget=forms.TextInput(attrs={'class': "form-control",
                                                              'placeholder': u'標題5-20個字符'}))
    
        memo = forms.CharField(required=False,
                               max_length=256,
                               widget=forms.widgets.Textarea(attrs={'class': "form-control no-radius", 'placeholder': u'詳細描述', 'rows': 3}))
    
        phone = forms.CharField(validators=[mobile_validate, ],
                                error_messages={'required': u'手機不能爲空'},
                                widget=forms.TextInput(attrs={'class': "form-control",
                                                              'placeholder': u'手機號碼'}))
    
        email = forms.EmailField(required=False,
                                error_messages={'required': u'郵箱不能爲空','invalid': u'郵箱格式錯誤'},
                                widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': u'郵箱'}))
    
    '''
    def __init__(self, *args, **kwargs):
        super(SampleImportForm, self).__init__(*args, **kwargs)
    
        self.fields['idc'].widget.choices = models.IDC.objects.all().order_by('id').values_list('id','display')
        self.fields['business_unit'].widget.choices = models.BusinessUnit.objects.all().order_by('id').values_list('id','name')
    
    Forms
    '''
    先寫好一個form
    def test_form_view(request):
        if request.method == 'POST':
            request_form = PublishForm(request.POST)
            if request_form.is_valid():
                request_dict = request_form.clean()
                print(request_dict)
            return render(request,'test.html', {'pub_form':request_form})
        else:
            pub_form = PublishForm()
            return render(request,'test.html',{'pub_form':pub_form})
    寫好視圖
    <div>
        <form method="post" action="{% url 'test_form' %}">{% csrf_token %}
    
            <div>{{ pub_form.user_type }} {{ pub_form.errors.title }}</div>
            <div>{{ pub_form.title }}</div>
            <div>{{ pub_form.email }}</div>
            <div>{{ pub_form.phone }}</div>
            <div>{{ pub_form.memo }}</div>
    
    
            {% if pub_form.errors %}
                {{ pub_form.errors }}
            {% endif %}
            <input type="submit" value="提交">
        </form>
    </div>
    模版文件

     

 

擴展:ModelForm

在使用Model和Form時,都需要對字段進行定義並指定類型,通過ModelForm則可以省去From中字段的定義

1
2
3
4
5
6
7
8
9
10
class AdminModelForm(forms.ModelForm):
       
    class Meta:
        model = models.Admin
        #fields = '__all__'
        fields = ('username''email')
           
        widgets = {
            'email' : forms.PasswordInput(attrs={'class':"alex"}),
        }

  

Django Admin

django amdin是django提供的一個後臺管理頁面,改管理頁面提供完善的html和css,使得你在通過Model創建完數據庫表之後,就可以對數據進行增刪改查,而使用django admin 則需要以下步驟:

  • 創建後臺管理員
  • 配置url
  • 註冊和配置django admin後臺管理頁面

1、創建後臺管理員

1
python manage.py createsuperuser

2、配置後臺管理url

1
url(r'^admin/', include(admin.site.urls))

3、註冊和配置django admin 後臺管理頁面

a、在admin中執行如下配置

1
2
3
4
5
6
7
8
from django.contrib import admin
   
from app01 import  models
   
admin.site.register(models.UserType)
admin.site.register(models.UserInfo)
admin.site.register(models.UserGroup)
admin.site.register(models.Asset)

b、設置數據表名稱

1
2
3
4
5
6
class UserType(models.Model):
    name = models.CharField(max_length=50)
   
    class Meta:
        verbose_name = '用戶類型'
        verbose_name_plural = '用戶類型'

c、自定義頁面展示

1
2
3
4
5
6
7
class UserInfoAdmin(admin.ModelAdmin):
    list_display = ('username''password''email')
   
   
admin.site.register(models.UserType)
admin.site.register(models.UserInfo,UserInfoAdmin)
admin.site.register(models.UserGroup)

d、添加頁面搜索過濾等功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.contrib import admin
   
from app01 import  models
   
class UserInfoAdmin(admin.ModelAdmin):
    list_display = ('username''password''email')
    search_fields = ('username''email')
    list_filter = ('username''email')
       
   
   
admin.site.register(models.UserType)
admin.site.register(models.UserInfo,UserInfoAdmin)
admin.site.register(models.UserGroup)
admin.site.register(models.Asset)

  

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章