mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
unicode: Merged changes from trunk up to [5182].
git-svn-id: http://code.djangoproject.com/svn/django/branches/unicode@5185 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
ce4722a52a
commit
a7a756e27e
6
AUTHORS
6
AUTHORS
@ -43,12 +43,14 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
|
|
||||||
adurdin@gmail.com
|
adurdin@gmail.com
|
||||||
alang@bright-green.com
|
alang@bright-green.com
|
||||||
|
Marty Alchin <gulopine@gamemusic.org>
|
||||||
Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com>
|
Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com>
|
||||||
Andreas
|
Andreas
|
||||||
andy@jadedplanet.net
|
andy@jadedplanet.net
|
||||||
Fabrice Aneche <akh@nobugware.com>
|
Fabrice Aneche <akh@nobugware.com>
|
||||||
ant9000@netwise.it
|
ant9000@netwise.it
|
||||||
David Ascher <http://ascher.ca/>
|
David Ascher <http://ascher.ca/>
|
||||||
|
david@kazserve.org
|
||||||
Arthur <avandorp@gmail.com>
|
Arthur <avandorp@gmail.com>
|
||||||
axiak@mit.edu
|
axiak@mit.edu
|
||||||
Jiri Barton
|
Jiri Barton
|
||||||
@ -68,6 +70,8 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Amit Chakradeo <http://amit.chakradeo.net/>
|
Amit Chakradeo <http://amit.chakradeo.net/>
|
||||||
ChaosKCW
|
ChaosKCW
|
||||||
ivan.chelubeev@gmail.com
|
ivan.chelubeev@gmail.com
|
||||||
|
Bryan Chow <bryan at verdjn dot com>
|
||||||
|
Michal Chruszcz <troll@pld-linux.org>
|
||||||
Ian Clelland <clelland@gmail.com>
|
Ian Clelland <clelland@gmail.com>
|
||||||
crankycoder@gmail.com
|
crankycoder@gmail.com
|
||||||
Matt Croydon <http://www.postneo.com/>
|
Matt Croydon <http://www.postneo.com/>
|
||||||
@ -145,7 +149,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
lerouxb@gmail.com
|
lerouxb@gmail.com
|
||||||
Waylan Limberg <waylan@gmail.com>
|
Waylan Limberg <waylan@gmail.com>
|
||||||
limodou
|
limodou
|
||||||
mattmcc
|
Matt McClanahan <http://mmcc.cx/>
|
||||||
Martin Maney <http://www.chipy.org/Martin_Maney>
|
Martin Maney <http://www.chipy.org/Martin_Maney>
|
||||||
masonsimon+django@gmail.com
|
masonsimon+django@gmail.com
|
||||||
Manuzhai
|
Manuzhai
|
||||||
|
Binary file not shown.
@ -367,11 +367,11 @@ msgstr "Abmelden"
|
|||||||
|
|
||||||
#: contrib/admin/templates/admin/base_site.html:4
|
#: contrib/admin/templates/admin/base_site.html:4
|
||||||
msgid "Django site admin"
|
msgid "Django site admin"
|
||||||
msgstr "Django Systemverwaltung"
|
msgstr "Django-Systemverwaltung"
|
||||||
|
|
||||||
#: contrib/admin/templates/admin/base_site.html:7
|
#: contrib/admin/templates/admin/base_site.html:7
|
||||||
msgid "Django administration"
|
msgid "Django administration"
|
||||||
msgstr "Django Verwaltung"
|
msgstr "Django-Verwaltung"
|
||||||
|
|
||||||
#: contrib/admin/templates/admin/change_form.html:15
|
#: contrib/admin/templates/admin/change_form.html:15
|
||||||
#: contrib/admin/templates/admin/index.html:28
|
#: contrib/admin/templates/admin/index.html:28
|
||||||
@ -385,7 +385,7 @@ msgstr "Geschichte"
|
|||||||
|
|
||||||
#: contrib/admin/templates/admin/change_form.html:22
|
#: contrib/admin/templates/admin/change_form.html:22
|
||||||
msgid "View on site"
|
msgid "View on site"
|
||||||
msgstr "Im Web Anzeigen"
|
msgstr "Im Web anzeigen"
|
||||||
|
|
||||||
#: contrib/admin/templates/admin/change_form.html:32
|
#: contrib/admin/templates/admin/change_form.html:32
|
||||||
#: contrib/admin/templates/admin/auth/user/change_password.html:24
|
#: contrib/admin/templates/admin/auth/user/change_password.html:24
|
||||||
@ -614,7 +614,7 @@ msgid ""
|
|||||||
"your computer is \"internal\").</p>\n"
|
"your computer is \"internal\").</p>\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"\n"
|
"\n"
|
||||||
"<p class=\"help\">Um Bookmarklets zu installieren müssen diese Links in die\n"
|
"<p class=\"help\">Um Bookmarklets zu installieren, müssen diese Links in die\n"
|
||||||
"Browser-Werkzeugleiste gezogen werden, oder mittels rechter Maustaste in "
|
"Browser-Werkzeugleiste gezogen werden, oder mittels rechter Maustaste in "
|
||||||
"die\n"
|
"die\n"
|
||||||
"Bookmarks gespeichert werden. Danach können die Bookmarklets von jeder "
|
"Bookmarks gespeichert werden. Danach können die Bookmarklets von jeder "
|
||||||
@ -998,7 +998,7 @@ msgstr "%s ist scheinbar kein urlpattern Objekt"
|
|||||||
|
|
||||||
#: contrib/admin/views/main.py:223
|
#: contrib/admin/views/main.py:223
|
||||||
msgid "Site administration"
|
msgid "Site administration"
|
||||||
msgstr "Website Verwaltung"
|
msgstr "Website-Verwaltung"
|
||||||
|
|
||||||
#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356
|
#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356
|
||||||
#, python-format
|
#, python-format
|
||||||
@ -1023,7 +1023,7 @@ msgstr "und"
|
|||||||
#: contrib/admin/views/main.py:337
|
#: contrib/admin/views/main.py:337
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Changed %s."
|
msgid "Changed %s."
|
||||||
msgstr "%s geändert"
|
msgstr "%s geändert."
|
||||||
|
|
||||||
#: contrib/admin/views/main.py:339
|
#: contrib/admin/views/main.py:339
|
||||||
#, python-format
|
#, python-format
|
||||||
@ -1490,8 +1490,8 @@ msgstr "Ihr Name:"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"This rating is required because you've entered at least one other rating."
|
"This rating is required because you've entered at least one other rating."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Diese Abstimmung ist zwingend erforderlich, da Du an mindestens einer "
|
"Diese Abstimmung ist zwingend erforderlich, da Sie an mindestens einer "
|
||||||
"weiteren Abstimmung teilnimmst."
|
"weiteren Abstimmung teilnehmen."
|
||||||
|
|
||||||
#: contrib/comments/views/comments.py:111
|
#: contrib/comments/views/comments.py:111
|
||||||
#, python-format
|
#, python-format
|
||||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -8,26 +8,14 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: djangojs 1.0\n"
|
"Project-Id-Version: djangojs 1.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2006-03-30 13:28+0200\n"
|
"POT-Creation-Date: 2007-05-06 13:08+0300\n"
|
||||||
"PO-Revision-Date: 2006-03-30 13:35+0200\n"
|
"PO-Revision-Date: 2007-05-06 13:08+0300\n"
|
||||||
"Last-Translator: Meir Kriheli <meir@mksoft.co.il>\n"
|
"Last-Translator: Meir Kriheli <meir@mksoft.co.il>\n"
|
||||||
"Language-Team: Hebrew\n"
|
"Language-Team: Hebrew\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit"
|
"Content-Transfer-Encoding: 8bit"
|
||||||
|
|
||||||
#: contrib/admin/media/js/dateparse.js:32
|
|
||||||
#: contrib/admin/media/js/calendar.js:24
|
|
||||||
msgid ""
|
|
||||||
"January February March April May June July August September October November "
|
|
||||||
"December"
|
|
||||||
msgstr ""
|
|
||||||
"ינואר פברואר מרץ אפריל מאי יוני יולי אוגוסט ספטמבר אוקטובר נובמבר דצמבר"
|
|
||||||
|
|
||||||
#: contrib/admin/media/js/dateparse.js:33
|
|
||||||
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
|
|
||||||
msgstr "ראשון שני שלישי רביעי חמישי שישי שבת"
|
|
||||||
|
|
||||||
#: contrib/admin/media/js/SelectFilter2.js:33
|
#: contrib/admin/media/js/SelectFilter2.js:33
|
||||||
#, perl-format
|
#, perl-format
|
||||||
msgid "Available %s"
|
msgid "Available %s"
|
||||||
@ -58,54 +46,75 @@ msgstr "יש לסמן את ההרשאות המבוקשות וללחוץ על "
|
|||||||
msgid "Clear all"
|
msgid "Clear all"
|
||||||
msgstr "איפוס הכל"
|
msgstr "איפוס הכל"
|
||||||
|
|
||||||
|
#: contrib/admin/media/js/calendar.js:24
|
||||||
|
#: contrib/admin/media/js/dateparse.js:32
|
||||||
|
msgid ""
|
||||||
|
"January February March April May June July August September October November "
|
||||||
|
"December"
|
||||||
|
msgstr ""
|
||||||
|
"ינואר פברואר מרץ אפריל מאי יוני יולי אוגוסט ספטמבר אוקטובר נובמבר דצמבר"
|
||||||
|
|
||||||
#: contrib/admin/media/js/calendar.js:25
|
#: contrib/admin/media/js/calendar.js:25
|
||||||
msgid "S M T W T F S"
|
msgid "S M T W T F S"
|
||||||
msgstr "ר ש ש ר ח ש ש"
|
msgstr "ר ש ש ר ח ש ש"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45
|
#: contrib/admin/media/js/dateparse.js:33
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80
|
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
|
||||||
|
msgstr "ראשון שני שלישי רביעי חמישי שישי שבת"
|
||||||
|
|
||||||
|
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34
|
||||||
|
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72
|
||||||
|
msgid "Show"
|
||||||
|
msgstr "הצג"
|
||||||
|
|
||||||
|
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63
|
||||||
|
msgid "Hide"
|
||||||
|
msgstr "הסתר"
|
||||||
|
|
||||||
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47
|
||||||
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
|
||||||
msgid "Now"
|
msgid "Now"
|
||||||
msgstr "כעת"
|
msgstr "כעת"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51
|
||||||
msgid "Clock"
|
msgid "Clock"
|
||||||
msgstr "שעון"
|
msgstr "שעון"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78
|
||||||
msgid "Choose a time"
|
msgid "Choose a time"
|
||||||
msgstr "בחירת שעה"
|
msgstr "בחירת שעה"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
|
||||||
msgid "Midnight"
|
msgid "Midnight"
|
||||||
msgstr "חצות"
|
msgstr "חצות"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
|
||||||
msgid "6 a.m."
|
msgid "6 a.m."
|
||||||
msgstr "6 בבוקר"
|
msgstr "6 בבוקר"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
|
||||||
msgid "Noon"
|
msgid "Noon"
|
||||||
msgstr "צהריים"
|
msgstr "צהריים"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "ביטול"
|
msgstr "ביטול"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177
|
||||||
msgid "Today"
|
msgid "Today"
|
||||||
msgstr "היום"
|
msgstr "היום"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132
|
||||||
msgid "Calendar"
|
msgid "Calendar"
|
||||||
msgstr "לוח שנה"
|
msgstr "לוח שנה"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175
|
||||||
msgid "Yesterday"
|
msgid "Yesterday"
|
||||||
msgstr "אתמול"
|
msgstr "אתמול"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
|
||||||
msgid "Tomorrow"
|
msgid "Tomorrow"
|
||||||
msgstr "מחר"
|
msgstr "מחר"
|
||||||
|
|
||||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -10,7 +10,7 @@ msgstr ""
|
|||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2007-02-15 10:46+1100\n"
|
"POT-Creation-Date: 2007-02-15 10:46+1100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: Gatis Tomsons <gatis.tomsons@gmail.com>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
@ -19,64 +19,65 @@ msgstr ""
|
|||||||
#: contrib/admin/media/js/SelectFilter2.js:33
|
#: contrib/admin/media/js/SelectFilter2.js:33
|
||||||
#, perl-format
|
#, perl-format
|
||||||
msgid "Available %s"
|
msgid "Available %s"
|
||||||
msgstr ""
|
msgstr "Pieejams %s"
|
||||||
|
|
||||||
#: contrib/admin/media/js/SelectFilter2.js:41
|
#: contrib/admin/media/js/SelectFilter2.js:41
|
||||||
msgid "Choose all"
|
msgid "Choose all"
|
||||||
msgstr ""
|
msgstr "Izvēlēties visu"
|
||||||
|
|
||||||
#: contrib/admin/media/js/SelectFilter2.js:46
|
#: contrib/admin/media/js/SelectFilter2.js:46
|
||||||
msgid "Add"
|
msgid "Add"
|
||||||
msgstr ""
|
msgstr "Pievienot"
|
||||||
|
|
||||||
#: contrib/admin/media/js/SelectFilter2.js:48
|
#: contrib/admin/media/js/SelectFilter2.js:48
|
||||||
msgid "Remove"
|
msgid "Remove"
|
||||||
msgstr ""
|
msgstr "Izņemt"
|
||||||
|
|
||||||
#: contrib/admin/media/js/SelectFilter2.js:53
|
#: contrib/admin/media/js/SelectFilter2.js:53
|
||||||
#, perl-format
|
#, perl-format
|
||||||
msgid "Chosen %s"
|
msgid "Chosen %s"
|
||||||
msgstr ""
|
msgstr "Izvēlies %s"
|
||||||
|
|
||||||
#: contrib/admin/media/js/SelectFilter2.js:54
|
#: contrib/admin/media/js/SelectFilter2.js:54
|
||||||
msgid "Select your choice(s) and click "
|
msgid "Select your choice(s) and click "
|
||||||
msgstr ""
|
msgstr "Izvēlies un klikšķini"
|
||||||
|
|
||||||
#: contrib/admin/media/js/SelectFilter2.js:59
|
#: contrib/admin/media/js/SelectFilter2.js:59
|
||||||
msgid "Clear all"
|
msgid "Clear all"
|
||||||
msgstr ""
|
msgstr "Attīrīt visu"
|
||||||
|
|
||||||
#: contrib/admin/media/js/dateparse.js:32
|
#: contrib/admin/media/js/dateparse.js:32
|
||||||
#: contrib/admin/media/js/calendar.js:24
|
#: contrib/admin/media/js/calendar.js:24
|
||||||
msgid ""
|
msgid ""
|
||||||
"January February March April May June July August September October November "
|
"January February March April May June July August September October November "
|
||||||
"December"
|
"December"
|
||||||
msgstr ""
|
msgstr "Janvāris Februāris Marts Aprīlis Maijs Jūnijs Jūlijs Augusts Septembris Oktobris Novembris"
|
||||||
|
"Decembris"
|
||||||
|
|
||||||
#: contrib/admin/media/js/dateparse.js:33
|
#: contrib/admin/media/js/dateparse.js:33
|
||||||
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
|
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
|
||||||
msgstr ""
|
msgstr "Svētdiena Pirmdiena Otrdiena Trešdiena Ceturtdiena Piektdiena Sestdiena"
|
||||||
|
|
||||||
#: contrib/admin/media/js/calendar.js:25
|
#: contrib/admin/media/js/calendar.js:25
|
||||||
msgid "S M T W T F S"
|
msgid "S M T W T F S"
|
||||||
msgstr ""
|
msgstr "S M T W T F S"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
|
||||||
msgid "Now"
|
msgid "Now"
|
||||||
msgstr ""
|
msgstr "Tagad"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51
|
||||||
msgid "Clock"
|
msgid "Clock"
|
||||||
msgstr ""
|
msgstr "Pulkstens"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78
|
||||||
msgid "Choose a time"
|
msgid "Choose a time"
|
||||||
msgstr ""
|
msgstr "Izvēlieties laiku"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
|
||||||
msgid "Midnight"
|
msgid "Midnight"
|
||||||
msgstr ""
|
msgstr "Pusnakts"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
|
||||||
msgid "6 a.m."
|
msgid "6 a.m."
|
||||||
@ -84,35 +85,35 @@ msgstr ""
|
|||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
|
||||||
msgid "Noon"
|
msgid "Noon"
|
||||||
msgstr ""
|
msgstr "Pusdienas laiks"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr ""
|
msgstr "Atcelt"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177
|
||||||
msgid "Today"
|
msgid "Today"
|
||||||
msgstr ""
|
msgstr "Šodien"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132
|
||||||
msgid "Calendar"
|
msgid "Calendar"
|
||||||
msgstr ""
|
msgstr "Kalendārs"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175
|
||||||
msgid "Yesterday"
|
msgid "Yesterday"
|
||||||
msgstr ""
|
msgstr "Vakar"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
|
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
|
||||||
msgid "Tomorrow"
|
msgid "Tomorrow"
|
||||||
msgstr ""
|
msgstr "Rīt"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34
|
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34
|
||||||
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72
|
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72
|
||||||
msgid "Show"
|
msgid "Show"
|
||||||
msgstr ""
|
msgstr "Parādīt"
|
||||||
|
|
||||||
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63
|
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63
|
||||||
msgid "Hide"
|
msgid "Hide"
|
||||||
msgstr ""
|
msgstr "Slēpt"
|
||||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -11,9 +11,12 @@ class AdminLogNode(template.Node):
|
|||||||
return "<GetAdminLog Node>"
|
return "<GetAdminLog Node>"
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
if self.user is not None and not self.user.isdigit():
|
if self.user is None:
|
||||||
self.user = context[self.user].id
|
context[self.varname] = LogEntry.objects.all().select_related()[:self.limit]
|
||||||
context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
|
else:
|
||||||
|
if not self.user.isdigit():
|
||||||
|
self.user = context[self.user].id
|
||||||
|
context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
class DoGetAdminLog:
|
class DoGetAdminLog:
|
||||||
|
3
django/core/cache/backends/base.py
vendored
3
django/core/cache/backends/base.py
vendored
@ -54,3 +54,6 @@ class BaseCache(object):
|
|||||||
Returns True if the key is in the cache and has not expired.
|
Returns True if the key is in the cache and has not expired.
|
||||||
"""
|
"""
|
||||||
return self.get(key) is not None
|
return self.get(key) is not None
|
||||||
|
|
||||||
|
__contains__ = has_key
|
||||||
|
|
||||||
|
@ -241,14 +241,14 @@ def _get_sql_for_pending_references(model, pending_references):
|
|||||||
|
|
||||||
def _get_many_to_many_sql_for_model(model):
|
def _get_many_to_many_sql_for_model(model):
|
||||||
from django.db import backend, get_creation_module
|
from django.db import backend, get_creation_module
|
||||||
from django.db.models import GenericRel
|
from django.contrib.contenttypes import generic
|
||||||
|
|
||||||
data_types = get_creation_module().DATA_TYPES
|
data_types = get_creation_module().DATA_TYPES
|
||||||
|
|
||||||
opts = model._meta
|
opts = model._meta
|
||||||
final_output = []
|
final_output = []
|
||||||
for f in opts.many_to_many:
|
for f in opts.many_to_many:
|
||||||
if not isinstance(f.rel, GenericRel):
|
if not isinstance(f.rel, generic.GenericRel):
|
||||||
table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
|
table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
|
||||||
style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' (']
|
style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' (']
|
||||||
table_output.append(' %s %s %s,' % \
|
table_output.append(' %s %s %s,' % \
|
||||||
|
@ -8,7 +8,6 @@ from django.db.models.manager import Manager
|
|||||||
from django.db.models.base import Model, AdminOptions
|
from django.db.models.base import Model, AdminOptions
|
||||||
from django.db.models.fields import *
|
from django.db.models.fields import *
|
||||||
from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED
|
from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED
|
||||||
from django.db.models.fields.generic import GenericRelation, GenericRel, GenericForeignKey
|
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
|
@ -42,11 +42,11 @@ class ModelBase(type):
|
|||||||
new_class._meta.parents.append(base)
|
new_class._meta.parents.append(base)
|
||||||
new_class._meta.parents.extend(base._meta.parents)
|
new_class._meta.parents.extend(base._meta.parents)
|
||||||
|
|
||||||
model_module = sys.modules[new_class.__module__]
|
|
||||||
|
|
||||||
if getattr(new_class._meta, 'app_label', None) is None:
|
if getattr(new_class._meta, 'app_label', None) is None:
|
||||||
# Figure out the app_label by looking one level up.
|
# Figure out the app_label by looking one level up.
|
||||||
# For 'django.contrib.sites.models', this would be 'sites'.
|
# For 'django.contrib.sites.models', this would be 'sites'.
|
||||||
|
model_module = sys.modules[new_class.__module__]
|
||||||
new_class._meta.app_label = model_module.__name__.split('.')[-2]
|
new_class._meta.app_label = model_module.__name__.split('.')[-2]
|
||||||
|
|
||||||
# Bail out early if we have already created this class.
|
# Bail out early if we have already created this class.
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from django.db import backend, connection, transaction
|
from django.db import backend, connection, transaction
|
||||||
from django.db.models.fields import DateField, FieldDoesNotExist
|
from django.db.models.fields import DateField, FieldDoesNotExist
|
||||||
from django.db.models.fields.generic import GenericRelation
|
from django.db.models import signals, loading
|
||||||
from django.db.models import signals
|
|
||||||
from django.dispatch import dispatcher
|
from django.dispatch import dispatcher
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
|
from django.contrib.contenttypes import generic
|
||||||
import operator
|
import operator
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -948,7 +948,7 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
|
|||||||
field_choices(current_opts.get_all_related_many_to_many_objects(), True) + \
|
field_choices(current_opts.get_all_related_many_to_many_objects(), True) + \
|
||||||
field_choices(current_opts.get_all_related_objects(), True) + \
|
field_choices(current_opts.get_all_related_objects(), True) + \
|
||||||
field_choices(current_opts.fields, False)
|
field_choices(current_opts.fields, False)
|
||||||
raise TypeError, "Cannot resolve keyword '%s' into field, choices are: %s" % (name, ", ".join(choices))
|
raise TypeError, "Cannot resolve keyword '%s' into field. Choices are: %s" % (name, ", ".join(choices))
|
||||||
|
|
||||||
# Check whether an intermediate join is required between current_table
|
# Check whether an intermediate join is required between current_table
|
||||||
# and new_table.
|
# and new_table.
|
||||||
@ -1041,7 +1041,7 @@ def delete_objects(seen_objs):
|
|||||||
|
|
||||||
pk_list = [pk for pk,instance in seen_objs[cls]]
|
pk_list = [pk for pk,instance in seen_objs[cls]]
|
||||||
for related in cls._meta.get_all_related_many_to_many_objects():
|
for related in cls._meta.get_all_related_many_to_many_objects():
|
||||||
if not isinstance(related.field, GenericRelation):
|
if not isinstance(related.field, generic.GenericRelation):
|
||||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||||
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
|
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
|
||||||
(qn(related.field.m2m_db_table()),
|
(qn(related.field.m2m_db_table()),
|
||||||
@ -1049,7 +1049,7 @@ def delete_objects(seen_objs):
|
|||||||
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
|
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
|
||||||
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
|
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
|
||||||
for f in cls._meta.many_to_many:
|
for f in cls._meta.many_to_many:
|
||||||
if isinstance(f, GenericRelation):
|
if isinstance(f, generic.GenericRelation):
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column
|
query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column
|
||||||
args_extra = [ContentType.objects.get_for_model(cls).id]
|
args_extra = [ContentType.objects.get_for_model(cls).id]
|
||||||
|
@ -100,6 +100,10 @@ libraries = {}
|
|||||||
# global list of libraries to load by default for a new parser
|
# global list of libraries to load by default for a new parser
|
||||||
builtins = []
|
builtins = []
|
||||||
|
|
||||||
|
# True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means
|
||||||
|
# uninitialised.
|
||||||
|
invalid_var_format_string = None
|
||||||
|
|
||||||
class TemplateSyntaxError(Exception):
|
class TemplateSyntaxError(Exception):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
try:
|
try:
|
||||||
@ -583,6 +587,11 @@ class FilterExpression(object):
|
|||||||
obj = None
|
obj = None
|
||||||
else:
|
else:
|
||||||
if settings.TEMPLATE_STRING_IF_INVALID:
|
if settings.TEMPLATE_STRING_IF_INVALID:
|
||||||
|
global invalid_var_format_string
|
||||||
|
if invalid_var_format_string is None:
|
||||||
|
invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
|
||||||
|
if invalid_var_format_string:
|
||||||
|
return settings.TEMPLATE_STRING_IF_INVALID % self.var
|
||||||
return settings.TEMPLATE_STRING_IF_INVALID
|
return settings.TEMPLATE_STRING_IF_INVALID
|
||||||
else:
|
else:
|
||||||
obj = settings.TEMPLATE_STRING_IF_INVALID
|
obj = settings.TEMPLATE_STRING_IF_INVALID
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import authenticate, login
|
||||||
|
from django.contrib.sessions.models import Session
|
||||||
|
from django.contrib.sessions.middleware import SessionWrapper
|
||||||
from django.core.handlers.base import BaseHandler
|
from django.core.handlers.base import BaseHandler
|
||||||
from django.core.handlers.wsgi import WSGIRequest
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
from django.core.signals import got_request_exception
|
from django.core.signals import got_request_exception
|
||||||
from django.dispatch import dispatcher
|
from django.dispatch import dispatcher
|
||||||
from django.http import urlencode, SimpleCookie
|
from django.http import urlencode, SimpleCookie, HttpRequest
|
||||||
from django.test import signals
|
from django.test import signals
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
|
|
||||||
@ -113,7 +117,6 @@ class Client:
|
|||||||
self.handler = ClientHandler()
|
self.handler = ClientHandler()
|
||||||
self.defaults = defaults
|
self.defaults = defaults
|
||||||
self.cookies = SimpleCookie()
|
self.cookies = SimpleCookie()
|
||||||
self.session = {}
|
|
||||||
self.exc_info = None
|
self.exc_info = None
|
||||||
|
|
||||||
def store_exc_info(self, *args, **kwargs):
|
def store_exc_info(self, *args, **kwargs):
|
||||||
@ -123,6 +126,15 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
self.exc_info = sys.exc_info()
|
self.exc_info = sys.exc_info()
|
||||||
|
|
||||||
|
def _session(self):
|
||||||
|
"Obtain the current session variables"
|
||||||
|
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
||||||
|
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
|
||||||
|
if cookie:
|
||||||
|
return SessionWrapper(cookie.value)
|
||||||
|
return {}
|
||||||
|
session = property(_session)
|
||||||
|
|
||||||
def request(self, **request):
|
def request(self, **request):
|
||||||
"""
|
"""
|
||||||
The master request method. Composes the environment dictionary
|
The master request method. Composes the environment dictionary
|
||||||
@ -171,16 +183,10 @@ class Client:
|
|||||||
if self.exc_info:
|
if self.exc_info:
|
||||||
raise self.exc_info[1], None, self.exc_info[2]
|
raise self.exc_info[1], None, self.exc_info[2]
|
||||||
|
|
||||||
# Update persistent cookie and session data
|
# Update persistent cookie data
|
||||||
if response.cookies:
|
if response.cookies:
|
||||||
self.cookies.update(response.cookies)
|
self.cookies.update(response.cookies)
|
||||||
|
|
||||||
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
|
||||||
from django.contrib.sessions.middleware import SessionWrapper
|
|
||||||
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
|
|
||||||
if cookie:
|
|
||||||
self.session = SessionWrapper(cookie.value)
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get(self, path, data={}, **extra):
|
def get(self, path, data={}, **extra):
|
||||||
@ -215,42 +221,34 @@ class Client:
|
|||||||
|
|
||||||
return self.request(**r)
|
return self.request(**r)
|
||||||
|
|
||||||
def login(self, path, username, password, **extra):
|
def login(self, **credentials):
|
||||||
|
"""Set the Client to appear as if it has sucessfully logged into a site.
|
||||||
|
|
||||||
|
Returns True if login is possible; False if the provided credentials
|
||||||
|
are incorrect, or if the Sessions framework is not available.
|
||||||
"""
|
"""
|
||||||
A specialized sequence of GET and POST to log into a view that
|
user = authenticate(**credentials)
|
||||||
is protected by a @login_required access decorator.
|
if user and 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
||||||
|
obj = Session.objects.get_new_session_object()
|
||||||
|
|
||||||
path should be the URL of the page that is login protected.
|
# Create a fake request to store login details
|
||||||
|
request = HttpRequest()
|
||||||
|
request.session = SessionWrapper(obj.session_key)
|
||||||
|
login(request, user)
|
||||||
|
|
||||||
Returns the response from GETting the requested URL after
|
# Set the cookie to represent the session
|
||||||
login is complete. Returns False if login process failed.
|
self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key
|
||||||
"""
|
self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
|
||||||
# First, GET the page that is login protected.
|
self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
|
||||||
# This page will redirect to the login page.
|
self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
|
||||||
response = self.get(path)
|
self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
|
||||||
if response.status_code != 302:
|
self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
|
||||||
|
|
||||||
|
# Set the session values
|
||||||
|
Session.objects.save(obj.session_key, request.session._session,
|
||||||
|
datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
|
||||||
|
|
||||||
|
return True
|
||||||
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
_, _, login_path, _, data, _= urlparse(response['Location'])
|
|
||||||
next = data.split('=')[1]
|
|
||||||
|
|
||||||
# Second, GET the login page; required to set up cookies
|
|
||||||
response = self.get(login_path, **extra)
|
|
||||||
if response.status_code != 200:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Last, POST the login data.
|
|
||||||
form_data = {
|
|
||||||
'username': username,
|
|
||||||
'password': password,
|
|
||||||
'next' : next,
|
|
||||||
}
|
|
||||||
response = self.post(login_path, data=form_data, **extra)
|
|
||||||
|
|
||||||
# Login page should 302 redirect to the originally requested page
|
|
||||||
if (response.status_code != 302 or
|
|
||||||
urlparse(response['Location'])[2] != path):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Since we are logged in, request the actual page again
|
|
||||||
return self.get(path)
|
|
||||||
|
@ -49,9 +49,12 @@ def build_suite(app_module):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# The module exists, so there must be an import error in the
|
# The module exists, so there must be an import error in the
|
||||||
# test module itself. We don't need the module; close the file
|
# test module itself. We don't need the module; so if the
|
||||||
# handle returned by find_module.
|
# module was a single file module (i.e., tests.py), close the file
|
||||||
mod[0].close()
|
# handle returned by find_module. Otherwise, the test module
|
||||||
|
# is a directory, and there is nothing to close.
|
||||||
|
if mod[0]:
|
||||||
|
mod[0].close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return suite
|
return suite
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import re, doctest, unittest
|
import re, doctest, unittest
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.core import management
|
from django.core import management, mail
|
||||||
from django.db.models import get_apps
|
from django.db.models import get_apps
|
||||||
from django.test.client import Client
|
from django.test.client import Client
|
||||||
|
|
||||||
@ -33,48 +33,116 @@ class DocTestRunner(doctest.DocTestRunner):
|
|||||||
transaction.rollback_unless_managed()
|
transaction.rollback_unless_managed()
|
||||||
|
|
||||||
class TestCase(unittest.TestCase):
|
class TestCase(unittest.TestCase):
|
||||||
def install_fixtures(self):
|
def _pre_setup(self):
|
||||||
"""If the Test Case class has a 'fixtures' member, clear the database and
|
"""Perform any pre-test setup. This includes:
|
||||||
install the named fixtures at the start of each test.
|
|
||||||
|
* If the Test Case class has a 'fixtures' member, clearing the
|
||||||
|
database and installing the named fixtures at the start of each test.
|
||||||
|
* Clearing the mail test outbox.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
management.flush(verbosity=0, interactive=False)
|
management.flush(verbosity=0, interactive=False)
|
||||||
if hasattr(self, 'fixtures'):
|
if hasattr(self, 'fixtures'):
|
||||||
management.load_data(self.fixtures, verbosity=0)
|
management.load_data(self.fixtures, verbosity=0)
|
||||||
|
mail.outbox = []
|
||||||
|
|
||||||
def run(self, result=None):
|
def run(self, result=None):
|
||||||
"""Wrapper around default run method so that user-defined Test Cases
|
"""Wrapper around default run method to perform common Django test set up.
|
||||||
automatically call install_fixtures without having to include a call to
|
This means that user-defined Test Cases aren't required to include a call
|
||||||
super().
|
to super().setUp().
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.client = Client()
|
self.client = Client()
|
||||||
self.install_fixtures()
|
self._pre_setup()
|
||||||
super(TestCase, self).run(result)
|
super(TestCase, self).run(result)
|
||||||
|
|
||||||
def assertRedirects(self, response, expected_path):
|
def assertRedirects(self, response, expected_path, status_code=302, target_status_code=200):
|
||||||
"""Assert that a response redirected to a specific URL, and that the
|
"""Assert that a response redirected to a specific URL, and that the
|
||||||
redirect URL can be loaded.
|
redirect URL can be loaded.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.assertEqual(response.status_code, 302,
|
self.assertEqual(response.status_code, status_code,
|
||||||
"Response didn't redirect: Reponse code was %d" % response.status_code)
|
"Response didn't redirect: Reponse code was %d (expected %d)" %
|
||||||
|
(response.status_code, status_code))
|
||||||
scheme, netloc, path, params, query, fragment = urlparse(response['Location'])
|
scheme, netloc, path, params, query, fragment = urlparse(response['Location'])
|
||||||
self.assertEqual(path, expected_path,
|
self.assertEqual(path, expected_path,
|
||||||
"Response redirected to '%s', expected '%s'" % (path, expected_path))
|
"Response redirected to '%s', expected '%s'" % (path, expected_path))
|
||||||
redirect_response = self.client.get(path)
|
redirect_response = self.client.get(path)
|
||||||
self.assertEqual(redirect_response.status_code, 200,
|
self.assertEqual(redirect_response.status_code, target_status_code,
|
||||||
"Couldn't retrieve redirection page '%s'" % path)
|
"Couldn't retrieve redirection page '%s': response code was %d (expected %d)" %
|
||||||
|
(path, response.status_code, status_code))
|
||||||
|
|
||||||
def assertContains(self, response, text, count=1):
|
def assertContains(self, response, text, count=1, status_code=200):
|
||||||
"""Assert that a response indicates that a page was retreived successfully,
|
"""Assert that a response indicates that a page was retreived successfully,
|
||||||
(i.e., the HTTP status code was 200), and that ``text`` occurs ``count``
|
(i.e., the HTTP status code was as expected), and that ``text`` occurs ``count``
|
||||||
times in the content of the response.
|
times in the content of the response.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.assertEqual(response.status_code, 200,
|
self.assertEqual(response.status_code, status_code,
|
||||||
"Couldn't retrieve page'")
|
"Couldn't retrieve page: Response code was %d (expected %d)'" %
|
||||||
|
(response.status_code, status_code))
|
||||||
real_count = response.content.count(text)
|
real_count = response.content.count(text)
|
||||||
self.assertEqual(real_count, count,
|
self.assertEqual(real_count, count,
|
||||||
"Could only find %d of %d instances of '%s' in response" % (real_count, count, text))
|
"Found %d instances of '%s' in response (expected %d)" % (real_count, text, count))
|
||||||
|
|
||||||
|
def assertFormError(self, response, form, field, errors):
|
||||||
|
"Assert that a form used to render the response has a specific field error"
|
||||||
|
if not response.context:
|
||||||
|
self.fail('Response did not use any contexts to render the response')
|
||||||
|
|
||||||
|
# If there is a single context, put it into a list to simplify processing
|
||||||
|
if not isinstance(response.context, list):
|
||||||
|
contexts = [response.context]
|
||||||
|
else:
|
||||||
|
contexts = response.context
|
||||||
|
|
||||||
|
# If a single error string is provided, make it a list to simplify processing
|
||||||
|
if not isinstance(errors, list):
|
||||||
|
errors = [errors]
|
||||||
|
|
||||||
|
# Search all contexts for the error.
|
||||||
|
found_form = False
|
||||||
|
for i,context in enumerate(contexts):
|
||||||
|
if form in context:
|
||||||
|
found_form = True
|
||||||
|
for err in errors:
|
||||||
|
if field:
|
||||||
|
if field in context[form].errors:
|
||||||
|
self.assertTrue(err in context[form].errors[field],
|
||||||
|
"The field '%s' on form '%s' in context %d does not contain the error '%s' (actual errors: %s)" %
|
||||||
|
(field, form, i, err, list(context[form].errors[field])))
|
||||||
|
elif field in context[form].fields:
|
||||||
|
self.fail("The field '%s' on form '%s' in context %d contains no errors" %
|
||||||
|
(field, form, i))
|
||||||
|
else:
|
||||||
|
self.fail("The form '%s' in context %d does not contain the field '%s'" % (form, i, field))
|
||||||
|
else:
|
||||||
|
self.assertTrue(err in context[form].non_field_errors(),
|
||||||
|
"The form '%s' in context %d does not contain the non-field error '%s' (actual errors: %s)" %
|
||||||
|
(form, i, err, list(context[form].non_field_errors())))
|
||||||
|
if not found_form:
|
||||||
|
self.fail("The form '%s' was not used to render the response" % form)
|
||||||
|
|
||||||
|
def assertTemplateUsed(self, response, template_name):
|
||||||
|
"Assert that the template with the provided name was used in rendering the response"
|
||||||
|
if isinstance(response.template, list):
|
||||||
|
template_names = [t.name for t in response.template]
|
||||||
|
self.assertTrue(template_name in template_names,
|
||||||
|
u"Template '%s' was not one of the templates used to render the response. Templates used: %s" %
|
||||||
|
(template_name, u', '.join(template_names)))
|
||||||
|
elif response.template:
|
||||||
|
self.assertEqual(template_name, response.template.name,
|
||||||
|
u"Template '%s' was not used to render the response. Actual template was '%s'" %
|
||||||
|
(template_name, response.template.name))
|
||||||
|
else:
|
||||||
|
self.fail('No templates used to render the response')
|
||||||
|
|
||||||
|
def assertTemplateNotUsed(self, response, template_name):
|
||||||
|
"Assert that the template with the provided name was NOT used in rendering the response"
|
||||||
|
if isinstance(response.template, list):
|
||||||
|
self.assertFalse(template_name in [t.name for t in response.template],
|
||||||
|
u"Template '%s' was used unexpectedly in rendering the response" % template_name)
|
||||||
|
elif response.template:
|
||||||
|
self.assertNotEqual(template_name, response.template.name,
|
||||||
|
u"Template '%s' was used unexpectedly in rendering the response" % template_name)
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
import sys, time
|
import sys, time
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection, transaction, backend
|
from django.db import connection, transaction, backend
|
||||||
from django.core import management
|
from django.core import management, mail
|
||||||
from django.dispatch import dispatcher
|
from django.dispatch import dispatcher
|
||||||
from django.test import signals
|
from django.test import signals
|
||||||
from django.template import Template
|
from django.template import Template
|
||||||
@ -18,24 +18,54 @@ def instrumented_test_render(self, context):
|
|||||||
dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
|
dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
|
||||||
return self.nodelist.render(context)
|
return self.nodelist.render(context)
|
||||||
|
|
||||||
|
class TestSMTPConnection(object):
|
||||||
|
"""A substitute SMTP connection for use during test sessions.
|
||||||
|
The test connection stores email messages in a dummy outbox,
|
||||||
|
rather than sending them out on the wire.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
def open(self):
|
||||||
|
"Mock the SMTPConnection open() interface"
|
||||||
|
pass
|
||||||
|
def close(self):
|
||||||
|
"Mock the SMTPConnection close() interface"
|
||||||
|
pass
|
||||||
|
def send_messages(self, messages):
|
||||||
|
"Redirect messages to the dummy outbox"
|
||||||
|
mail.outbox.extend(messages)
|
||||||
|
|
||||||
def setup_test_environment():
|
def setup_test_environment():
|
||||||
"""Perform any global pre-test setup. This involves:
|
"""Perform any global pre-test setup. This involves:
|
||||||
|
|
||||||
- Installing the instrumented test renderer
|
- Installing the instrumented test renderer
|
||||||
|
- Diverting the email sending functions to a test buffer
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Template.original_render = Template.render
|
Template.original_render = Template.render
|
||||||
Template.render = instrumented_test_render
|
Template.render = instrumented_test_render
|
||||||
|
|
||||||
|
mail.original_SMTPConnection = mail.SMTPConnection
|
||||||
|
mail.SMTPConnection = TestSMTPConnection
|
||||||
|
|
||||||
|
mail.outbox = []
|
||||||
|
|
||||||
def teardown_test_environment():
|
def teardown_test_environment():
|
||||||
"""Perform any global post-test teardown. This involves:
|
"""Perform any global post-test teardown. This involves:
|
||||||
|
|
||||||
- Restoring the original test renderer
|
- Restoring the original test renderer
|
||||||
|
- Restoring the email sending functions
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Template.render = Template.original_render
|
Template.render = Template.original_render
|
||||||
del Template.original_render
|
del Template.original_render
|
||||||
|
|
||||||
|
mail.SMTPConnection = mail.original_SMTPConnection
|
||||||
|
del mail.original_SMTPConnection
|
||||||
|
|
||||||
|
del mail.outbox
|
||||||
|
|
||||||
def _set_autocommit(connection):
|
def _set_autocommit(connection):
|
||||||
"Make sure a connection is in autocommit mode."
|
"Make sure a connection is in autocommit mode."
|
||||||
if hasattr(connection.connection, "autocommit"):
|
if hasattr(connection.connection, "autocommit"):
|
||||||
|
@ -396,10 +396,11 @@ To run the tests, ``cd`` to the ``tests/`` directory and type::
|
|||||||
./runtests.py --settings=path.to.django.settings
|
./runtests.py --settings=path.to.django.settings
|
||||||
|
|
||||||
Yes, the unit tests need a settings module, but only for database connection
|
Yes, the unit tests need a settings module, but only for database connection
|
||||||
info -- the ``DATABASE_ENGINE``, ``DATABASE_USER`` and ``DATABASE_PASSWORD``.
|
info -- the ``DATABASE_NAME`` (required, but will be ignored),
|
||||||
You will also need a ``ROOT_URLCONF`` setting (its value is ignored; it just
|
``DATABASE_ENGINE``, ``DATABASE_USER`` and ``DATABASE_PASSWORD`` settings. You
|
||||||
needs to be present) and a ``SITE_ID`` setting (any integer value will do) in
|
will also need a ``ROOT_URLCONF`` setting (its value is ignored; it just needs
|
||||||
order for all the tests to pass.
|
to be present) and a ``SITE_ID`` setting (any integer value will do) in order
|
||||||
|
for all the tests to pass.
|
||||||
|
|
||||||
The unit tests will not touch your existing databases; they create a new
|
The unit tests will not touch your existing databases; they create a new
|
||||||
database, called ``django_test_db``, which is deleted when the tests are
|
database, called ``django_test_db``, which is deleted when the tests are
|
||||||
|
@ -20,14 +20,14 @@ In two lines::
|
|||||||
send_mail('Subject here', 'Here is the message.', 'from@example.com',
|
send_mail('Subject here', 'Here is the message.', 'from@example.com',
|
||||||
['to@example.com'], fail_silently=False)
|
['to@example.com'], fail_silently=False)
|
||||||
|
|
||||||
Mail will be sent using the SMTP host and port specified in the `EMAIL_HOST`_
|
Mail is sent using the SMTP host and port specified in the `EMAIL_HOST`_ and
|
||||||
and `EMAIL_PORT`_ settings. The `EMAIL_HOST_USER`_ and `EMAIL_HOST_PASSWORD`_
|
`EMAIL_PORT`_ settings. The `EMAIL_HOST_USER`_ and `EMAIL_HOST_PASSWORD`_
|
||||||
settings, if set, will be used to authenticate to the SMTP server and the
|
settings, if set, are used to authenticate to the SMTP server, and the
|
||||||
`EMAIL_USE_TLS`_ settings will control whether a secure connection is used.
|
`EMAIL_USE_TLS`_ setting controls whether a secure connection is used.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The character set of email sent with ``django.core.mail`` will be set to
|
The character set of e-mail sent with ``django.core.mail`` will be set to
|
||||||
the value of your `DEFAULT_CHARSET setting`_.
|
the value of your `DEFAULT_CHARSET setting`_.
|
||||||
|
|
||||||
.. _DEFAULT_CHARSET setting: ../settings/#default-charset
|
.. _DEFAULT_CHARSET setting: ../settings/#default-charset
|
||||||
@ -37,7 +37,6 @@ settings, if set, will be used to authenticate to the SMTP server and the
|
|||||||
.. _EMAIL_HOST_PASSWORD: ../settings/#email-host-password
|
.. _EMAIL_HOST_PASSWORD: ../settings/#email-host-password
|
||||||
.. _EMAIL_USE_TLS: ../settings/#email-use-tls
|
.. _EMAIL_USE_TLS: ../settings/#email-use-tls
|
||||||
|
|
||||||
|
|
||||||
send_mail()
|
send_mail()
|
||||||
===========
|
===========
|
||||||
|
|
||||||
@ -193,57 +192,64 @@ The EmailMessage and SMTPConnection classes
|
|||||||
|
|
||||||
Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
|
Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
|
||||||
wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
|
wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
|
||||||
in ``django.mail``. If you ever need to customize the way Django sends email,
|
in ``django.core.mail``. If you ever need to customize the way Django sends
|
||||||
you can subclass these two classes to suit your needs.
|
e-mail, you can subclass these two classes to suit your needs.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Not all features of the ``EmailMessage`` class are available through the
|
Not all features of the ``EmailMessage`` class are available through the
|
||||||
``send_mail()`` and related wrapper functions. If you wish to use advanced
|
``send_mail()`` and related wrapper functions. If you wish to use advanced
|
||||||
features such as including BCC recipients or multi-part email, you will
|
features, such as BCC'ed recipients or multi-part e-mail, you'll need to
|
||||||
need to create ``EmailMessage`` instances directly.
|
create ``EmailMessage`` instances directly.
|
||||||
|
|
||||||
In general, ``EmailMessage`` is responsible for creating the email message
|
In general, ``EmailMessage`` is responsible for creating the e-mail message
|
||||||
itself. ``SMTPConnection`` is responsible for the network connection side of
|
itself. ``SMTPConnection`` is responsible for the network connection side of
|
||||||
the operation. This means you can reuse the same connection (an
|
the operation. This means you can reuse the same connection (an
|
||||||
``SMTPConnection`` instance) for multiple messages.
|
``SMTPConnection`` instance) for multiple messages.
|
||||||
|
|
||||||
The ``EmailMessage`` class is initialised as follows::
|
The ``EmailMessage`` class is initialized as follows::
|
||||||
|
|
||||||
email = EmailMessage(subject, body, from_email, to, bcc, connection)
|
email = EmailMessage(subject, body, from_email, to, bcc, connection)
|
||||||
|
|
||||||
All of these parameters are optional. If ``from_email`` is omitted, the value
|
All of these parameters are optional. If ``from_email`` is omitted, the value
|
||||||
from ``settings.DEFAULT_FROM_EMAIL`` is used. Both the ``to`` and ``bcc``
|
from ``settings.DEFAULT_FROM_EMAIL`` is used. Both the ``to`` and ``bcc``
|
||||||
parameters are lists of addresses.
|
parameters are lists of addresses, as strings.
|
||||||
|
|
||||||
The class has the following methods that you can use:
|
For example::
|
||||||
|
|
||||||
* ``send()`` sends the message, using either the connection that is specified
|
email = EmailMessage('Hello', 'Body goes here', 'from@example.com',
|
||||||
in the ``connection`` attribute, or creating a new connection if none already
|
['to1@example.com', 'to2@example.com'],
|
||||||
exists.
|
['bcc@example.com'])
|
||||||
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
|
|
||||||
sub-class of Python's ``email.MIMEText.MIMEText`` class) holding the
|
The class has the following methods:
|
||||||
message to be sent. If you ever need to extend the `EmailMessage` class,
|
|
||||||
you will probably want to override this method to put the content you wish
|
* ``send()`` sends the message, using either the connection that is
|
||||||
into the MIME object.
|
specified in the ``connection`` attribute, or creating a new connection
|
||||||
* ``recipients()`` returns a lists of all the recipients of the message,
|
if none already exists.
|
||||||
whether they are recorded in the ``to`` or ``bcc`` attributes. This is
|
|
||||||
another method you need to possibly override when sub-classing, since the
|
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
|
||||||
SMTP server needs to be told the full list of recipients when the message
|
sub-class of Python's ``email.MIMEText.MIMEText`` class) holding the
|
||||||
is sent. If you add another way to specify recipients in your class, they
|
message to be sent. If you ever need to extend the `EmailMessage` class,
|
||||||
need to be returned from this method as well.
|
you'll probably want to override this method to put the content you wish
|
||||||
|
into the MIME object.
|
||||||
|
|
||||||
|
* ``recipients()`` returns a list of all the recipients of the message,
|
||||||
|
whether they're recorded in the ``to`` or ``bcc`` attributes. This is
|
||||||
|
another method you might need to override when sub-classing, because the
|
||||||
|
SMTP server needs to be told the full list of recipients when the message
|
||||||
|
is sent. If you add another way to specify recipients in your class, they
|
||||||
|
need to be returned from this method as well.
|
||||||
|
|
||||||
The ``SMTPConnection`` class is initialized with the host, port, username and
|
The ``SMTPConnection`` class is initialized with the host, port, username and
|
||||||
password for the SMTP server. If you don't specify one or more of those
|
password for the SMTP server. If you don't specify one or more of those
|
||||||
options, they are read from your settings file.
|
options, they are read from your settings file.
|
||||||
|
|
||||||
If you are sending lots of messages at once, the ``send_messages()`` method of
|
If you're sending lots of messages at once, the ``send_messages()`` method of
|
||||||
the ``SMTPConnection`` class will be useful. It takes a list of ``EmailMessage``
|
the ``SMTPConnection`` class is useful. It takes a list of ``EmailMessage``
|
||||||
instances (or sub-classes) and sends them over a single connection. For
|
instances (or subclasses) and sends them over a single connection. For example,
|
||||||
example, if you have a function called ``get_notification_email()`` that returns a
|
if you have a function called ``get_notification_email()`` that returns a
|
||||||
list of ``EmailMessage`` objects representing some periodic email you wish to
|
list of ``EmailMessage`` objects representing some periodic e-mail you wish to
|
||||||
send out, you could send this with::
|
send out, you could send this with::
|
||||||
|
|
||||||
connection = SMTPConnection() # Use default settings for connection
|
connection = SMTPConnection() # Use default settings for connection
|
||||||
messages = get_notification_email()
|
messages = get_notification_email()
|
||||||
connection.send_messages(messages)
|
connection.send_messages(messages)
|
||||||
|
|
||||||
|
@ -310,7 +310,7 @@ To create or update a message file, run this command::
|
|||||||
|
|
||||||
...where ``de`` is the language code for the message file you want to create.
|
...where ``de`` is the language code for the message file you want to create.
|
||||||
The language code, in this case, is in locale format. For example, it's
|
The language code, in this case, is in locale format. For example, it's
|
||||||
``pt_BR`` for Brazilian and ``de_AT`` for Austrian German.
|
``pt_BR`` for Brazilian Portugese and ``de_AT`` for Austrian German.
|
||||||
|
|
||||||
The script should be run from one of three places:
|
The script should be run from one of three places:
|
||||||
|
|
||||||
@ -463,8 +463,8 @@ following this algorithm:
|
|||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
* In each of these places, the language preference is expected to be in the
|
* In each of these places, the language preference is expected to be in the
|
||||||
standard language format, as a string. For example, Brazilian is
|
standard language format, as a string. For example, Brazilian Portugese
|
||||||
``pt-br``.
|
is ``pt-br``.
|
||||||
* If a base language is available but the sublanguage specified is not,
|
* If a base language is available but the sublanguage specified is not,
|
||||||
Django uses the base language. For example, if a user specifies ``de-at``
|
Django uses the base language. For example, if a user specifies ``de-at``
|
||||||
(Austrian German) but Django only has ``de`` available, Django uses
|
(Austrian German) but Django only has ``de`` available, Django uses
|
||||||
|
@ -459,7 +459,7 @@ string, not ``NULL``.
|
|||||||
``blank``
|
``blank``
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
If ``True``, the field is allowed to be blank.
|
If ``True``, the field is allowed to be blank. Default is ``False``.
|
||||||
|
|
||||||
Note that this is different than ``null``. ``null`` is purely
|
Note that this is different than ``null``. ``null`` is purely
|
||||||
database-related, whereas ``blank`` is validation-related. If a field has
|
database-related, whereas ``blank`` is validation-related. If a field has
|
||||||
|
@ -109,7 +109,7 @@ serializer, you must pass ``ensure_ascii=False`` as a parameter to the
|
|||||||
|
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
json_serializer = serializers.get_serializer("json")
|
json_serializer = serializers.get_serializer("json")()
|
||||||
json_serializer.serialize(queryset, ensure_ascii=False, stream=response)
|
json_serializer.serialize(queryset, ensure_ascii=False, stream=response)
|
||||||
|
|
||||||
Writing custom serializers
|
Writing custom serializers
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
The sitemap framework
|
The sitemap framework
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
**New in Django development version**.
|
|
||||||
|
|
||||||
Django comes with a high-level sitemap-generating framework that makes
|
Django comes with a high-level sitemap-generating framework that makes
|
||||||
creating sitemap_ XML files easy.
|
creating sitemap_ XML files easy.
|
||||||
|
|
||||||
|
@ -212,6 +212,9 @@ template tags. If an invalid variable is provided to one of these template
|
|||||||
tags, the variable will be interpreted as ``None``. Filters are always
|
tags, the variable will be interpreted as ``None``. Filters are always
|
||||||
applied to invalid variables within these template tags.
|
applied to invalid variables within these template tags.
|
||||||
|
|
||||||
|
If ``TEMPLATE_STRING_IF_INVALID`` contains a ``'%s'``, the format marker will
|
||||||
|
be replaced with the name of the invalid variable.
|
||||||
|
|
||||||
.. admonition:: For debug purposes only!
|
.. admonition:: For debug purposes only!
|
||||||
|
|
||||||
While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
|
While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
|
||||||
@ -866,7 +869,7 @@ current context, available in the ``render`` method::
|
|||||||
try:
|
try:
|
||||||
actual_date = resolve_variable(self.date_to_be_formatted, context)
|
actual_date = resolve_variable(self.date_to_be_formatted, context)
|
||||||
return actual_date.strftime(self.format_string)
|
return actual_date.strftime(self.format_string)
|
||||||
except VariableDoesNotExist:
|
except template.VariableDoesNotExist:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
``resolve_variable`` will try to resolve ``blog_entry.date_updated`` and then
|
``resolve_variable`` will try to resolve ``blog_entry.date_updated`` and then
|
||||||
|
179
docs/testing.txt
179
docs/testing.txt
@ -2,19 +2,29 @@
|
|||||||
Testing Django applications
|
Testing Django applications
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
Automated testing is an extremely useful weapon in the bug-killing arsenal
|
Automated testing is an extremely useful bug-killing tool for the modern
|
||||||
of the modern developer. When initially writing code, a test suite can be
|
Web developer. You can use a collection of tests -- a **test suite** -- to
|
||||||
used to validate that code behaves as expected. When refactoring or
|
to solve, or avoid, a number of problems:
|
||||||
modifying code, tests serve as a guide to ensure that behavior hasn't
|
|
||||||
changed unexpectedly as a result of the refactor.
|
|
||||||
|
|
||||||
Testing a web application is a complex task, as there are many
|
* When you're writing new code, you can use tests to validate your code
|
||||||
components of a web application that must be validated and tested. To
|
works as expected.
|
||||||
help you test your application, Django provides a test execution
|
|
||||||
framework, and range of utilities that can be used to simulate and
|
|
||||||
inspect various facets of a web application.
|
|
||||||
|
|
||||||
This testing framework is currently under development, and may change
|
* When you're refactoring or modifying old code, you can use tests to
|
||||||
|
ensure your changes haven't affected your application's behavior
|
||||||
|
unexpectedly.
|
||||||
|
|
||||||
|
Testing a Web application is a complex task, because a Web application is made
|
||||||
|
of several layers of logic -- from HTTP-level request handling, to form
|
||||||
|
validation and processing, to template rendering. With Django's test-execution
|
||||||
|
framework and assorted utilities, you can simulate requests, insert test data,
|
||||||
|
inspect your application's output and generally verify your code is doing what
|
||||||
|
it should be doing.
|
||||||
|
|
||||||
|
The best part is, it's really easy.
|
||||||
|
|
||||||
|
.. admonition:: Note
|
||||||
|
|
||||||
|
This testing framework is currently under development. It may change
|
||||||
slightly before the next official Django release.
|
slightly before the next official Django release.
|
||||||
|
|
||||||
(That's *no* excuse not to write tests, though!)
|
(That's *no* excuse not to write tests, though!)
|
||||||
@ -167,6 +177,7 @@ tools that can be used to establish tests and test conditions.
|
|||||||
|
|
||||||
* `Test Client`_
|
* `Test Client`_
|
||||||
* `TestCase`_
|
* `TestCase`_
|
||||||
|
* `Email services`_
|
||||||
|
|
||||||
Test Client
|
Test Client
|
||||||
-----------
|
-----------
|
||||||
@ -246,29 +257,42 @@ can be invoked on the ``Client`` instance.
|
|||||||
file name), and `attachment_file` (containing the file data). Note that you
|
file name), and `attachment_file` (containing the file data). Note that you
|
||||||
need to manually close the file after it has been provided to the POST.
|
need to manually close the file after it has been provided to the POST.
|
||||||
|
|
||||||
``login(path, username, password)``
|
``login(**credentials)``
|
||||||
In a production site, it is likely that some views will be protected with
|
**New in Django development version**
|
||||||
the @login_required decorator provided by ``django.contrib.auth``. Interacting
|
|
||||||
with a URL that has been login protected is a slightly complex operation,
|
|
||||||
so the Test Client provides a simple method to automate the login process. A
|
|
||||||
call to ``login()`` stimulates the series of GET and POST calls required
|
|
||||||
to log a user into a @login_required protected view.
|
|
||||||
|
|
||||||
If login is possible, the final return value of ``login()`` is the response
|
On a production site, it is likely that some views will be protected from
|
||||||
that is generated by issuing a GET request on the protected URL. If login
|
anonymous access through the use of the @login_required decorator, or some
|
||||||
is not possible, ``login()`` returns False.
|
other login checking mechanism. The ``login()`` method can be used to
|
||||||
|
simulate the effect of a user logging into the site. As a result of calling
|
||||||
|
this method, the Client will have all the cookies and session data required
|
||||||
|
to pass any login-based tests that may form part of a view.
|
||||||
|
|
||||||
|
In most cases, the ``credentials`` required by this method are the username
|
||||||
|
and password of the user that wants to log in, provided as keyword
|
||||||
|
arguments::
|
||||||
|
|
||||||
|
c = Client()
|
||||||
|
c.login(username='fred', password='secret')
|
||||||
|
# Now you can access a login protected view
|
||||||
|
|
||||||
|
If you are using a different authentication backend, this method may
|
||||||
|
require different credentials.
|
||||||
|
|
||||||
|
``login()`` returns ``True`` if it the credentials were accepted and login
|
||||||
|
was successful.
|
||||||
|
|
||||||
Note that since the test suite will be executed using the test database,
|
Note that since the test suite will be executed using the test database,
|
||||||
which contains no users by default. As a result, logins for your production
|
which contains no users by default. As a result, logins that are valid
|
||||||
site will not work. You will need to create users as part of the test suite
|
on your production site will not work under test conditions. You will
|
||||||
to be able to test logins to your application.
|
need to create users as part of the test suite (either manually, or
|
||||||
|
using a test fixture).
|
||||||
|
|
||||||
Testing Responses
|
Testing Responses
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The ``get()``, ``post()`` and ``login()`` methods all return a Response
|
The ``get()`` and ``post()`` methods both return a Response object. This
|
||||||
object. This Response object has the following properties that can be used
|
Response object has the following properties that can be used for testing
|
||||||
for testing purposes:
|
purposes:
|
||||||
|
|
||||||
=============== ==========================================================
|
=============== ==========================================================
|
||||||
Property Description
|
Property Description
|
||||||
@ -373,7 +397,7 @@ extra facilities.
|
|||||||
|
|
||||||
Default Test Client
|
Default Test Client
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
** New in Django development version **
|
**New in Django development version**
|
||||||
|
|
||||||
Every test case in a ``django.test.TestCase`` instance has access to an
|
Every test case in a ``django.test.TestCase`` instance has access to an
|
||||||
instance of a Django `Test Client`_. This Client can be accessed as
|
instance of a Django `Test Client`_. This Client can be accessed as
|
||||||
@ -430,24 +454,98 @@ This flush/load procedure is repeated for each test in the test case, so you
|
|||||||
can be certain that the outcome of a test will not be affected by
|
can be certain that the outcome of a test will not be affected by
|
||||||
another test, or the order of test execution.
|
another test, or the order of test execution.
|
||||||
|
|
||||||
|
Emptying the test outbox
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
At the start of each test case, in addition to installing fixtures,
|
||||||
|
Django clears the contents of the test email outbox.
|
||||||
|
|
||||||
|
For more detail on email services during tests, see `Email services`_.
|
||||||
|
|
||||||
Assertions
|
Assertions
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
** New in Django development version **
|
**New in Django development version**
|
||||||
|
|
||||||
Normal Python unit tests have a wide range of assertions, such as
|
Normal Python unit tests have a wide range of assertions, such as
|
||||||
``assertTrue`` and ``assertEquals`` that can be used to validate behavior.
|
``assertTrue`` and ``assertEquals`` that can be used to validate behavior.
|
||||||
``django.TestCase`` adds to these, providing some assertions
|
``django.TestCase`` adds to these, providing some assertions
|
||||||
that can be useful in testing the behavior of web sites.
|
that can be useful in testing the behavior of web sites.
|
||||||
|
|
||||||
``assertRedirects(response, expected_path)``
|
``assertContains(response, text, count=1, status_code=200)``
|
||||||
Assert that the response received redirects the browser to the provided
|
Assert that a response indicates that a page could be retrieved and
|
||||||
path, and that the expected_path can be retrieved.
|
produced the nominated status code, and that ``text`` occurs ``count``
|
||||||
|
|
||||||
``assertContains(response, text, count=1)``
|
|
||||||
Assert that a response indicates that a page was retreived successfully,
|
|
||||||
(i.e., the HTTP status code was 200), and that ``text`` occurs ``count``
|
|
||||||
times in the content of the response.
|
times in the content of the response.
|
||||||
|
|
||||||
|
``assertFormError(response, form, field, errors)``
|
||||||
|
Assert that a field on a form raised the provided list of errors when
|
||||||
|
rendered on the form.
|
||||||
|
|
||||||
|
``form`` is the name the form object was given in the template context.
|
||||||
|
|
||||||
|
``field`` is the name of the field on the form to check. If ``field``
|
||||||
|
has a value of ``None``, non-field errors will be checked.
|
||||||
|
|
||||||
|
``errors`` is an error string, or a list of error strings, that are
|
||||||
|
expected as a result of form validation.
|
||||||
|
|
||||||
|
``assertTemplateNotUsed(response, template_name)``
|
||||||
|
Assert that the template with the given name was *not* used in rendering
|
||||||
|
the response.
|
||||||
|
|
||||||
|
``assertRedirects(response, expected_path, status_code=302, target_status_code=200)``
|
||||||
|
Assert that the response received produced the nominated status code,
|
||||||
|
redirects the browser to the provided path, and that retrieving the provided
|
||||||
|
path yields a response with the target status code.
|
||||||
|
|
||||||
|
``assertTemplateUsed(response, template_name)``
|
||||||
|
Assert that the template with the given name was used in rendering the
|
||||||
|
response.
|
||||||
|
|
||||||
|
Email services
|
||||||
|
--------------
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
If your view makes use of the `Django email services`_, you don't really
|
||||||
|
want email to be sent every time you run a test using that view.
|
||||||
|
|
||||||
|
When the Django test framework is initialized, it transparently replaces the
|
||||||
|
normal `SMTPConnection`_ class with a dummy implementation that redirects all
|
||||||
|
email to a dummy outbox. This outbox, stored as ``django.core.mail.outbox``,
|
||||||
|
is a simple list of all `EmailMessage`_ instances that have been sent.
|
||||||
|
For example, during test conditions, it would be possible to run the following
|
||||||
|
code::
|
||||||
|
|
||||||
|
from django.core import mail
|
||||||
|
|
||||||
|
# Send message
|
||||||
|
mail.send_mail('Subject here', 'Here is the message.', 'from@example.com',
|
||||||
|
['to@example.com'], fail_silently=False)
|
||||||
|
|
||||||
|
# One message has been sent
|
||||||
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
|
# Subject of first message is correct
|
||||||
|
self.assertEqual(mail.outbox[0].subject, 'Subject here')
|
||||||
|
|
||||||
|
The ``mail.outbox`` object does not exist under normal execution conditions.
|
||||||
|
The outbox is created during test setup, along with the dummy `SMTPConnection`_.
|
||||||
|
When the test framework is torn down, the standard `SMTPConnection`_ class
|
||||||
|
is restored, and the test outbox is destroyed.
|
||||||
|
|
||||||
|
As noted `previously`_, the test outbox is emptied at the start of every
|
||||||
|
test in a Django TestCase. To empty the outbox manually, assign the empty list
|
||||||
|
to mail.outbox::
|
||||||
|
|
||||||
|
from django.core import mail
|
||||||
|
|
||||||
|
# Empty the test outbox
|
||||||
|
mail.outbox = []
|
||||||
|
|
||||||
|
.. _`Django email services`: ../email/
|
||||||
|
.. _`SMTPConnection`: ../email/#the-emailmessage-and-smtpconnection-classes
|
||||||
|
.. _`EmailMessage`: ../email/#the-emailmessage-and-smtpconnection-classes
|
||||||
|
.. _`previously`: #emptying-the-test-outbox
|
||||||
|
|
||||||
Running tests
|
Running tests
|
||||||
=============
|
=============
|
||||||
|
|
||||||
@ -472,6 +570,10 @@ database settings will the same as they would be for the project normally.
|
|||||||
If you wish to use a name other than the default for the test database,
|
If you wish to use a name other than the default for the test database,
|
||||||
you can use the ``TEST_DATABASE_NAME`` setting to provide a name.
|
you can use the ``TEST_DATABASE_NAME`` setting to provide a name.
|
||||||
|
|
||||||
|
The test database is created by the user in the ``DATABASE_USER`` setting.
|
||||||
|
This user needs to have sufficient privileges to create a new database on the
|
||||||
|
system.
|
||||||
|
|
||||||
Once the test database has been established, Django will run your tests.
|
Once the test database has been established, Django will run your tests.
|
||||||
If everything goes well, at the end you'll see::
|
If everything goes well, at the end you'll see::
|
||||||
|
|
||||||
@ -562,11 +664,12 @@ a number of utility methods in the ``django.test.utils`` module.
|
|||||||
|
|
||||||
``setup_test_environment()``
|
``setup_test_environment()``
|
||||||
Performs any global pre-test setup, such as the installing the
|
Performs any global pre-test setup, such as the installing the
|
||||||
instrumentation of the template rendering system.
|
instrumentation of the template rendering system and setting up
|
||||||
|
the dummy SMTPConnection.
|
||||||
|
|
||||||
``teardown_test_environment()``
|
``teardown_test_environment()``
|
||||||
Performs any global post-test teardown, such as removing the instrumentation
|
Performs any global post-test teardown, such as removing the instrumentation
|
||||||
of the template rendering system.
|
of the template rendering system and restoring normal email services.
|
||||||
|
|
||||||
``create_test_db(verbosity=1, autoclobber=False)``
|
``create_test_db(verbosity=1, autoclobber=False)``
|
||||||
Creates a new test database, and run ``syncdb`` against it.
|
Creates a new test database, and run ``syncdb`` against it.
|
||||||
|
@ -71,7 +71,7 @@ __test__ = {'API_TESTS':"""
|
|||||||
>>> Author.objects.filter(firstname__exact='John')
|
>>> Author.objects.filter(firstname__exact='John')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TypeError: Cannot resolve keyword 'firstname' into field, choices are: article, id, first_name, last_name
|
TypeError: Cannot resolve keyword 'firstname' into field. Choices are: article, id, first_name, last_name
|
||||||
|
|
||||||
>>> a = Author.objects.get(last_name__exact='Smith')
|
>>> a = Author.objects.get(last_name__exact='Smith')
|
||||||
>>> a.first_name
|
>>> a.first_name
|
||||||
|
@ -11,6 +11,7 @@ from complete).
|
|||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.contrib.contenttypes import generic
|
||||||
|
|
||||||
class TaggedItem(models.Model):
|
class TaggedItem(models.Model):
|
||||||
"""A tag on an item."""
|
"""A tag on an item."""
|
||||||
@ -18,7 +19,7 @@ class TaggedItem(models.Model):
|
|||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
|
|
||||||
content_object = models.GenericForeignKey()
|
content_object = generic.GenericForeignKey()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["tag"]
|
ordering = ["tag"]
|
||||||
@ -30,7 +31,7 @@ class Animal(models.Model):
|
|||||||
common_name = models.CharField(maxlength=150)
|
common_name = models.CharField(maxlength=150)
|
||||||
latin_name = models.CharField(maxlength=150)
|
latin_name = models.CharField(maxlength=150)
|
||||||
|
|
||||||
tags = models.GenericRelation(TaggedItem)
|
tags = generic.GenericRelation(TaggedItem)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.common_name
|
return self.common_name
|
||||||
@ -39,7 +40,7 @@ class Vegetable(models.Model):
|
|||||||
name = models.CharField(maxlength=150)
|
name = models.CharField(maxlength=150)
|
||||||
is_yucky = models.BooleanField(default=True)
|
is_yucky = models.BooleanField(default=True)
|
||||||
|
|
||||||
tags = models.GenericRelation(TaggedItem)
|
tags = generic.GenericRelation(TaggedItem)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -223,11 +223,11 @@ DoesNotExist: Article matching query does not exist.
|
|||||||
>>> Article.objects.filter(pub_date_year='2005').count()
|
>>> Article.objects.filter(pub_date_year='2005').count()
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TypeError: Cannot resolve keyword 'pub_date_year' into field, choices are: id, headline, pub_date
|
TypeError: Cannot resolve keyword 'pub_date_year' into field. Choices are: id, headline, pub_date
|
||||||
|
|
||||||
>>> Article.objects.filter(headline__starts='Article')
|
>>> Article.objects.filter(headline__starts='Article')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TypeError: Cannot resolve keyword 'headline__starts' into field, choices are: id, headline, pub_date
|
TypeError: Cannot resolve keyword 'headline__starts' into field. Choices are: id, headline, pub_date
|
||||||
|
|
||||||
"""}
|
"""}
|
||||||
|
@ -174,13 +174,13 @@ False
|
|||||||
>>> Article.objects.filter(reporter_id__exact=1)
|
>>> Article.objects.filter(reporter_id__exact=1)
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TypeError: Cannot resolve keyword 'reporter_id' into field, choices are: id, headline, pub_date, reporter
|
TypeError: Cannot resolve keyword 'reporter_id' into field. Choices are: id, headline, pub_date, reporter
|
||||||
|
|
||||||
# You need to specify a comparison clause
|
# You need to specify a comparison clause
|
||||||
>>> Article.objects.filter(reporter_id=1)
|
>>> Article.objects.filter(reporter_id=1)
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TypeError: Cannot resolve keyword 'reporter_id' into field, choices are: id, headline, pub_date, reporter
|
TypeError: Cannot resolve keyword 'reporter_id' into field. Choices are: id, headline, pub_date, reporter
|
||||||
|
|
||||||
# You can also instantiate an Article by passing
|
# You can also instantiate an Article by passing
|
||||||
# the Reporter's ID instead of a Reporter object.
|
# the Reporter's ID instead of a Reporter object.
|
||||||
|
@ -55,5 +55,5 @@ __test__ = {'API_TESTS':"""
|
|||||||
>>> Poll.objects.get(choice__name__exact="This is the answer")
|
>>> Poll.objects.get(choice__name__exact="This is the answer")
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TypeError: Cannot resolve keyword 'choice' into field, choices are: poll_choice, related_choice, id, question, creator
|
TypeError: Cannot resolve keyword 'choice' into field. Choices are: poll_choice, related_choice, id, question, creator
|
||||||
"""}
|
"""}
|
||||||
|
@ -20,6 +20,7 @@ rather than the HTML rendered to the end-user.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
|
from django.core import mail
|
||||||
|
|
||||||
class ClientTest(TestCase):
|
class ClientTest(TestCase):
|
||||||
fixtures = ['testdata.json']
|
fixtures = ['testdata.json']
|
||||||
@ -40,6 +41,8 @@ class ClientTest(TestCase):
|
|||||||
# Check some response details
|
# Check some response details
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.template.name, 'Empty GET Template')
|
self.assertEqual(response.template.name, 'Empty GET Template')
|
||||||
|
self.assertTemplateUsed(response, 'Empty GET Template')
|
||||||
|
self.assertTemplateNotUsed(response, 'Empty POST Template')
|
||||||
|
|
||||||
def test_empty_post(self):
|
def test_empty_post(self):
|
||||||
"POST an empty dictionary to a view"
|
"POST an empty dictionary to a view"
|
||||||
@ -48,6 +51,8 @@ class ClientTest(TestCase):
|
|||||||
# Check some response details
|
# Check some response details
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.template.name, 'Empty POST Template')
|
self.assertEqual(response.template.name, 'Empty POST Template')
|
||||||
|
self.assertTemplateNotUsed(response, 'Empty GET Template')
|
||||||
|
self.assertTemplateUsed(response, 'Empty POST Template')
|
||||||
|
|
||||||
def test_post(self):
|
def test_post(self):
|
||||||
"POST some data to a view"
|
"POST some data to a view"
|
||||||
@ -63,6 +68,7 @@ class ClientTest(TestCase):
|
|||||||
self.failUnless('Data received' in response.content)
|
self.failUnless('Data received' in response.content)
|
||||||
|
|
||||||
def test_raw_post(self):
|
def test_raw_post(self):
|
||||||
|
"POST raw data (with a content type) to a view"
|
||||||
test_doc = """<?xml version="1.0" encoding="utf-8"?><library><book><title>Blink</title><author>Malcolm Gladwell</author></book></library>"""
|
test_doc = """<?xml version="1.0" encoding="utf-8"?><library><book><title>Blink</title><author>Malcolm Gladwell</author></book></library>"""
|
||||||
response = self.client.post("/test_client/raw_post_view/", test_doc,
|
response = self.client.post("/test_client/raw_post_view/", test_doc,
|
||||||
content_type="text/xml")
|
content_type="text/xml")
|
||||||
@ -77,6 +83,28 @@ class ClientTest(TestCase):
|
|||||||
# Check that the response was a 302 (redirect)
|
# Check that the response was a 302 (redirect)
|
||||||
self.assertRedirects(response, '/test_client/get_view/')
|
self.assertRedirects(response, '/test_client/get_view/')
|
||||||
|
|
||||||
|
def test_permanent_redirect(self):
|
||||||
|
"GET a URL that redirects permanently elsewhere"
|
||||||
|
response = self.client.get('/test_client/permanent_redirect_view/')
|
||||||
|
|
||||||
|
# Check that the response was a 301 (permanent redirect)
|
||||||
|
self.assertRedirects(response, '/test_client/get_view/', status_code=301)
|
||||||
|
|
||||||
|
def test_redirect_to_strange_location(self):
|
||||||
|
"GET a URL that redirects to a non-200 page"
|
||||||
|
response = self.client.get('/test_client/double_redirect_view/')
|
||||||
|
|
||||||
|
# Check that the response was a 302, and that
|
||||||
|
# the attempt to get the redirection location returned 301 when retrieved
|
||||||
|
self.assertRedirects(response, '/test_client/permanent_redirect_view/', target_status_code=301)
|
||||||
|
|
||||||
|
def test_notfound_response(self):
|
||||||
|
"GET a URL that responds as '404:Not Found'"
|
||||||
|
response = self.client.get('/test_client/bad_view/')
|
||||||
|
|
||||||
|
# Check that the response was a 404, and that the content contains MAGIC
|
||||||
|
self.assertContains(response, 'MAGIC', status_code=404)
|
||||||
|
|
||||||
def test_valid_form(self):
|
def test_valid_form(self):
|
||||||
"POST valid data to a form"
|
"POST valid data to a form"
|
||||||
post_data = {
|
post_data = {
|
||||||
@ -88,7 +116,7 @@ class ClientTest(TestCase):
|
|||||||
}
|
}
|
||||||
response = self.client.post('/test_client/form_view/', post_data)
|
response = self.client.post('/test_client/form_view/', post_data)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.template.name, "Valid POST Template")
|
self.assertTemplateUsed(response, "Valid POST Template")
|
||||||
|
|
||||||
def test_incomplete_data_form(self):
|
def test_incomplete_data_form(self):
|
||||||
"POST incomplete data to a form"
|
"POST incomplete data to a form"
|
||||||
@ -97,8 +125,13 @@ class ClientTest(TestCase):
|
|||||||
'value': 37
|
'value': 37
|
||||||
}
|
}
|
||||||
response = self.client.post('/test_client/form_view/', post_data)
|
response = self.client.post('/test_client/form_view/', post_data)
|
||||||
self.assertContains(response, 'This field is required', 3)
|
self.assertContains(response, 'This field is required.', 3)
|
||||||
self.assertEqual(response.template.name, "Invalid POST Template")
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTemplateUsed(response, "Invalid POST Template")
|
||||||
|
|
||||||
|
self.assertFormError(response, 'form', 'email', 'This field is required.')
|
||||||
|
self.assertFormError(response, 'form', 'single', 'This field is required.')
|
||||||
|
self.assertFormError(response, 'form', 'multi', 'This field is required.')
|
||||||
|
|
||||||
def test_form_error(self):
|
def test_form_error(self):
|
||||||
"POST erroneous data to a form"
|
"POST erroneous data to a form"
|
||||||
@ -111,7 +144,57 @@ class ClientTest(TestCase):
|
|||||||
}
|
}
|
||||||
response = self.client.post('/test_client/form_view/', post_data)
|
response = self.client.post('/test_client/form_view/', post_data)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.template.name, "Invalid POST Template")
|
self.assertTemplateUsed(response, "Invalid POST Template")
|
||||||
|
|
||||||
|
self.assertFormError(response, 'form', 'email', 'Enter a valid e-mail address.')
|
||||||
|
|
||||||
|
def test_valid_form_with_template(self):
|
||||||
|
"POST valid data to a form using multiple templates"
|
||||||
|
post_data = {
|
||||||
|
'text': 'Hello World',
|
||||||
|
'email': 'foo@example.com',
|
||||||
|
'value': 37,
|
||||||
|
'single': 'b',
|
||||||
|
'multi': ('b','c','e')
|
||||||
|
}
|
||||||
|
response = self.client.post('/test_client/form_view_with_template/', post_data)
|
||||||
|
self.assertContains(response, 'POST data OK')
|
||||||
|
self.assertTemplateUsed(response, "form_view.html")
|
||||||
|
self.assertTemplateUsed(response, 'base.html')
|
||||||
|
self.assertTemplateNotUsed(response, "Valid POST Template")
|
||||||
|
|
||||||
|
def test_incomplete_data_form_with_template(self):
|
||||||
|
"POST incomplete data to a form using multiple templates"
|
||||||
|
post_data = {
|
||||||
|
'text': 'Hello World',
|
||||||
|
'value': 37
|
||||||
|
}
|
||||||
|
response = self.client.post('/test_client/form_view_with_template/', post_data)
|
||||||
|
self.assertContains(response, 'POST data has errors')
|
||||||
|
self.assertTemplateUsed(response, 'form_view.html')
|
||||||
|
self.assertTemplateUsed(response, 'base.html')
|
||||||
|
self.assertTemplateNotUsed(response, "Invalid POST Template")
|
||||||
|
|
||||||
|
self.assertFormError(response, 'form', 'email', 'This field is required.')
|
||||||
|
self.assertFormError(response, 'form', 'single', 'This field is required.')
|
||||||
|
self.assertFormError(response, 'form', 'multi', 'This field is required.')
|
||||||
|
|
||||||
|
def test_form_error_with_template(self):
|
||||||
|
"POST erroneous data to a form using multiple templates"
|
||||||
|
post_data = {
|
||||||
|
'text': 'Hello World',
|
||||||
|
'email': 'not an email address',
|
||||||
|
'value': 37,
|
||||||
|
'single': 'b',
|
||||||
|
'multi': ('b','c','e')
|
||||||
|
}
|
||||||
|
response = self.client.post('/test_client/form_view_with_template/', post_data)
|
||||||
|
self.assertContains(response, 'POST data has errors')
|
||||||
|
self.assertTemplateUsed(response, "form_view.html")
|
||||||
|
self.assertTemplateUsed(response, 'base.html')
|
||||||
|
self.assertTemplateNotUsed(response, "Invalid POST Template")
|
||||||
|
|
||||||
|
self.assertFormError(response, 'form', 'email', 'Enter a valid e-mail address.')
|
||||||
|
|
||||||
def test_unknown_page(self):
|
def test_unknown_page(self):
|
||||||
"GET an invalid URL"
|
"GET an invalid URL"
|
||||||
@ -127,18 +210,19 @@ class ClientTest(TestCase):
|
|||||||
response = self.client.get('/test_client/login_protected_view/')
|
response = self.client.get('/test_client/login_protected_view/')
|
||||||
self.assertRedirects(response, '/accounts/login/')
|
self.assertRedirects(response, '/accounts/login/')
|
||||||
|
|
||||||
|
# Log in
|
||||||
|
self.client.login(username='testclient', password='password')
|
||||||
|
|
||||||
# Request a page that requires a login
|
# Request a page that requires a login
|
||||||
response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password')
|
response = self.client.get('/test_client/login_protected_view/')
|
||||||
self.failUnless(response)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.context['user'].username, 'testclient')
|
self.assertEqual(response.context['user'].username, 'testclient')
|
||||||
self.assertEqual(response.template.name, 'Login Template')
|
|
||||||
|
|
||||||
def test_view_with_bad_login(self):
|
def test_view_with_bad_login(self):
|
||||||
"Request a page that is protected with @login, but use bad credentials"
|
"Request a page that is protected with @login, but use bad credentials"
|
||||||
|
|
||||||
response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword')
|
login = self.client.login(username='otheruser', password='nopassword')
|
||||||
self.failIf(response)
|
self.failIf(login)
|
||||||
|
|
||||||
def test_session_modifying_view(self):
|
def test_session_modifying_view(self):
|
||||||
"Request a page that modifies the session"
|
"Request a page that modifies the session"
|
||||||
@ -165,3 +249,36 @@ class ClientTest(TestCase):
|
|||||||
self.fail('Should raise an error')
|
self.fail('Should raise an error')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_mail_sending(self):
|
||||||
|
"Test that mail is redirected to a dummy outbox during test setup"
|
||||||
|
|
||||||
|
response = self.client.get('/test_client/mail_sending_view/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
|
self.assertEqual(mail.outbox[0].subject, 'Test message')
|
||||||
|
self.assertEqual(mail.outbox[0].body, 'This is a test email')
|
||||||
|
self.assertEqual(mail.outbox[0].from_email, 'from@example.com')
|
||||||
|
self.assertEqual(mail.outbox[0].to[0], 'first@example.com')
|
||||||
|
self.assertEqual(mail.outbox[0].to[1], 'second@example.com')
|
||||||
|
|
||||||
|
def test_mass_mail_sending(self):
|
||||||
|
"Test that mass mail is redirected to a dummy outbox during test setup"
|
||||||
|
|
||||||
|
response = self.client.get('/test_client/mass_mail_sending_view/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
self.assertEqual(len(mail.outbox), 2)
|
||||||
|
self.assertEqual(mail.outbox[0].subject, 'First Test message')
|
||||||
|
self.assertEqual(mail.outbox[0].body, 'This is the first test email')
|
||||||
|
self.assertEqual(mail.outbox[0].from_email, 'from@example.com')
|
||||||
|
self.assertEqual(mail.outbox[0].to[0], 'first@example.com')
|
||||||
|
self.assertEqual(mail.outbox[0].to[1], 'second@example.com')
|
||||||
|
|
||||||
|
self.assertEqual(mail.outbox[1].subject, 'Second Test message')
|
||||||
|
self.assertEqual(mail.outbox[1].body, 'This is the second test email')
|
||||||
|
self.assertEqual(mail.outbox[1].from_email, 'from@example.com')
|
||||||
|
self.assertEqual(mail.outbox[1].to[0], 'second@example.com')
|
||||||
|
self.assertEqual(mail.outbox[1].to[1], 'third@example.com')
|
||||||
|
|
@ -1,4 +1,5 @@
|
|||||||
from django.conf.urls.defaults import *
|
from django.conf.urls.defaults import *
|
||||||
|
from django.views.generic.simple import redirect_to
|
||||||
import views
|
import views
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
@ -6,8 +7,14 @@ urlpatterns = patterns('',
|
|||||||
(r'^post_view/$', views.post_view),
|
(r'^post_view/$', views.post_view),
|
||||||
(r'^raw_post_view/$', views.raw_post_view),
|
(r'^raw_post_view/$', views.raw_post_view),
|
||||||
(r'^redirect_view/$', views.redirect_view),
|
(r'^redirect_view/$', views.redirect_view),
|
||||||
|
(r'^permanent_redirect_view/$', redirect_to, { 'url': '/test_client/get_view/' }),
|
||||||
|
(r'^double_redirect_view/$', views.double_redirect_view),
|
||||||
|
(r'^bad_view/$', views.bad_view),
|
||||||
(r'^form_view/$', views.form_view),
|
(r'^form_view/$', views.form_view),
|
||||||
|
(r'^form_view_with_template/$', views.form_view_with_template),
|
||||||
(r'^login_protected_view/$', views.login_protected_view),
|
(r'^login_protected_view/$', views.login_protected_view),
|
||||||
(r'^session_view/$', views.session_view),
|
(r'^session_view/$', views.session_view),
|
||||||
(r'^broken_view/$', views.broken_view)
|
(r'^broken_view/$', views.broken_view),
|
||||||
|
(r'^mail_sending_view/$', views.mail_sending_view),
|
||||||
|
(r'^mass_mail_sending_view/$', views.mass_mail_sending_view)
|
||||||
)
|
)
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
from xml.dom.minidom import parseString
|
from xml.dom.minidom import parseString
|
||||||
|
from django.core.mail import EmailMessage, SMTPConnection
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
from django.http import HttpResponse, HttpResponseRedirect
|
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.newforms.forms import Form
|
from django.newforms.forms import Form
|
||||||
from django.newforms import fields
|
from django.newforms import fields
|
||||||
|
from django.shortcuts import render_to_response
|
||||||
|
|
||||||
def get_view(request):
|
def get_view(request):
|
||||||
"A simple view that expects a GET request, and returns a rendered template"
|
"A simple view that expects a GET request, and returns a rendered template"
|
||||||
@ -48,6 +50,14 @@ def redirect_view(request):
|
|||||||
"A view that redirects all requests to the GET view"
|
"A view that redirects all requests to the GET view"
|
||||||
return HttpResponseRedirect('/test_client/get_view/')
|
return HttpResponseRedirect('/test_client/get_view/')
|
||||||
|
|
||||||
|
def double_redirect_view(request):
|
||||||
|
"A view that redirects all requests to a redirection view"
|
||||||
|
return HttpResponseRedirect('/test_client/permanent_redirect_view/')
|
||||||
|
|
||||||
|
def bad_view(request):
|
||||||
|
"A view that returns a 404 with some error content"
|
||||||
|
return HttpResponseNotFound('Not found!. This page contains some MAGIC content')
|
||||||
|
|
||||||
TestChoices = (
|
TestChoices = (
|
||||||
('a', 'First Choice'),
|
('a', 'First Choice'),
|
||||||
('b', 'Second Choice'),
|
('b', 'Second Choice'),
|
||||||
@ -80,6 +90,25 @@ def form_view(request):
|
|||||||
|
|
||||||
return HttpResponse(t.render(c))
|
return HttpResponse(t.render(c))
|
||||||
|
|
||||||
|
def form_view_with_template(request):
|
||||||
|
"A view that tests a simple form"
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = TestForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
message = 'POST data OK'
|
||||||
|
else:
|
||||||
|
message = 'POST data has errors'
|
||||||
|
else:
|
||||||
|
form = TestForm()
|
||||||
|
message = 'GET form page'
|
||||||
|
return render_to_response('form_view.html',
|
||||||
|
{
|
||||||
|
'form': form,
|
||||||
|
'message': message
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def login_protected_view(request):
|
def login_protected_view(request):
|
||||||
"A simple view that is login protected."
|
"A simple view that is login protected."
|
||||||
t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')
|
t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')
|
||||||
@ -100,3 +129,28 @@ def session_view(request):
|
|||||||
def broken_view(request):
|
def broken_view(request):
|
||||||
"""A view which just raises an exception, simulating a broken view."""
|
"""A view which just raises an exception, simulating a broken view."""
|
||||||
raise KeyError("Oops! Looks like you wrote some bad code.")
|
raise KeyError("Oops! Looks like you wrote some bad code.")
|
||||||
|
|
||||||
|
def mail_sending_view(request):
|
||||||
|
EmailMessage(
|
||||||
|
"Test message",
|
||||||
|
"This is a test email",
|
||||||
|
"from@example.com",
|
||||||
|
['first@example.com', 'second@example.com']).send()
|
||||||
|
return HttpResponse("Mail sent")
|
||||||
|
|
||||||
|
def mass_mail_sending_view(request):
|
||||||
|
m1 = EmailMessage(
|
||||||
|
'First Test message',
|
||||||
|
'This is the first test email',
|
||||||
|
'from@example.com',
|
||||||
|
['first@example.com', 'second@example.com'])
|
||||||
|
m2 = EmailMessage(
|
||||||
|
'Second Test message',
|
||||||
|
'This is the second test email',
|
||||||
|
'from@example.com',
|
||||||
|
['second@example.com', 'third@example.com'])
|
||||||
|
|
||||||
|
c = SMTPConnection()
|
||||||
|
c.send_messages([m1,m2])
|
||||||
|
|
||||||
|
return HttpResponse("Mail sent")
|
||||||
|
5
tests/regressiontests/cache/tests.py
vendored
5
tests/regressiontests/cache/tests.py
vendored
@ -46,6 +46,11 @@ class Cache(unittest.TestCase):
|
|||||||
self.assertEqual(cache.has_key("hello"), True)
|
self.assertEqual(cache.has_key("hello"), True)
|
||||||
self.assertEqual(cache.has_key("goodbye"), False)
|
self.assertEqual(cache.has_key("goodbye"), False)
|
||||||
|
|
||||||
|
def test_in(self):
|
||||||
|
cache.set("hello", "goodbye")
|
||||||
|
self.assertEqual("hello" in cache, True)
|
||||||
|
self.assertEqual("goodbye" in cache, False)
|
||||||
|
|
||||||
def test_data_types(self):
|
def test_data_types(self):
|
||||||
# test data types
|
# test data types
|
||||||
stuff = {
|
stuff = {
|
||||||
|
@ -32,7 +32,7 @@ __test__ = {'API_TESTS':"""
|
|||||||
>>> Choice.objects.filter(foo__exact=None)
|
>>> Choice.objects.filter(foo__exact=None)
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TypeError: Cannot resolve keyword 'foo' into field, choices are: id, poll, choice
|
TypeError: Cannot resolve keyword 'foo' into field. Choices are: id, poll, choice
|
||||||
|
|
||||||
# Can't use None on anything other than __exact
|
# Can't use None on anything other than __exact
|
||||||
>>> Choice.objects.filter(id__gt=None)
|
>>> Choice.objects.filter(id__gt=None)
|
||||||
|
@ -6,6 +6,7 @@ This class sets up a model for each model field type
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.contenttypes import generic
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
# The following classes are for testing basic data
|
# The following classes are for testing basic data
|
||||||
@ -80,7 +81,7 @@ class Tag(models.Model):
|
|||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
|
|
||||||
content_object = models.GenericForeignKey()
|
content_object = generic.GenericForeignKey()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["data"]
|
ordering = ["data"]
|
||||||
@ -88,7 +89,7 @@ class Tag(models.Model):
|
|||||||
class GenericData(models.Model):
|
class GenericData(models.Model):
|
||||||
data = models.CharField(maxlength=30)
|
data = models.CharField(maxlength=30)
|
||||||
|
|
||||||
tags = models.GenericRelation(Tag)
|
tags = generic.GenericRelation(Tag)
|
||||||
|
|
||||||
# The following test classes are all for validation
|
# The following test classes are all for validation
|
||||||
# of related objects; in particular, forward, backward,
|
# of related objects; in particular, forward, backward,
|
||||||
|
@ -592,6 +592,8 @@ class Templates(unittest.TestCase):
|
|||||||
'invalidstr03': ('{% for v in var %}({{ v }}){% endfor %}', {}, ''),
|
'invalidstr03': ('{% for v in var %}({{ v }}){% endfor %}', {}, ''),
|
||||||
'invalidstr04': ('{% if var %}Yes{% else %}No{% endif %}', {}, 'No'),
|
'invalidstr04': ('{% if var %}Yes{% else %}No{% endif %}', {}, 'No'),
|
||||||
'invalidstr04': ('{% if var|default:"Foo" %}Yes{% else %}No{% endif %}', {}, 'Yes'),
|
'invalidstr04': ('{% if var|default:"Foo" %}Yes{% else %}No{% endif %}', {}, 'Yes'),
|
||||||
|
'invalidstr05': ('{{ var }}', {}, ('', 'INVALID %s', 'var')),
|
||||||
|
'invalidstr06': ('{{ var.prop }}', {'var': {}}, ('', 'INVALID %s', 'var.prop')),
|
||||||
|
|
||||||
### MULTILINE #############################################################
|
### MULTILINE #############################################################
|
||||||
|
|
||||||
@ -743,6 +745,7 @@ class Templates(unittest.TestCase):
|
|||||||
|
|
||||||
# Set TEMPLATE_STRING_IF_INVALID to a known string
|
# Set TEMPLATE_STRING_IF_INVALID to a known string
|
||||||
old_invalid = settings.TEMPLATE_STRING_IF_INVALID
|
old_invalid = settings.TEMPLATE_STRING_IF_INVALID
|
||||||
|
expected_invalid_str = 'INVALID'
|
||||||
|
|
||||||
for name, vals in tests:
|
for name, vals in tests:
|
||||||
install()
|
install()
|
||||||
@ -750,6 +753,10 @@ class Templates(unittest.TestCase):
|
|||||||
if isinstance(vals[2], tuple):
|
if isinstance(vals[2], tuple):
|
||||||
normal_string_result = vals[2][0]
|
normal_string_result = vals[2][0]
|
||||||
invalid_string_result = vals[2][1]
|
invalid_string_result = vals[2][1]
|
||||||
|
if '%s' in invalid_string_result:
|
||||||
|
expected_invalid_str = 'INVALID %s'
|
||||||
|
invalid_string_result = invalid_string_result % vals[2][2]
|
||||||
|
template.invalid_var_format_string = True
|
||||||
else:
|
else:
|
||||||
normal_string_result = vals[2]
|
normal_string_result = vals[2]
|
||||||
invalid_string_result = vals[2]
|
invalid_string_result = vals[2]
|
||||||
@ -760,7 +767,7 @@ class Templates(unittest.TestCase):
|
|||||||
activate('en-us')
|
activate('en-us')
|
||||||
|
|
||||||
for invalid_str, result in [('', normal_string_result),
|
for invalid_str, result in [('', normal_string_result),
|
||||||
('INVALID', invalid_string_result)]:
|
(expected_invalid_str, invalid_string_result)]:
|
||||||
settings.TEMPLATE_STRING_IF_INVALID = invalid_str
|
settings.TEMPLATE_STRING_IF_INVALID = invalid_str
|
||||||
try:
|
try:
|
||||||
output = loader.get_template(name).render(template.Context(vals[1]))
|
output = loader.get_template(name).render(template.Context(vals[1]))
|
||||||
@ -774,6 +781,10 @@ class Templates(unittest.TestCase):
|
|||||||
if 'LANGUAGE_CODE' in vals[1]:
|
if 'LANGUAGE_CODE' in vals[1]:
|
||||||
deactivate()
|
deactivate()
|
||||||
|
|
||||||
|
if template.invalid_var_format_string:
|
||||||
|
expected_invalid_str = 'INVALID'
|
||||||
|
template.invalid_var_format_string = False
|
||||||
|
|
||||||
loader.template_source_loaders = old_template_loaders
|
loader.template_source_loaders = old_template_loaders
|
||||||
deactivate()
|
deactivate()
|
||||||
settings.TEMPLATE_DEBUG = old_td
|
settings.TEMPLATE_DEBUG = old_td
|
||||||
|
136
tests/regressiontests/test_client_regress/models.py
Normal file
136
tests/regressiontests/test_client_regress/models.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
"""
|
||||||
|
Regression tests for the Test Client, especially the customized assertions.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.test import Client, TestCase
|
||||||
|
from django.core import mail
|
||||||
|
|
||||||
|
class AssertTemplateUsedTests(TestCase):
|
||||||
|
fixtures = ['testdata.json']
|
||||||
|
|
||||||
|
def test_no_context(self):
|
||||||
|
"Template usage assertions work then templates aren't in use"
|
||||||
|
response = self.client.get('/test_client_regress/no_template_view/')
|
||||||
|
|
||||||
|
# Check that the no template case doesn't mess with the template assertions
|
||||||
|
self.assertTemplateNotUsed(response, 'GET Template')
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.assertTemplateUsed(response, 'GET Template')
|
||||||
|
except AssertionError, e:
|
||||||
|
self.assertEquals(str(e), "No templates used to render the response")
|
||||||
|
|
||||||
|
def test_single_context(self):
|
||||||
|
"Template assertions work when there is a single context"
|
||||||
|
response = self.client.get('/test_client/post_view/', {})
|
||||||
|
|
||||||
|
#
|
||||||
|
try:
|
||||||
|
self.assertTemplateNotUsed(response, 'Empty GET Template')
|
||||||
|
except AssertionError, e:
|
||||||
|
self.assertEquals(str(e), "Template 'Empty GET Template' was used unexpectedly in rendering the response")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.assertTemplateUsed(response, 'Empty POST Template')
|
||||||
|
except AssertionError, e:
|
||||||
|
self.assertEquals(str(e), "Template 'Empty POST Template' was not used to render the response. Actual template was 'Empty GET Template'")
|
||||||
|
|
||||||
|
def test_multiple_context(self):
|
||||||
|
"Template assertions work when there are multiple contexts"
|
||||||
|
post_data = {
|
||||||
|
'text': 'Hello World',
|
||||||
|
'email': 'foo@example.com',
|
||||||
|
'value': 37,
|
||||||
|
'single': 'b',
|
||||||
|
'multi': ('b','c','e')
|
||||||
|
}
|
||||||
|
response = self.client.post('/test_client/form_view_with_template/', post_data)
|
||||||
|
self.assertContains(response, 'POST data OK')
|
||||||
|
try:
|
||||||
|
self.assertTemplateNotUsed(response, "form_view.html")
|
||||||
|
except AssertionError, e:
|
||||||
|
self.assertEquals(str(e), "Template 'form_view.html' was used unexpectedly in rendering the response")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.assertTemplateNotUsed(response, 'base.html')
|
||||||
|
except AssertionError, e:
|
||||||
|
self.assertEquals(str(e), "Template 'base.html' was used unexpectedly in rendering the response")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.assertTemplateUsed(response, "Valid POST Template")
|
||||||
|
except AssertionError, e:
|
||||||
|
self.assertEquals(str(e), "Template 'Valid POST Template' was not one of the templates used to render the response. Templates used: form_view.html, base.html")
|
||||||
|
|
||||||
|
class AssertFormErrorTests(TestCase):
|
||||||
|
def test_unknown_form(self):
|
||||||
|
"An assertion is raised if the form name is unknown"
|
||||||
|
post_data = {
|
||||||
|
'text': 'Hello World',
|
||||||
|
'email': 'not an email address',
|
||||||
|
'value': 37,
|
||||||
|
'single': 'b',
|
||||||
|
'multi': ('b','c','e')
|
||||||
|
}
|
||||||
|
response = self.client.post('/test_client/form_view/', post_data)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTemplateUsed(response, "Invalid POST Template")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.')
|
||||||
|
except AssertionError, e:
|
||||||
|
self.assertEqual(str(e), "The form 'wrong_form' was not used to render the response")
|
||||||
|
|
||||||
|
def test_unknown_field(self):
|
||||||
|
"An assertion is raised if the field name is unknown"
|
||||||
|
post_data = {
|
||||||
|
'text': 'Hello World',
|
||||||
|
'email': 'not an email address',
|
||||||
|
'value': 37,
|
||||||
|
'single': 'b',
|
||||||
|
'multi': ('b','c','e')
|
||||||
|
}
|
||||||
|
response = self.client.post('/test_client/form_view/', post_data)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTemplateUsed(response, "Invalid POST Template")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.assertFormError(response, 'form', 'some_field', 'Some error.')
|
||||||
|
except AssertionError, e:
|
||||||
|
self.assertEqual(str(e), "The form 'form' in context 0 does not contain the field 'some_field'")
|
||||||
|
|
||||||
|
def test_noerror_field(self):
|
||||||
|
"An assertion is raised if the field doesn't have any errors"
|
||||||
|
post_data = {
|
||||||
|
'text': 'Hello World',
|
||||||
|
'email': 'not an email address',
|
||||||
|
'value': 37,
|
||||||
|
'single': 'b',
|
||||||
|
'multi': ('b','c','e')
|
||||||
|
}
|
||||||
|
response = self.client.post('/test_client/form_view/', post_data)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTemplateUsed(response, "Invalid POST Template")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.assertFormError(response, 'form', 'value', 'Some error.')
|
||||||
|
except AssertionError, e:
|
||||||
|
self.assertEqual(str(e), "The field 'value' on form 'form' in context 0 contains no errors")
|
||||||
|
|
||||||
|
def test_unknown_error(self):
|
||||||
|
"An assertion is raised if the field doesn't contain the provided error"
|
||||||
|
post_data = {
|
||||||
|
'text': 'Hello World',
|
||||||
|
'email': 'not an email address',
|
||||||
|
'value': 37,
|
||||||
|
'single': 'b',
|
||||||
|
'multi': ('b','c','e')
|
||||||
|
}
|
||||||
|
response = self.client.post('/test_client/form_view/', post_data)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTemplateUsed(response, "Invalid POST Template")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.assertFormError(response, 'form', 'email', 'Some error.')
|
||||||
|
except AssertionError, e:
|
||||||
|
self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])")
|
||||||
|
|
7
tests/regressiontests/test_client_regress/urls.py
Normal file
7
tests/regressiontests/test_client_regress/urls.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.conf.urls.defaults import *
|
||||||
|
from django.views.generic.simple import redirect_to
|
||||||
|
import views
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
(r'^no_template_view/$', views.no_template_view),
|
||||||
|
)
|
8
tests/regressiontests/test_client_regress/views.py
Normal file
8
tests/regressiontests/test_client_regress/views.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.core.mail import EmailMessage, SMTPConnection
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import render_to_response
|
||||||
|
|
||||||
|
def no_template_view(request):
|
||||||
|
"A simple view that expects a GET request, and returns a rendered template"
|
||||||
|
return HttpResponse("No template used")
|
||||||
|
|
8
tests/templates/base.html
Normal file
8
tests/templates/base.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>
|
||||||
|
<h1>Django Internal Tests: {% block title %}{% endblock %}</h1>
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
15
tests/templates/form_view.html
Normal file
15
tests/templates/form_view.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Submit data{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ message }}</h1>
|
||||||
|
<form method='post' action='.'>
|
||||||
|
{% if form.errors %}
|
||||||
|
<p class='warning'>Please correct the errors below:</p>
|
||||||
|
{% endif %}
|
||||||
|
<ul class='form'>
|
||||||
|
{{ form }}
|
||||||
|
<li><input type='submit' value='Submit'></li>
|
||||||
|
</ul>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -1,7 +1,6 @@
|
|||||||
<html>
|
{% extends "base.html" %}
|
||||||
<head></head>
|
{% block title %}Login{% endblock %}
|
||||||
<body>
|
{% block content %}
|
||||||
<h1>Django Internal Tests: Login</h1>
|
|
||||||
{% if form.has_errors %}
|
{% if form.has_errors %}
|
||||||
<p>Your username and password didn't match. Please try again.</p>
|
<p>Your username and password didn't match. Please try again.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -15,5 +14,4 @@
|
|||||||
<input type="submit" value="login" />
|
<input type="submit" value="login" />
|
||||||
<input type="hidden" name="next" value="{{ next }}" />
|
<input type="hidden" name="next" value="{{ next }}" />
|
||||||
</form>
|
</form>
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
@ -3,6 +3,7 @@ from django.conf.urls.defaults import *
|
|||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
# test_client modeltest urls
|
# test_client modeltest urls
|
||||||
(r'^test_client/', include('modeltests.test_client.urls')),
|
(r'^test_client/', include('modeltests.test_client.urls')),
|
||||||
|
(r'^test_client_regress/', include('regressiontests.test_client_regress.urls')),
|
||||||
|
|
||||||
# Always provide the auth system login and logout views
|
# Always provide the auth system login and logout views
|
||||||
(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
|
(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user