summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Bronn <jbronn@gmail.com>2008-07-04 20:16:22 +0000
committerJustin Bronn <jbronn@gmail.com>2008-07-04 20:16:22 +0000
commitbc3d6b49089ef0ea7d174eb383b1019b6a4a061a (patch)
treeb95728880e8a968ae6a921a21cec501f6a278d08
parentaef8a8305d2190b00386b8b1603deb03a2949f5b (diff)
gis: Merged revisions 7772-7808,7811-7814,7816-7823,7826-7829,7831-7833,7835 via svnmerge from trunk. Modified `GeoWhereNode` accordingly for changes in r7835.
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7836 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--AUTHORS6
-rw-r--r--django/conf/global_settings.py15
-rw-r--r--django/conf/locale/mk/LC_MESSAGES/django.mobin57210 -> 81806 bytes
-rw-r--r--django/conf/locale/mk/LC_MESSAGES/django.po4720
-rw-r--r--django/conf/locale/tr/LC_MESSAGES/django.mobin40120 -> 40265 bytes
-rw-r--r--django/conf/locale/tr/LC_MESSAGES/django.po7
-rw-r--r--django/contrib/auth/backends.py28
-rw-r--r--django/contrib/auth/fixtures/authtestdata.json56
-rw-r--r--django/contrib/auth/models.py4
-rw-r--r--django/contrib/auth/tests.py24
-rw-r--r--django/contrib/auth/urls.py13
-rw-r--r--django/contrib/flatpages/models.py2
-rw-r--r--django/contrib/formtools/tests.py6
-rw-r--r--django/contrib/gis/db/backend/mysql/query.py8
-rw-r--r--django/contrib/gis/db/backend/oracle/query.py16
-rw-r--r--django/contrib/gis/db/backend/postgis/query.py27
-rw-r--r--django/contrib/gis/db/models/fields/__init__.py2
-rw-r--r--django/contrib/gis/db/models/sql/query.py19
-rw-r--r--django/contrib/gis/db/models/sql/where.py71
-rw-r--r--django/contrib/gis/tests/distapp/tests.py12
-rw-r--r--django/contrib/gis/tests/geoapp/tests.py23
-rw-r--r--django/contrib/sitemaps/management/commands/ping_google.py14
-rw-r--r--django/core/files/__init__.py0
-rw-r--r--django/core/files/locks.py66
-rw-r--r--django/core/files/move.py59
-rw-r--r--django/core/files/uploadedfile.py190
-rw-r--r--django/core/files/uploadhandler.py245
-rw-r--r--django/core/handlers/modpython.py3
-rw-r--r--django/core/handlers/wsgi.py5
-rw-r--r--django/core/management/commands/createcachetable.py4
-rw-r--r--django/core/management/commands/loaddata.py6
-rw-r--r--django/core/management/sql.py13
-rw-r--r--django/core/paginator.py5
-rw-r--r--django/core/servers/basehttp.py3
-rw-r--r--django/core/servers/fastcgi.py4
-rw-r--r--django/core/urlresolvers.py5
-rw-r--r--django/db/backends/__init__.py2
-rw-r--r--django/db/backends/mysql/base.py3
-rw-r--r--django/db/backends/mysql_old/base.py3
-rw-r--r--django/db/backends/oracle/base.py22
-rw-r--r--django/db/backends/oracle/creation.py2
-rw-r--r--django/db/backends/postgresql/operations.py2
-rw-r--r--django/db/models/base.py92
-rw-r--r--django/db/models/fields/__init__.py38
-rw-r--r--django/db/models/fields/related.py35
-rw-r--r--django/db/models/options.py37
-rw-r--r--django/db/models/query.py5
-rw-r--r--django/db/models/query_utils.py17
-rw-r--r--django/db/models/sql/query.py167
-rw-r--r--django/db/models/sql/where.py95
-rw-r--r--django/db/transaction.py5
-rw-r--r--django/http/__init__.py64
-rw-r--r--django/http/multipartparser.py658
-rw-r--r--django/middleware/http.py4
-rw-r--r--django/newforms/fields.py72
-rw-r--r--django/oldforms/__init__.py19
-rw-r--r--django/test/client.py105
-rw-r--r--django/test/testcases.py25
-rw-r--r--django/utils/daemonize.py9
-rw-r--r--django/utils/datastructures.py50
-rw-r--r--django/utils/text.py24
-rw-r--r--django/utils/tree.py31
-rw-r--r--docs/apache_auth.txt8
-rw-r--r--docs/db-api.txt25
-rw-r--r--docs/flatpages.txt7
-rw-r--r--docs/model-api.txt2
-rw-r--r--docs/newforms.txt4
-rw-r--r--docs/request_response.txt27
-rw-r--r--docs/settings.txt39
-rw-r--r--docs/sitemaps.txt11
-rw-r--r--docs/templates.txt4
-rw-r--r--docs/testing.txt31
-rw-r--r--docs/upload_handling.txt346
-rw-r--r--tests/modeltests/model_forms/models.py96
-rw-r--r--tests/modeltests/pagination/models.py23
-rw-r--r--tests/regressiontests/bug639/tests.py11
-rw-r--r--tests/regressiontests/datastructures/tests.py25
-rw-r--r--tests/regressiontests/extra_regress/__init__.py0
-rw-r--r--tests/regressiontests/extra_regress/models.py55
-rw-r--r--tests/regressiontests/file_uploads/__init__.py0
-rw-r--r--tests/regressiontests/file_uploads/models.py2
-rw-r--r--tests/regressiontests/file_uploads/tests.py158
-rw-r--r--tests/regressiontests/file_uploads/uploadhandler.py26
-rw-r--r--tests/regressiontests/file_uploads/urls.py10
-rw-r--r--tests/regressiontests/file_uploads/views.py70
-rw-r--r--tests/regressiontests/fixtures_regress/fixtures/big-fixture.json83
-rw-r--r--tests/regressiontests/fixtures_regress/fixtures/model-inheritance.json4
-rw-r--r--tests/regressiontests/fixtures_regress/models.py48
-rw-r--r--tests/regressiontests/forms/error_messages.py9
-rw-r--r--tests/regressiontests/forms/fields.py33
-rw-r--r--tests/regressiontests/forms/forms.py5
-rw-r--r--tests/regressiontests/many_to_one_regress/models.py40
-rw-r--r--tests/regressiontests/model_fields/tests.py17
-rw-r--r--tests/regressiontests/model_inheritance_regress/models.py22
-rw-r--r--tests/regressiontests/model_inheritance_select_related/__init__.py0
-rw-r--r--tests/regressiontests/model_inheritance_select_related/models.py47
-rw-r--r--tests/regressiontests/queries/models.py132
-rw-r--r--tests/regressiontests/select_related_regress/__init__.py0
-rw-r--r--tests/regressiontests/select_related_regress/models.py60
-rw-r--r--tests/regressiontests/string_lookup/models.py6
-rw-r--r--tests/regressiontests/test_client_regress/models.py31
-rw-r--r--tests/regressiontests/test_client_regress/urls.py1
-rw-r--r--tests/regressiontests/test_client_regress/views.py24
-rw-r--r--tests/urls.py3
104 files changed, 6765 insertions, 2047 deletions
diff --git a/AUTHORS b/AUTHORS
index 94dc8891a4..164ec50404 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -59,7 +59,7 @@ answer newbie questions, and generally made Django that much better:
Arthur <avandorp@gmail.com>
av0000@mail.ru
David Avsajanishvili <avsd05@gmail.com>
- axiak@mit.edu
+ Mike Axiak <axiak@mit.edu>
Niran Babalola <niran@niran.org>
Morten Bagai <m@bagai.com>
Mikaël Barbero <mikael.barbero nospam at nospam free.fr>
@@ -94,6 +94,7 @@ answer newbie questions, and generally made Django that much better:
Sengtha Chay <sengtha@e-khmer.com>
ivan.chelubeev@gmail.com
Bryan Chow <bryan at verdjn dot com>
+ Antonis Christofides <anthony@itia.ntua.gr>
Michal Chruszcz <troll@pld-linux.org>
Can Burak Çilingir <canburak@cs.bilgi.edu.tr>
Ian Clelland <clelland@gmail.com>
@@ -140,7 +141,9 @@ answer newbie questions, and generally made Django that much better:
Marc Fargas <telenieko@telenieko.com>
Szilveszter Farkas <szilveszter.farkas@gmail.com>
favo@exoweb.net
+ fdr <drfarina@gmail.com>
Dmitri Fedortchenko <zeraien@gmail.com>
+ Jonathan Feignberg <jdf@pobox.com>
Liang Feng <hutuworm@gmail.com>
Bill Fenner <fenner@gmail.com>
Stefane Fermgier <sf@fermigier.com>
@@ -192,6 +195,7 @@ answer newbie questions, and generally made Django that much better:
james_027@yahoo.com
jcrasta@gmail.com
Zak Johnson <zakj@nox.cx>
+ Nis Jørgensen <nis@superlativ.dk>
Michael Josephson <http://www.sdjournal.com/>
jpellerin@gmail.com
junzhang.jn@gmail.com
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 006ab421dd..2c9720da10 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -231,6 +231,21 @@ MEDIA_ROOT = ''
# Example: "http://media.lawrence.com"
MEDIA_URL = ''
+# List of upload handler classes to be applied in order.
+FILE_UPLOAD_HANDLERS = (
+ 'django.core.files.uploadhandler.MemoryFileUploadHandler',
+ 'django.core.files.uploadhandler.TemporaryFileUploadHandler',
+)
+
+# Maximum size, in bytes, of a request before it will be streamed to the
+# file system instead of into memory.
+FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB
+
+# Directory in which upload streamed files will be temporarily saved. A value of
+# `None` will make Django use the operating system's default temporary directory
+# (i.e. "/tmp" on *nix systems).
+FILE_UPLOAD_TEMP_DIR = None
+
# Default formatting for date objects. See all available format strings here:
# http://www.djangoproject.com/documentation/templates/#now
DATE_FORMAT = 'N j, Y'
diff --git a/django/conf/locale/mk/LC_MESSAGES/django.mo b/django/conf/locale/mk/LC_MESSAGES/django.mo
index 21e3ff7c5d..6dfd651e76 100644
--- a/django/conf/locale/mk/LC_MESSAGES/django.mo
+++ b/django/conf/locale/mk/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/mk/LC_MESSAGES/django.po b/django/conf/locale/mk/LC_MESSAGES/django.po
index c28d098515..5e531a4d12 100644
--- a/django/conf/locale/mk/LC_MESSAGES/django.po
+++ b/django/conf/locale/mk/LC_MESSAGES/django.po
@@ -1,12 +1,12 @@
# translation of django.po to Macedonian
#
-# Georgi Stanojevski <glisha@gmail.com>, 2006, 2007.
+# Georgi Stanojevski <glisha@gmail.com>, 2006, 2007, 2008.
msgid ""
msgstr ""
-"Project-Id-Version: Django\n"
+"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-04-07 19:51+0200\n"
-"PO-Revision-Date: 2007-04-07 20:29+0200\n"
+"POT-Creation-Date: 2008-06-18 12:26+0200\n"
+"PO-Revision-Date: 2008-06-28 16:36+0200\n"
"Last-Translator: Georgi Stanojevski <glisha@gmail.com>\n"
"Language-Team: Macedonian <ossm-members@hedona.on.net.mk>\n"
"MIME-Version: 1.0\n"
@@ -15,750 +15,195 @@ msgstr ""
"X-Generator: KBabel 1.11.4\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-#: db/models/manipulators.py:307
-#, python-format
-msgid "%(object)s with this %(type)s already exists for the given %(field)s."
-msgstr "%(object)s од овој тип %(type)s веќе постои за даденото %(field)s."
-
-#: db/models/manipulators.py:308 contrib/admin/views/main.py:335
-#: contrib/admin/views/main.py:337 contrib/admin/views/main.py:339
-msgid "and"
-msgstr "и"
-
-#: db/models/fields/related.py:53
-#, python-format
-msgid "Please enter a valid %s."
-msgstr "Ве молам внесете правилно %s."
-
-#: db/models/fields/related.py:642
-msgid "Separate multiple IDs with commas."
-msgstr "Одвојте ги идентификационите броеви со запирки."
-
-#: db/models/fields/related.py:644
-msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
-msgstr "Држете го „Control“ или „Command“ на Мекинтош за да изберете повеќе од едно."
-
-#: db/models/fields/related.py:691
-#, python-format
-msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
-msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid."
-msgstr[0] ""
-"Ве молам внесете правилен %(self)s идентификацион број. Оваа вредност %"
-"(value)r е неправилна."
-msgstr[1] ""
-"Ве молам внесете правилен %(self)s идентификацион број. Вредностите %(value)"
-"r се неправилни."
-
-#: db/models/fields/__init__.py:42
-#, python-format
-msgid "%(optname)s with this %(fieldname)s already exists."
-msgstr "%(optname)s со ова %(fieldname)s веќе постои."
-
-#: db/models/fields/__init__.py:117 db/models/fields/__init__.py:274
-#: db/models/fields/__init__.py:610 db/models/fields/__init__.py:621
-#: oldforms/__init__.py:357 newforms/fields.py:80 newforms/fields.py:376
-#: newforms/fields.py:452 newforms/fields.py:463 newforms/models.py:178
-msgid "This field is required."
-msgstr "Ова поле е задолжително."
-
-#: db/models/fields/__init__.py:367
-msgid "This value must be an integer."
-msgstr "Оваа вредност мора да биде цел број."
-
-#: db/models/fields/__init__.py:402
-msgid "This value must be either True or False."
-msgstr "Оваа вредност мора да биде или точно или неточно."
-
-#: db/models/fields/__init__.py:423
-msgid "This field cannot be null."
-msgstr "Оваа вредност неможе да биде null."
-
-#: db/models/fields/__init__.py:457 core/validators.py:148
-msgid "Enter a valid date in YYYY-MM-DD format."
-msgstr "Внесете правилен датум во форматот ГГГГ-ММ-ДД."
-
-#: db/models/fields/__init__.py:526 core/validators.py:157
-msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
-msgstr "Внесете правилен датум/време во форматот ГГГГ-ММ-ДД ЧЧ:ММ."
-
-#: db/models/fields/__init__.py:630
-msgid "Enter a valid filename."
-msgstr "Внесите правилно име на датотека."
-
-#: db/models/fields/__init__.py:751
-msgid "This value must be either None, True or False."
-msgstr "Оваа вредност мора да биде празна, точно или неточно."
-
-#: conf/global_settings.py:39
+#: conf/global_settings.py:44
msgid "Arabic"
msgstr "Арапски"
-#: conf/global_settings.py:40
+#: conf/global_settings.py:45
msgid "Bengali"
msgstr "Бенгалски"
-#: conf/global_settings.py:41
+#: conf/global_settings.py:46
+msgid "Bulgarian"
+msgstr "Бугарски"
+
+#: conf/global_settings.py:47
msgid "Catalan"
msgstr "Каталански"
-#: conf/global_settings.py:42
+#: conf/global_settings.py:48
msgid "Czech"
msgstr "Чешки"
-#: conf/global_settings.py:43
+#: conf/global_settings.py:49
msgid "Welsh"
msgstr "Велшки"
-#: conf/global_settings.py:44
+#: conf/global_settings.py:50
msgid "Danish"
msgstr "Дански"
-#: conf/global_settings.py:45
+#: conf/global_settings.py:51
msgid "German"
msgstr "Германски"
-#: conf/global_settings.py:46
+#: conf/global_settings.py:52
msgid "Greek"
msgstr "Грчки"
-#: conf/global_settings.py:47
+#: conf/global_settings.py:53
msgid "English"
msgstr "Англиски"
-#: conf/global_settings.py:48
+#: conf/global_settings.py:54
msgid "Spanish"
msgstr "Шпански"
-#: conf/global_settings.py:49
+#: conf/global_settings.py:55
msgid "Argentinean Spanish"
msgstr "Аргентиско шпански"
-#: conf/global_settings.py:50
+#: conf/global_settings.py:56
+msgid "Basque"
+msgstr "Баскиски"
+
+#: conf/global_settings.py:57
+msgid "Persian"
+msgstr "Персиски"
+
+#: conf/global_settings.py:58
msgid "Finnish"
msgstr "Фински"
-#: conf/global_settings.py:51
+#: conf/global_settings.py:59
msgid "French"
msgstr "Француски"
-#: conf/global_settings.py:52
+#: conf/global_settings.py:60
+msgid "Irish"
+msgstr "Ирски"
+
+#: conf/global_settings.py:61
msgid "Galician"
msgstr "Галски"
-#: conf/global_settings.py:53
+#: conf/global_settings.py:62
msgid "Hungarian"
msgstr "Унгарски"
-#: conf/global_settings.py:54
+#: conf/global_settings.py:63
msgid "Hebrew"
msgstr "Еврејски"
-#: conf/global_settings.py:55
+#: conf/global_settings.py:64
+msgid "Croatian"
+msgstr "Хрватски"
+
+#: conf/global_settings.py:65
msgid "Icelandic"
msgstr "Исландски"
-#: conf/global_settings.py:56
+#: conf/global_settings.py:66
msgid "Italian"
msgstr "Италијански"
-#: conf/global_settings.py:57
+#: conf/global_settings.py:67
msgid "Japanese"
msgstr "Јапонски"
-#: conf/global_settings.py:58
+#: conf/global_settings.py:68
+msgid "Georgian"
+msgstr "Грузиски"
+
+#: conf/global_settings.py:69
+msgid "Korean"
+msgstr "Корејски"
+
+#: conf/global_settings.py:70
+msgid "Khmer"
+msgstr "Кмер"
+
+#: conf/global_settings.py:71
msgid "Kannada"
msgstr "Канада"
-#: conf/global_settings.py:59
+#: conf/global_settings.py:72
msgid "Latvian"
msgstr "Латвиски"
-#: conf/global_settings.py:60
+#: conf/global_settings.py:73
msgid "Macedonian"
msgstr "Македонски"
-#: conf/global_settings.py:61
+#: conf/global_settings.py:74
msgid "Dutch"
msgstr "Холандски"
-#: conf/global_settings.py:62
+#: conf/global_settings.py:75
msgid "Norwegian"
msgstr "Норвешки"
-#: conf/global_settings.py:63
+#: conf/global_settings.py:76
msgid "Polish"
msgstr "Полски"
-#: conf/global_settings.py:64
+#: conf/global_settings.py:77
msgid "Portugese"
msgstr "Португалкски"
-#: conf/global_settings.py:65
-msgid "Brazilian"
-msgstr "Бразилски"
+#: conf/global_settings.py:78
+msgid "Brazilian Portuguese"
+msgstr "Бразилско португалски"
-#: conf/global_settings.py:66
+#: conf/global_settings.py:79
msgid "Romanian"
msgstr "Романски"
-#: conf/global_settings.py:67
+#: conf/global_settings.py:80
msgid "Russian"
msgstr "Руски"
-#: conf/global_settings.py:68
+#: conf/global_settings.py:81
msgid "Slovak"
msgstr "Словачки"
-#: conf/global_settings.py:69
+#: conf/global_settings.py:82
msgid "Slovenian"
msgstr "Словенечки"
-#: conf/global_settings.py:70
+#: conf/global_settings.py:83
msgid "Serbian"
msgstr "Српски"
-#: conf/global_settings.py:71
+#: conf/global_settings.py:84
msgid "Swedish"
msgstr "Шведски"
-#: conf/global_settings.py:72
+#: conf/global_settings.py:85
msgid "Tamil"
msgstr "Тамил"
-#: conf/global_settings.py:73
+#: conf/global_settings.py:86
msgid "Telugu"
msgstr "Телугу"
-#: conf/global_settings.py:74
+#: conf/global_settings.py:87
msgid "Turkish"
msgstr "Турски"
-#: conf/global_settings.py:75
+#: conf/global_settings.py:88
msgid "Ukrainian"
msgstr "Украински"
-#: conf/global_settings.py:76
+#: conf/global_settings.py:89
msgid "Simplified Chinese"
msgstr "Упростен кинески"
-#: conf/global_settings.py:77
+#: conf/global_settings.py:90
msgid "Traditional Chinese"
msgstr "Традиционален кинески"
-#: core/validators.py:64
-msgid "This value must contain only letters, numbers and underscores."
-msgstr "Оваа вредност смее да има само букви, бројки или долни црти."
-
-#: core/validators.py:68
-msgid ""
-"This value must contain only letters, numbers, underscores, dashes or "
-"slashes."
-msgstr "Оваа вредност смее да има само букви, бројки, долни црти, црти или коси црти."
-
-#: core/validators.py:72
-msgid "This value must contain only letters, numbers, underscores or hyphens."
-msgstr "Оваа вредност смее да содржи само букви, бројки, долни црти или црти."
-
-#: core/validators.py:76
-msgid "Uppercase letters are not allowed here."
-msgstr "Големи букви не се дозволени."
-
-#: core/validators.py:80
-msgid "Lowercase letters are not allowed here."
-msgstr "Мали букви не се дозволени."
-
-#: core/validators.py:87
-msgid "Enter only digits separated by commas."
-msgstr "Внесете само цифри одделени со запирки."
-
-#: core/validators.py:99
-msgid "Enter valid e-mail addresses separated by commas."
-msgstr "Внесете валидни адреси за е-пошта одделени со запирки."
-
-#: core/validators.py:103
-msgid "Please enter a valid IP address."
-msgstr "Ве молам внесете валидна ИП адреса."
-
-#: core/validators.py:107
-msgid "Empty values are not allowed here."
-msgstr "Празни вредности не се дозволени."
-
-#: core/validators.py:111
-msgid "Non-numeric characters aren't allowed here."
-msgstr "Ненумерички знаци не се дозволени тука."
-
-#: core/validators.py:115
-msgid "This value can't be comprised solely of digits."
-msgstr "Оваа вредност не смее да биде само од цифри."
-
-#: core/validators.py:120 newforms/fields.py:128
-msgid "Enter a whole number."
-msgstr "Внеси цел број."
-
-#: core/validators.py:124
-msgid "Only alphabetical characters are allowed here."
-msgstr "Дозволени се само букви."
-
-#: core/validators.py:139
-msgid "Year must be 1900 or later."
-msgstr "Годината мора да биде 1900 или покасно."
-
-#: core/validators.py:143
-#, python-format
-msgid "Invalid date: %s"
-msgstr "Неправилен датум: %s"
-
-#: core/validators.py:153
-msgid "Enter a valid time in HH:MM format."
-msgstr "Внесете правилно време во форматот HH:MM."
-
-#: core/validators.py:162 newforms/fields.py:271
-msgid "Enter a valid e-mail address."
-msgstr "Внесeте правилна адреса за е-пошта."
-
-#: core/validators.py:174 core/validators.py:445 oldforms/__init__.py:672
-msgid "No file was submitted. Check the encoding type on the form."
-msgstr "Не беше пратена датотека. Проверете го типот на енкодирање на формата."
-
-#: core/validators.py:178
-msgid ""
-"Upload a valid image. The file you uploaded was either not an image or a "
-"corrupted image."
-msgstr ""
-"Качете валидна фотографија. Датотеката која ја качивте или не беше "
-"фотографија или беше расипана датотеката."
-
-#: core/validators.py:185
-#, python-format
-msgid "The URL %s does not point to a valid image."
-msgstr "Адресата %s не покажува кон валидна фотографија."
-
-#: core/validators.py:189
-#, python-format
-msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid."
-msgstr ""
-"Телефонските броеви мора да бидат во XXX-XXX-XXXX форматот. „%s“ не е "
-"валиден."
-
-#: core/validators.py:197
-#, python-format
-msgid "The URL %s does not point to a valid QuickTime video."
-msgstr "Адресата „%s“ не покажува кон QuickTime видео."
-
-#: core/validators.py:201
-msgid "A valid URL is required."
-msgstr "Задолжителна е правилна адреса."
-
-#: core/validators.py:215
-#, python-format
-msgid ""
-"Valid HTML is required. Specific errors are:\n"
-"%s"
-msgstr ""
-"Задолжителен е правилен HTML. Грешките се:\n"
-"%s"
-
-#: core/validators.py:222
-#, python-format
-msgid "Badly formed XML: %s"
-msgstr "Неправилно формиран XML: %s"
-
-#: core/validators.py:239
-#, python-format
-msgid "Invalid URL: %s"
-msgstr "Неправилна адреса: %s"
-
-#: core/validators.py:244 core/validators.py:246
-#, python-format
-msgid "The URL %s is a broken link."
-msgstr "Адресата %s е скршена врска."
-
-#: core/validators.py:252
-msgid "Enter a valid U.S. state abbreviation."
-msgstr "Внесете правилна скратеница за држава во САД."
-
-#: core/validators.py:266
-#, python-format
-msgid "Watch your mouth! The word %s is not allowed here."
-msgid_plural "Watch your mouth! The words %s are not allowed here."
-msgstr[0] "Внимавајте на јазикот. Тука не е дозволен зборот %s."
-msgstr[1] "Внимавајте на јазикот. Тука не се дозволени зборовите %s."
-
-#: core/validators.py:273
-#, python-format
-msgid "This field must match the '%s' field."
-msgstr "Ова поле мора да соодејствува со полето „%s“."
-
-#: core/validators.py:292
-msgid "Please enter something for at least one field."
-msgstr "Ве молам внесете нешто во барем едно поле."
-
-#: core/validators.py:301 core/validators.py:312
-msgid "Please enter both fields or leave them both empty."
-msgstr "Ве молам внесете во двете полиња или оставете ги двете празни."
-
-#: core/validators.py:320
-#, python-format
-msgid "This field must be given if %(field)s is %(value)s"
-msgstr "Ова поле мора да биде зададено ако %(field)s е %(value)s"
-
-#: core/validators.py:333
-#, python-format
-msgid "This field must be given if %(field)s is not %(value)s"
-msgstr "Ова поле мора да биде зададено ако %(field)s не е %(value)s"
-
-#: core/validators.py:352
-msgid "Duplicate values are not allowed."
-msgstr "Дупликат вредности не се дозволени."
-
-#: core/validators.py:367
-#, python-format
-msgid "This value must be between %(lower)s and %(upper)s."
-msgstr "Оваа вредноста мора да биде помеѓу %(lower)s и %(upper)s."
-
-#: core/validators.py:369
-#, python-format
-msgid "This value must be at least %s."
-msgstr "Оваа вредноста мора да биде најмалку %s."
-
-#: core/validators.py:371
-#, python-format
-msgid "This value must be no more than %s."
-msgstr "Оваа вредност не смее да биде поголема од %s."
-
-#: core/validators.py:407
-#, python-format
-msgid "This value must be a power of %s."
-msgstr "Оваа вредноста мора да биде степен од %s."
-
-#: core/validators.py:418
-msgid "Please enter a valid decimal number."
-msgstr "Ве молам внесете правилен децимален број."
-
-#: core/validators.py:422
-#, python-format
-msgid "Please enter a valid decimal number with at most %s total digit."
-msgid_plural "Please enter a valid decimal number with at most %s total digits."
-msgstr[0] "Ве молам внесете правилен децимален број со најмногу %s цифрa."
-msgstr[1] "Ве молам внесете правилен децимален број со најмногу %s вкупно цифри."
-
-#: core/validators.py:425
-#, python-format
-msgid "Please enter a valid decimal number with a whole part of at most %s digit."
-msgid_plural "Please enter a valid decimal number with a whole part of at most %s digits."
-msgstr[0] ""
-"Ве молам внесете правилен децимален број кој во целиот број има најмногу %s "
-"цифра."
-msgstr[1] ""
-"Ве молам внесете правилен децимален број кој во целиот број има најмногу %s "
-"цифри."
-
-#: core/validators.py:428
-#, python-format
-msgid "Please enter a valid decimal number with at most %s decimal place."
-msgid_plural "Please enter a valid decimal number with at most %s decimal places."
-msgstr[0] "Ве молам внесете правилен децимален број кој има најмногу %s децимална цифра."
-msgstr[1] "Ве молам внесете правилен децимален број кој има најмногу %s децимални цифри."
-
-#: core/validators.py:438
-#, python-format
-msgid "Make sure your uploaded file is at least %s bytes big."
-msgstr "Потврдете дека качената датотека има најмалку %s бајти."
-
-#: core/validators.py:439
-#, python-format
-msgid "Make sure your uploaded file is at most %s bytes big."
-msgstr "Потврдете дека качената датотека има најмногу %s бајти."
-
-#: core/validators.py:456
-msgid "The format for this field is wrong."
-msgstr "Форматот за ова поле е грешен."
-
-#: core/validators.py:471
-msgid "This field is invalid."
-msgstr "Ова поле не е правилно."
-
-#: core/validators.py:507
-#, python-format
-msgid "Could not retrieve anything from %s."
-msgstr "Неможев да извадам ништо од %s."
-
-#: core/validators.py:510
-#, python-format
-msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'."
-msgstr "Адресата %(url)s врати неправилно заглавје Content-Type „%(contenttype)s“."
-
-#: core/validators.py:543
-#, python-format
-msgid ""
-"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "
-"\"%(start)s\".)"
-msgstr ""
-"Ве молам затворете го отворениот %(tag)s таг од линијата %(line)s. (линијата "
-"почнува со „%(start)s“.)"
-
-#: core/validators.py:547
-#, python-format
-msgid ""
-"Some text starting on line %(line)s is not allowed in that context. (Line "
-"starts with \"%(start)s\".)"
-msgstr ""
-"Некој текст кој почнува на линијата %(line)s не е дозволен во тој контекст. "
-"(Линијата започнува со „%(start)s“.)"
-
-#: core/validators.py:552
-#, python-format
-msgid ""
-"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%"
-"(start)s\".)"
-msgstr ""
-"„%(attr)s“ на линија %(line)s е неправилен атрибут. (линијата започнува со „%"
-"(start)s“.)"
-
-#: core/validators.py:557
-#, python-format
-msgid ""
-"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%"
-"(start)s\".)"
-msgstr ""
-"„<%(tag)s>“ на линија %(line)s е неправилен таг. (линијата започнува со „%"
-"(start)s“.)"
-
-#: core/validators.py:561
-#, python-format
-msgid ""
-"A tag on line %(line)s is missing one or more required attributes. (Line "
-"starts with \"%(start)s\".)"
-msgstr ""
-"На таг од линијата %(line)s му недостасува еден или повеќе од потребните "
-"атрибути (линијата започнува со „%(start)s“)."
-
-#: core/validators.py:566
-#, python-format
-msgid ""
-"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line "
-"starts with \"%(start)s\".)"
-msgstr ""
-"Атрибутот „%(attr)s“ на линијата %(line)s има неправилна вредност (линијата "
-"започнува со „%(start)s“)."
-
-#: contrib/auth/forms.py:17 contrib/auth/forms.py:138
-msgid "The two password fields didn't match."
-msgstr "Двете полиња со лозинките не се совпаѓаат."
-
-#: contrib/auth/forms.py:25
-msgid "A user with that username already exists."
-msgstr "Веќе постои корисник со тоа корисничко име."
-
-#: contrib/auth/forms.py:53
-msgid ""
-"Your Web browser doesn't appear to have cookies enabled. Cookies are "
-"required for logging in."
-msgstr ""
-"Не изгледа дека вашиот прелистувач има овозможено колачиња. Колачињата се "
-"потребни за да се најавите."
-
-#: contrib/auth/forms.py:60 contrib/admin/views/decorators.py:10
-msgid ""
-"Please enter a correct username and password. Note that both fields are case-"
-"sensitive."
-msgstr ""
-"Ве молам внесете точно корисничко име и лозинка. Имајте на ум дека и во "
-"двете полиња се битни големите и малите букви."
-
-#: contrib/auth/forms.py:62
-msgid "This account is inactive."
-msgstr "Оваа сметка е неактивна."
-
-#: contrib/auth/forms.py:85
-msgid ""
-"That e-mail address doesn't have an associated user account. Are you sure "
-"you've registered?"
-msgstr ""
-"Нема регистрирано корисник со оваа адреса за е-пошта. Сигурни ли сте дека "
-"сте регистрирани?"
-
-#: contrib/auth/forms.py:117
-msgid "The two 'new password' fields didn't match."
-msgstr "Двете нови лозинки не се совпаѓаат."
-
-#: contrib/auth/forms.py:124
-msgid "Your old password was entered incorrectly. Please enter it again."
-msgstr "Не ја внесовте точно вашата стара лозинка. Ве молам внесете ја повторно."
-
-#: contrib/auth/models.py:38 contrib/auth/models.py:58
-msgid "name"
-msgstr "име"
-
-#: contrib/auth/models.py:40
-msgid "codename"
-msgstr "кодно име"
-
-#: contrib/auth/models.py:43
-msgid "permission"
-msgstr "привилегија"
-
-#: contrib/auth/models.py:44 contrib/auth/models.py:59
-msgid "permissions"
-msgstr "привилегии"
-
-#: contrib/auth/models.py:62
-msgid "group"
-msgstr "група"
-
-#: contrib/auth/models.py:63 contrib/auth/models.py:103
-msgid "groups"
-msgstr "групи"
-
-#: contrib/auth/models.py:93
-msgid "username"
-msgstr "корисничко име"
-
-#: contrib/auth/models.py:93
-msgid ""
-"Required. 30 characters or fewer. Alphanumeric characters only (letters, "
-"digits and underscores)."
-msgstr ""
-"Дозволени се најмногу 30 знаци. Дозволени се само алфанумерички знаци "
-"(букви, цифри и долна црта)."
-
-#: contrib/auth/models.py:94
-msgid "first name"
-msgstr "име"
-
-#: contrib/auth/models.py:95
-msgid "last name"
-msgstr "презиме"
-
-#: contrib/auth/models.py:96
-msgid "e-mail address"
-msgstr "е-пошта"
-
-#: contrib/auth/models.py:97
-msgid "password"
-msgstr "лозинка"
-
-#: contrib/auth/models.py:97
-msgid ""
-"Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change "
-"password form</a>."
-msgstr ""
-"Користете '[algo]$[salt]$[hexdigest]' или користете ја <a href=\"password/"
-"\">формата за промена на лозинката</a>."
-
-#: contrib/auth/models.py:98
-msgid "staff status"
-msgstr "статус на администраторите"
-
-#: contrib/auth/models.py:98
-msgid "Designates whether the user can log into this admin site."
-msgstr "Означува дали корисникот може да се логира во сајтот за администрација."
-
-#: contrib/auth/models.py:99
-msgid "active"
-msgstr "активен"
-
-#: contrib/auth/models.py:99
-msgid ""
-"Designates whether this user can log into the Django admin. Unselect this "
-"instead of deleting accounts."
-msgstr ""
-"Означува дали корисникот може да се логира. Одштиклирајте го ова наместо да "
-"бришете корисници."
-
-#: contrib/auth/models.py:100
-msgid "superuser status"
-msgstr "статус на суперкорисник"
-
-#: contrib/auth/models.py:100
-msgid ""
-"Designates that this user has all permissions without explicitly assigning "
-"them."
-msgstr ""
-"Означува дека овој корисник ги има сите привилегии без експлицитно да се "
-"доделуваат сите."
-
-#: contrib/auth/models.py:101
-msgid "last login"
-msgstr "последна најава"
-
-#: contrib/auth/models.py:102
-msgid "date joined"
-msgstr "датум на зачленување"
-
-#: contrib/auth/models.py:104
-msgid ""
-"In addition to the permissions manually assigned, this user will also get "
-"all permissions granted to each group he/she is in."
-msgstr ""
-"Како дополнување на рачно доделени привилегии, овој корисник ќе ги добие "
-"автоматски и сите привилегии за секоја група во која тој/таа членува."
-
-#: contrib/auth/models.py:105
-msgid "user permissions"
-msgstr "кориснички привилегии"
-
-#: contrib/auth/models.py:109
-msgid "user"
-msgstr "корисник"
-
-#: contrib/auth/models.py:110
-msgid "users"
-msgstr "корисници"
-
-#: contrib/auth/models.py:116
-msgid "Personal info"
-msgstr "Лични информации"
-
-#: contrib/auth/models.py:117
-msgid "Permissions"
-msgstr "Привилегии"
-
-#: contrib/auth/models.py:118
-msgid "Important dates"
-msgstr "Важни датуми"
-
-#: contrib/auth/models.py:119
-msgid "Groups"
-msgstr "Групи"
-
-#: contrib/auth/models.py:263
-msgid "message"
-msgstr "порака"
-
-#: contrib/auth/views.py:39
-msgid "Logged out"
-msgstr "Одјавен"
-
-#: contrib/admin/models.py:16
-msgid "action time"
-msgstr "време на акција"
-
-#: contrib/admin/models.py:19
-msgid "object id"
-msgstr "идентификационен број на објект"
-
-#: contrib/admin/models.py:20
-msgid "object repr"
-msgstr "object repr"
-
-#: contrib/admin/models.py:21
-msgid "action flag"
-msgstr "знакче за акција"
-
-#: contrib/admin/models.py:22
-msgid "change message"
-msgstr "измени ја пораката"
-
-#: contrib/admin/models.py:25
-msgid "log entry"
-msgstr "ставка во записникот"
-
-#: contrib/admin/models.py:26
-msgid "log entries"
-msgstr "ставки во записникот"
-
-#: contrib/admin/filterspecs.py:40
+#: contrib/admin/filterspecs.py:44
#, python-format
msgid ""
"<h3>By %s:</h3>\n"
@@ -767,411 +212,188 @@ msgstr ""
"<h3>Од %s:</h3>\n"
"<ul>\n"
-#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88
-#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169
+#: contrib/admin/filterspecs.py:74 contrib/admin/filterspecs.py:92
+#: contrib/admin/filterspecs.py:147 contrib/admin/filterspecs.py:173
msgid "All"
msgstr "Сите"
-#: contrib/admin/filterspecs.py:109
+#: contrib/admin/filterspecs.py:113
msgid "Any date"
msgstr "Било кој датум"
-#: contrib/admin/filterspecs.py:110
+#: contrib/admin/filterspecs.py:114
msgid "Today"
msgstr "Денеска"
-#: contrib/admin/filterspecs.py:113
+#: contrib/admin/filterspecs.py:117
msgid "Past 7 days"
msgstr "Последните 7 дена"
-#: contrib/admin/filterspecs.py:115
+#: contrib/admin/filterspecs.py:119
msgid "This month"
msgstr "Овој месец"
-#: contrib/admin/filterspecs.py:117
+#: contrib/admin/filterspecs.py:121
msgid "This year"
msgstr "Оваа година"
-#: contrib/admin/filterspecs.py:143 oldforms/__init__.py:577
-#: newforms/widgets.py:174
+#: contrib/admin/filterspecs.py:147 newforms/widgets.py:231
+#: oldforms/__init__.py:592
msgid "Yes"
msgstr "Да"
-#: contrib/admin/filterspecs.py:143 oldforms/__init__.py:577
-#: newforms/widgets.py:174
+#: contrib/admin/filterspecs.py:147 newforms/widgets.py:231
+#: oldforms/__init__.py:592
msgid "No"
msgstr "Не"
-#: contrib/admin/filterspecs.py:150 oldforms/__init__.py:577
-#: newforms/widgets.py:174
+#: contrib/admin/filterspecs.py:154 newforms/widgets.py:231
+#: oldforms/__init__.py:592
msgid "Unknown"
msgstr "Непознато"
-#: contrib/admin/views/decorators.py:24
-#: contrib/admin/templates/admin/login.html:25
-msgid "Log in"
-msgstr "Најава"
-
-#: contrib/admin/views/decorators.py:62
-msgid ""
-"Please log in again, because your session has expired. Don't worry: Your "
-"submission has been saved."
-msgstr ""
-"Ве молам најавете се повторно бидејќи вашата сесија е истечена. Не се "
-"грижете. Вашите внесови беа зачувани."
-
-#: contrib/admin/views/decorators.py:69
-msgid ""
-"Looks like your browser isn't configured to accept cookies. Please enable "
-"cookies, reload this page, and try again."
-msgstr ""
-"Изгледа дека вашиот прелистувач не е конфигуриран да прифаќа колачиња. Ве "
-"молам овозможете ги колачињата, превчитајте ја страта и пробајте повторно."
+#: contrib/admin/models.py:18
+msgid "action time"
+msgstr "време на акција"
-#: contrib/admin/views/decorators.py:83
-msgid "Usernames cannot contain the '@' character."
-msgstr "Корисничките имиња неможе да го содржат „@“ знакот."
+#: contrib/admin/models.py:21
+msgid "object id"
+msgstr "идентификационен број на објект"
-#: contrib/admin/views/decorators.py:85
-#, python-format
-msgid "Your e-mail address is not your username. Try '%s' instead."
-msgstr "Вашата е-пошта не е вашето корисничко име. Пробајте со „%s“."
+#: contrib/admin/models.py:22
+msgid "object repr"
+msgstr "object repr"
-#: contrib/admin/views/main.py:223
-msgid "Site administration"
-msgstr "Администрација на сајт"
+#: contrib/admin/models.py:23
+msgid "action flag"
+msgstr "знакче за акција"
-#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:19
-#, python-format
-msgid "The %(name)s \"%(obj)s\" was added successfully."
-msgstr "%(name)s \"%(obj)s\" беше успешно додаден."
+#: contrib/admin/models.py:24
+msgid "change message"
+msgstr "измени ја пораката"
-#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347
-#: contrib/admin/views/auth.py:24
-msgid "You may edit it again below."
-msgstr "Подолу можете повторно да го уредите."
+#: contrib/admin/models.py:27
+msgid "log entry"
+msgstr "ставка во записникот"
-#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356
-#, python-format
-msgid "You may add another %s below."
-msgstr "Подолу можете да додате уште еден %s."
+#: contrib/admin/models.py:28
+msgid "log entries"
+msgstr "ставки во записникот"
-#: contrib/admin/views/main.py:289
-#, python-format
-msgid "Add %s"
-msgstr "Додади %s"
+#: contrib/admin/templates/admin/404.html:4
+#: contrib/admin/templates/admin/404.html:8
+msgid "Page not found"
+msgstr "Страницата не е најдена"
-#: contrib/admin/views/main.py:335
-#, python-format
-msgid "Added %s."
-msgstr "Додадено %s."
+#: contrib/admin/templates/admin/404.html:10
+msgid "We're sorry, but the requested page could not be found."
+msgstr "Се извинуваме, но неможе да ја најдеме страницата која ја баравте."
-#: contrib/admin/views/main.py:337
-#, python-format
-msgid "Changed %s."
-msgstr "Изменета %s."
+#: contrib/admin/templates/admin/500.html:4
+#: contrib/admin/templates/admin/base.html:37
+#: contrib/admin/templates/admin/change_form.html:12
+#: contrib/admin/templates/admin/change_list.html:5
+#: contrib/admin/templates/admin/delete_confirmation.html:5
+#: contrib/admin/templates/admin/invalid_setup.html:4
+#: contrib/admin/templates/admin/object_history.html:4
+#: contrib/admin/templates/admin/auth/user/change_password.html:11
+#: contrib/admin/templates/admin_doc/bookmarklets.html:3
+#: contrib/admin/templates/registration/logged_out.html:4
+#: contrib/admin/templates/registration/password_change_done.html:3
+#: contrib/admin/templates/registration/password_change_form.html:3
+#: contrib/admin/templates/registration/password_reset_done.html:4
+#: contrib/admin/templates/registration/password_reset_form.html:4
+msgid "Home"
+msgstr "Дома"
-#: contrib/admin/views/main.py:339
-#, python-format
-msgid "Deleted %s."
-msgstr "Избришана %s."
+#: contrib/admin/templates/admin/500.html:4
+msgid "Server error"
+msgstr "Грешка со серверот"
-#: contrib/admin/views/main.py:342
-msgid "No fields changed."
-msgstr "Не беше изменето ниедно поле."
+#: contrib/admin/templates/admin/500.html:6
+msgid "Server error (500)"
+msgstr "Грешка со серверот (500)"
-#: contrib/admin/views/main.py:345
-#, python-format
-msgid "The %(name)s \"%(obj)s\" was changed successfully."
-msgstr "%(name)s \"%(obj)s\" беше успешно изменета."
+#: contrib/admin/templates/admin/500.html:9
+msgid "Server Error <em>(500)</em>"
+msgstr "Грешка со серверот <em>(500)</em>"
-#: contrib/admin/views/main.py:353
-#, python-format
-msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
+#: contrib/admin/templates/admin/500.html:10
+msgid ""
+"There's been an error. It's been reported to the site administrators via e-"
+"mail and should be fixed shortly. Thanks for your patience."
msgstr ""
-"%(name)s \"%(obj)s\" беше успешно додадена.Подолу можете повторно да ја "
-"уредите."
-
-#: contrib/admin/views/main.py:391
-#, python-format
-msgid "Change %s"
-msgstr "Измени %s"
-
-#: contrib/admin/views/main.py:476
-#, python-format
-msgid "One or more %(fieldname)s in %(name)s: %(obj)s"
-msgstr "Еден или повеќе %(fieldname)s во %(name)s: %(obj)s"
-
-#: contrib/admin/views/main.py:481
-#, python-format
-msgid "One or more %(fieldname)s in %(name)s:"
-msgstr "Еден или повеќе %(fieldname)s во %(name)s:"
-
-#: contrib/admin/views/main.py:514
-#, python-format
-msgid "The %(name)s \"%(obj)s\" was deleted successfully."
-msgstr "%(name)s \"%(obj)s\" беше избришана успешно."
-
-#: contrib/admin/views/main.py:517
-msgid "Are you sure?"
-msgstr "Сигурни сте?"
-
-#: contrib/admin/views/main.py:539
-#, python-format
-msgid "Change history: %s"
-msgstr "Историја на измени: %s"
-
-#: contrib/admin/views/main.py:573
-#, python-format
-msgid "Select %s"
-msgstr "Изберет %s"
-
-#: contrib/admin/views/main.py:573
-#, python-format
-msgid "Select %s to change"
-msgstr "Изберете %s за измена"
-
-#: contrib/admin/views/main.py:768
-msgid "Database error"
-msgstr "Грешка во базата со податоци"
-
-#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48
-#: contrib/admin/views/doc.py:50
-msgid "tag:"
-msgstr "таг:"
-
-#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79
-#: contrib/admin/views/doc.py:81
-msgid "filter:"
-msgstr "филтер:"
-
-#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137
-#: contrib/admin/views/doc.py:139
-msgid "view:"
-msgstr "поглед:"
-
-#: contrib/admin/views/doc.py:164
-#, python-format
-msgid "App %r not found"
-msgstr "Не е најдена апликацијата %r"
-
-#: contrib/admin/views/doc.py:171
-#, python-format
-msgid "Model %(name)r not found in app %(label)r"
-msgstr "Моделот %(name)r не е најден во апликацијата %(label)r"
-
-#: contrib/admin/views/doc.py:183
-#, python-format
-msgid "the related `%(label)s.%(type)s` object"
-msgstr "поврзаниот`%(label)s.%(type)s` објект"
-
-#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205
-#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224
-msgid "model:"
-msgstr "модел:"
-
-#: contrib/admin/views/doc.py:214
-#, python-format
-msgid "related `%(label)s.%(name)s` objects"
-msgstr "поврзани `%(label)s.%(name)s` објекти"
-
-#: contrib/admin/views/doc.py:219
-#, python-format
-msgid "all %s"
-msgstr "сите %s"
-
-#: contrib/admin/views/doc.py:224
-#, python-format
-msgid "number of %s"
-msgstr "број на %s"
-
-#: contrib/admin/views/doc.py:229
-#, python-format
-msgid "Fields on %s objects"
-msgstr "Полиња на %s објекти"
-
-#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301
-#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309
-#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312
-msgid "Integer"
-msgstr "Цел број"
-
-#: contrib/admin/views/doc.py:292
-msgid "Boolean (Either True or False)"
-msgstr "Логичка (или точно или неточно)"
-
-#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311
-#, python-format
-msgid "String (up to %(maxlength)s)"
-msgstr "Збор (до %(maxlength)s)"
-
-#: contrib/admin/views/doc.py:294
-msgid "Comma-separated integers"
-msgstr "Целобројни вредности одделени со запирка"
-
-#: contrib/admin/views/doc.py:295
-msgid "Date (without time)"
-msgstr "Датум (без час)"
-
-#: contrib/admin/views/doc.py:296
-msgid "Date (with time)"
-msgstr "Датум (со час)"
-
-#: contrib/admin/views/doc.py:297
-msgid "E-mail address"
-msgstr "Адреса на е-пошта"
-
-#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299
-#: contrib/admin/views/doc.py:302
-msgid "File path"
-msgstr "Патека на датотека"
-
-#: contrib/admin/views/doc.py:300
-msgid "Decimal number"
-msgstr "Децимален број"
+"Имаше грешка. Истата беше пријавена на администраторите и ќе биде поправена "
+"во брзо време. Ви благодариме за вашето трпение."
-#: contrib/admin/views/doc.py:304 contrib/comments/models.py:85
-msgid "IP address"
-msgstr "ИП адреса"
+#: contrib/admin/templates/admin/base.html:26
+msgid "Welcome,"
+msgstr "Добредојдовте,"
-#: contrib/admin/views/doc.py:306
-msgid "Boolean (Either True, False or None)"
-msgstr "Логичка (точно,неточно или празно)"
+#: contrib/admin/templates/admin/base.html:28
+#: contrib/admin/templates/admin_doc/bookmarklets.html:3
+msgid "Documentation"
+msgstr "Документација"
-#: contrib/admin/views/doc.py:307
-msgid "Relation to parent model"
-msgstr "Релација со родителскиот модел"
+#: contrib/admin/templates/admin/base.html:29
+#: contrib/admin/templates/admin/auth/user/change_password.html:14
+#: contrib/admin/templates/admin/auth/user/change_password.html:45
+msgid "Change password"
+msgstr "Промени лозинка"
-#: contrib/admin/views/doc.py:308
-msgid "Phone number"
-msgstr "Телефонски број"
+#: contrib/admin/templates/admin/base.html:30
+#: contrib/comments/templates/comments/form.html:6
+msgid "Log out"
+msgstr "Одјава"
-#: contrib/admin/views/doc.py:313
-msgid "Text"
-msgstr "Текст"
+#: contrib/admin/templates/admin/base_site.html:4
+msgid "Django site admin"
+msgstr "Џанго администрација на сајт"
-#: contrib/admin/views/doc.py:314
-msgid "Time"
-msgstr "Час"
+#: contrib/admin/templates/admin/base_site.html:7
+msgid "Django administration"
+msgstr "Џанго администрација"
-#: contrib/admin/views/doc.py:315 contrib/flatpages/models.py:7
-msgid "URL"
-msgstr "URL"
+#: contrib/admin/templates/admin/change_form.html:14
+#: contrib/admin/templates/admin/index.html:28
+msgid "Add"
+msgstr "Додади"
-#: contrib/admin/views/doc.py:316
-msgid "U.S. state (two uppercase letters)"
-msgstr "Држава во САД (две големи букви)"
+#: contrib/admin/templates/admin/change_form.html:20
+#: contrib/admin/templates/admin/object_history.html:4
+msgid "History"
+msgstr "Историја"
-#: contrib/admin/views/doc.py:317
-msgid "XML text"
-msgstr "XML текст"
+#: contrib/admin/templates/admin/change_form.html:21
+msgid "View on site"
+msgstr "Погледни на сајтот"
-#: contrib/admin/views/doc.py:343
-#, python-format
-msgid "%s does not appear to be a urlpattern object"
-msgstr "%s не изгледа дека е url објект"
+#: contrib/admin/templates/admin/change_form.html:31
+#: contrib/admin/templates/admin/auth/user/change_password.html:23
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] "Ве молам поправете ја грешката подолу."
+msgstr[1] "Ве молам поправете ги грешките подолу."
-#: contrib/admin/views/auth.py:30
-msgid "Add user"
-msgstr "Додади корисник"
+#: contrib/admin/templates/admin/change_form.html:49
+msgid "Ordering"
+msgstr "Подредување"
-#: contrib/admin/views/auth.py:57
-msgid "Password changed successfully."
-msgstr "Успешна промена на лозинката."
+#: contrib/admin/templates/admin/change_form.html:52
+msgid "Order:"
+msgstr "Подреди:"
-#: contrib/admin/views/auth.py:64
+#: contrib/admin/templates/admin/change_list.html:11
#, python-format
-msgid "Change password: %s"
-msgstr "Промени лозинка: %s"
-
-#: contrib/admin/templatetags/admin_list.py:247
-msgid "All dates"
-msgstr "Сите датуми"
-
-#: contrib/admin/templates/admin/pagination.html:10
-msgid "Show all"
-msgstr "Прикажи ги сите"
-
-#: contrib/admin/templates/admin/delete_confirmation.html:3
-#: contrib/admin/templates/admin/change_form.html:10
-#: contrib/admin/templates/admin/change_list.html:5
-#: contrib/admin/templates/admin/object_history.html:3
-#: contrib/admin/templates/admin/base.html:25
-#: contrib/admin/templates/admin/auth/user/change_password.html:9
-#: contrib/admin/templates/admin_doc/bookmarklets.html:3
-#: contrib/admin/templates/registration/password_change_form.html:3
-#: contrib/admin/templates/registration/password_change_done.html:3
-msgid "Documentation"
-msgstr "Документација"
-
-#: contrib/admin/templates/admin/delete_confirmation.html:3
-#: contrib/admin/templates/admin/change_form.html:10
-#: contrib/admin/templates/admin/change_list.html:5
-#: contrib/admin/templates/admin/object_history.html:3
-#: contrib/admin/templates/admin/base.html:25
-#: contrib/admin/templates/admin/auth/user/change_password.html:9
-#: contrib/admin/templates/admin/auth/user/change_password.html:15
-#: contrib/admin/templates/admin/auth/user/change_password.html:46
-#: contrib/admin/templates/admin_doc/template_filter_index.html:5
-#: contrib/admin/templates/admin_doc/bookmarklets.html:4
-#: contrib/admin/templates/admin_doc/template_tag_index.html:5
-#: contrib/admin/templates/admin_doc/index.html:4
-#: contrib/admin/templates/admin_doc/model_detail.html:3
-#: contrib/admin/templates/admin_doc/missing_docutils.html:4
-#: contrib/admin/templates/admin_doc/template_detail.html:4
-#: contrib/admin/templates/admin_doc/view_index.html:5
-#: contrib/admin/templates/admin_doc/model_index.html:5
-#: contrib/admin/templates/admin_doc/view_detail.html:4
-#: contrib/admin/templates/registration/password_change_form.html:3
-#: contrib/admin/templates/registration/password_change_done.html:3
-msgid "Change password"
-msgstr "Промени лозинка"
-
-#: contrib/admin/templates/admin/delete_confirmation.html:3
-#: contrib/admin/templates/admin/change_form.html:10
-#: contrib/admin/templates/admin/change_list.html:5
-#: contrib/admin/templates/admin/object_history.html:3
-#: contrib/admin/templates/admin/base.html:25
-#: contrib/admin/templates/admin/auth/user/change_password.html:9
-#: contrib/admin/templates/admin_doc/template_filter_index.html:5
-#: contrib/admin/templates/admin_doc/bookmarklets.html:4
-#: contrib/admin/templates/admin_doc/template_tag_index.html:5
-#: contrib/admin/templates/admin_doc/index.html:4
-#: contrib/admin/templates/admin_doc/model_detail.html:3
-#: contrib/admin/templates/admin_doc/missing_docutils.html:4
-#: contrib/admin/templates/admin_doc/template_detail.html:4
-#: contrib/admin/templates/admin_doc/view_index.html:5
-#: contrib/admin/templates/admin_doc/model_index.html:5
-#: contrib/admin/templates/admin_doc/view_detail.html:4
-#: contrib/admin/templates/registration/password_change_form.html:3
-#: contrib/admin/templates/registration/password_change_done.html:3
-#: contrib/comments/templates/comments/form.html:6
-msgid "Log out"
-msgstr "Одјава"
-
-#: contrib/admin/templates/admin/delete_confirmation.html:6
-#: contrib/admin/templates/admin/change_form.html:13
-#: contrib/admin/templates/admin/change_list.html:6
-#: contrib/admin/templates/admin/object_history.html:5
-#: contrib/admin/templates/admin/500.html:4
-#: contrib/admin/templates/admin/invalid_setup.html:4
-#: contrib/admin/templates/admin/base.html:30
-#: contrib/admin/templates/admin/auth/user/change_password.html:12
-#: contrib/admin/templates/admin_doc/bookmarklets.html:3
-#: contrib/admin/templates/registration/password_reset_form.html:4
-#: contrib/admin/templates/registration/logged_out.html:4
-#: contrib/admin/templates/registration/password_reset_done.html:4
-#: contrib/admin/templates/registration/password_change_form.html:4
-#: contrib/admin/templates/registration/password_change_done.html:4
-msgid "Home"
-msgstr "Дома"
+msgid "Add %(name)s"
+msgstr "Додади %(name)s"
-#: contrib/admin/templates/admin/delete_confirmation.html:9
+#: contrib/admin/templates/admin/delete_confirmation.html:8
#: contrib/admin/templates/admin/submit_line.html:3
msgid "Delete"
msgstr "Избриши"
-#: contrib/admin/templates/admin/delete_confirmation.html:14
+#: contrib/admin/templates/admin/delete_confirmation.html:13
#, python-format
msgid ""
"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting "
@@ -1182,7 +404,7 @@ msgstr ""
"поврзаните објекти, но со вашата сметка немате доволно привилегии да ги "
"бришете следните типови на објекти:"
-#: contrib/admin/templates/admin/delete_confirmation.html:21
+#: contrib/admin/templates/admin/delete_confirmation.html:20
#, python-format
msgid ""
"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? "
@@ -1191,73 +413,18 @@ msgstr ""
"Сигурне сте дека сакате да ги бришете %(object_name)s „%(escaped_object)s“? "
"Сите овие ставки ќе бидат избришани:"
-#: contrib/admin/templates/admin/delete_confirmation.html:26
+#: contrib/admin/templates/admin/delete_confirmation.html:25
msgid "Yes, I'm sure"
msgstr "Да, сигурен сум"
-#: contrib/admin/templates/admin/404.html:4
-#: contrib/admin/templates/admin/404.html:8
-msgid "Page not found"
-msgstr "Страницата не е најдена"
-
-#: contrib/admin/templates/admin/404.html:10
-msgid "We're sorry, but the requested page could not be found."
-msgstr "Се извинуваме, но неможе да ја најдеме страницата која ја баравте."
-
-#: contrib/admin/templates/admin/change_form.html:15
-#: contrib/admin/templates/admin/index.html:28
-msgid "Add"
-msgstr "Додади"
-
-#: contrib/admin/templates/admin/change_form.html:21
-#: contrib/admin/templates/admin/object_history.html:5
-msgid "History"
-msgstr "Историја"
-
-#: contrib/admin/templates/admin/change_form.html:22
-msgid "View on site"
-msgstr "Погледни на сајтот"
-
-#: contrib/admin/templates/admin/change_form.html:32
-#: contrib/admin/templates/admin/auth/user/change_password.html:24
-msgid "Please correct the error below."
-msgid_plural "Please correct the errors below."
-msgstr[0] "Ве молам поправете ја грешката подолу."
-msgstr[1] "Ве молам поправете ги грешките подолу."
-
-#: contrib/admin/templates/admin/change_form.html:50
-msgid "Ordering"
-msgstr "Подредување"
-
-#: contrib/admin/templates/admin/change_form.html:53
-msgid "Order:"
-msgstr "Подреди:"
-
#: contrib/admin/templates/admin/filter.html:2
#, python-format
msgid " By %(filter_title)s "
msgstr " Од %(filter_title)s "
-#: contrib/admin/templates/admin/submit_line.html:4
-msgid "Save as new"
-msgstr "Сними како нова"
-
-#: contrib/admin/templates/admin/submit_line.html:5
-msgid "Save and add another"
-msgstr "Сними и додади уште"
-
-#: contrib/admin/templates/admin/submit_line.html:6
-msgid "Save and continue editing"
-msgstr "Сними и продолжи со уредување"
-
-#: contrib/admin/templates/admin/submit_line.html:7
-msgid "Save"
-msgstr "Сними"
-
-#: contrib/admin/templates/admin/change_list.html:12
-#, python-format
-msgid "Add %(name)s"
-msgstr "Додади %(name)s"
+#: contrib/admin/templates/admin/filters.html:4
+msgid "Filter"
+msgstr "Филтер"
#: contrib/admin/templates/admin/index.html:17
#, python-format
@@ -1289,31 +456,49 @@ msgstr "Мои акции"
msgid "None available"
msgstr "Ништо не е достапно"
-#: contrib/admin/templates/admin/base_site.html:4
-msgid "Django site admin"
-msgstr "Џанго администрација на сајт"
+#: contrib/admin/templates/admin/invalid_setup.html:8
+msgid ""
+"Something's wrong with your database installation. Make sure the appropriate "
+"database tables have been created, and make sure the database is readable by "
+"the appropriate user."
+msgstr ""
+"Нешто не е во ред со инсталацијата на базата на податоци. Потврдете дека "
+"соодветни табели во базата се направени и потврдете дека базата може да биде "
+"прочитана од соодветниот корисник."
-#: contrib/admin/templates/admin/base_site.html:7
-msgid "Django administration"
-msgstr "Џанго администрација"
+#: contrib/admin/templates/admin/login.html:17
+#: contrib/comments/templates/comments/form.html:6
+#: contrib/comments/templates/comments/form.html:8
+msgid "Username:"
+msgstr "Корисник:"
-#: contrib/admin/templates/admin/object_history.html:18
+#: contrib/admin/templates/admin/login.html:20
+#: contrib/comments/templates/comments/form.html:8
+msgid "Password:"
+msgstr "Лозинка:"
+
+#: contrib/admin/templates/admin/login.html:25
+#: contrib/admin/views/decorators.py:31
+msgid "Log in"
+msgstr "Најава"
+
+#: contrib/admin/templates/admin/object_history.html:17
msgid "Date/time"
msgstr "Датум/час"
-#: contrib/admin/templates/admin/object_history.html:19
+#: contrib/admin/templates/admin/object_history.html:18
msgid "User"
msgstr "Корисник"
-#: contrib/admin/templates/admin/object_history.html:20
+#: contrib/admin/templates/admin/object_history.html:19
msgid "Action"
msgstr "Акција"
-#: contrib/admin/templates/admin/object_history.html:26
+#: contrib/admin/templates/admin/object_history.html:25
msgid "DATE_WITH_TIME_FULL"
msgstr "j M Y, H:i"
-#: contrib/admin/templates/admin/object_history.html:36
+#: contrib/admin/templates/admin/object_history.html:35
msgid ""
"This object doesn't have a change history. It probably wasn't added via this "
"admin site."
@@ -1321,35 +506,9 @@ msgstr ""
"Овој објект нема историја на измени. Најверојатно не бил додаден со админ "
"сајтот."
-#: contrib/admin/templates/admin/500.html:4
-msgid "Server error"
-msgstr "Грешка со серверот"
-
-#: contrib/admin/templates/admin/500.html:6
-msgid "Server error (500)"
-msgstr "Грешка со серверот (500)"
-
-#: contrib/admin/templates/admin/500.html:9
-msgid "Server Error <em>(500)</em>"
-msgstr "Грешка со серверот <em>(500)</em>"
-
-#: contrib/admin/templates/admin/500.html:10
-msgid ""
-"There's been an error. It's been reported to the site administrators via e-"
-"mail and should be fixed shortly. Thanks for your patience."
-msgstr ""
-"Имаше грешка. Истата беше пријавена на администраторите и ќе биде поправена "
-"во брзо време. Ви благодариме за вашето трпение."
-
-#: contrib/admin/templates/admin/invalid_setup.html:8
-msgid ""
-"Something's wrong with your database installation. Make sure the appropriate "
-"database tables have been created, and make sure the database is readable by "
-"the appropriate user."
-msgstr ""
-"Нешто не е во ред со инсталацијата на базата на податоци. Потврдете дека "
-"соодветни табели во базата се направени и потврдете дека базата може да биде "
-"прочитана од соодветниот корисник."
+#: contrib/admin/templates/admin/pagination.html:10
+msgid "Show all"
+msgstr "Прикажи ги сите"
#: contrib/admin/templates/admin/search_form.html:8
msgid "Go"
@@ -1367,24 +526,21 @@ msgstr[1] "%(counter)s резултати"
msgid "%(full_result_count)s total"
msgstr "вкупно %(full_result_count)s"
-#: contrib/admin/templates/admin/filters.html:4
-msgid "Filter"
-msgstr "Филтер"
+#: contrib/admin/templates/admin/submit_line.html:4
+msgid "Save as new"
+msgstr "Сними како нова"
-#: contrib/admin/templates/admin/login.html:17
-#: contrib/comments/templates/comments/form.html:6
-#: contrib/comments/templates/comments/form.html:8
-msgid "Username:"
-msgstr "Корисник:"
+#: contrib/admin/templates/admin/submit_line.html:5
+msgid "Save and add another"
+msgstr "Сними и додади уште"
-#: contrib/admin/templates/admin/login.html:20
-#: contrib/comments/templates/comments/form.html:8
-msgid "Password:"
-msgstr "Лозинка:"
+#: contrib/admin/templates/admin/submit_line.html:6
+msgid "Save and continue editing"
+msgstr "Сними и продолжи со уредување"
-#: contrib/admin/templates/admin/base.html:25
-msgid "Welcome,"
-msgstr "Добредојдовте,"
+#: contrib/admin/templates/admin/submit_line.html:7
+msgid "Save"
+msgstr "Сними"
#: contrib/admin/templates/admin/auth/user/add_form.html:6
msgid ""
@@ -1399,21 +555,21 @@ msgid "Username"
msgstr "Корисник"
#: contrib/admin/templates/admin/auth/user/add_form.html:18
-#: contrib/admin/templates/admin/auth/user/change_password.html:34
+#: contrib/admin/templates/admin/auth/user/change_password.html:33
msgid "Password"
msgstr "Лозинка"
#: contrib/admin/templates/admin/auth/user/add_form.html:23
-#: contrib/admin/templates/admin/auth/user/change_password.html:39
+#: contrib/admin/templates/admin/auth/user/change_password.html:38
msgid "Password (again)"
msgstr "Лозинка (повторно)"
#: contrib/admin/templates/admin/auth/user/add_form.html:24
-#: contrib/admin/templates/admin/auth/user/change_password.html:40
+#: contrib/admin/templates/admin/auth/user/change_password.html:39
msgid "Enter the same password as above, for verification."
msgstr "Заради верификација внесете ја истата лозинка како и горе."
-#: contrib/admin/templates/admin/auth/user/change_password.html:28
+#: contrib/admin/templates/admin/auth/user/change_password.html:27
#, python-format
msgid "Enter a new password for the user <strong>%(username)s</strong>."
msgstr "Внесете нова лозинка за корисникот <strong>%(username)s</strong>."
@@ -1422,11 +578,11 @@ msgstr "Внесете нова лозинка за корисникот <strong
msgid "Bookmarklets"
msgstr "Обележувачи"
-#: contrib/admin/templates/admin_doc/bookmarklets.html:5
+#: contrib/admin/templates/admin_doc/bookmarklets.html:4
msgid "Documentation bookmarklets"
msgstr "Обележувачи на документација"
-#: contrib/admin/templates/admin_doc/bookmarklets.html:9
+#: contrib/admin/templates/admin_doc/bookmarklets.html:8
msgid ""
"\n"
"<p class=\"help\">To install bookmarklets, drag the link to your bookmarks\n"
@@ -1447,11 +603,11 @@ msgstr ""
"сајтот од компјутер кој е означен како „внатрешен“ (разговарајте со вашиот \n"
"администратор ако не сте сигурни).</p>\n"
-#: contrib/admin/templates/admin_doc/bookmarklets.html:19
+#: contrib/admin/templates/admin_doc/bookmarklets.html:18
msgid "Documentation for this page"
msgstr "Документација за оваа страница"
-#: contrib/admin/templates/admin_doc/bookmarklets.html:20
+#: contrib/admin/templates/admin_doc/bookmarklets.html:19
msgid ""
"Jumps you from any page to the documentation for the view that generates "
"that page."
@@ -1459,11 +615,11 @@ msgstr ""
"Ве носи од било која страница од документацијата до погледот кој ја генерира "
"таа страница."
-#: contrib/admin/templates/admin_doc/bookmarklets.html:22
+#: contrib/admin/templates/admin_doc/bookmarklets.html:21
msgid "Show object ID"
msgstr "Прикажи идентификационен број на објектот"
-#: contrib/admin/templates/admin_doc/bookmarklets.html:23
+#: contrib/admin/templates/admin_doc/bookmarklets.html:22
msgid ""
"Shows the content-type and unique ID for pages that represent a single "
"object."
@@ -1471,60 +627,89 @@ msgstr ""
"Го прикажува типот на содржината и уникатниот идентификационен број за "
"страници кои претставуваат единечен објект."
-#: contrib/admin/templates/admin_doc/bookmarklets.html:25
+#: contrib/admin/templates/admin_doc/bookmarklets.html:24
msgid "Edit this object (current window)"
msgstr "Уреди го овој објект (во овој прозорец)"
-#: contrib/admin/templates/admin_doc/bookmarklets.html:26
+#: contrib/admin/templates/admin_doc/bookmarklets.html:25
msgid "Jumps to the admin page for pages that represent a single object."
msgstr "Скокнува до админ страницата за страници кои претставуваат единечен објект."
-#: contrib/admin/templates/admin_doc/bookmarklets.html:28
+#: contrib/admin/templates/admin_doc/bookmarklets.html:27
msgid "Edit this object (new window)"
msgstr "Уреди го овој објект (во нов прозорец)"
-#: contrib/admin/templates/admin_doc/bookmarklets.html:29
+#: contrib/admin/templates/admin_doc/bookmarklets.html:28
msgid "As above, but opens the admin page in a new window."
msgstr "Како погоре, но ја отвара админ страницата во нов прозорец."
-#: contrib/admin/templates/widget/date_time.html:3
-msgid "Date:"
-msgstr "Датум:"
+#: contrib/admin/templates/registration/logged_out.html:8
+msgid "Thanks for spending some quality time with the Web site today."
+msgstr "Ви благодариме што денеска поминавте квалитетно време со интернет страницава."
-#: contrib/admin/templates/widget/date_time.html:4
-msgid "Time:"
-msgstr "Време:"
+#: contrib/admin/templates/registration/logged_out.html:10
+msgid "Log in again"
+msgstr "Логирајте се повторно"
-#: contrib/admin/templates/widget/file.html:2
-msgid "Currently:"
-msgstr "Моментално:"
+#: contrib/admin/templates/registration/password_change_done.html:3
+#: contrib/admin/templates/registration/password_change_form.html:3
+#: contrib/admin/templates/registration/password_change_form.html:5
+#: contrib/admin/templates/registration/password_change_form.html:9
+msgid "Password change"
+msgstr "Измена на лозинка"
-#: contrib/admin/templates/widget/file.html:3
-msgid "Change:"
-msgstr "Измена:"
+#: contrib/admin/templates/registration/password_change_done.html:5
+#: contrib/admin/templates/registration/password_change_done.html:9
+msgid "Password change successful"
+msgstr "Успешна промена на лозинката"
+
+#: contrib/admin/templates/registration/password_change_done.html:11
+msgid "Your password was changed."
+msgstr "Вашата лозинка беше сменета."
+
+#: contrib/admin/templates/registration/password_change_form.html:11
+msgid ""
+"Please enter your old password, for security's sake, and then enter your new "
+"password twice so we can verify you typed it in correctly."
+msgstr ""
+"Заради сигурност ве молам внесете ја вашата стара лозинка и потоа внесете ја "
+"новата двапати за да може да се потврди дека правилно сте ја искуцале."
+
+#: contrib/admin/templates/registration/password_change_form.html:16
+msgid "Old password:"
+msgstr "Стара лозинка:"
+
+#: contrib/admin/templates/registration/password_change_form.html:18
+msgid "New password:"
+msgstr "Нова лозинка:"
+
+#: contrib/admin/templates/registration/password_change_form.html:20
+msgid "Confirm password:"
+msgstr "Потврди лозинка:"
+
+#: contrib/admin/templates/registration/password_change_form.html:22
+msgid "Change my password"
+msgstr "Промени ја мојата лозинка"
+#: contrib/admin/templates/registration/password_reset_done.html:4
#: contrib/admin/templates/registration/password_reset_form.html:4
#: contrib/admin/templates/registration/password_reset_form.html:6
#: contrib/admin/templates/registration/password_reset_form.html:10
-#: contrib/admin/templates/registration/password_reset_done.html:4
msgid "Password reset"
msgstr "Ресетирање на лозинка"
-#: contrib/admin/templates/registration/password_reset_form.html:12
+#: contrib/admin/templates/registration/password_reset_done.html:6
+#: contrib/admin/templates/registration/password_reset_done.html:10
+msgid "Password reset successful"
+msgstr "Успешно е ресетирањето на лозинката"
+
+#: contrib/admin/templates/registration/password_reset_done.html:12
msgid ""
-"Forgotten your password? Enter your e-mail address below, and we'll reset "
-"your password and e-mail the new one to you."
+"We've e-mailed a new password to the e-mail address you submitted. You "
+"should be receiving it shortly."
msgstr ""
-"Сте ја заборавиле вашата лозинка? Внесете ја вашата е-пошта подолу, ќе ја "
-"ресетираме вашата лозинка и новата ќе ви ја пратиме по е-пошта."
-
-#: contrib/admin/templates/registration/password_reset_form.html:16
-msgid "E-mail address:"
-msgstr "Е-пошта:"
-
-#: contrib/admin/templates/registration/password_reset_form.html:16
-msgid "Reset my password"
-msgstr "Ресетирај ја мојата лозинка"
+"Ви пративме нова лозинка на адресата која ја внесовте.Треба да ја примите за "
+"кратко време."
#: contrib/admin/templates/registration/password_reset_email.html:2
msgid "You're receiving this e-mail because you requested a password reset"
@@ -1557,298 +742,579 @@ msgstr "Ви благодариме што го користите овој са
msgid "The %(site_name)s team"
msgstr "Тимот на %(site_name)s"
-#: contrib/admin/templates/registration/logged_out.html:8
-msgid "Thanks for spending some quality time with the Web site today."
-msgstr "Ви благодариме што денеска поминавте квалитетно време со интернет страницава."
+#: contrib/admin/templates/registration/password_reset_form.html:12
+msgid ""
+"Forgotten your password? Enter your e-mail address below, and we'll reset "
+"your password and e-mail the new one to you."
+msgstr ""
+"Сте ја заборавиле вашата лозинка? Внесете ја вашата е-пошта подолу, ќе ја "
+"ресетираме вашата лозинка и новата ќе ви ја пратиме по е-пошта."
-#: contrib/admin/templates/registration/logged_out.html:10
-msgid "Log in again"
-msgstr "Логирајте се повторно"
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "E-mail address:"
+msgstr "Е-пошта:"
-#: contrib/admin/templates/registration/password_reset_done.html:6
-#: contrib/admin/templates/registration/password_reset_done.html:10
-msgid "Password reset successful"
-msgstr "Успешно е ресетирањето на лозинката"
+#: contrib/admin/templates/registration/password_reset_form.html:16
+msgid "Reset my password"
+msgstr "Ресетирај ја мојата лозинка"
-#: contrib/admin/templates/registration/password_reset_done.html:12
+#: contrib/admin/templates/widget/date_time.html:3
+msgid "Date:"
+msgstr "Датум:"
+
+#: contrib/admin/templates/widget/date_time.html:4
+msgid "Time:"
+msgstr "Време:"
+
+#: contrib/admin/templates/widget/file.html:2
+msgid "Currently:"
+msgstr "Моментално:"
+
+#: contrib/admin/templates/widget/file.html:3
+msgid "Change:"
+msgstr "Измена:"
+
+#: contrib/admin/templatetags/admin_list.py:257
+msgid "All dates"
+msgstr "Сите датуми"
+
+#: contrib/admin/views/auth.py:20 contrib/admin/views/main.py:267
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was added successfully."
+msgstr "%(name)s \"%(obj)s\" беше успешно додаден."
+
+#: contrib/admin/views/auth.py:25 contrib/admin/views/main.py:271
+#: contrib/admin/views/main.py:356
+msgid "You may edit it again below."
+msgstr "Подолу можете повторно да го уредите."
+
+#: contrib/admin/views/auth.py:31
+msgid "Add user"
+msgstr "Додади корисник"
+
+#: contrib/admin/views/auth.py:58
+msgid "Password changed successfully."
+msgstr "Успешна промена на лозинката."
+
+#: contrib/admin/views/auth.py:65
+#, python-format
+msgid "Change password: %s"
+msgstr "Промени лозинка: %s"
+
+#: contrib/admin/views/decorators.py:17 contrib/auth/forms.py:60
msgid ""
-"We've e-mailed a new password to the e-mail address you submitted. You "
-"should be receiving it shortly."
+"Please enter a correct username and password. Note that both fields are case-"
+"sensitive."
msgstr ""
-"Ви пративме нова лозинка на адресата која ја внесовте.Треба да ја примите за "
-"кратко време."
+"Ве молам внесете точно корисничко име и лозинка. Имајте на ум дека и во "
+"двете полиња се битни големите и малите букви."
-#: contrib/admin/templates/registration/password_change_form.html:4
-#: contrib/admin/templates/registration/password_change_form.html:6
-#: contrib/admin/templates/registration/password_change_form.html:10
-#: contrib/admin/templates/registration/password_change_done.html:4
-msgid "Password change"
-msgstr "Измена на лозинка"
+#: contrib/admin/views/decorators.py:69
+msgid ""
+"Please log in again, because your session has expired. Don't worry: Your "
+"submission has been saved."
+msgstr ""
+"Ве молам најавете се повторно бидејќи вашата сесија е истечена. Не се "
+"грижете. Вашите внесови беа зачувани."
-#: contrib/admin/templates/registration/password_change_form.html:12
+#: contrib/admin/views/decorators.py:76
msgid ""
-"Please enter your old password, for security's sake, and then enter your new "
-"password twice so we can verify you typed it in correctly."
+"Looks like your browser isn't configured to accept cookies. Please enable "
+"cookies, reload this page, and try again."
msgstr ""
-"Заради сигурност ве молам внесете ја вашата стара лозинка и потоа внесете ја "
-"новата двапати за да може да се потврди дека правилно сте ја искуцале."
+"Изгледа дека вашиот прелистувач не е конфигуриран да прифаќа колачиња. Ве "
+"молам овозможете ги колачињата, превчитајте ја страта и пробајте повторно."
-#: contrib/admin/templates/registration/password_change_form.html:17
-msgid "Old password:"
-msgstr "Стара лозинка:"
+#: contrib/admin/views/decorators.py:89
+#, python-format
+msgid "Your e-mail address is not your username. Try '%s' instead."
+msgstr "Вашата е-пошта не е вашето корисничко име. Пробајте со „%s“."
-#: contrib/admin/templates/registration/password_change_form.html:19
-msgid "New password:"
-msgstr "Нова лозинка:"
+#: contrib/admin/views/decorators.py:93
+msgid "Usernames cannot contain the '@' character."
+msgstr "Корисничките имиња неможе да го содржат „@“ знакот."
-#: contrib/admin/templates/registration/password_change_form.html:21
-msgid "Confirm password:"
-msgstr "Потврди лозинка:"
+#: contrib/admin/views/doc.py:48 contrib/admin/views/doc.py:50
+#: contrib/admin/views/doc.py:52
+msgid "tag:"
+msgstr "таг:"
-#: contrib/admin/templates/registration/password_change_form.html:23
-msgid "Change my password"
-msgstr "Промени ја мојата лозинка"
+#: contrib/admin/views/doc.py:79 contrib/admin/views/doc.py:81
+#: contrib/admin/views/doc.py:83
+msgid "filter:"
+msgstr "филтер:"
-#: contrib/admin/templates/registration/password_change_done.html:6
-#: contrib/admin/templates/registration/password_change_done.html:10
-msgid "Password change successful"
-msgstr "Успешна промена на лозинката"
+#: contrib/admin/views/doc.py:137 contrib/admin/views/doc.py:139
+#: contrib/admin/views/doc.py:141
+msgid "view:"
+msgstr "поглед:"
-#: contrib/admin/templates/registration/password_change_done.html:12
-msgid "Your password was changed."
-msgstr "Вашата лозинка беше сменета."
+#: contrib/admin/views/doc.py:166
+#, python-format
+msgid "App %r not found"
+msgstr "Не е најдена апликацијата %r"
-#: contrib/sites/models.py:10
-msgid "domain name"
-msgstr "домејн"
+#: contrib/admin/views/doc.py:173
+#, python-format
+msgid "Model %(name)r not found in app %(label)r"
+msgstr "Моделот %(name)r не е најден во апликацијата %(label)r"
-#: contrib/sites/models.py:11
-msgid "display name"
-msgstr "име кое се прикажува"
+#: contrib/admin/views/doc.py:185
+#, python-format
+msgid "the related `%(label)s.%(type)s` object"
+msgstr "поврзаниот`%(label)s.%(type)s` објект"
-#: contrib/sites/models.py:15
-msgid "site"
-msgstr "сајт"
+#: contrib/admin/views/doc.py:185 contrib/admin/views/doc.py:207
+#: contrib/admin/views/doc.py:221 contrib/admin/views/doc.py:226
+msgid "model:"
+msgstr "модел:"
-#: contrib/sites/models.py:16
-msgid "sites"
-msgstr "сајтови"
+#: contrib/admin/views/doc.py:216
+#, python-format
+msgid "related `%(label)s.%(name)s` objects"
+msgstr "поврзани `%(label)s.%(name)s` објекти"
-#: contrib/humanize/templatetags/humanize.py:17
-msgid "th"
-msgstr "th"
+#: contrib/admin/views/doc.py:221
+#, python-format
+msgid "all %s"
+msgstr "сите %s"
-#: contrib/humanize/templatetags/humanize.py:17
-msgid "st"
-msgstr "st"
+#: contrib/admin/views/doc.py:226
+#, python-format
+msgid "number of %s"
+msgstr "број на %s"
-#: contrib/humanize/templatetags/humanize.py:17
-msgid "nd"
-msgstr "nd"
+#: contrib/admin/views/doc.py:231
+#, python-format
+msgid "Fields on %s objects"
+msgstr "Полиња на %s објекти"
-#: contrib/humanize/templatetags/humanize.py:17
-msgid "rd"
-msgstr "rd"
+#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:304
+#: contrib/admin/views/doc.py:306 contrib/admin/views/doc.py:312
+#: contrib/admin/views/doc.py:313 contrib/admin/views/doc.py:315
+msgid "Integer"
+msgstr "Цел број"
-#: contrib/humanize/templatetags/humanize.py:47
+#: contrib/admin/views/doc.py:294
+msgid "Boolean (Either True or False)"
+msgstr "Логичка (или точно или неточно)"
+
+#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:314
#, python-format
-msgid "%(value).1f million"
-msgid_plural "%(value).1f million"
-msgstr[0] "%(value).1f милион"
-msgstr[1] "%(value).1f милион"
+msgid "String (up to %(max_length)s)"
+msgstr "Збор (до %(max_length)s)"
+
+#: contrib/admin/views/doc.py:296
+msgid "Comma-separated integers"
+msgstr "Целобројни вредности одделени со запирка"
-#: contrib/humanize/templatetags/humanize.py:50
+#: contrib/admin/views/doc.py:297
+msgid "Date (without time)"
+msgstr "Датум (без час)"
+
+#: contrib/admin/views/doc.py:298
+msgid "Date (with time)"
+msgstr "Датум (со час)"
+
+#: contrib/admin/views/doc.py:299
+msgid "Decimal number"
+msgstr "Децимален број"
+
+#: contrib/admin/views/doc.py:300
+msgid "E-mail address"
+msgstr "Адреса на е-пошта"
+
+#: contrib/admin/views/doc.py:301 contrib/admin/views/doc.py:302
+#: contrib/admin/views/doc.py:305
+msgid "File path"
+msgstr "Патека на датотека"
+
+#: contrib/admin/views/doc.py:303
+msgid "Floating point number"
+msgstr "Децимален број"
+
+#: contrib/admin/views/doc.py:307 contrib/comments/models.py:89
+msgid "IP address"
+msgstr "ИП адреса"
+
+#: contrib/admin/views/doc.py:309
+msgid "Boolean (Either True, False or None)"
+msgstr "Логичка (точно,неточно или празно)"
+
+#: contrib/admin/views/doc.py:310
+msgid "Relation to parent model"
+msgstr "Релација со родителскиот модел"
+
+#: contrib/admin/views/doc.py:311
+msgid "Phone number"
+msgstr "Телефонски број"
+
+#: contrib/admin/views/doc.py:316
+msgid "Text"
+msgstr "Текст"
+
+#: contrib/admin/views/doc.py:317
+msgid "Time"
+msgstr "Час"
+
+#: contrib/admin/views/doc.py:318 contrib/flatpages/models.py:8
+msgid "URL"
+msgstr "URL"
+
+#: contrib/admin/views/doc.py:319
+msgid "U.S. state (two uppercase letters)"
+msgstr "Држава во САД (две големи букви)"
+
+#: contrib/admin/views/doc.py:320
+msgid "XML text"
+msgstr "XML текст"
+
+#: contrib/admin/views/doc.py:346
#, python-format
-msgid "%(value).1f billion"
-msgid_plural "%(value).1f billion"
-msgstr[0] "%(value).1f милијарда"
-msgstr[1] "%(value).1f милијарда"
+msgid "%s does not appear to be a urlpattern object"
+msgstr "%s не изгледа дека е url објект"
-#: contrib/humanize/templatetags/humanize.py:53
+#: contrib/admin/views/main.py:233
+msgid "Site administration"
+msgstr "Администрација на сајт"
+
+#: contrib/admin/views/main.py:280 contrib/admin/views/main.py:365
#, python-format
-msgid "%(value).1f trillion"
-msgid_plural "%(value).1f trillion"
-msgstr[0] "%(value).1f трилион"
-msgstr[1] "%(value).1f трилион"
+msgid "You may add another %s below."
+msgstr "Подолу можете да додате уште еден %s."
-#: contrib/humanize/templatetags/humanize.py:68
-msgid "one"
-msgstr "еден"
+#: contrib/admin/views/main.py:298
+#, python-format
+msgid "Add %s"
+msgstr "Додади %s"
-#: contrib/humanize/templatetags/humanize.py:68
-msgid "two"
-msgstr "два"
+#: contrib/admin/views/main.py:344
+#, python-format
+msgid "Added %s."
+msgstr "Додадено %s."
-#: contrib/humanize/templatetags/humanize.py:68
-msgid "three"
-msgstr "три"
+#: contrib/admin/views/main.py:344 contrib/admin/views/main.py:346
+#: contrib/admin/views/main.py:348 core/validators.py:283
+#: db/models/manipulators.py:309
+msgid "and"
+msgstr "и"
-#: contrib/humanize/templatetags/humanize.py:68
-msgid "four"
-msgstr "четири"
+#: contrib/admin/views/main.py:346
+#, python-format
+msgid "Changed %s."
+msgstr "Изменета %s."
-#: contrib/humanize/templatetags/humanize.py:68
-msgid "five"
-msgstr "пет"
+#: contrib/admin/views/main.py:348
+#, python-format
+msgid "Deleted %s."
+msgstr "Избришана %s."
-#: contrib/humanize/templatetags/humanize.py:68
-msgid "six"
-msgstr "шест"
+#: contrib/admin/views/main.py:351
+msgid "No fields changed."
+msgstr "Не беше изменето ниедно поле."
-#: contrib/humanize/templatetags/humanize.py:68
-msgid "seven"
-msgstr "седум"
+#: contrib/admin/views/main.py:354
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was changed successfully."
+msgstr "%(name)s \"%(obj)s\" беше успешно изменета."
-#: contrib/humanize/templatetags/humanize.py:68
-msgid "eight"
-msgstr "осум"
+#: contrib/admin/views/main.py:362
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
+msgstr ""
+"%(name)s \"%(obj)s\" беше успешно додадена.Подолу можете повторно да ја "
+"уредите."
-#: contrib/humanize/templatetags/humanize.py:68
-msgid "nine"
-msgstr "девет"
+#: contrib/admin/views/main.py:400
+#, python-format
+msgid "Change %s"
+msgstr "Измени %s"
-#: contrib/flatpages/models.py:8
-msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes."
+#: contrib/admin/views/main.py:487
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s: %(obj)s"
+msgstr "Еден или повеќе %(fieldname)s во %(name)s: %(obj)s"
+
+#: contrib/admin/views/main.py:492
+#, python-format
+msgid "One or more %(fieldname)s in %(name)s:"
+msgstr "Еден или повеќе %(fieldname)s во %(name)s:"
+
+#: contrib/admin/views/main.py:524
+#, python-format
+msgid "The %(name)s \"%(obj)s\" was deleted successfully."
+msgstr "%(name)s \"%(obj)s\" беше избришана успешно."
+
+#: contrib/admin/views/main.py:527
+msgid "Are you sure?"
+msgstr "Сигурни сте?"
+
+#: contrib/admin/views/main.py:549
+#, python-format
+msgid "Change history: %s"
+msgstr "Историја на измени: %s"
+
+#: contrib/admin/views/main.py:583
+#, python-format
+msgid "Select %s"
+msgstr "Изберет %s"
+
+#: contrib/admin/views/main.py:583
+#, python-format
+msgid "Select %s to change"
+msgstr "Изберете %s за измена"
+
+#: contrib/admin/views/main.py:765
+msgid "Database error"
+msgstr "Грешка во базата со податоци"
+
+#: contrib/auth/forms.py:17 contrib/auth/forms.py:138
+msgid "The two password fields didn't match."
+msgstr "Двете полиња со лозинките не се совпаѓаат."
+
+#: contrib/auth/forms.py:25
+msgid "A user with that username already exists."
+msgstr "Веќе постои корисник со тоа корисничко име."
+
+#: contrib/auth/forms.py:53
+msgid ""
+"Your Web browser doesn't appear to have cookies enabled. Cookies are "
+"required for logging in."
msgstr ""
-"На пр. „/za/kontakt/“. Осигурајте се да имате коса црта и на крајот и на "
-"почетокот."
+"Не изгледа дека вашиот прелистувач има овозможено колачиња. Колачињата се "
+"потребни за да се најавите."
-#: contrib/flatpages/models.py:9
-msgid "title"
-msgstr "наслов"
+#: contrib/auth/forms.py:62
+msgid "This account is inactive."
+msgstr "Оваа сметка е неактивна."
-#: contrib/flatpages/models.py:10
-msgid "content"
-msgstr "содржина"
+#: contrib/auth/forms.py:84
+msgid ""
+"That e-mail address doesn't have an associated user account. Are you sure "
+"you've registered?"
+msgstr ""
+"Нема регистрирано корисник со оваа адреса за е-пошта. Сигурни ли сте дека "
+"сте регистрирани?"
-#: contrib/flatpages/models.py:11
-msgid "enable comments"
-msgstr "овозможи коментари"
+#: contrib/auth/forms.py:107
+#, python-format
+msgid "Password reset on %s"
+msgstr "Ресетирање на лозинка на %s"
-#: contrib/flatpages/models.py:12
-msgid "template name"
-msgstr "име на шаблонот"
+#: contrib/auth/forms.py:117
+msgid "The two 'new password' fields didn't match."
+msgstr "Двете нови лозинки не се совпаѓаат."
-#: contrib/flatpages/models.py:13
+#: contrib/auth/forms.py:124
+msgid "Your old password was entered incorrectly. Please enter it again."
+msgstr "Не ја внесовте точно вашата стара лозинка. Ве молам внесете ја повторно."
+
+#: contrib/auth/models.py:73 contrib/auth/models.py:93
+msgid "name"
+msgstr "име"
+
+#: contrib/auth/models.py:75
+msgid "codename"
+msgstr "кодно име"
+
+#: contrib/auth/models.py:78
+msgid "permission"
+msgstr "привилегија"
+
+#: contrib/auth/models.py:79 contrib/auth/models.py:94
+msgid "permissions"
+msgstr "привилегии"
+
+#: contrib/auth/models.py:97
+msgid "group"
+msgstr "група"
+
+#: contrib/auth/models.py:98 contrib/auth/models.py:148
+msgid "groups"
+msgstr "групи"
+
+#: contrib/auth/models.py:138
+msgid "username"
+msgstr "корисничко име"
+
+#: contrib/auth/models.py:138
msgid ""
-"Example: 'flatpages/contact_page.html'. If this isn't provided, the system "
-"will use 'flatpages/default.html'."
+"Required. 30 characters or fewer. Alphanumeric characters only (letters, "
+"digits and underscores)."
msgstr ""
-"На пр. „flatpages/kontakt.html'. Ако не го внесете ова, системот ќе користи "
-"„flatpages/default.html“."
+"Дозволени се најмногу 30 знаци. Дозволени се само алфанумерички знаци "
+"(букви, цифри и долна црта)."
-#: contrib/flatpages/models.py:14
-msgid "registration required"
-msgstr "потребна е регистрација"
+#: contrib/auth/models.py:139
+msgid "first name"
+msgstr "име"
-#: contrib/flatpages/models.py:14
-msgid "If this is checked, only logged-in users will be able to view the page."
+#: contrib/auth/models.py:140
+msgid "last name"
+msgstr "презиме"
+
+#: contrib/auth/models.py:141
+msgid "e-mail address"
+msgstr "е-пошта"
+
+#: contrib/auth/models.py:142
+msgid "password"
+msgstr "лозинка"
+
+#: contrib/auth/models.py:142
+msgid ""
+"Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change "
+"password form</a>."
msgstr ""
-"Ако ова е штиклирано, само најавените корисници ќе можат да ја гледаат оваа "
-"страница."
+"Користете '[algo]$[salt]$[hexdigest]' или користете ја <a href=\"password/"
+"\">формата за промена на лозинката</a>."
-#: contrib/flatpages/models.py:18
-msgid "flat page"
-msgstr "статична страница"
+#: contrib/auth/models.py:143
+msgid "staff status"
+msgstr "статус на администраторите"
-#: contrib/flatpages/models.py:19
-msgid "flat pages"
-msgstr "статични страници"
+#: contrib/auth/models.py:143
+msgid "Designates whether the user can log into this admin site."
+msgstr "Означува дали корисникот може да се логира во сајтот за администрација."
-#: contrib/redirects/models.py:7
-msgid "redirect from"
-msgstr "пренасочено од"
+#: contrib/auth/models.py:144
+msgid "active"
+msgstr "активен"
-#: contrib/redirects/models.py:8
+#: contrib/auth/models.py:144
msgid ""
-"This should be an absolute path, excluding the domain name. Example: '/"
-"events/search/'."
+"Designates whether this user should be treated as active. Unselect this "
+"instead of deleting accounts."
msgstr ""
-"Ова треба да биде апсолутна патека без името на домејнот. На пр. „/nastani/"
-"prebaraj/“."
+"Означува дали корисникот треба да биде активен. Одштиклирајте го ова наместо да "
+"бришете корисници."
-#: contrib/redirects/models.py:9
-msgid "redirect to"
-msgstr "пренасочи кон"
+#: contrib/auth/models.py:145
+msgid "superuser status"
+msgstr "статус на суперкорисник"
-#: contrib/redirects/models.py:10
+#: contrib/auth/models.py:145
msgid ""
-"This can be either an absolute path (as above) or a full URL starting with "
-"'http://'."
+"Designates that this user has all permissions without explicitly assigning "
+"them."
msgstr ""
-"Ова може да биде или апсолутна патека (како погоре) или цела адреса "
-"почувајќи со „http://“."
+"Означува дека овој корисник ги има сите привилегии без експлицитно да се "
+"доделуваат сите."
-#: contrib/redirects/models.py:13
-msgid "redirect"
-msgstr "пренасочување"
+#: contrib/auth/models.py:146
+msgid "last login"
+msgstr "последна најава"
-#: contrib/redirects/models.py:14
-msgid "redirects"
-msgstr "пренасочувања"
+#: contrib/auth/models.py:147
+msgid "date joined"
+msgstr "датум на зачленување"
+
+#: contrib/auth/models.py:149
+msgid ""
+"In addition to the permissions manually assigned, this user will also get "
+"all permissions granted to each group he/she is in."
+msgstr ""
+"Како дополнување на рачно доделени привилегии, овој корисник ќе ги добие "
+"автоматски и сите привилегии за секоја група во која тој/таа членува."
+
+#: contrib/auth/models.py:150
+msgid "user permissions"
+msgstr "кориснички привилегии"
+
+#: contrib/auth/models.py:154
+msgid "user"
+msgstr "корисник"
+
+#: contrib/auth/models.py:155
+msgid "users"
+msgstr "корисници"
+
+#: contrib/auth/models.py:161
+msgid "Personal info"
+msgstr "Лични информации"
-#: contrib/comments/models.py:67 contrib/comments/models.py:166
+#: contrib/auth/models.py:162
+msgid "Permissions"
+msgstr "Привилегии"
+
+#: contrib/auth/models.py:163
+msgid "Important dates"
+msgstr "Важни датуми"
+
+#: contrib/auth/models.py:164
+msgid "Groups"
+msgstr "Групи"
+
+#: contrib/auth/models.py:323
+msgid "message"
+msgstr "порака"
+
+#: contrib/auth/views.py:47
+msgid "Logged out"
+msgstr "Одјавен"
+
+#: contrib/comments/models.py:71 contrib/comments/models.py:176
msgid "object ID"
msgstr "object ID"
-#: contrib/comments/models.py:68
+#: contrib/comments/models.py:72
msgid "headline"
msgstr "наслов"
-#: contrib/comments/models.py:69 contrib/comments/models.py:90
-#: contrib/comments/models.py:167
+#: contrib/comments/models.py:73 contrib/comments/models.py:95
+#: contrib/comments/models.py:177
msgid "comment"
msgstr "коментар"
-#: contrib/comments/models.py:70
+#: contrib/comments/models.py:74
msgid "rating #1"
msgstr "популарност #1"
-#: contrib/comments/models.py:71
+#: contrib/comments/models.py:75
msgid "rating #2"
msgstr "популарност #2"
-#: contrib/comments/models.py:72
+#: contrib/comments/models.py:76
msgid "rating #3"
msgstr "популарност #3"
-#: contrib/comments/models.py:73
+#: contrib/comments/models.py:77
msgid "rating #4"
msgstr "популарност #4"
-#: contrib/comments/models.py:74
+#: contrib/comments/models.py:78
msgid "rating #5"
msgstr "популарност #5"
-#: contrib/comments/models.py:75
+#: contrib/comments/models.py:79
msgid "rating #6"
msgstr "популарност #6"
-#: contrib/comments/models.py:76
+#: contrib/comments/models.py:80
msgid "rating #7"
msgstr "популарност #7"
-#: contrib/comments/models.py:77
+#: contrib/comments/models.py:81
msgid "rating #8"
msgstr "популарност #8"
-#: contrib/comments/models.py:82
+#: contrib/comments/models.py:86
msgid "is valid rating"
msgstr "е валидна популарност"
-#: contrib/comments/models.py:83 contrib/comments/models.py:169
+#: contrib/comments/models.py:87 contrib/comments/models.py:179
msgid "date/time submitted"
msgstr "датум/време пријавен"
-#: contrib/comments/models.py:84 contrib/comments/models.py:170
+#: contrib/comments/models.py:88 contrib/comments/models.py:180
msgid "is public"
msgstr "е јавен"
-#: contrib/comments/models.py:86
+#: contrib/comments/models.py:90
msgid "is removed"
msgstr "е отстранет"
-#: contrib/comments/models.py:86
+#: contrib/comments/models.py:90
msgid ""
"Check this box if the comment is inappropriate. A \"This comment has been "
"removed\" message will be displayed instead."
@@ -1856,15 +1322,15 @@ msgstr ""
"Штиклирајте го ова поле ако коментарот не е пригоден. Наместо него пораката "
"„Овој коментар беше отстранет“ ќе биде прикажана."
-#: contrib/comments/models.py:91
+#: contrib/comments/models.py:96
msgid "comments"
msgstr "коментари"
-#: contrib/comments/models.py:131 contrib/comments/models.py:207
+#: contrib/comments/models.py:140 contrib/comments/models.py:222
msgid "Content object"
msgstr "Content објект"
-#: contrib/comments/models.py:159
+#: contrib/comments/models.py:168
#, python-format
msgid ""
"Posted by %(user)s at %(date)s\n"
@@ -1879,48 +1345,48 @@ msgstr ""
"\n"
"http://%(domain)s%(url)s"
-#: contrib/comments/models.py:168
+#: contrib/comments/models.py:178
msgid "person's name"
msgstr "име на личноста"
-#: contrib/comments/models.py:171
+#: contrib/comments/models.py:181
msgid "ip address"
msgstr "ип адреса"
-#: contrib/comments/models.py:173
+#: contrib/comments/models.py:183
msgid "approved by staff"
msgstr "одобрено од администраторите"
-#: contrib/comments/models.py:176
+#: contrib/comments/models.py:187
msgid "free comment"
msgstr "слободен коментар"
-#: contrib/comments/models.py:177
+#: contrib/comments/models.py:188
msgid "free comments"
msgstr "слободни коментари"
-#: contrib/comments/models.py:233
+#: contrib/comments/models.py:250
msgid "score"
msgstr "поени"
-#: contrib/comments/models.py:234
+#: contrib/comments/models.py:251
msgid "score date"
msgstr "датум поени"
-#: contrib/comments/models.py:237
+#: contrib/comments/models.py:255
msgid "karma score"
msgstr "карма поен"
-#: contrib/comments/models.py:238
+#: contrib/comments/models.py:256
msgid "karma scores"
msgstr "карма поени"
-#: contrib/comments/models.py:242
+#: contrib/comments/models.py:260
#, python-format
msgid "%(score)d rating by %(user)s"
msgstr "%(score)d гласање за популарност од %(user)s"
-#: contrib/comments/models.py:258
+#: contrib/comments/models.py:277
#, python-format
msgid ""
"This comment was flagged by %(user)s:\n"
@@ -1931,59 +1397,83 @@ msgstr ""
"\n"
"%(text)s"
-#: contrib/comments/models.py:265
+#: contrib/comments/models.py:285
msgid "flag date"
msgstr "датум на означување"
-#: contrib/comments/models.py:268
+#: contrib/comments/models.py:289
msgid "user flag"
msgstr "корисничка ознака"
-#: contrib/comments/models.py:269
+#: contrib/comments/models.py:290
msgid "user flags"
msgstr "кориснички ознаки"
-#: contrib/comments/models.py:273
+#: contrib/comments/models.py:294
#, python-format
msgid "Flag by %r"
msgstr "Означено од %r"
-#: contrib/comments/models.py:278
+#: contrib/comments/models.py:300
msgid "deletion date"
msgstr "датум на бришење"
-#: contrib/comments/models.py:280
+#: contrib/comments/models.py:303
msgid "moderator deletion"
msgstr "бришење од модератор"
-#: contrib/comments/models.py:281
+#: contrib/comments/models.py:304
msgid "moderator deletions"
msgstr "бришења од модератор"
-#: contrib/comments/models.py:285
+#: contrib/comments/models.py:308
#, python-format
msgid "Moderator deletion by %r"
msgstr "Бришење од модератор од %r"
-#: contrib/comments/views/karma.py:19
-msgid "Anonymous users cannot vote"
-msgstr "Анонимните корисници неможе да гласаат"
+#: contrib/comments/templates/comments/form.html:8
+msgid "Forgotten your password?"
+msgstr "Ја заборавите вашата лозинка?"
-#: contrib/comments/views/karma.py:23
-msgid "Invalid comment ID"
-msgstr "Невалидно ИД на коментарот"
+#: contrib/comments/templates/comments/form.html:12
+msgid "Ratings"
+msgstr "Популарност"
-#: contrib/comments/views/karma.py:25
-msgid "No voting for yourself"
-msgstr "Нема гласање за самиот себе"
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Required"
+msgstr "Потребно"
+
+#: contrib/comments/templates/comments/form.html:12
+#: contrib/comments/templates/comments/form.html:23
+msgid "Optional"
+msgstr "По желба"
+
+#: contrib/comments/templates/comments/form.html:23
+msgid "Post a photo"
+msgstr "Објави фотографија"
+
+#: contrib/comments/templates/comments/form.html:28
+#: contrib/comments/templates/comments/freeform.html:5
+msgid "Comment:"
+msgstr "Коментар:"
+
+#: contrib/comments/templates/comments/form.html:35
+#: contrib/comments/templates/comments/freeform.html:10
+msgid "Preview comment"
+msgstr "Прегледај"
-#: contrib/comments/views/comments.py:27
+#: contrib/comments/templates/comments/freeform.html:4
+msgid "Your name:"
+msgstr "Вашето име:"
+
+#: contrib/comments/views/comments.py:28
msgid "This rating is required because you've entered at least one other rating."
msgstr ""
"Ова гласање за популарност е потребно бидејќи внесовте најмалку уште едно "
"друго."
-#: contrib/comments/views/comments.py:111
+#: contrib/comments/views/comments.py:112
#, python-format
msgid ""
"This comment was posted by a user who has posted fewer than %(count)s "
@@ -2006,7 +1496,7 @@ msgstr[1] ""
"\n"
"%(text)s"
-#: contrib/comments/views/comments.py:116
+#: contrib/comments/views/comments.py:117
#, python-format
msgid ""
"This comment was posted by a sketchy user:\n"
@@ -2017,23 +1507,23 @@ msgstr ""
"\n"
"%(text)s"
-#: contrib/comments/views/comments.py:188
-#: contrib/comments/views/comments.py:280
+#: contrib/comments/views/comments.py:190
+#: contrib/comments/views/comments.py:283
msgid "Only POSTs are allowed"
msgstr "Дозволено е само POST"
-#: contrib/comments/views/comments.py:192
-#: contrib/comments/views/comments.py:284
+#: contrib/comments/views/comments.py:194
+#: contrib/comments/views/comments.py:287
msgid "One or more of the required fields wasn't submitted"
msgstr "Едно или повеќе од потребните полиња не беше пополнето"
-#: contrib/comments/views/comments.py:196
-#: contrib/comments/views/comments.py:286
+#: contrib/comments/views/comments.py:198
+#: contrib/comments/views/comments.py:289
msgid "Somebody tampered with the comment form (security violation)"
msgstr "Некој ја променил формата за коментари (сигурносен прекршок)"
-#: contrib/comments/views/comments.py:206
-#: contrib/comments/views/comments.py:292
+#: contrib/comments/views/comments.py:208
+#: contrib/comments/views/comments.py:295
msgid ""
"The comment form had an invalid 'target' parameter -- the object ID was "
"invalid"
@@ -2041,65 +1531,363 @@ msgstr ""
"Формата за коментар имаше неправилен „target“ параметар - идентификациониот "
"број на објектот беше неправилен"
-#: contrib/comments/views/comments.py:257
-#: contrib/comments/views/comments.py:321
+#: contrib/comments/views/comments.py:259
+#: contrib/comments/views/comments.py:324
msgid "The comment form didn't provide either 'preview' or 'post'"
msgstr "Формата за коментар не овозможи ниту „преглед“ ниту „праќање“"
-#: contrib/comments/templates/comments/form.html:8
-msgid "Forgotten your password?"
-msgstr "Ја заборавите вашата лозинка?"
+#: contrib/comments/views/karma.py:21
+msgid "Anonymous users cannot vote"
+msgstr "Анонимните корисници неможе да гласаат"
-#: contrib/comments/templates/comments/form.html:12
-msgid "Ratings"
-msgstr "Популарност"
+#: contrib/comments/views/karma.py:25
+msgid "Invalid comment ID"
+msgstr "Невалидно ИД на коментарот"
-#: contrib/comments/templates/comments/form.html:12
-#: contrib/comments/templates/comments/form.html:23
-msgid "Required"
-msgstr "Потребно"
+#: contrib/comments/views/karma.py:27
+msgid "No voting for yourself"
+msgstr "Нема гласање за самиот себе"
-#: contrib/comments/templates/comments/form.html:12
-#: contrib/comments/templates/comments/form.html:23
-msgid "Optional"
-msgstr "По желба"
+#: contrib/contenttypes/models.py:67
+msgid "python model class name"
+msgstr "има на класата на питон моделите"
-#: contrib/comments/templates/comments/form.html:23
-msgid "Post a photo"
-msgstr "Објави фотографија"
+#: contrib/contenttypes/models.py:71
+msgid "content type"
+msgstr "content type"
-#: contrib/comments/templates/comments/form.html:28
-#: contrib/comments/templates/comments/freeform.html:5
-msgid "Comment:"
-msgstr "Коментар:"
+#: contrib/contenttypes/models.py:72
+msgid "content types"
+msgstr "content types"
-#: contrib/comments/templates/comments/form.html:35
-#: contrib/comments/templates/comments/freeform.html:10
-msgid "Preview comment"
-msgstr "Прегледај"
+#: contrib/flatpages/models.py:9
+msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes."
+msgstr ""
+"На пр. „/za/kontakt/“. Осигурајте се да имате коса црта и на крајот и на "
+"почетокот."
-#: contrib/comments/templates/comments/freeform.html:4
-msgid "Your name:"
-msgstr "Вашето име:"
+#: contrib/flatpages/models.py:10
+msgid "title"
+msgstr "наслов"
+
+#: contrib/flatpages/models.py:11
+msgid "content"
+msgstr "содржина"
+
+#: contrib/flatpages/models.py:12
+msgid "enable comments"
+msgstr "овозможи коментари"
+
+#: contrib/flatpages/models.py:13
+msgid "template name"
+msgstr "име на шаблонот"
+
+#: contrib/flatpages/models.py:14
+msgid ""
+"Example: 'flatpages/contact_page.html'. If this isn't provided, the system "
+"will use 'flatpages/default.html'."
+msgstr ""
+"На пр. „flatpages/kontakt.html'. Ако не го внесете ова, системот ќе користи "
+"„flatpages/default.html“."
+
+#: contrib/flatpages/models.py:15
+msgid "registration required"
+msgstr "потребна е регистрација"
+
+#: contrib/flatpages/models.py:15
+msgid "If this is checked, only logged-in users will be able to view the page."
+msgstr ""
+"Ако ова е штиклирано, само најавените корисници ќе можат да ја гледаат оваа "
+"страница."
+
+#: contrib/flatpages/models.py:20
+msgid "flat page"
+msgstr "статична страница"
+
+#: contrib/flatpages/models.py:21
+msgid "flat pages"
+msgstr "статични страници"
+
+#: contrib/flatpages/models.py:27
+msgid "Advanced options"
+msgstr "Напредни можности"
+
+#: contrib/humanize/templatetags/humanize.py:19
+msgid "th"
+msgstr "th"
+
+#: contrib/humanize/templatetags/humanize.py:19
+msgid "st"
+msgstr "st"
+
+#: contrib/humanize/templatetags/humanize.py:19
+msgid "nd"
+msgstr "nd"
+
+#: contrib/humanize/templatetags/humanize.py:19
+msgid "rd"
+msgstr "rd"
+
+#: contrib/humanize/templatetags/humanize.py:51
+#, python-format
+msgid "%(value).1f million"
+msgid_plural "%(value).1f million"
+msgstr[0] "%(value).1f милион"
+msgstr[1] "%(value).1f милион"
+
+#: contrib/humanize/templatetags/humanize.py:54
+#, python-format
+msgid "%(value).1f billion"
+msgid_plural "%(value).1f billion"
+msgstr[0] "%(value).1f милијарда"
+msgstr[1] "%(value).1f милијарда"
+
+#: contrib/humanize/templatetags/humanize.py:57
+#, python-format
+msgid "%(value).1f trillion"
+msgid_plural "%(value).1f trillion"
+msgstr[0] "%(value).1f трилион"
+msgstr[1] "%(value).1f трилион"
+
+#: contrib/humanize/templatetags/humanize.py:73
+msgid "one"
+msgstr "еден"
+
+#: contrib/humanize/templatetags/humanize.py:73
+msgid "two"
+msgstr "два"
+
+#: contrib/humanize/templatetags/humanize.py:73
+msgid "three"
+msgstr "три"
+
+#: contrib/humanize/templatetags/humanize.py:73
+msgid "four"
+msgstr "четири"
+
+#: contrib/humanize/templatetags/humanize.py:73
+msgid "five"
+msgstr "пет"
+
+#: contrib/humanize/templatetags/humanize.py:73
+msgid "six"
+msgstr "шест"
-#: contrib/localflavor/br/forms.py:18
+#: contrib/humanize/templatetags/humanize.py:73
+msgid "seven"
+msgstr "седум"
+
+#: contrib/humanize/templatetags/humanize.py:73
+msgid "eight"
+msgstr "осум"
+
+#: contrib/humanize/templatetags/humanize.py:73
+msgid "nine"
+msgstr "девет"
+
+#: contrib/humanize/templatetags/humanize.py:93
+msgid "today"
+msgstr "денеска"
+
+#: contrib/humanize/templatetags/humanize.py:95
+msgid "tomorrow"
+msgstr "утре"
+
+#: contrib/humanize/templatetags/humanize.py:97
+msgid "yesterday"
+msgstr "вчера"
+
+#: contrib/localflavor/ar/forms.py:27
+msgid "Enter a postal code in the format NNNN or ANNNNAAA."
+msgstr "Внесете поштенски број во форматот NNNN или ANNNNAAA."
+
+#: contrib/localflavor/ar/forms.py:49 contrib/localflavor/br/forms.py:96
+#: contrib/localflavor/br/forms.py:135 contrib/localflavor/pe/forms.py:23
+#: contrib/localflavor/pe/forms.py:51
+msgid "This field requires only numbers."
+msgstr "Во ова поле смее да бидат само бројки."
+
+#: contrib/localflavor/ar/forms.py:50
+msgid "This field requires 7 or 8 digits."
+msgstr "Во ова поле смее да има 7 или 8 цифри."
+
+#: contrib/localflavor/ar/forms.py:79
+msgid "Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format."
+msgstr "Внесете правилен CUIT во XX-XXXXXXXX-X or XXXXXXXXXXXX формат."
+
+#: contrib/localflavor/ar/forms.py:80
+msgid "Invalid CUIT."
+msgstr "Неправилен CUIT."
+
+#: contrib/localflavor/au/forms.py:16
+msgid "Enter a 4 digit post code."
+msgstr "Внесете 4 цифрен поштенски број."
+
+#: contrib/localflavor/br/forms.py:21
msgid "Enter a zip code in the format XXXXX-XXX."
-msgstr "Внесете правилен поштенски број во форматот XXXXX-XXX."
+msgstr "Внесете правилен поштенски број во форматот XXXXX-XXX."
#: contrib/localflavor/br/forms.py:30
msgid "Phone numbers must be in XX-XXXX-XXXX format."
msgstr "Телефонските броеви мора да бидат во XX-XXXX-XXXX форматот."
-#: contrib/localflavor/de/forms.py:16 contrib/localflavor/fi/forms.py:14
-#: contrib/localflavor/fr/forms.py:17 contrib/localflavor/it/forms.py:14
-msgid "Enter a zip code in the format XXXXX."
-msgstr "Внесете правилен поштенски број во формат XXXXXX."
+#: contrib/localflavor/br/forms.py:58
+msgid ""
+"Select a valid brazilian state. That state is not one of the available "
+"states."
+msgstr "Изберете правилна бразилска држава. Оваа држава не е од достапните држави."
+
+#: contrib/localflavor/br/forms.py:94
+msgid "Invalid CPF number."
+msgstr "Неправилен CPF број."
+
+#: contrib/localflavor/br/forms.py:95
+msgid "This field requires at most 11 digits or 14 characters."
+msgstr "Во ова поле смее да има најмногу 11 цифри или 14 знаци."
+
+#: contrib/localflavor/br/forms.py:134
+msgid "Invalid CNPJ number."
+msgstr "Неправилен CNPJ број."
+
+#: contrib/localflavor/br/forms.py:136
+msgid "This field requires at least 14 digits"
+msgstr "Во ова поле треба да има најмалку 14 цифри"
+
+#: contrib/localflavor/ca/forms.py:17
+msgid "Enter a postal code in the format XXX XXX."
+msgstr "Внесете правилен поштенски број во формат XXX XXX."
+
+#: contrib/localflavor/ca/forms.py:88
+msgid "Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format."
+msgstr "Внесте правилен канадски број за осигурување во XXX-XXX-XXX форматот."
+
+#: contrib/localflavor/ch/ch_states.py:5
+msgid "Aargau"
+msgstr "Aargau"
+
+#: contrib/localflavor/ch/ch_states.py:6
+msgid "Appenzell Innerrhoden"
+msgstr "Appenzell Innerrhoden"
+
+#: contrib/localflavor/ch/ch_states.py:7
+msgid "Appenzell Ausserrhoden"
+msgstr "Appenzell Ausserrhoden"
+
+#: contrib/localflavor/ch/ch_states.py:8
+msgid "Basel-Stadt"
+msgstr "Basel-Stadt"
+
+#: contrib/localflavor/ch/ch_states.py:9
+msgid "Basel-Land"
+msgstr "Basel-Land"
+
+#: contrib/localflavor/ch/ch_states.py:10
+msgid "Berne"
+msgstr "Berne"
+
+#: contrib/localflavor/ch/ch_states.py:11
+msgid "Fribourg"
+msgstr "Fribourg"
+
+#: contrib/localflavor/ch/ch_states.py:12
+msgid "Geneva"
+msgstr "Женева"
+
+#: contrib/localflavor/ch/ch_states.py:13
+msgid "Glarus"
+msgstr "Glarus"
+
+#: contrib/localflavor/ch/ch_states.py:14
+msgid "Graubuenden"
+msgstr "Graubuenden"
+
+#: contrib/localflavor/ch/ch_states.py:15
+msgid "Jura"
+msgstr "Jura"
+
+#: contrib/localflavor/ch/ch_states.py:16
+msgid "Lucerne"
+msgstr "Lucerne"
+
+#: contrib/localflavor/ch/ch_states.py:17
+msgid "Neuchatel"
+msgstr "Neuchatel"
+
+#: contrib/localflavor/ch/ch_states.py:18
+msgid "Nidwalden"
+msgstr "Nidwalden"
+
+#: contrib/localflavor/ch/ch_states.py:19
+msgid "Obwalden"
+msgstr "Obwalden"
-#: contrib/localflavor/de/forms.py:60
+#: contrib/localflavor/ch/ch_states.py:20
+msgid "Schaffhausen"
+msgstr "Schaffhausen"
+
+#: contrib/localflavor/ch/ch_states.py:21
+msgid "Schwyz"
+msgstr "Schwyz"
+
+#: contrib/localflavor/ch/ch_states.py:22
+msgid "Solothurn"
+msgstr "Solothurn"
+
+#: contrib/localflavor/ch/ch_states.py:23
+msgid "St. Gallen"
+msgstr "St. Gallen"
+
+#: contrib/localflavor/ch/ch_states.py:24
+msgid "Thurgau"
+msgstr "Thurgau"
+
+#: contrib/localflavor/ch/ch_states.py:25
+msgid "Ticino"
+msgstr "Ticino"
+
+#: contrib/localflavor/ch/ch_states.py:26
+msgid "Uri"
+msgstr "Uri"
+
+#: contrib/localflavor/ch/ch_states.py:27
+msgid "Valais"
+msgstr "Valais"
+
+#: contrib/localflavor/ch/ch_states.py:28
+msgid "Vaud"
+msgstr "Vaud"
+
+#: contrib/localflavor/ch/ch_states.py:29
+msgid "Zug"
+msgstr "Zug"
+
+#: contrib/localflavor/ch/ch_states.py:30
+msgid "Zurich"
+msgstr "Цирих"
+
+#: contrib/localflavor/ch/forms.py:16 contrib/localflavor/no/forms.py:12
+msgid "Enter a zip code in the format XXXX."
+msgstr "Внесете правилен поштенски број во форматот XXXX."
+
+#: contrib/localflavor/ch/forms.py:64
msgid ""
-"Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X "
-"format."
-msgstr "Внесете правилен број на лична карта во Германија во XXXXXXXXXXX-XXXXXXX-XXXXXXX-X форматот."
+"Enter a valid Swiss identity or passport card number in X1234567<0 or "
+"1234567890 format."
+msgstr ""
+"Внесете правилен швајцарски број на пасош во X1234567<0 или "
+" 1234567890 формат."
+
+#: contrib/localflavor/cl/forms.py:29
+msgid "Enter a valid Chilean RUT."
+msgstr "Внесете правилeн RUT за Чиле."
+
+#: contrib/localflavor/cl/forms.py:30
+msgid "Enter a valid Chilean RUT. The format is XX.XXX.XXX-X."
+msgstr "Внесете правилен RUT за Чиле. Форматот е Xx.XXX.XXX-X."
+
+#: contrib/localflavor/cl/forms.py:31
+msgid "The Chilean RUT is not valid."
+msgstr "RUT бројот за Чиле е невалиден."
#: contrib/localflavor/de/de_states.py:5
msgid "Baden-Wuerttemberg"
@@ -2165,11 +1953,353 @@ msgstr "Шлесвиг-Холштајн"
msgid "Thuringia"
msgstr "Турингиа"
-#: contrib/localflavor/fi/forms.py:40 contrib/localflavor/fi/forms.py:46
+#: contrib/localflavor/de/forms.py:14 contrib/localflavor/fi/forms.py:12
+#: contrib/localflavor/fr/forms.py:15
+msgid "Enter a zip code in the format XXXXX."
+msgstr "Внесете правилен поштенски број во формат XXXXXX."
+
+#: contrib/localflavor/de/forms.py:41
+msgid ""
+"Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X "
+"format."
+msgstr ""
+"Внесете правилен број на лична карта во Германија во XXXXXXXXXXX-XXXXXXX-"
+"XXXXXXX-X форматот."
+
+#: contrib/localflavor/es/es_provinces.py:5
+msgid "Arava"
+msgstr "Arava"
+
+#: contrib/localflavor/es/es_provinces.py:6
+msgid "Albacete"
+msgstr "Albacete"
+
+#: contrib/localflavor/es/es_provinces.py:7
+msgid "Alacant"
+msgstr "Alacant"
+
+#: contrib/localflavor/es/es_provinces.py:8
+msgid "Almeria"
+msgstr "Almeria"
+
+#: contrib/localflavor/es/es_provinces.py:9
+msgid "Avila"
+msgstr "Авила"
+
+#: contrib/localflavor/es/es_provinces.py:10
+msgid "Badajoz"
+msgstr "Badajoz"
+
+#: contrib/localflavor/es/es_provinces.py:11
+msgid "Illes Balears"
+msgstr "Illes Balears"
+
+#: contrib/localflavor/es/es_provinces.py:12
+msgid "Barcelona"
+msgstr "Барцелона"
+
+#: contrib/localflavor/es/es_provinces.py:13
+msgid "Burgos"
+msgstr "Burgos"
+
+#: contrib/localflavor/es/es_provinces.py:14
+msgid "Caceres"
+msgstr "Caceres"
+
+#: contrib/localflavor/es/es_provinces.py:15
+msgid "Cadiz"
+msgstr "Cadiz"
+
+#: contrib/localflavor/es/es_provinces.py:16
+msgid "Castello"
+msgstr "Castello"
+
+#: contrib/localflavor/es/es_provinces.py:17
+msgid "Ciudad Real"
+msgstr "Ciudad Real"
+
+#: contrib/localflavor/es/es_provinces.py:18
+msgid "Cordoba"
+msgstr "Кордоба"
+
+#: contrib/localflavor/es/es_provinces.py:19
+msgid "A Coruna"
+msgstr "A Coruna"
+
+#: contrib/localflavor/es/es_provinces.py:20
+msgid "Cuenca"
+msgstr "Cuenca"
+
+#: contrib/localflavor/es/es_provinces.py:21
+msgid "Girona"
+msgstr "Girona"
+
+#: contrib/localflavor/es/es_provinces.py:22
+msgid "Granada"
+msgstr "Granada"
+
+#: contrib/localflavor/es/es_provinces.py:23
+msgid "Guadalajara"
+msgstr "Guadalajara"
+
+#: contrib/localflavor/es/es_provinces.py:24
+msgid "Guipuzkoa"
+msgstr "Guipuzkoa"
+
+#: contrib/localflavor/es/es_provinces.py:25
+msgid "Huelva"
+msgstr "Huelva"
+
+#: contrib/localflavor/es/es_provinces.py:26
+msgid "Huesca"
+msgstr "Huesca"
+
+#: contrib/localflavor/es/es_provinces.py:27
+msgid "Jaen"
+msgstr "Jaen"
+
+#: contrib/localflavor/es/es_provinces.py:28
+msgid "Leon"
+msgstr "Леон"
+
+#: contrib/localflavor/es/es_provinces.py:29
+msgid "Lleida"
+msgstr "Lleida"
+
+#: contrib/localflavor/es/es_provinces.py:30
+#: contrib/localflavor/es/es_regions.py:17
+msgid "La Rioja"
+msgstr "La Rioja"
+
+#: contrib/localflavor/es/es_provinces.py:31
+msgid "Lugo"
+msgstr "Луго"
+
+#: contrib/localflavor/es/es_provinces.py:32
+#: contrib/localflavor/es/es_regions.py:18
+msgid "Madrid"
+msgstr "Мадрид"
+
+#: contrib/localflavor/es/es_provinces.py:33
+msgid "Malaga"
+msgstr "Малага"
+
+#: contrib/localflavor/es/es_provinces.py:34
+msgid "Murcia"
+msgstr "Murcia"
+
+#: contrib/localflavor/es/es_provinces.py:35
+msgid "Navarre"
+msgstr "Navarre"
+
+#: contrib/localflavor/es/es_provinces.py:36
+msgid "Ourense"
+msgstr "Ourense"
+
+#: contrib/localflavor/es/es_provinces.py:37
+msgid "Asturias"
+msgstr "Asturias"
+
+#: contrib/localflavor/es/es_provinces.py:38
+msgid "Palencia"
+msgstr "Palencia"
+
+#: contrib/localflavor/es/es_provinces.py:39
+msgid "Las Palmas"
+msgstr "Las Palmas"
+
+#: contrib/localflavor/es/es_provinces.py:40
+msgid "Pontevedra"
+msgstr "Pontevedra"
+
+#: contrib/localflavor/es/es_provinces.py:41
+msgid "Salamanca"
+msgstr "Salamanca"
+
+#: contrib/localflavor/es/es_provinces.py:42
+msgid "Santa Cruz de Tenerife"
+msgstr "Санта Круз и Тенерифе"
+
+#: contrib/localflavor/es/es_provinces.py:43
+#: contrib/localflavor/es/es_regions.py:11
+msgid "Cantabria"
+msgstr "Cantabria"
+
+#: contrib/localflavor/es/es_provinces.py:44
+msgid "Segovia"
+msgstr "Segovia"
+
+#: contrib/localflavor/es/es_provinces.py:45
+msgid "Seville"
+msgstr "Севиља"
+
+#: contrib/localflavor/es/es_provinces.py:46
+msgid "Soria"
+msgstr "Soria"
+
+#: contrib/localflavor/es/es_provinces.py:47
+msgid "Tarragona"
+msgstr "Tarragona"
+
+#: contrib/localflavor/es/es_provinces.py:48
+msgid "Teruel"
+msgstr "Teruel"
+
+#: contrib/localflavor/es/es_provinces.py:49
+msgid "Toledo"
+msgstr "Толедо"
+
+#: contrib/localflavor/es/es_provinces.py:50
+msgid "Valencia"
+msgstr "Валенсија"
+
+#: contrib/localflavor/es/es_provinces.py:51
+msgid "Valladolid"
+msgstr "Valladolid"
+
+#: contrib/localflavor/es/es_provinces.py:52
+msgid "Bizkaia"
+msgstr "Bizkaia"
+
+#: contrib/localflavor/es/es_provinces.py:53
+msgid "Zamora"
+msgstr "Zamora"
+
+#: contrib/localflavor/es/es_provinces.py:54
+msgid "Zaragoza"
+msgstr "Сарагоса"
+
+#: contrib/localflavor/es/es_provinces.py:55
+msgid "Ceuta"
+msgstr "Ceuta"
+
+#: contrib/localflavor/es/es_provinces.py:56
+msgid "Melilla"
+msgstr "Melilla"
+
+#: contrib/localflavor/es/es_regions.py:5
+msgid "Andalusia"
+msgstr "Андалузија"
+
+#: contrib/localflavor/es/es_regions.py:6
+msgid "Aragon"
+msgstr "Арагон"
+
+#: contrib/localflavor/es/es_regions.py:7
+msgid "Principality of Asturias"
+msgstr "Principality of Asturias"
+
+#: contrib/localflavor/es/es_regions.py:8
+msgid "Balearic Islands"
+msgstr "Острови Балеарик"
+
+#: contrib/localflavor/es/es_regions.py:9
+msgid "Basque Country"
+msgstr "Баскија"
+
+#: contrib/localflavor/es/es_regions.py:10
+msgid "Canary Islands"
+msgstr "Канарски острови"
+
+#: contrib/localflavor/es/es_regions.py:12
+msgid "Castile-La Mancha"
+msgstr "Кастиља ла Манча"
+
+#: contrib/localflavor/es/es_regions.py:13
+msgid "Castile and Leon"
+msgstr "Кастиња и Леон"
+
+#: contrib/localflavor/es/es_regions.py:14
+msgid "Catalonia"
+msgstr "Каталонија"
+
+#: contrib/localflavor/es/es_regions.py:15
+msgid "Extremadura"
+msgstr "Екстремадура"
+
+#: contrib/localflavor/es/es_regions.py:16
+msgid "Galicia"
+msgstr "Галиција"
+
+#: contrib/localflavor/es/es_regions.py:19
+msgid "Region of Murcia"
+msgstr "Регион Мурција"
+
+#: contrib/localflavor/es/es_regions.py:20
+msgid "Foral Community of Navarre"
+msgstr "Foral Community of Navarre"
+
+#: contrib/localflavor/es/es_regions.py:21
+msgid "Valencian Community"
+msgstr "Valencian Community"
+
+#: contrib/localflavor/es/forms.py:19
+msgid "Enter a valid postal code in the range and format 01XXX - 52XXX."
+msgstr "Внесете поштенски број во опсег и формат 01XXX - 52XXX."
+
+#: contrib/localflavor/es/forms.py:39
+msgid ""
+"Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or "
+"9XXXXXXXX."
+msgstr "Внесете правилен телефонски број во еден од формативе 6XXXXXXXX, 8XXXXXXXX или 9XXXXXXXX."
+
+#: contrib/localflavor/es/forms.py:66
+msgid "Please enter a valid NIF, NIE, or CIF."
+msgstr "Ве молам внесете правиелн NIF, NIE или CIF."
+
+#: contrib/localflavor/es/forms.py:67
+msgid "Please enter a valid NIF or NIE."
+msgstr "Ве молам внесете валиден NIF или NIE."
+
+#: contrib/localflavor/es/forms.py:68
+msgid "Invalid checksum for NIF."
+msgstr "Неправилна контролна сума за NIF."
+
+#: contrib/localflavor/es/forms.py:69
+msgid "Invalid checksum for NIE."
+msgstr "Неправилна контролна сума за NIE."
+
+#: contrib/localflavor/es/forms.py:70
+msgid "Invalid checksum for CIF."
+msgstr "Неправилна контролна сум за CIF."
+
+#: contrib/localflavor/es/forms.py:142
+msgid "Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX."
+msgstr "Внесете правилна банкарска сметка во формат XXXX-XXXX-XX-XXXXXXXXXX."
+
+#: contrib/localflavor/es/forms.py:143
+msgid "Invalid checksum for bank account number."
+msgstr "Неправилна контролна сума за бројот на банкарската сметка."
+
+#: contrib/localflavor/fi/forms.py:28
msgid "Enter a valid Finnish social security number."
msgstr "Внесте правилен фински матичен број."
-#: contrib/localflavor/jp/forms.py:21
+#: contrib/localflavor/in_/forms.py:14
+msgid "Enter a zip code in the format XXXXXXX."
+msgstr "Внесете правилен поштенски број во форматот XXXXXXXX."
+
+#: contrib/localflavor/is_/forms.py:17
+msgid "Enter a valid Icelandic identification number. The format is XXXXXX-XXXX."
+msgstr "Внесете валиден идентификационен број од Исланд. Форматот е XXXXXX-XXXX."
+
+#: contrib/localflavor/is_/forms.py:18
+msgid "The Icelandic identification number is not valid."
+msgstr "Исландскиот идентификационент број е невалиден."
+
+#: contrib/localflavor/it/forms.py:14
+msgid "Enter a valid zip code."
+msgstr "Внесете правилен поштенски број."
+
+#: contrib/localflavor/it/forms.py:43
+msgid "Enter a valid Social Security number."
+msgstr "Внесете правилен осигурителен број."
+
+#: contrib/localflavor/it/forms.py:68
+msgid "Enter a valid VAT number."
+msgstr "Внесете правилен даночен број."
+
+#: contrib/localflavor/jp/forms.py:17
msgid "Enter a postal code in the format XXXXXXX or XXX-XXXX."
msgstr "Внесете поштенски број во форматот XXXXXXX или XXX-XXXX."
@@ -2361,104 +2491,1596 @@ msgstr "Кагошима"
msgid "Okinawa"
msgstr "Окинава"
-#: contrib/localflavor/no/forms.py:15
-msgid "Enter a zip code in the format XXXX."
-msgstr "Внесете правилен поштенски број во форматот XXXX."
+#: contrib/localflavor/mx/mx_states.py:12
+msgid "Aguascalientes"
+msgstr "Aguascalientes"
+
+#: contrib/localflavor/mx/mx_states.py:13
+msgid "Baja California"
+msgstr "Баја Калифорнија"
+
+#: contrib/localflavor/mx/mx_states.py:14
+msgid "Baja California Sur"
+msgstr "Baja California Sur"
+
+#: contrib/localflavor/mx/mx_states.py:15
+msgid "Campeche"
+msgstr "Кампеш"
+
+#: contrib/localflavor/mx/mx_states.py:16
+msgid "Chihuahua"
+msgstr "Chihuahua"
+
+#: contrib/localflavor/mx/mx_states.py:17
+msgid "Chiapas"
+msgstr "Чиапас"
+
+#: contrib/localflavor/mx/mx_states.py:18
+msgid "Coahuila"
+msgstr "Coahuila"
+
+#: contrib/localflavor/mx/mx_states.py:19
+msgid "Colima"
+msgstr "Колима"
+
+#: contrib/localflavor/mx/mx_states.py:20
+msgid "Distrito Federal"
+msgstr "Distrito Federal"
+
+#: contrib/localflavor/mx/mx_states.py:21
+msgid "Durango"
+msgstr "Дуранго"
+
+#: contrib/localflavor/mx/mx_states.py:22
+msgid "Guerrero"
+msgstr "Гуереро"
+
+#: contrib/localflavor/mx/mx_states.py:23
+msgid "Guanajuato"
+msgstr "Guanajuato"
+
+#: contrib/localflavor/mx/mx_states.py:24
+msgid "Hidalgo"
+msgstr "Хидалго"
+
+#: contrib/localflavor/mx/mx_states.py:25
+msgid "Jalisco"
+msgstr "Џалиско"
+
+#: contrib/localflavor/mx/mx_states.py:26
+msgid "Estado de México"
+msgstr "Estado de México"
-#: contrib/localflavor/no/forms.py:36
+#: contrib/localflavor/mx/mx_states.py:27
+msgid "Michoacán"
+msgstr "Michoacán"
+
+#: contrib/localflavor/mx/mx_states.py:28
+msgid "Morelos"
+msgstr "Morelos"
+
+#: contrib/localflavor/mx/mx_states.py:29
+msgid "Nayarit"
+msgstr "Nayarit"
+
+#: contrib/localflavor/mx/mx_states.py:30
+msgid "Nuevo León"
+msgstr "Nuevo León"
+
+#: contrib/localflavor/mx/mx_states.py:31
+msgid "Oaxaca"
+msgstr "Oaxaca"
+
+#: contrib/localflavor/mx/mx_states.py:32
+msgid "Puebla"
+msgstr "Puebla"
+
+#: contrib/localflavor/mx/mx_states.py:33
+msgid "Querétaro"
+msgstr "Querétaro"
+
+#: contrib/localflavor/mx/mx_states.py:34
+msgid "Quintana Roo"
+msgstr "Куантана Ро"
+
+#: contrib/localflavor/mx/mx_states.py:35
+msgid "Sinaloa"
+msgstr "Синалоа"
+
+#: contrib/localflavor/mx/mx_states.py:36
+msgid "San Luis Potosí"
+msgstr "Сан Луис Потоси"
+
+#: contrib/localflavor/mx/mx_states.py:37
+msgid "Sonora"
+msgstr "Сонора"
+
+#: contrib/localflavor/mx/mx_states.py:38
+msgid "Tabasco"
+msgstr "Табаско"
+
+#: contrib/localflavor/mx/mx_states.py:39
+msgid "Tamaulipas"
+msgstr "Тамаулипас"
+
+#: contrib/localflavor/mx/mx_states.py:40
+msgid "Tlaxcala"
+msgstr "Tlaxcala"
+
+#: contrib/localflavor/mx/mx_states.py:41
+msgid "Veracruz"
+msgstr "Веракруз"
+
+#: contrib/localflavor/mx/mx_states.py:42
+msgid "Yucatán"
+msgstr "Јукатан"
+
+#: contrib/localflavor/mx/mx_states.py:43
+msgid "Zacatecas"
+msgstr "Зацатекас"
+
+#: contrib/localflavor/nl/forms.py:21
+msgid "Enter a valid postal code"
+msgstr "Внесете правилен поштенски код"
+
+#: contrib/localflavor/nl/forms.py:52
+msgid "Enter a valid phone number"
+msgstr "Внесете валиден телефонски број"
+
+#: contrib/localflavor/nl/forms.py:78
+msgid "Enter a valid SoFi number"
+msgstr "Внесете валиден осигурителен број"
+
+#: contrib/localflavor/nl/nl_provinces.py:4
+msgid "Drente"
+msgstr "Дренте"
+
+#: contrib/localflavor/nl/nl_provinces.py:5
+msgid "Flevoland"
+msgstr "Флеволанд"
+
+#: contrib/localflavor/nl/nl_provinces.py:6
+msgid "Friesland"
+msgstr "Фраисланд"
+
+#: contrib/localflavor/nl/nl_provinces.py:7
+msgid "Gelderland"
+msgstr "Гелдерланд"
+
+#: contrib/localflavor/nl/nl_provinces.py:8
+msgid "Groningen"
+msgstr "Гронинген"
+
+#: contrib/localflavor/nl/nl_provinces.py:9
+msgid "Limburg"
+msgstr "Лимбург"
+
+#: contrib/localflavor/nl/nl_provinces.py:10
+msgid "Noord-Brabant"
+msgstr "Noord-Brabant"
+
+#: contrib/localflavor/nl/nl_provinces.py:11
+msgid "Noord-Holland"
+msgstr "Noord-Holland"
+
+#: contrib/localflavor/nl/nl_provinces.py:12
+msgid "Overijssel"
+msgstr "Overijssel"
+
+#: contrib/localflavor/nl/nl_provinces.py:13
+msgid "Utrecht"
+msgstr "Утрехт"
+
+#: contrib/localflavor/nl/nl_provinces.py:14
+msgid "Zeeland"
+msgstr "Зиланд"
+
+#: contrib/localflavor/nl/nl_provinces.py:15
+msgid "Zuid-Holland"
+msgstr "Zuid-Holland"
+
+#: contrib/localflavor/no/forms.py:33
msgid "Enter a valid Norwegian social security number."
msgstr "Ве молам внесете правилен норвешки матичен број."
-#: contrib/localflavor/uk/forms.py:18
-msgid "Enter a postcode. A space is required between the two postcode parts."
-msgstr "Внесете поштенски број. Задолжително е празно место помеѓу двата дела на поштенскиот број."
+#: contrib/localflavor/pe/forms.py:24
+msgid "This field requires 8 digits."
+msgstr "Во ова поле мора да се внесат 8 цифри."
+
+#: contrib/localflavor/pe/forms.py:52
+msgid "This field requires 11 digits."
+msgstr "Во ова поле се потребни 11 цифри."
+
+#: contrib/localflavor/pl/forms.py:39
+msgid "National Identification Number consists of 11 digits."
+msgstr "Националниот идентификационен број се состои од 11 цифири."
+
+#: contrib/localflavor/pl/forms.py:40
+msgid "Wrong checksum for the National Identification Number."
+msgstr "Погрешна проверка за Националниот идентификационен број."
+
+#: contrib/localflavor/pl/forms.py:72
+msgid "Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX."
+msgstr "Внесете даночен број (NIP) во форматот XXX-XXX-XX-XX или XX-XX-XXX-XXX."
+
+#: contrib/localflavor/pl/forms.py:73
+msgid "Wrong checksum for the Tax Number (NIP)."
+msgstr "Даночниот број (NIP) е погрешен."
+
+#: contrib/localflavor/pl/forms.py:112
+msgid "National Business Register Number (REGON) consists of 7 or 9 digits."
+msgstr "Националниот бизнис регистрациски број (REGON) се состои од 7 или 9 цифри."
+
+#: contrib/localflavor/pl/forms.py:113
+msgid "Wrong checksum for the National Business Register Number (REGON)."
+msgstr "Неправилна контролна сум за Националниот бизнис регистрационен број (REGON)."
+
+#: contrib/localflavor/pl/forms.py:156
+msgid "Enter a postal code in the format XX-XXX."
+msgstr "Внесете правилен поштенски број во формат XX-XXX."
+
+#: contrib/localflavor/pl/pl_voivodeships.py:8
+msgid "Lower Silesia"
+msgstr "Долна Силесиа"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:9
+msgid "Kuyavia-Pomerania"
+msgstr "Kuyavia-Pomerania"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:10
+msgid "Lublin"
+msgstr "Лублин"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:11
+msgid "Lubusz"
+msgstr "Лубус"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:12
+msgid "Lodz"
+msgstr "Лодз"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:13
+msgid "Lesser Poland"
+msgstr "Lesser Poland"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:14
+msgid "Masovia"
+msgstr "Масовиа"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:15
+msgid "Opole"
+msgstr "Ополе"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:16
+msgid "Subcarpatia"
+msgstr "Субкарпатиа"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:17
+msgid "Podlasie"
+msgstr "Подласи"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:18
+msgid "Pomerania"
+msgstr "Померанија"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:19
+msgid "Silesia"
+msgstr "Силесиа"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:20
+msgid "Swietokrzyskie"
+msgstr "Swietokrzyskie"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:21
+msgid "Warmia-Masuria"
+msgstr "Warmia-Masuria"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:22
+msgid "Greater Poland"
+msgstr "Greater Poland"
+
+#: contrib/localflavor/pl/pl_voivodeships.py:23
+msgid "West Pomerania"
+msgstr "Западна Померанија"
+
+#: contrib/localflavor/sk/forms.py:30
+msgid "Enter a postal code in the format XXXXX or XXX XX."
+msgstr "Внесете поштенски број во форматот XXXXX или XXX XX."
+
+#: contrib/localflavor/sk/sk_districts.py:8
+msgid "Banska Bystrica"
+msgstr "Банска Бистрица"
+
+#: contrib/localflavor/sk/sk_districts.py:9
+msgid "Banska Stiavnica"
+msgstr "Банска Стиавница"
+
+#: contrib/localflavor/sk/sk_districts.py:10
+msgid "Bardejov"
+msgstr "Бардејов"
+
+#: contrib/localflavor/sk/sk_districts.py:11
+msgid "Banovce nad Bebravou"
+msgstr "Бановце над Бебраво"
+
+#: contrib/localflavor/sk/sk_districts.py:12
+msgid "Brezno"
+msgstr "Брезно"
+
+#: contrib/localflavor/sk/sk_districts.py:13
+msgid "Bratislava I"
+msgstr "Братислава I"
+
+#: contrib/localflavor/sk/sk_districts.py:14
+msgid "Bratislava II"
+msgstr "Братислава II"
+
+#: contrib/localflavor/sk/sk_districts.py:15
+msgid "Bratislava III"
+msgstr "Братислава III"
+
+#: contrib/localflavor/sk/sk_districts.py:16
+msgid "Bratislava IV"
+msgstr "Братислава IV"
+
+#: contrib/localflavor/sk/sk_districts.py:17
+msgid "Bratislava V"
+msgstr "Братислава V"
+
+#: contrib/localflavor/sk/sk_districts.py:18
+msgid "Bytca"
+msgstr "Битка"
+
+#: contrib/localflavor/sk/sk_districts.py:19
+msgid "Cadca"
+msgstr "Кадка"
+
+#: contrib/localflavor/sk/sk_districts.py:20
+msgid "Detva"
+msgstr "Детва"
+
+#: contrib/localflavor/sk/sk_districts.py:21
+msgid "Dolny Kubin"
+msgstr "Dolny Kubin"
+
+#: contrib/localflavor/sk/sk_districts.py:22
+msgid "Dunajska Streda"
+msgstr "Dunajska Streda"
+
+#: contrib/localflavor/sk/sk_districts.py:23
+msgid "Galanta"
+msgstr "Galanta"
+
+#: contrib/localflavor/sk/sk_districts.py:24
+msgid "Gelnica"
+msgstr "Гелника"
+
+#: contrib/localflavor/sk/sk_districts.py:25
+msgid "Hlohovec"
+msgstr "Хлоховец"
+
+#: contrib/localflavor/sk/sk_districts.py:26
+msgid "Humenne"
+msgstr "Хумен"
+
+#: contrib/localflavor/sk/sk_districts.py:27
+msgid "Ilava"
+msgstr "Ilava"
+
+#: contrib/localflavor/sk/sk_districts.py:28
+msgid "Kezmarok"
+msgstr "Кезмарок"
+
+#: contrib/localflavor/sk/sk_districts.py:29
+msgid "Komarno"
+msgstr "Комарно"
+
+#: contrib/localflavor/sk/sk_districts.py:30
+msgid "Kosice I"
+msgstr "Кошице I"
+
+#: contrib/localflavor/sk/sk_districts.py:31
+msgid "Kosice II"
+msgstr "Кошице II"
+
+#: contrib/localflavor/sk/sk_districts.py:32
+msgid "Kosice III"
+msgstr "Кошице III"
+
+#: contrib/localflavor/sk/sk_districts.py:33
+msgid "Kosice IV"
+msgstr "Кошице IV"
+
+#: contrib/localflavor/sk/sk_districts.py:34
+msgid "Kosice - okolie"
+msgstr "Кошице - околина"
-#: contrib/localflavor/usa/forms.py:18
+#: contrib/localflavor/sk/sk_districts.py:35
+msgid "Krupina"
+msgstr "Крупина"
+
+#: contrib/localflavor/sk/sk_districts.py:36
+msgid "Kysucke Nove Mesto"
+msgstr "Kysucke Nove Mesto"
+
+#: contrib/localflavor/sk/sk_districts.py:37
+msgid "Levice"
+msgstr "Левице"
+
+#: contrib/localflavor/sk/sk_districts.py:38
+msgid "Levoca"
+msgstr "Левока"
+
+#: contrib/localflavor/sk/sk_districts.py:39
+msgid "Liptovsky Mikulas"
+msgstr "Liptovsky Mikulas"
+
+#: contrib/localflavor/sk/sk_districts.py:40
+msgid "Lucenec"
+msgstr "Луценец"
+
+#: contrib/localflavor/sk/sk_districts.py:41
+msgid "Malacky"
+msgstr "Malacky"
+
+#: contrib/localflavor/sk/sk_districts.py:42
+msgid "Martin"
+msgstr "Мартин"
+
+#: contrib/localflavor/sk/sk_districts.py:43
+msgid "Medzilaborce"
+msgstr "Medzilaborce"
+
+#: contrib/localflavor/sk/sk_districts.py:44
+msgid "Michalovce"
+msgstr "Michalovce"
+
+#: contrib/localflavor/sk/sk_districts.py:45
+msgid "Myjava"
+msgstr "Myjava"
+
+#: contrib/localflavor/sk/sk_districts.py:46
+msgid "Namestovo"
+msgstr "Наместово"
+
+#: contrib/localflavor/sk/sk_districts.py:47
+msgid "Nitra"
+msgstr "Нитра"
+
+#: contrib/localflavor/sk/sk_districts.py:48
+msgid "Nove Mesto nad Vahom"
+msgstr "Nove Mesto nad Vahom"
+
+#: contrib/localflavor/sk/sk_districts.py:49
+msgid "Nove Zamky"
+msgstr "Nove Zamky"
+
+#: contrib/localflavor/sk/sk_districts.py:50
+msgid "Partizanske"
+msgstr "Партизанске"
+
+#: contrib/localflavor/sk/sk_districts.py:51
+msgid "Pezinok"
+msgstr "Пезинок"
+
+#: contrib/localflavor/sk/sk_districts.py:52
+msgid "Piestany"
+msgstr "Piestany"
+
+#: contrib/localflavor/sk/sk_districts.py:53
+msgid "Poltar"
+msgstr "Полтар"
+
+#: contrib/localflavor/sk/sk_districts.py:54
+msgid "Poprad"
+msgstr "Попрад"
+
+#: contrib/localflavor/sk/sk_districts.py:55
+msgid "Povazska Bystrica"
+msgstr "Povazska Bystrica"
+
+#: contrib/localflavor/sk/sk_districts.py:56
+msgid "Presov"
+msgstr "Пресов"
+
+#: contrib/localflavor/sk/sk_districts.py:57
+msgid "Prievidza"
+msgstr "Prievidza"
+
+#: contrib/localflavor/sk/sk_districts.py:58
+msgid "Puchov"
+msgstr "Пучов"
+
+#: contrib/localflavor/sk/sk_districts.py:59
+msgid "Revuca"
+msgstr "Ревука"
+
+#: contrib/localflavor/sk/sk_districts.py:60
+msgid "Rimavska Sobota"
+msgstr "Rimavska Sobota"
+
+#: contrib/localflavor/sk/sk_districts.py:61
+msgid "Roznava"
+msgstr "Рознава"
+
+#: contrib/localflavor/sk/sk_districts.py:62
+msgid "Ruzomberok"
+msgstr "Рузомберок"
+
+#: contrib/localflavor/sk/sk_districts.py:63
+msgid "Sabinov"
+msgstr "Сабинов"
+
+#: contrib/localflavor/sk/sk_districts.py:64
+msgid "Senec"
+msgstr "Сенец"
+
+#: contrib/localflavor/sk/sk_districts.py:65
+msgid "Senica"
+msgstr "Сеника"
+
+#: contrib/localflavor/sk/sk_districts.py:66
+msgid "Skalica"
+msgstr "Скалица"
+
+#: contrib/localflavor/sk/sk_districts.py:67
+msgid "Snina"
+msgstr "Шнина"
+
+#: contrib/localflavor/sk/sk_districts.py:68
+msgid "Sobrance"
+msgstr "Собранце"
+
+#: contrib/localflavor/sk/sk_districts.py:69
+msgid "Spisska Nova Ves"
+msgstr "Spisska Nova Ves"
+
+#: contrib/localflavor/sk/sk_districts.py:70
+msgid "Stara Lubovna"
+msgstr "Стара Љубовна"
+
+#: contrib/localflavor/sk/sk_districts.py:71
+msgid "Stropkov"
+msgstr "Стропков"
+
+#: contrib/localflavor/sk/sk_districts.py:72
+msgid "Svidnik"
+msgstr "Свидник"
+
+#: contrib/localflavor/sk/sk_districts.py:73
+msgid "Sala"
+msgstr "Сала"
+
+#: contrib/localflavor/sk/sk_districts.py:74
+msgid "Topolcany"
+msgstr "Тополчани"
+
+#: contrib/localflavor/sk/sk_districts.py:75
+msgid "Trebisov"
+msgstr "Требисов"
+
+#: contrib/localflavor/sk/sk_districts.py:76
+msgid "Trencin"
+msgstr "Тренцин"
+
+#: contrib/localflavor/sk/sk_districts.py:77
+msgid "Trnava"
+msgstr "Трнава"
+
+#: contrib/localflavor/sk/sk_districts.py:78
+msgid "Turcianske Teplice"
+msgstr "Turcianske Teplice"
+
+#: contrib/localflavor/sk/sk_districts.py:79
+msgid "Tvrdosin"
+msgstr "Тврдосин"
+
+#: contrib/localflavor/sk/sk_districts.py:80
+msgid "Velky Krtis"
+msgstr "Velky Krtis"
+
+#: contrib/localflavor/sk/sk_districts.py:81
+msgid "Vranov nad Toplou"
+msgstr "Vranov nad Toplou"
+
+#: contrib/localflavor/sk/sk_districts.py:82
+msgid "Zlate Moravce"
+msgstr "Злате Моравце"
+
+#: contrib/localflavor/sk/sk_districts.py:83
+msgid "Zvolen"
+msgstr "Зволен"
+
+#: contrib/localflavor/sk/sk_districts.py:84
+msgid "Zarnovica"
+msgstr "Зарновица"
+
+#: contrib/localflavor/sk/sk_districts.py:85
+msgid "Ziar nad Hronom"
+msgstr "Ziar nad Hronom"
+
+#: contrib/localflavor/sk/sk_districts.py:86
+msgid "Zilina"
+msgstr "Зилина"
+
+#: contrib/localflavor/sk/sk_regions.py:8
+msgid "Banska Bystrica region"
+msgstr "Banska Bystrica region"
+
+#: contrib/localflavor/sk/sk_regions.py:9
+msgid "Bratislava region"
+msgstr "Братиславски регион"
+
+#: contrib/localflavor/sk/sk_regions.py:10
+msgid "Kosice region"
+msgstr "Кошице регион"
+
+#: contrib/localflavor/sk/sk_regions.py:11
+msgid "Nitra region"
+msgstr "Нитра регион"
+
+#: contrib/localflavor/sk/sk_regions.py:12
+msgid "Presov region"
+msgstr "Пресов регион"
+
+#: contrib/localflavor/sk/sk_regions.py:13
+msgid "Trencin region"
+msgstr "Тренцин регион"
+
+#: contrib/localflavor/sk/sk_regions.py:14
+msgid "Trnava region"
+msgstr "Трнава регион"
+
+#: contrib/localflavor/sk/sk_regions.py:15
+msgid "Zilina region"
+msgstr "Зилина регион"
+
+#: contrib/localflavor/uk/forms.py:21
+msgid "Enter a valid postcode."
+msgstr "Внесете правилен поштенски код."
+
+#: contrib/localflavor/uk/uk_regions.py:11
+msgid "Bedfordshire"
+msgstr "Бедфордшир"
+
+#: contrib/localflavor/uk/uk_regions.py:12
+msgid "Buckinghamshire"
+msgstr "Букингхамшир"
+
+#: contrib/localflavor/uk/uk_regions.py:14
+msgid "Cheshire"
+msgstr "Чешир"
+
+#: contrib/localflavor/uk/uk_regions.py:15
+msgid "Cornwall and Isles of Scilly"
+msgstr "Cornwall and Isles of Scilly"
+
+#: contrib/localflavor/uk/uk_regions.py:16
+msgid "Cumbria"
+msgstr "Кумбриа"
+
+#: contrib/localflavor/uk/uk_regions.py:17
+msgid "Derbyshire"
+msgstr "Дербишир"
+
+#: contrib/localflavor/uk/uk_regions.py:18
+msgid "Devon"
+msgstr "Девон"
+
+#: contrib/localflavor/uk/uk_regions.py:19
+msgid "Dorset"
+msgstr "Дорсет"
+
+#: contrib/localflavor/uk/uk_regions.py:20
+msgid "Durham"
+msgstr "Дурам"
+
+#: contrib/localflavor/uk/uk_regions.py:21
+msgid "East Sussex"
+msgstr "Источен Сасекс"
+
+#: contrib/localflavor/uk/uk_regions.py:22
+msgid "Essex"
+msgstr "Есекс"
+
+#: contrib/localflavor/uk/uk_regions.py:23
+msgid "Gloucestershire"
+msgstr "Gloucestershire"
+
+#: contrib/localflavor/uk/uk_regions.py:24
+msgid "Greater London"
+msgstr "Поширок Лондон"
+
+#: contrib/localflavor/uk/uk_regions.py:25
+msgid "Greater Manchester"
+msgstr "Поширок Манчестер"
+
+#: contrib/localflavor/uk/uk_regions.py:26
+msgid "Hampshire"
+msgstr "Хемпшир"
+
+#: contrib/localflavor/uk/uk_regions.py:27
+msgid "Hertfordshire"
+msgstr "Хертфордшир"
+
+#: contrib/localflavor/uk/uk_regions.py:28
+msgid "Kent"
+msgstr "Кент"
+
+#: contrib/localflavor/uk/uk_regions.py:29
+msgid "Lancashire"
+msgstr "Ланкашир"
+
+#: contrib/localflavor/uk/uk_regions.py:30
+msgid "Leicestershire"
+msgstr "Leicestershire"
+
+#: contrib/localflavor/uk/uk_regions.py:31
+msgid "Lincolnshire"
+msgstr "Линколншир"
+
+#: contrib/localflavor/uk/uk_regions.py:32
+msgid "Merseyside"
+msgstr "Мерсејсајд"
+
+#: contrib/localflavor/uk/uk_regions.py:33
+msgid "Norfolk"
+msgstr "Норфолк"
+
+#: contrib/localflavor/uk/uk_regions.py:34
+msgid "North Yorkshire"
+msgstr "Северен Јоркшир"
+
+#: contrib/localflavor/uk/uk_regions.py:35
+msgid "Northamptonshire"
+msgstr "Нортхамтоншир"
+
+#: contrib/localflavor/uk/uk_regions.py:36
+msgid "Northumberland"
+msgstr "Northumberland"
+
+#: contrib/localflavor/uk/uk_regions.py:37
+msgid "Nottinghamshire"
+msgstr "Нотингхамшир"
+
+#: contrib/localflavor/uk/uk_regions.py:38
+msgid "Oxfordshire"
+msgstr "Оксфордшир"
+
+#: contrib/localflavor/uk/uk_regions.py:39
+msgid "Shropshire"
+msgstr "Шропшир"
+
+#: contrib/localflavor/uk/uk_regions.py:40
+msgid "Somerset"
+msgstr "Сомерсет"
+
+#: contrib/localflavor/uk/uk_regions.py:41
+msgid "South Yorkshire"
+msgstr "Јужен Јоркшир"
+
+#: contrib/localflavor/uk/uk_regions.py:42
+msgid "Staffordshire"
+msgstr "Стафордшир"
+
+#: contrib/localflavor/uk/uk_regions.py:43
+msgid "Suffolk"
+msgstr "Суфолк"
+
+#: contrib/localflavor/uk/uk_regions.py:44
+msgid "Surrey"
+msgstr "Surrey"
+
+#: contrib/localflavor/uk/uk_regions.py:45
+msgid "Tyne and Wear"
+msgstr "Tyne and Wear"
+
+#: contrib/localflavor/uk/uk_regions.py:46
+msgid "Warwickshire"
+msgstr "Warwickshire"
+
+#: contrib/localflavor/uk/uk_regions.py:47
+msgid "West Midlands"
+msgstr "Западен Мидландс"
+
+#: contrib/localflavor/uk/uk_regions.py:48
+msgid "West Sussex"
+msgstr "Западен Сасекс"
+
+#: contrib/localflavor/uk/uk_regions.py:49
+msgid "West Yorkshire"
+msgstr "Западен Јоркшир"
+
+#: contrib/localflavor/uk/uk_regions.py:50
+msgid "Wiltshire"
+msgstr "Вилтшир"
+
+#: contrib/localflavor/uk/uk_regions.py:51
+msgid "Worcestershire"
+msgstr "Worcestershire"
+
+#: contrib/localflavor/uk/uk_regions.py:55
+msgid "County Antrim"
+msgstr "County Antrim"
+
+#: contrib/localflavor/uk/uk_regions.py:56
+msgid "County Armagh"
+msgstr "County Armagh"
+
+#: contrib/localflavor/uk/uk_regions.py:57
+msgid "County Down"
+msgstr "County Down"
+
+#: contrib/localflavor/uk/uk_regions.py:58
+msgid "County Fermanagh"
+msgstr "County Fermanagh"
+
+#: contrib/localflavor/uk/uk_regions.py:59
+msgid "County Londonderry"
+msgstr "County Londonderry"
+
+#: contrib/localflavor/uk/uk_regions.py:60
+msgid "County Tyrone"
+msgstr "County Tyrone"
+
+#: contrib/localflavor/uk/uk_regions.py:64
+msgid "Clwyd"
+msgstr "Clwyd"
+
+#: contrib/localflavor/uk/uk_regions.py:65
+msgid "Dyfed"
+msgstr "Dyfed"
+
+#: contrib/localflavor/uk/uk_regions.py:66
+msgid "Gwent"
+msgstr "Гвент"
+
+#: contrib/localflavor/uk/uk_regions.py:67
+msgid "Gwynedd"
+msgstr "Gwynedd"
+
+#: contrib/localflavor/uk/uk_regions.py:68
+msgid "Mid Glamorgan"
+msgstr "Mid Glamorgan"
+
+#: contrib/localflavor/uk/uk_regions.py:69
+msgid "Powys"
+msgstr "Powys"
+
+#: contrib/localflavor/uk/uk_regions.py:70
+msgid "South Glamorgan"
+msgstr "Јужен Гламорга"
+
+#: contrib/localflavor/uk/uk_regions.py:71
+msgid "West Glamorgan"
+msgstr "Западен Гламорган"
+
+#: contrib/localflavor/uk/uk_regions.py:75
+msgid "Borders"
+msgstr "Borders"
+
+#: contrib/localflavor/uk/uk_regions.py:76
+msgid "Central Scotland"
+msgstr "Централна Шкотска"
+
+#: contrib/localflavor/uk/uk_regions.py:77
+msgid "Dumfries and Galloway"
+msgstr "Dumfries and Galloway"
+
+#: contrib/localflavor/uk/uk_regions.py:78
+msgid "Fife"
+msgstr "Fife"
+
+#: contrib/localflavor/uk/uk_regions.py:79
+msgid "Grampian"
+msgstr "Грампиан"
+
+#: contrib/localflavor/uk/uk_regions.py:80
+msgid "Highland"
+msgstr "Highland"
+
+#: contrib/localflavor/uk/uk_regions.py:81
+msgid "Lothian"
+msgstr "Lothian"
+
+#: contrib/localflavor/uk/uk_regions.py:82
+msgid "Orkney Islands"
+msgstr "Orkney Islands"
+
+#: contrib/localflavor/uk/uk_regions.py:83
+msgid "Shetland Islands"
+msgstr "Shetland Islands"
+
+#: contrib/localflavor/uk/uk_regions.py:84
+msgid "Strathclyde"
+msgstr "Strathclyde"
+
+#: contrib/localflavor/uk/uk_regions.py:85
+msgid "Tayside"
+msgstr "Tayside"
+
+#: contrib/localflavor/uk/uk_regions.py:86
+msgid "Western Isles"
+msgstr "Western Isles"
+
+#: contrib/localflavor/uk/uk_regions.py:90
+msgid "England"
+msgstr "Англија"
+
+#: contrib/localflavor/uk/uk_regions.py:91
+msgid "Northern Ireland"
+msgstr "Северна Ирска"
+
+#: contrib/localflavor/uk/uk_regions.py:92
+msgid "Scotland"
+msgstr "Шкотска"
+
+#: contrib/localflavor/uk/uk_regions.py:93
+msgid "Wales"
+msgstr "Велс"
+
+#: contrib/localflavor/us/forms.py:16
msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX."
msgstr "Внесете поштенски број во форматот XXXXX или XXXXX-XXXX."
-#: contrib/localflavor/usa/forms.py:51
+#: contrib/localflavor/us/forms.py:54
msgid "Enter a valid U.S. Social Security number in XXX-XX-XXXX format."
msgstr "Внесте правилен матичен број за САД во XXX-XX-XXXX форматот."
-#: contrib/sessions/models.py:68
+#: contrib/localflavor/za/forms.py:20
+msgid "Enter a valid South African ID number"
+msgstr "Внесете правилен јужно афрички број за идентификација"
+
+#: contrib/localflavor/za/forms.py:54
+msgid "Enter a valid South African postal code"
+msgstr "Внесете правилен јужно афрички поштенски код"
+
+#: contrib/localflavor/za/za_provinces.py:4
+msgid "Eastern Cape"
+msgstr "Источен Кејп"
+
+#: contrib/localflavor/za/za_provinces.py:5
+msgid "Free State"
+msgstr "Free State"
+
+#: contrib/localflavor/za/za_provinces.py:6
+msgid "Gauteng"
+msgstr "Gauteng"
+
+#: contrib/localflavor/za/za_provinces.py:7
+msgid "KwaZulu-Natal"
+msgstr "KwaZulu-Natal"
+
+#: contrib/localflavor/za/za_provinces.py:8
+msgid "Limpopo"
+msgstr "Limpopo"
+
+#: contrib/localflavor/za/za_provinces.py:9
+msgid "Mpumalanga"
+msgstr "Mpumalanga"
+
+#: contrib/localflavor/za/za_provinces.py:10
+msgid "Northern Cape"
+msgstr "Northern Cape"
+
+#: contrib/localflavor/za/za_provinces.py:11
+msgid "North West"
+msgstr "North West"
+
+#: contrib/localflavor/za/za_provinces.py:12
+msgid "Western Cape"
+msgstr "Western Cape"
+
+#: contrib/redirects/models.py:7
+msgid "redirect from"
+msgstr "пренасочи од"
+
+#: contrib/redirects/models.py:8
+msgid ""
+"This should be an absolute path, excluding the domain name. Example: '/"
+"events/search/'."
+msgstr ""
+"Ова треба да биде апсолутна патека без името на домејнот. На пр. „/nastani/"
+"prebaraj/“."
+
+#: contrib/redirects/models.py:9
+msgid "redirect to"
+msgstr "пренасочи кон"
+
+#: contrib/redirects/models.py:10
+msgid ""
+"This can be either an absolute path (as above) or a full URL starting with "
+"'http://'."
+msgstr ""
+"Ова може да биде или апсолутна патека (како погоре) или цела адреса "
+"почувајќи со „http://“."
+
+#: contrib/redirects/models.py:13
+msgid "redirect"
+msgstr "пренасочување"
+
+#: contrib/redirects/models.py:14
+msgid "redirects"
+msgstr "пренасочувања"
+
+#: contrib/sessions/models.py:41
msgid "session key"
msgstr "клуч на сесијата"
-#: contrib/sessions/models.py:69
+#: contrib/sessions/models.py:42
msgid "session data"
msgstr "податоци од сесијата"
-#: contrib/sessions/models.py:70
+#: contrib/sessions/models.py:43
msgid "expire date"
msgstr "датум на истекување"
-#: contrib/sessions/models.py:74
+#: contrib/sessions/models.py:48
msgid "session"
msgstr "сесија"
-#: contrib/sessions/models.py:75
+#: contrib/sessions/models.py:49
msgid "sessions"
msgstr "сесии"
-#: contrib/contenttypes/models.py:36
-msgid "python model class name"
-msgstr "има на класата на питон моделите"
+#: contrib/sites/models.py:32
+msgid "domain name"
+msgstr "домејн"
-#: contrib/contenttypes/models.py:39
-msgid "content type"
-msgstr "content type"
+#: contrib/sites/models.py:33
+msgid "display name"
+msgstr "име кое се прикажува"
-#: contrib/contenttypes/models.py:40
-msgid "content types"
-msgstr "content types"
+#: contrib/sites/models.py:37
+msgid "site"
+msgstr "сајт"
+
+#: contrib/sites/models.py:38
+msgid "sites"
+msgstr "сајтови"
+
+#: core/validators.py:72
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "Оваа вредност смее да има само букви, бројки или долни црти."
+
+#: core/validators.py:76
+msgid ""
+"This value must contain only letters, numbers, underscores, dashes or "
+"slashes."
+msgstr "Оваа вредност смее да има само букви, бројки, долни црти, црти или коси црти."
+
+#: core/validators.py:80
+msgid "This value must contain only letters, numbers, underscores or hyphens."
+msgstr "Оваа вредност смее да содржи само букви, бројки, долни црти или црти."
+
+#: core/validators.py:84
+msgid "Uppercase letters are not allowed here."
+msgstr "Големи букви не се дозволени."
+
+#: core/validators.py:88
+msgid "Lowercase letters are not allowed here."
+msgstr "Мали букви не се дозволени."
+
+#: core/validators.py:95
+msgid "Enter only digits separated by commas."
+msgstr "Внесете само цифри одделени со запирки."
+
+#: core/validators.py:107
+msgid "Enter valid e-mail addresses separated by commas."
+msgstr "Внесете валидни адреси за е-пошта одделени со запирки."
+
+#: core/validators.py:111
+msgid "Please enter a valid IP address."
+msgstr "Ве молам внесете валидна ИП адреса."
+
+#: core/validators.py:115
+msgid "Empty values are not allowed here."
+msgstr "Празни вредности не се дозволени."
+
+#: core/validators.py:119
+msgid "Non-numeric characters aren't allowed here."
+msgstr "Ненумерички знаци не се дозволени тука."
+
+#: core/validators.py:123
+msgid "This value can't be comprised solely of digits."
+msgstr "Оваа вредност не смее да биде само од цифри."
+
+#: core/validators.py:128 newforms/fields.py:152
+msgid "Enter a whole number."
+msgstr "Внеси цел број."
+
+#: core/validators.py:132
+msgid "Only alphabetical characters are allowed here."
+msgstr "Дозволени се само букви."
+
+#: core/validators.py:147
+msgid "Year must be 1900 or later."
+msgstr "Годината мора да биде 1900 или покасно."
+
+#: core/validators.py:151
+#, python-format
+msgid "Invalid date: %s"
+msgstr "Неправилен датум: %s"
+
+#: core/validators.py:156 db/models/fields/__init__.py:548
+msgid "Enter a valid date in YYYY-MM-DD format."
+msgstr "Внесете правилен датум во форматот ГГГГ-ММ-ДД."
+
+#: core/validators.py:161
+msgid "Enter a valid time in HH:MM format."
+msgstr "Внесете правилно време во форматот HH:MM."
+
+#: core/validators.py:165 db/models/fields/__init__.py:625
+msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
+msgstr "Внесете правилен датум/време во форматот ГГГГ-ММ-ДД ЧЧ:ММ."
+
+#: core/validators.py:170 newforms/fields.py:403
+msgid "Enter a valid e-mail address."
+msgstr "Внесeте правилна адреса за е-пошта."
+
+#: core/validators.py:182 core/validators.py:474 newforms/fields.py:433
+#: oldforms/__init__.py:687
+msgid "No file was submitted. Check the encoding type on the form."
+msgstr "Не беше пратена датотека. Проверете го типот на енкодирање на формата."
+
+#: core/validators.py:193 newforms/fields.py:459
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+"Качете валидна фотографија. Датотеката која ја качивте или не беше "
+"фотографија или беше расипана датотеката."
+
+#: core/validators.py:200
+#, python-format
+msgid "The URL %s does not point to a valid image."
+msgstr "Адресата %s не покажува кон валидна фотографија."
+
+#: core/validators.py:204
+#, python-format
+msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid."
+msgstr ""
+"Телефонските броеви мора да бидат во XXX-XXX-XXXX форматот. „%s“ не е "
+"валиден."
+
+#: core/validators.py:212
+#, python-format
+msgid "The URL %s does not point to a valid QuickTime video."
+msgstr "Адресата „%s“ не покажува кон QuickTime видео."
+
+#: core/validators.py:216
+msgid "A valid URL is required."
+msgstr "Задолжителна е правилна адреса."
+
+#: core/validators.py:230
+#, python-format
+msgid ""
+"Valid HTML is required. Specific errors are:\n"
+"%s"
+msgstr ""
+"Задолжителен е правилен HTML. Грешките се:\n"
+"%s"
+
+#: core/validators.py:237
+#, python-format
+msgid "Badly formed XML: %s"
+msgstr "Неправилно формиран XML: %s"
+
+#: core/validators.py:254
+#, python-format
+msgid "Invalid URL: %s"
+msgstr "Неправилна адреса: %s"
+
+#: core/validators.py:259 core/validators.py:261
+#, python-format
+msgid "The URL %s is a broken link."
+msgstr "Адресата %s е скршена врска."
+
+#: core/validators.py:267
+msgid "Enter a valid U.S. state abbreviation."
+msgstr "Внесете правилна скратеница за држава во САД."
+
+#: core/validators.py:281
+#, python-format
+msgid "Watch your mouth! The word %s is not allowed here."
+msgid_plural "Watch your mouth! The words %s are not allowed here."
+msgstr[0] "Внимавајте на јазикот. Тука не е дозволен зборот %s."
+msgstr[1] "Внимавајте на јазикот. Тука не се дозволени зборовите %s."
+
+#: core/validators.py:288
+#, python-format
+msgid "This field must match the '%s' field."
+msgstr "Ова поле мора да соодејствува со полето „%s“."
+
+#: core/validators.py:307
+msgid "Please enter something for at least one field."
+msgstr "Ве молам внесете нешто во барем едно поле."
+
+#: core/validators.py:316 core/validators.py:327
+msgid "Please enter both fields or leave them both empty."
+msgstr "Ве молам внесете во двете полиња или оставете ги двете празни."
+
+#: core/validators.py:335
+#, python-format
+msgid "This field must be given if %(field)s is %(value)s"
+msgstr "Ова поле мора да биде зададено ако %(field)s е %(value)s"
+
+#: core/validators.py:348
+#, python-format
+msgid "This field must be given if %(field)s is not %(value)s"
+msgstr "Ова поле мора да биде зададено ако %(field)s не е %(value)s"
+
+#: core/validators.py:367
+msgid "Duplicate values are not allowed."
+msgstr "Дупликат вредности не се дозволени."
+
+#: core/validators.py:382
+#, python-format
+msgid "This value must be between %(lower)s and %(upper)s."
+msgstr "Оваа вредноста мора да биде помеѓу %(lower)s и %(upper)s."
-#: oldforms/__init__.py:392
+#: core/validators.py:384
+#, python-format
+msgid "This value must be at least %s."
+msgstr "Оваа вредноста мора да биде најмалку %s."
+
+#: core/validators.py:386
+#, python-format
+msgid "This value must be no more than %s."
+msgstr "Оваа вредност не смее да биде поголема од %s."
+
+#: core/validators.py:427
+#, python-format
+msgid "This value must be a power of %s."
+msgstr "Оваа вредноста мора да биде степен од %s."
+
+#: core/validators.py:437
+msgid "Please enter a valid decimal number."
+msgstr "Ве молам внесете правилен децимален број."
+
+#: core/validators.py:444
+#, python-format
+msgid "Please enter a valid decimal number with at most %s total digit."
+msgid_plural "Please enter a valid decimal number with at most %s total digits."
+msgstr[0] "Ве молам внесете правилен децимален број со најмногу %s цифрa."
+msgstr[1] "Ве молам внесете правилен децимален број со најмногу %s вкупно цифри."
+
+#: core/validators.py:447
+#, python-format
+msgid "Please enter a valid decimal number with a whole part of at most %s digit."
+msgid_plural "Please enter a valid decimal number with a whole part of at most %s digits."
+msgstr[0] ""
+"Ве молам внесете правилен децимален број кој во целиот број има најмногу %s "
+"цифра."
+msgstr[1] ""
+"Ве молам внесете правилен децимален број кој во целиот број има најмногу %s "
+"цифри."
+
+#: core/validators.py:450
+#, python-format
+msgid "Please enter a valid decimal number with at most %s decimal place."
+msgid_plural "Please enter a valid decimal number with at most %s decimal places."
+msgstr[0] "Ве молам внесете правилен децимален број кој има најмногу %s децимална цифра."
+msgstr[1] "Ве молам внесете правилен децимален број кој има најмногу %s децимални цифри."
+
+#: core/validators.py:458
+msgid "Please enter a valid floating point number."
+msgstr "Ве молам внесете правилен децимален број."
+
+#: core/validators.py:467
+#, python-format
+msgid "Make sure your uploaded file is at least %s bytes big."
+msgstr "Потврдете дека качената датотека има најмалку %s бајти."
+
+#: core/validators.py:468
+#, python-format
+msgid "Make sure your uploaded file is at most %s bytes big."
+msgstr "Потврдете дека качената датотека има најмногу %s бајти."
+
+#: core/validators.py:485
+msgid "The format for this field is wrong."
+msgstr "Форматот за ова поле е грешен."
+
+#: core/validators.py:500
+msgid "This field is invalid."
+msgstr "Ова поле не е правилно."
+
+#: core/validators.py:536
+#, python-format
+msgid "Could not retrieve anything from %s."
+msgstr "Неможев да извадам ништо од %s."
+
+#: core/validators.py:539
+#, python-format
+msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'."
+msgstr "Адресата %(url)s врати неправилно заглавје Content-Type „%(contenttype)s“."
+
+#: core/validators.py:572
+#, python-format
+msgid ""
+"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "
+"\"%(start)s\".)"
+msgstr ""
+"Ве молам затворете го отворениот %(tag)s таг од линијата %(line)s. (линијата "
+"почнува со „%(start)s“.)"
+
+#: core/validators.py:576
+#, python-format
+msgid ""
+"Some text starting on line %(line)s is not allowed in that context. (Line "
+"starts with \"%(start)s\".)"
+msgstr ""
+"Некој текст кој почнува на линијата %(line)s не е дозволен во тој контекст. "
+"(Линијата започнува со „%(start)s“.)"
+
+#: core/validators.py:581
+#, python-format
+msgid ""
+"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%"
+"(start)s\".)"
+msgstr ""
+"„%(attr)s“ на линија %(line)s е неправилен атрибут. (линијата започнува со „%"
+"(start)s“.)"
+
+#: core/validators.py:586
+#, python-format
+msgid ""
+"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%"
+"(start)s\".)"
+msgstr ""
+"„<%(tag)s>“ на линија %(line)s е неправилен таг. (линијата започнува со „%"
+"(start)s“.)"
+
+#: core/validators.py:590
+#, python-format
+msgid ""
+"A tag on line %(line)s is missing one or more required attributes. (Line "
+"starts with \"%(start)s\".)"
+msgstr ""
+"На таг од линијата %(line)s му недостасува еден или повеќе од потребните "
+"атрибути (линијата започнува со „%(start)s“)."
+
+#: core/validators.py:595
+#, python-format
+msgid ""
+"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line "
+"starts with \"%(start)s\".)"
+msgstr "Атрибутот „%(attr)s“ на линијата %(line)s има неправилна вредност (линијата започнува со „%(start)s“)."
+
+#: db/models/manipulators.py:308
+#, python-format
+msgid "%(object)s with this %(type)s already exists for the given %(field)s."
+msgstr "%(object)s од овој тип %(type)s веќе постои за даденото %(field)s."
+
+#: db/models/fields/__init__.py:51
+#, python-format
+msgid "%(optname)s with this %(fieldname)s already exists."
+msgstr "%(optname)s со ова %(fieldname)s веќе постои."
+
+#: db/models/fields/__init__.py:176 db/models/fields/__init__.py:348
+#: db/models/fields/__init__.py:780 db/models/fields/__init__.py:791
+#: newforms/fields.py:46 oldforms/__init__.py:374
+msgid "This field is required."
+msgstr "Ова поле е задолжително."
+
+#: db/models/fields/__init__.py:448
+msgid "This value must be an integer."
+msgstr "Оваа вредност мора да биде цел број."
+
+#: db/models/fields/__init__.py:487
+msgid "This value must be either True or False."
+msgstr "Оваа вредност мора да биде или точно или неточно."
+
+#: db/models/fields/__init__.py:511
+msgid "This field cannot be null."
+msgstr "Оваа вредност неможе да биде null."
+
+#: db/models/fields/__init__.py:689
+msgid "This value must be a decimal number."
+msgstr "Оваа вредност мора да биде децимален број."
+
+#: db/models/fields/__init__.py:800
+msgid "Enter a valid filename."
+msgstr "Внесите правилно име на датотека."
+
+#: db/models/fields/__init__.py:981
+msgid "This value must be either None, True or False."
+msgstr "Оваа вредност мора да биде празна, точно или неточно."
+
+#: db/models/fields/related.py:94
+#, python-format
+msgid "Please enter a valid %s."
+msgstr "Ве молам внесете правилно %s."
+
+#: db/models/fields/related.py:746
+msgid "Separate multiple IDs with commas."
+msgstr "Одвојте ги идентификационите броеви со запирки."
+
+#: db/models/fields/related.py:748
+msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
+msgstr "Држете го „Control“ или „Command“ на Мекинтош за да изберете повеќе од едно."
+
+#: db/models/fields/related.py:795
+#, python-format
+msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
+msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid."
+msgstr[0] ""
+"Ве молам внесете правилен %(self)s идентификацион број. Оваа вредност %"
+"(value)r е неправилна."
+msgstr[1] ""
+"Ве молам внесете правилен %(self)s идентификацион број. Вредностите %(value)"
+"r се неправилни."
+
+#: newforms/fields.py:47
+msgid "Enter a valid value."
+msgstr "Внесете правилна вредност."
+
+#: newforms/fields.py:124
+#, python-format
+msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
+msgstr "Осигурајте се дека оваа вредност има најмногу %(max)d знаци (има %(length)d)."
+
+#: newforms/fields.py:125
+#, python-format
+msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
+msgstr "Осигурајте се дека оваа вредност има најмалку %(min)d знаци (има %(length)d)."
+
+#: newforms/fields.py:153 newforms/fields.py:182 newforms/fields.py:211
+#, python-format
+msgid "Ensure this value is less than or equal to %s."
+msgstr "Осигурајте се дека оваа вредност е помала или еднаква со %s."
+
+#: newforms/fields.py:154 newforms/fields.py:183 newforms/fields.py:212
+#, python-format
+msgid "Ensure this value is greater than or equal to %s."
+msgstr "Осигурајте се дека оваа вредност е поголема или еднаква со %s."
+
+#: newforms/fields.py:181 newforms/fields.py:210
+msgid "Enter a number."
+msgstr "Внесете број."
+
+#: newforms/fields.py:213
+#, python-format
+msgid "Ensure that there are no more than %s digits in total."
+msgstr "Осигурајте се дека нема вкупно повеќе од %s цифри."
+
+#: newforms/fields.py:214
+#, python-format
+msgid "Ensure that there are no more than %s decimal places."
+msgstr "Осигурајте се дека нема повеќе од %s децимални места."
+
+#: newforms/fields.py:215
+#, python-format
+msgid "Ensure that there are no more than %s digits before the decimal point."
+msgstr "Осигурајте се дека нема повеќе од %s цифри пред децималната запирка."
+
+#: newforms/fields.py:263 newforms/fields.py:750
+msgid "Enter a valid date."
+msgstr "Внесете правилен датум."
+
+#: newforms/fields.py:296 newforms/fields.py:751
+msgid "Enter a valid time."
+msgstr "Внесете правилно време."
+
+#: newforms/fields.py:335
+msgid "Enter a valid date/time."
+msgstr "Внесете правилен датум со време."
+
+#: newforms/fields.py:434
+msgid "No file was submitted."
+msgstr "Не беше пратена датотека."
+
+#: newforms/fields.py:435 oldforms/__init__.py:689
+msgid "The submitted file is empty."
+msgstr "Пратената датотека е празна."
+
+#: newforms/fields.py:497
+msgid "Enter a valid URL."
+msgstr "Внесете правилна адреса."
+
+#: newforms/fields.py:498
+msgid "This URL appears to be a broken link."
+msgstr "Оваа адреса изгледа дека не е достапна."
+
+#: newforms/fields.py:559 newforms/models.py:299
+msgid "Select a valid choice. That choice is not one of the available choices."
+msgstr "Изберете правилно. Тоа не е едно од можните избори."
+
+#: newforms/fields.py:598
+#, python-format
+msgid "Select a valid choice. %(value)s is not one of the available choices."
+msgstr "Внесете валиден избор. %(value)s не е еден од можните избори."
+
+#: newforms/fields.py:599 newforms/fields.py:661 newforms/models.py:365
+msgid "Enter a list of values."
+msgstr "Внесете листа на вредности."
+
+#: newforms/fields.py:779
+msgid "Enter a valid IPv4 address."
+msgstr "Внесeте правилна IPv4 адреса."
+
+#: newforms/models.py:366
+#, python-format
+msgid "Select a valid choice. %s is not one of the available choices."
+msgstr "Внесете правилно. %s не е еден од достапните вредности."
+
+#: oldforms/__init__.py:409
#, python-format
msgid "Ensure your text is less than %s character."
msgid_plural "Ensure your text is less than %s characters."
msgstr[0] "Осигурајте се дека вашиот текст има помалку од %s знак."
msgstr[1] "Осигурајте се дека вашиот текст има помалку од %s знаци."
-#: oldforms/__init__.py:397
+#: oldforms/__init__.py:414
msgid "Line breaks are not allowed here."
msgstr "Тука не се дозволени прекини на линија."
-#: oldforms/__init__.py:498 oldforms/__init__.py:571 oldforms/__init__.py:610
+#: oldforms/__init__.py:512 oldforms/__init__.py:586 oldforms/__init__.py:625
#, python-format
msgid "Select a valid choice; '%(data)s' is not in %(choices)s."
msgstr "Изберете правилно, %(data)s' не е во %(choices)s."
-#: oldforms/__init__.py:674
-msgid "The submitted file is empty."
-msgstr "Пратената датотека е празна."
-
-#: oldforms/__init__.py:730
+#: oldforms/__init__.py:745
msgid "Enter a whole number between -32,768 and 32,767."
msgstr "Внесете цел број помеѓу -32,768 и 32,767."
-#: oldforms/__init__.py:740
+#: oldforms/__init__.py:755
msgid "Enter a positive number."
msgstr "Внесете позитивен број."
-#: oldforms/__init__.py:750
+#: oldforms/__init__.py:765
msgid "Enter a whole number between 0 and 32,767."
msgstr "Внесете цел број помеѓу 0 и 32,767."
-#: views/generic/create_update.py:43
+#: template/defaultfilters.py:698
+msgid "yes,no,maybe"
+msgstr "да, не, можеби"
+
+#: template/defaultfilters.py:729
#, python-format
-msgid "The %(verbose_name)s was created successfully."
-msgstr "%(verbose_name)s беше успешно создаден."
+msgid "%(size)d byte"
+msgid_plural "%(size)d bytes"
+msgstr[0] "%(size)d бајт"
+msgstr[1] "%(size)d бајти"
-#: views/generic/create_update.py:117
+#: template/defaultfilters.py:731
#, python-format
-msgid "The %(verbose_name)s was updated successfully."
-msgstr "%(verbose_name)s беше успешно ажуриран."
+msgid "%.1f KB"
+msgstr "%.1f КБ"
-#: views/generic/create_update.py:184
+#: template/defaultfilters.py:733
#, python-format
-msgid "The %(verbose_name)s was deleted."
-msgstr "%(verbose_name)s беше избришан."
+msgid "%.1f MB"
+msgstr "%.1f МБ"
+
+#: template/defaultfilters.py:734
+#, python-format
+msgid "%.1f GB"
+msgstr "%.1f ГБ"
+
+#: utils/dateformat.py:41
+msgid "p.m."
+msgstr "попладне"
+
+#: utils/dateformat.py:42
+msgid "a.m."
+msgstr "наутро"
+
+#: utils/dateformat.py:47
+msgid "PM"
+msgstr "попладне"
+
+#: utils/dateformat.py:48
+msgid "AM"
+msgstr "наутро"
+
+#: utils/dateformat.py:97
+msgid "midnight"
+msgstr "полноќ"
+
+#: utils/dateformat.py:99
+msgid "noon"
+msgstr "пладне"
#: utils/dates.py:6
msgid "Monday"
@@ -2488,268 +4110,244 @@ msgstr "сабота"
msgid "Sunday"
msgstr "недела"
-#: utils/dates.py:14
+#: utils/dates.py:10
+msgid "Mon"
+msgstr "пон"
+
+#: utils/dates.py:10
+msgid "Tue"
+msgstr "вто"
+
+#: utils/dates.py:10
+msgid "Wed"
+msgstr "сре"
+
+#: utils/dates.py:10
+msgid "Thu"
+msgstr "чет"
+
+#: utils/dates.py:10
+msgid "Fri"
+msgstr "пет"
+
+#: utils/dates.py:11
+msgid "Sat"
+msgstr "саб"
+
+#: utils/dates.py:11
+msgid "Sun"
+msgstr "нед"
+
+#: utils/dates.py:18
msgid "January"
msgstr "јануари"
-#: utils/dates.py:14
+#: utils/dates.py:18
msgid "February"
msgstr "февруари"
-#: utils/dates.py:14 utils/dates.py:27
+#: utils/dates.py:18 utils/dates.py:31
msgid "March"
msgstr "март"
-#: utils/dates.py:14 utils/dates.py:27
+#: utils/dates.py:18 utils/dates.py:31
msgid "April"
msgstr "април"
-#: utils/dates.py:14 utils/dates.py:27
+#: utils/dates.py:18 utils/dates.py:31
msgid "May"
msgstr "мај"
-#: utils/dates.py:14 utils/dates.py:27
+#: utils/dates.py:18 utils/dates.py:31
msgid "June"
msgstr "јуни"
-#: utils/dates.py:15 utils/dates.py:27
+#: utils/dates.py:19 utils/dates.py:31
msgid "July"
msgstr "јули"
-#: utils/dates.py:15
+#: utils/dates.py:19
msgid "August"
msgstr "август"
-#: utils/dates.py:15
+#: utils/dates.py:19
msgid "September"
msgstr "септември"
-#: utils/dates.py:15
+#: utils/dates.py:19
msgid "October"
msgstr "октомври"
-#: utils/dates.py:15
+#: utils/dates.py:19
msgid "November"
msgstr "ноември"
-#: utils/dates.py:16
+#: utils/dates.py:20
msgid "December"
msgstr "декември"
-#: utils/dates.py:19
+#: utils/dates.py:23
msgid "jan"
msgstr "јан"
-#: utils/dates.py:19
+#: utils/dates.py:23
msgid "feb"
msgstr "фев"
-#: utils/dates.py:19
+#: utils/dates.py:23
msgid "mar"
msgstr "мар"
-#: utils/dates.py:19
+#: utils/dates.py:23
msgid "apr"
msgstr "апр"
-#: utils/dates.py:19
+#: utils/dates.py:23
msgid "may"
msgstr "мај"
-#: utils/dates.py:19
+#: utils/dates.py:23
msgid "jun"
msgstr "јун"
-#: utils/dates.py:20
+#: utils/dates.py:24
msgid "jul"
msgstr "јул"
-#: utils/dates.py:20
+#: utils/dates.py:24
msgid "aug"
msgstr "авг"
-#: utils/dates.py:20
+#: utils/dates.py:24
msgid "sep"
msgstr "сеп"
-#: utils/dates.py:20
+#: utils/dates.py:24
msgid "oct"
msgstr "окт"
-#: utils/dates.py:20
+#: utils/dates.py:24
msgid "nov"
msgstr "ное"
-#: utils/dates.py:20
+#: utils/dates.py:24
msgid "dec"
msgstr "дек"
-#: utils/dates.py:27
+#: utils/dates.py:31
msgid "Jan."
msgstr "јан."
-#: utils/dates.py:27
+#: utils/dates.py:31
msgid "Feb."
msgstr "фев."
-#: utils/dates.py:28
+#: utils/dates.py:32
msgid "Aug."
msgstr "авг."
-#: utils/dates.py:28
+#: utils/dates.py:32
msgid "Sept."
msgstr "сеп."
-#: utils/dates.py:28
+#: utils/dates.py:32
msgid "Oct."
msgstr "окт."
-#: utils/dates.py:28
+#: utils/dates.py:32
msgid "Nov."
msgstr "ное."
-#: utils/dates.py:28
+#: utils/dates.py:32
msgid "Dec."
msgstr "дек."
-#: utils/timesince.py:12
+#: utils/text.py:127
+msgid "or"
+msgstr "или"
+
+#: utils/timesince.py:21
msgid "year"
msgid_plural "years"
msgstr[0] "година"
msgstr[1] "години"
-#: utils/timesince.py:13
+#: utils/timesince.py:22
msgid "month"
msgid_plural "months"
msgstr[0] "месец"
msgstr[1] "месеци"
-#: utils/timesince.py:14
+#: utils/timesince.py:23
msgid "week"
msgid_plural "weeks"
msgstr[0] "седмица"
msgstr[1] "седмици"
-#: utils/timesince.py:15
+#: utils/timesince.py:24
msgid "day"
msgid_plural "days"
msgstr[0] "ден"
msgstr[1] "дена"
-#: utils/timesince.py:16
+#: utils/timesince.py:25
msgid "hour"
msgid_plural "hours"
msgstr[0] "час"
msgstr[1] "часа"
-#: utils/timesince.py:17
+#: utils/timesince.py:26
msgid "minute"
msgid_plural "minutes"
msgstr[0] "минута"
msgstr[1] "минути"
-#: utils/dateformat.py:40
-msgid "p.m."
-msgstr "попладне"
-
-#: utils/dateformat.py:41
-msgid "a.m."
-msgstr "наутро"
-
-#: utils/dateformat.py:46
-msgid "PM"
-msgstr "попладне"
+#: utils/timesince.py:46
+msgid "minutes"
+msgstr "минути"
-#: utils/dateformat.py:47
-msgid "AM"
-msgstr "наутро"
-
-#: utils/dateformat.py:95
-msgid "midnight"
-msgstr "полноќ"
+#: utils/timesince.py:51
+#, python-format
+msgid "%(number)d %(type)s"
+msgstr "%(number)d %(type)s"
-#: utils/dateformat.py:97
-msgid "noon"
-msgstr "пладне"
+#: utils/timesince.py:57
+#, python-format
+msgid ", %(number)d %(type)s"
+msgstr ", %(number)d %(type)s"
-#: utils/translation/trans_real.py:358
+#: utils/translation/trans_real.py:403
msgid "DATE_FORMAT"
msgstr "j M Y"
-#: utils/translation/trans_real.py:359
+#: utils/translation/trans_real.py:404
msgid "DATETIME_FORMAT"
msgstr "j M Y, P"
-#: utils/translation/trans_real.py:360
+#: utils/translation/trans_real.py:405
msgid "TIME_FORMAT"
msgstr "P"
-#: utils/translation/trans_real.py:376
+#: utils/translation/trans_real.py:421
msgid "YEAR_MONTH_FORMAT"
msgstr "F Y"
-#: utils/translation/trans_real.py:377
+#: utils/translation/trans_real.py:422
msgid "MONTH_DAY_FORMAT"
msgstr "F j"
-#: template/defaultfilters.py:491
-msgid "yes,no,maybe"
-msgstr "да, не, можеби"
-
-#: newforms/fields.py:103 newforms/fields.py:256
-#, python-format
-msgid "Ensure this value has at most %d characters."
-msgstr "Осигурајте се дека оваа вредност има најмногу %d знаци."
-
-#: newforms/fields.py:105 newforms/fields.py:258
-#, python-format
-msgid "Ensure this value has at least %d characters."
-msgstr "Осигурајте се дека оваа вредност има најмалку %d знаци."
-
-#: newforms/fields.py:130
+#: views/generic/create_update.py:43
#, python-format
-msgid "Ensure this value is less than or equal to %s."
-msgstr "Осигурајте се дека оваа вредност е помала или еднаква на %s."
+msgid "The %(verbose_name)s was created successfully."
+msgstr "%(verbose_name)s беше успешно создаден."
-#: newforms/fields.py:132
+#: views/generic/create_update.py:117
#, python-format
-msgid "Ensure this value is greater than or equal to %s."
-msgstr "Осигурајте се дека оваа вредност е поголема или еднаква со %s."
-
-#: newforms/fields.py:165
-msgid "Enter a valid date."
-msgstr "Внесете правилен датум."
-
-#: newforms/fields.py:192
-msgid "Enter a valid time."
-msgstr "Внесете правилно време."
-
-#: newforms/fields.py:228
-msgid "Enter a valid date/time."
-msgstr "Внесете правилен датум со време."
-
-#: newforms/fields.py:242
-msgid "Enter a valid value."
-msgstr "Внесете правилна вредност."
-
-#: newforms/fields.py:289 newforms/fields.py:311
-msgid "Enter a valid URL."
-msgstr "Внесете правилна адреса."
-
-#: newforms/fields.py:313
-msgid "This URL appears to be a broken link."
-msgstr "Оваа адреса изгледа дека не е достапна."
-
-#: newforms/fields.py:362 newforms/models.py:165
-msgid "Select a valid choice. That choice is not one of the available choices."
-msgstr "Изберете правилно. Тоа не е едно од можните избори."
-
-#: newforms/fields.py:380 newforms/fields.py:456 newforms/models.py:182
-msgid "Enter a list of values."
-msgstr "Внесете листа на вредности."
+msgid "The %(verbose_name)s was updated successfully."
+msgstr "%(verbose_name)s беше успешно ажуриран."
-#: newforms/fields.py:389 newforms/models.py:188
+#: views/generic/create_update.py:184
#, python-format
-msgid "Select a valid choice. %s is not one of the available choices."
-msgstr "Внесете правилно. %s не е еден од достапните вредности."
+msgid "The %(verbose_name)s was deleted."
+msgstr "%(verbose_name)s беше избришан."
diff --git a/django/conf/locale/tr/LC_MESSAGES/django.mo b/django/conf/locale/tr/LC_MESSAGES/django.mo
index 2d409001f4..bb3b1af83c 100644
--- a/django/conf/locale/tr/LC_MESSAGES/django.mo
+++ b/django/conf/locale/tr/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/tr/LC_MESSAGES/django.po b/django/conf/locale/tr/LC_MESSAGES/django.po
index dd2c592cfb..224cc7fba5 100644
--- a/django/conf/locale/tr/LC_MESSAGES/django.po
+++ b/django/conf/locale/tr/LC_MESSAGES/django.po
@@ -6,7 +6,7 @@ msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-03-14 01:38+0200\n"
-"PO-Revision-Date: 2007-12-30 12:15+0200\n"
+"PO-Revision-Date: 2008-07-02 23:37+0200\n"
"Last-Translator: Can Burak Çilingir <canburak@cs.bilgi.edu.tr>\n"
"Language-Team: Turkish <bahadir@pardus.org.tr>\n"
"MIME-Version: 1.0\n"
@@ -2466,3 +2466,8 @@ msgstr "evet,hayır,olabilir"
#~ msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
#~ msgstr "<a href=\"/password_reset/\">Şifrenizi mi unuttunuz?</a>"
+
+#: contrib/auth/forms.py:107
+#, python-format
+msgid "Password reset on %s"
+msgstr "%s sitesindeki hesabınızın parolasının sıfırlanması"
diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py
index bd14c9b4b9..bba883ba78 100644
--- a/django/contrib/auth/backends.py
+++ b/django/contrib/auth/backends.py
@@ -1,14 +1,15 @@
+try:
+ set
+except NameError:
+ from sets import Set as set # Python 2.3 fallback
+
from django.db import connection
from django.contrib.auth.models import User
-try:
- set
-except NameError:
- from sets import Set as set # Python 2.3 fallback
-
+
class ModelBackend(object):
"""
- Authenticate against django.contrib.auth.models.User
+ Authenticates against django.contrib.auth.models.User.
"""
# TODO: Model, login attribute name and password attribute name should be
# configurable.
@@ -21,7 +22,10 @@ class ModelBackend(object):
return None
def get_group_permissions(self, user_obj):
- "Returns a list of permission strings that this user has through his/her groups."
+ """
+ Returns a set of permission strings that this user has through his/her
+ groups.
+ """
if not hasattr(user_obj, '_group_perm_cache'):
cursor = connection.cursor()
# The SQL below works out to the following, after DB quoting:
@@ -50,7 +54,7 @@ class ModelBackend(object):
cursor.execute(sql, [user_obj.id])
user_obj._group_perm_cache = set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()])
return user_obj._group_perm_cache
-
+
def get_all_permissions(self, user_obj):
if not hasattr(user_obj, '_perm_cache'):
user_obj._perm_cache = set([u"%s.%s" % (p.content_type.app_label, p.codename) for p in user_obj.user_permissions.select_related()])
@@ -61,7 +65,13 @@ class ModelBackend(object):
return perm in self.get_all_permissions(user_obj)
def has_module_perms(self, user_obj, app_label):
- return bool(len([p for p in self.get_all_permissions(user_obj) if p[:p.index('.')] == app_label]))
+ """
+ Returns True if user_obj has any permissions in the given app_label.
+ """
+ for perm in self.get_all_permissions(user_obj):
+ if perm[:perm.index('.')] == app_label:
+ return True
+ return False
def get_user(self, user_id):
try:
diff --git a/django/contrib/auth/fixtures/authtestdata.json b/django/contrib/auth/fixtures/authtestdata.json
new file mode 100644
index 0000000000..e0bdc24e72
--- /dev/null
+++ b/django/contrib/auth/fixtures/authtestdata.json
@@ -0,0 +1,56 @@
+[
+ {
+ "pk": "1",
+ "model": "auth.user",
+ "fields": {
+ "username": "testclient",
+ "first_name": "Test",
+ "last_name": "Client",
+ "is_active": true,
+ "is_superuser": false,
+ "is_staff": false,
+ "last_login": "2006-12-17 07:03:31",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
+ "email": "testclient@example.com",
+ "date_joined": "2006-12-17 07:03:31"
+ }
+ },
+ {
+ "pk": "2",
+ "model": "auth.user",
+ "fields": {
+ "username": "inactive",
+ "first_name": "Inactive",
+ "last_name": "User",
+ "is_active": false,
+ "is_superuser": false,
+ "is_staff": false,
+ "last_login": "2006-12-17 07:03:31",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
+ "email": "testclient@example.com",
+ "date_joined": "2006-12-17 07:03:31"
+ }
+ },
+ {
+ "pk": "3",
+ "model": "auth.user",
+ "fields": {
+ "username": "staff",
+ "first_name": "Staff",
+ "last_name": "Member",
+ "is_active": true,
+ "is_superuser": false,
+ "is_staff": true,
+ "last_login": "2006-12-17 07:03:31",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
+ "email": "staffmember@example.com",
+ "date_joined": "2006-12-17 07:03:31"
+ }
+ }
+]
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index f74d1d7761..379a9f4c64 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -96,10 +96,10 @@ class Group(models.Model):
class Meta:
verbose_name = _('group')
verbose_name_plural = _('groups')
- ordering = ('name',)
class Admin:
search_fields = ('name',)
+ ordering = ('name',)
def __unicode__(self):
return self.name
@@ -153,7 +153,6 @@ class User(models.Model):
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
- ordering = ('username',)
class Admin:
fields = (
@@ -166,6 +165,7 @@ class User(models.Model):
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_superuser')
search_fields = ('username', 'first_name', 'last_name', 'email')
+ ordering = ('username',)
def __unicode__(self):
return self.username
diff --git a/django/contrib/auth/tests.py b/django/contrib/auth/tests.py
index 81094ca85f..ea1ac26c21 100644
--- a/django/contrib/auth/tests.py
+++ b/django/contrib/auth/tests.py
@@ -1,5 +1,5 @@
"""
->>> from models import User, AnonymousUser
+>>> from django.contrib.auth.models import User, AnonymousUser
>>> u = User.objects.create_user('testuser', 'test@example.com', 'testpw')
>>> u.has_usable_password()
True
@@ -52,4 +52,24 @@ Superuser created successfully.
u'joe@somewhere.org'
>>> u.password
u'!'
-""" \ No newline at end of file
+"""
+
+from django.test import TestCase
+from django.core import mail
+
+class PasswordResetTest(TestCase):
+ fixtures = ['authtestdata.json']
+ urls = 'django.contrib.auth.urls'
+ def test_email_not_found(self):
+ "Error is raised if the provided email address isn't currently registered"
+ response = self.client.get('/password_reset/')
+ self.assertEquals(response.status_code, 200)
+ response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'})
+ self.assertContains(response, "That e-mail address doesn&#39;t have an associated user account")
+ self.assertEquals(len(mail.outbox), 0)
+
+ def test_email_found(self):
+ "Email is sent if a valid email address is provided for password reset"
+ response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
+ self.assertEquals(response.status_code, 302)
+ self.assertEquals(len(mail.outbox), 1)
diff --git a/django/contrib/auth/urls.py b/django/contrib/auth/urls.py
new file mode 100644
index 0000000000..5ddfcf15e8
--- /dev/null
+++ b/django/contrib/auth/urls.py
@@ -0,0 +1,13 @@
+# These URLs are normally mapped to /admin/urls.py. This URLs file is
+# provided as a convenience to those who want to deploy these URLs elsewhere.
+# This file is also used to provide a reliable view deployment for test purposes.
+
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ ('^logout/$', 'django.contrib.auth.views.logout'),
+ ('^password_change/$', 'django.contrib.auth.views.password_change'),
+ ('^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
+ ('^password_reset/$', 'django.contrib.auth.views.password_reset')
+)
+
diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py
index ddd5678379..d61e9a3b1c 100644
--- a/django/contrib/flatpages/models.py
+++ b/django/contrib/flatpages/models.py
@@ -8,7 +8,7 @@ class FlatPage(models.Model):
url = models.CharField(_('URL'), max_length=100, validator_list=[validators.isAlphaNumericURL], db_index=True,
help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
title = models.CharField(_('title'), max_length=200)
- content = models.TextField(_('content'))
+ content = models.TextField(_('content'), blank=True)
enable_comments = models.BooleanField(_('enable comments'))
template_name = models.CharField(_('template name'), max_length=70, blank=True,
help_text=_("Example: 'flatpages/contact_page.html'. If this isn't provided, the system will use 'flatpages/default.html'."))
diff --git a/django/contrib/formtools/tests.py b/django/contrib/formtools/tests.py
index 143857ad58..ba241e9dc6 100644
--- a/django/contrib/formtools/tests.py
+++ b/django/contrib/formtools/tests.py
@@ -21,18 +21,14 @@ class TestForm(forms.Form):
class PreviewTests(TestCase):
+ urls = 'django.contrib.formtools.test_urls'
def setUp(self):
- self._old_root_urlconf = settings.ROOT_URLCONF
- settings.ROOT_URLCONF = 'django.contrib.formtools.test_urls'
# Create a FormPreview instance to share between tests
self.preview = preview.FormPreview(TestForm)
input_template = '<input type="hidden" name="%s" value="%s" />'
self.input = input_template % (self.preview.unused_name('stage'), "%d")
- def tearDown(self):
- settings.ROOT_URLCONF = self._old_root_urlconf
-
def test_unused_name(self):
"""
Verifies name mangling to get uniue field name.
diff --git a/django/contrib/gis/db/backend/mysql/query.py b/django/contrib/gis/db/backend/mysql/query.py
index 3f21478240..6522235135 100644
--- a/django/contrib/gis/db/backend/mysql/query.py
+++ b/django/contrib/gis/db/backend/mysql/query.py
@@ -38,12 +38,12 @@ MISC_TERMS = ['isnull']
# Assacceptable lookup types for Oracle spatial.
MYSQL_GIS_TERMS = MYSQL_GIS_FUNCTIONS.keys()
MYSQL_GIS_TERMS += MISC_TERMS
-MYSQL_GIS_TERMS = tuple(MYSQL_GIS_TERMS) # Making immutable
+MYSQL_GIS_TERMS = dict((term, None) for term in MYSQL_GIS_TERMS) # Making dictionary
-def get_geo_where_clause(lookup_type, table_prefix, field, value):
+def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
"Returns the SQL WHERE clause for use in MySQL spatial SQL construction."
# Getting the quoted field as `geo_col`.
- geo_col = '%s.%s' % (qn(table_prefix), qn(field.column))
+ geo_col = '%s.%s' % (qn(table_alias), qn(name))
# See if a MySQL Geometry function matches the lookup type next
lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False)
@@ -54,6 +54,6 @@ def get_geo_where_clause(lookup_type, table_prefix, field, value):
# TODO: Is this needed because MySQL cannot handle NULL
# geometries in its spatial indices.
if lookup_type == 'isnull':
- return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
+ return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
diff --git a/django/contrib/gis/db/backend/oracle/query.py b/django/contrib/gis/db/backend/oracle/query.py
index 6f5c0dfe94..a073b7b45f 100644
--- a/django/contrib/gis/db/backend/oracle/query.py
+++ b/django/contrib/gis/db/backend/oracle/query.py
@@ -110,10 +110,10 @@ ORACLE_SPATIAL_TERMS += MISC_TERMS
ORACLE_SPATIAL_TERMS = tuple(ORACLE_SPATIAL_TERMS) # Making immutable
#### The `get_geo_where_clause` function for Oracle ####
-def get_geo_where_clause(lookup_type, table_prefix, field, value):
+def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
"Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
# Getting the quoted table name as `geo_col`.
- geo_col = '%s.%s' % (qn(table_prefix), qn(field.column))
+ geo_col = '%s.%s' % (qn(table_alias), qn(name))
# See if a Oracle Geometry function matches the lookup type next
lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False)
@@ -126,19 +126,19 @@ def get_geo_where_clause(lookup_type, table_prefix, field, value):
sdo_op, arg_type = lookup_info
# Ensuring that a tuple _value_ was passed in from the user
- if not isinstance(value, tuple):
+ if not isinstance(geo_annot.value, tuple):
raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
- if len(value) != 2:
+ if len(geo_annot.value) != 2:
raise ValueError('2-element tuple required for %s lookup type.' % lookup_type)
# Ensuring the argument type matches what we expect.
- if not isinstance(value[1], arg_type):
- raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
+ if not isinstance(geo_annot.value[1], arg_type):
+ raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
if lookup_type == 'relate':
# The SDORelate class handles construction for these queries,
# and verifies the mask argument.
- return sdo_op(value[1]).as_sql(geo_col)
+ return sdo_op(geo_annot.value[1]).as_sql(geo_col)
else:
# Otherwise, just call the `as_sql` method on the SDOOperation instance.
return sdo_op.as_sql(geo_col)
@@ -149,6 +149,6 @@ def get_geo_where_clause(lookup_type, table_prefix, field, value):
return lookup_info.as_sql(geo_col)
elif lookup_type == 'isnull':
# Handling 'isnull' lookup type
- return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
+ return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
diff --git a/django/contrib/gis/db/backend/postgis/query.py b/django/contrib/gis/db/backend/postgis/query.py
index 90063e1887..8780780402 100644
--- a/django/contrib/gis/db/backend/postgis/query.py
+++ b/django/contrib/gis/db/backend/postgis/query.py
@@ -218,7 +218,7 @@ MISC_TERMS = ['isnull']
POSTGIS_TERMS = POSTGIS_OPERATORS.keys() # Getting the operators first
POSTGIS_TERMS += POSTGIS_GEOMETRY_FUNCTIONS.keys() # Adding on the Geometry Functions
POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
-POSTGIS_TERMS = tuple(POSTGIS_TERMS) # Making immutable
+POSTGIS_TERMS = dict((term, None) for term in POSTGIS_TERMS) # Making a dictionary for fast lookups
# For checking tuple parameters -- not very pretty but gets job done.
def exactly_two(val): return val == 2
@@ -228,10 +228,10 @@ def num_params(lookup_type, val):
else: return exactly_two(val)
#### The `get_geo_where_clause` function for PostGIS. ####
-def get_geo_where_clause(lookup_type, table_prefix, field, value):
+def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
"Returns the SQL WHERE clause for use in PostGIS SQL construction."
# Getting the quoted field as `geo_col`.
- geo_col = '%s.%s' % (qn(table_prefix), qn(field.column))
+ geo_col = '%s.%s' % (qn(table_alias), qn(name))
if lookup_type in POSTGIS_OPERATORS:
# See if a PostGIS operator matches the lookup type.
return POSTGIS_OPERATORS[lookup_type].as_sql(geo_col)
@@ -248,30 +248,31 @@ def get_geo_where_clause(lookup_type, table_prefix, field, value):
op, arg_type = tmp
# Ensuring that a tuple _value_ was passed in from the user
- if not isinstance(value, (tuple, list)):
+ if not isinstance(geo_annot.value, (tuple, list)):
raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
+
# Number of valid tuple parameters depends on the lookup type.
- nparams = len(value)
+ nparams = len(geo_annot.value)
if not num_params(lookup_type, nparams):
raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type)
# Ensuring the argument type matches what we expect.
- if not isinstance(value[1], arg_type):
- raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
+ if not isinstance(geo_annot.value[1], arg_type):
+ raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
# For lookup type `relate`, the op instance is not yet created (has
# to be instantiated here to check the pattern parameter).
if lookup_type == 'relate':
- op = op(value[1])
+ op = op(geo_annot.value[1])
elif lookup_type in DISTANCE_FUNCTIONS and lookup_type != 'dwithin':
- if field.geodetic:
+ if geo_annot.geodetic:
# Geodetic distances are only availble from Points to PointFields.
- if field._geom != 'POINT':
+ if geo_annot.geom_type != 'POINT':
raise TypeError('PostGIS spherical operations are only valid on PointFields.')
- if value[0].geom_typeid != 0:
+ if geo_annot.value[0].geom_typeid != 0:
raise TypeError('PostGIS geometry distance parameter is required to be of type Point.')
# Setting up the geodetic operation appropriately.
- if nparams == 3 and value[2] == 'spheroid': op = op[2]
+ if nparams == 3 and geo_annot.value[2] == 'spheroid': op = op[2]
else: op = op[1]
else:
op = op[0]
@@ -281,6 +282,6 @@ def get_geo_where_clause(lookup_type, table_prefix, field, value):
return op.as_sql(geo_col)
elif lookup_type == 'isnull':
# Handling 'isnull' lookup type
- return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
+ return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
diff --git a/django/contrib/gis/db/models/fields/__init__.py b/django/contrib/gis/db/models/fields/__init__.py
index 0a0d144dcc..4bb294c52d 100644
--- a/django/contrib/gis/db/models/fields/__init__.py
+++ b/django/contrib/gis/db/models/fields/__init__.py
@@ -111,7 +111,7 @@ class GeometryField(SpatialBackend.Field):
except SpatialBackend.GeometryException:
raise ValueError('Could not create geometry from lookup value: %s' % str(value))
else:
- raise TypeError('Cannot use parameter of `%s` type as lookup parameter.' % type(value))
+ raise TypeError('Cannot use parameter of `%s` type as a geometry lookup parameter.' % type(value))
# Assigning the SRID value.
geom.srid = self.get_srid(geom)
diff --git a/django/contrib/gis/db/models/sql/query.py b/django/contrib/gis/db/models/sql/query.py
index d5ea54e116..20f695b26f 100644
--- a/django/contrib/gis/db/models/sql/query.py
+++ b/django/contrib/gis/db/models/sql/query.py
@@ -103,7 +103,8 @@ class GeoQuery(sql.Query):
self._select_aliases = aliases
return result
- def get_default_columns(self, with_aliases=False, col_aliases=None):
+ def get_default_columns(self, with_aliases=False, col_aliases=None,
+ start_alias=None, opts=None, as_pairs=False):
"""
Computes the default columns for selecting every field in the base
model.
@@ -115,11 +116,14 @@ class GeoQuery(sql.Query):
geometry columns.
"""
result = []
- table_alias = self.tables[0]
+ if opts is None:
+ opts = self.model._meta
+ if start_alias:
+ table_alias = start_alias
+ else:
+ table_alias = self.tables[0]
root_pk = self.model._meta.pk.column
seen = {None: table_alias}
- qn = self.quote_name_unless_alias
- qn2 = self.connection.ops.quote_name
aliases = set()
for field, model in self.model._meta.get_fields_with_model():
try:
@@ -128,12 +132,13 @@ class GeoQuery(sql.Query):
alias = self.join((table_alias, model._meta.db_table,
root_pk, model._meta.pk.column))
seen[model] = alias
-
+ if as_pairs:
+ result.append((alias, field.column))
+ continue
# This part of the function is customized for GeoQuery. We
# see if there was any custom selection specified in the
# dictionary, and set up the selection format appropriately.
field_sel = self.get_field_select(field, alias)
-
if with_aliases and field.column in col_aliases:
c_alias = 'Col%d' % len(col_aliases)
result.append('%s AS %s' % (field_sel, c_alias))
@@ -145,6 +150,8 @@ class GeoQuery(sql.Query):
aliases.add(r)
if with_aliases:
col_aliases.add(field.column)
+ if as_pairs:
+ return result, None
return result, aliases
def get_ordering(self):
diff --git a/django/contrib/gis/db/models/sql/where.py b/django/contrib/gis/db/models/sql/where.py
index d9c75eaec3..68f5e247a0 100644
--- a/django/contrib/gis/db/models/sql/where.py
+++ b/django/contrib/gis/db/models/sql/where.py
@@ -1,21 +1,78 @@
+import datetime
+from django.db.models.fields import Field
from django.db.models.sql.where import WhereNode
from django.contrib.gis.db.backend import get_geo_where_clause, SpatialBackend
+class GeoAnnotation(object):
+ """
+ The annotation used for GeometryFields; basically a placeholder
+ for metadata needed by the `get_geo_where_clause` of the spatial
+ backend.
+ """
+ def __init__(self, field, value, where):
+ self.geodetic = field.geodetic
+ self.geom_type = field._geom
+ self.value = value
+ self.where = tuple(where)
+
class GeoWhereNode(WhereNode):
"""
- The GeoWhereNode calls the `get_geo_where_clause` from the appropriate
- spatial backend in order to construct correct spatial SQL.
+ Used to represent the SQL where-clause for spatial databases --
+ these are tied to the GeoQuery class that created it.
"""
+ def add(self, data, connector):
+ """
+ This is overridden from the regular WhereNode to handle the
+ peculiarties of GeometryFields, because they need a special
+ annotation object that contains the spatial metadata from the
+ field so that the correct spatial SQL is generated.
+ """
+ if not isinstance(data, (list, tuple)):
+ super(WhereNode, self).add(data, connector)
+ return
+
+ alias, col, field, lookup_type, value = data
+ # Do we have a geographic field?
+ geo_field = hasattr(field, '_geom')
+ if field:
+ if geo_field:
+ # `GeometryField.get_db_prep_lookup` returns a where clause
+ # substitution array in addition to the parameters.
+ where, params = field.get_db_prep_lookup(lookup_type, value)
+ else:
+ params = field.get_db_prep_lookup(lookup_type, value)
+ db_type = field.db_type()
+ else:
+ # This is possible when we add a comparison to NULL sometimes (we
+ # don't really need to waste time looking up the associated field
+ # object).
+ params = Field().get_db_prep_lookup(lookup_type, value)
+ db_type = None
+
+ if geo_field:
+ # The annotation will be a `GeoAnnotation` object that
+ # will contain the necessary geometry field metadata for
+ # the `get_geo_where_clause` to construct the appropriate
+ # spatial SQL when `make_atom` is called.
+ annotation = GeoAnnotation(field, value, where)
+ elif isinstance(value, datetime.datetime):
+ annotation = datetime.datetime
+ else:
+ annotation = bool(value)
+
+ super(WhereNode, self).add((alias, col, db_type, lookup_type,
+ annotation, params), connector)
+
def make_atom(self, child, qn):
- table_alias, name, field, lookup_type, value = child
- if hasattr(field, '_geom'):
+ table_alias, name, db_type, lookup_type, value_annot, params = child
+
+ if isinstance(value_annot, GeoAnnotation):
if lookup_type in SpatialBackend.gis_terms:
# Getting the geographic where clause; substitution parameters
# will be populated in the GeoFieldSQL object returned by the
# GeometryField.
- gwc = get_geo_where_clause(lookup_type, table_alias, field, value)
- where, params = field.get_db_prep_lookup(lookup_type, value)
- return gwc % tuple(where), params
+ gwc = get_geo_where_clause(table_alias, name, lookup_type, value_annot)
+ return gwc % value_annot.where, params
else:
raise TypeError('Invalid lookup type: %r' % lookup_type)
else:
diff --git a/django/contrib/gis/tests/distapp/tests.py b/django/contrib/gis/tests/distapp/tests.py
index b17e3d741c..057e2f715e 100644
--- a/django/contrib/gis/tests/distapp/tests.py
+++ b/django/contrib/gis/tests/distapp/tests.py
@@ -88,14 +88,14 @@ class DistanceTest(unittest.TestCase):
else: dist = dist[0]
# Creating the query set.
- qs = AustraliaCity.objects.filter(point__dwithin=(self.au_pnt, dist)).order_by('name')
+ qs = AustraliaCity.objects.order_by('name')
if type_error:
# A TypeError should be raised on PostGIS when trying to pass
# Distance objects into a DWithin query using a geodetic field.
- self.assertRaises(TypeError, qs.count)
+ self.assertRaises(TypeError, AustraliaCity.objects.filter, point__dwithin=(self.au_pnt, dist))
else:
- self.assertEqual(au_cities, self.get_names(qs))
-
+ self.assertEqual(au_cities, self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))))
+
def test03a_distance_method(self):
"Testing the `distance` GeoQuerySet method on projected coordinate systems."
# The point for La Grange, TX
@@ -217,11 +217,11 @@ class DistanceTest(unittest.TestCase):
AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100))))
# Too many params (4 in this case) should raise a ValueError.
self.assertRaises(ValueError,
- AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4')).count)
+ AustraliaCity.objects.filter, point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4'))
# Not enough params should raise a ValueError.
self.assertRaises(ValueError,
- AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)',)).count)
+ AustraliaCity.objects.filter, point__distance_lte=('POINT(5 23)',))
# Getting all cities w/in 550 miles of Hobart.
hobart = AustraliaCity.objects.get(name='Hobart')
diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py
index 82dc5e9a9e..f3cbf9b1f6 100644
--- a/django/contrib/gis/tests/geoapp/tests.py
+++ b/django/contrib/gis/tests/geoapp/tests.py
@@ -198,6 +198,7 @@ class GeoModelTest(unittest.TestCase):
@no_oracle
def test06_make_line(self):
"Testing the `make_line` GeoQuerySet method."
+ if DISABLE: return
# Ensuring that a `TypeError` is raised on models without PointFields.
self.assertRaises(TypeError, State.objects.make_line)
self.assertRaises(TypeError, Country.objects.make_line)
@@ -381,14 +382,13 @@ class GeoModelTest(unittest.TestCase):
pnt1 = fromstr('POINT (649287.0363174 4177429.4494686)', srid=2847)
pnt2 = fromstr('POINT(-98.4919715741052 29.4333344025053)', srid=4326)
- # Testing bad argument tuples that should return a TypeError or
- # a ValueError.
- bad_args = [((pnt1, 0), TypeError),
- ((pnt2, 'T*T***FF*', 0), ValueError),
- ((23, 'foo'), ValueError),
- ]
- for args, e in bad_args:
- qs = Country.objects.filter(mpoly__relate=args)
+ # Not passing in a geometry as first param shoud
+ # raise a type error when initializing the GeoQuerySet
+ self.assertRaises(TypeError, Country.objects.filter, mpoly__relate=(23, 'foo'))
+ # Making sure the right exception is raised for the given
+ # bad arguments.
+ for bad_args, e in [((pnt1, 0), TypeError), ((pnt2, 'T*T***FF*', 0), ValueError)]:
+ qs = Country.objects.filter(mpoly__relate=bad_args)
self.assertRaises(e, qs.count)
# Relate works differently for the different backends.
@@ -471,6 +471,7 @@ class GeoModelTest(unittest.TestCase):
def test19_centroid(self):
"Testing the `centroid` GeoQuerySet method."
+ if DISABLE: return
qs = State.objects.exclude(poly__isnull=True).centroid()
if oracle: tol = 0.1
else: tol = 0.000000001
@@ -479,6 +480,7 @@ class GeoModelTest(unittest.TestCase):
def test20_pointonsurface(self):
"Testing the `point_on_surface` GeoQuerySet method."
+ if DISABLE: return
# Reference values.
if SpatialBackend.oracle:
# SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY;
@@ -497,6 +499,7 @@ class GeoModelTest(unittest.TestCase):
@no_oracle
def test21_scale(self):
"Testing the `scale` GeoQuerySet method."
+ if DISABLE: return
xfac, yfac = 2, 3
qs = Country.objects.scale(xfac, yfac, model_att='scaled')
for c in qs:
@@ -509,6 +512,7 @@ class GeoModelTest(unittest.TestCase):
@no_oracle
def test22_translate(self):
"Testing the `translate` GeoQuerySet method."
+ if DISABLE: return
xfac, yfac = 5, -23
qs = Country.objects.translate(xfac, yfac, model_att='translated')
for c in qs:
@@ -520,6 +524,7 @@ class GeoModelTest(unittest.TestCase):
def test23_numgeom(self):
"Testing the `num_geom` GeoQuerySet method."
+ if DISABLE: return
# Both 'countries' only have two geometries.
for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom)
for c in City.objects.filter(point__isnull=False).num_geom():
@@ -530,6 +535,7 @@ class GeoModelTest(unittest.TestCase):
def test24_numpoints(self):
"Testing the `num_points` GeoQuerySet method."
+ if DISABLE: return
for c in Country.objects.num_points(): self.assertEqual(c.mpoly.num_points, c.num_points)
if postgis:
# Oracle cannot count vertices in Point geometries.
@@ -538,6 +544,7 @@ class GeoModelTest(unittest.TestCase):
@no_oracle
def test25_geoset(self):
"Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods."
+ if DISABLE: return
geom = Point(5, 23)
for c in Country.objects.all().intersection(geom).difference(geom).sym_difference(geom).union(geom):
self.assertEqual(c.mpoly.difference(geom), c.difference)
diff --git a/django/contrib/sitemaps/management/commands/ping_google.py b/django/contrib/sitemaps/management/commands/ping_google.py
new file mode 100644
index 0000000000..afff04b39c
--- /dev/null
+++ b/django/contrib/sitemaps/management/commands/ping_google.py
@@ -0,0 +1,14 @@
+from django.core.management.base import BaseCommand
+from django.contrib.sitemaps import ping_google
+
+
+class Command(BaseCommand):
+ help = "Ping google with an updated sitemap, pass optional url of sitemap"
+
+ def execute(self, *args, **options):
+ if len(args) == 1:
+ sitemap_url = args[0]
+ else:
+ sitemap_url = None
+ ping_google(sitemap_url=sitemap_url)
+
diff --git a/django/core/files/__init__.py b/django/core/files/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/core/files/__init__.py
diff --git a/django/core/files/locks.py b/django/core/files/locks.py
new file mode 100644
index 0000000000..212b51a73d
--- /dev/null
+++ b/django/core/files/locks.py
@@ -0,0 +1,66 @@
+"""
+Portable file locking utilities.
+
+Based partially on example by Jonathan Feignberg <jdf@pobox.com> in the Python
+Cookbook, licensed under the Python Software License.
+
+ http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203
+
+Example Usage::
+
+ >>> from django.core.files import locks
+ >>> f = open('./file', 'wb')
+ >>> locks.lock(f, locks.LOCK_EX)
+ >>> f.write('Django')
+ >>> f.close()
+"""
+
+__all__ = ('LOCK_EX','LOCK_SH','LOCK_NB','lock','unlock')
+
+system_type = None
+
+try:
+ import win32con
+ import win32file
+ import pywintypes
+ LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
+ LOCK_SH = 0
+ LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
+ __overlapped = pywintypes.OVERLAPPED()
+ system_type = 'nt'
+except (ImportError, AttributeError):
+ pass
+
+try:
+ import fcntl
+ LOCK_EX = fcntl.LOCK_EX
+ LOCK_SH = fcntl.LOCK_SH
+ LOCK_NB = fcntl.LOCK_NB
+ system_type = 'posix'
+except (ImportError, AttributeError):
+ pass
+
+if system_type == 'nt':
+ def lock(file, flags):
+ hfile = win32file._get_osfhandle(file.fileno())
+ win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped)
+
+ def unlock(file):
+ hfile = win32file._get_osfhandle(file.fileno())
+ win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped)
+elif system_type == 'posix':
+ def lock(file, flags):
+ fcntl.flock(file.fileno(), flags)
+
+ def unlock(file):
+ fcntl.flock(file.fileno(), fcntl.LOCK_UN)
+else:
+ # File locking is not supported.
+ LOCK_EX = LOCK_SH = LOCK_NB = None
+
+ # Dummy functions that don't do anything.
+ def lock(file, flags):
+ pass
+
+ def unlock(file):
+ pass
diff --git a/django/core/files/move.py b/django/core/files/move.py
new file mode 100644
index 0000000000..66873d450c
--- /dev/null
+++ b/django/core/files/move.py
@@ -0,0 +1,59 @@
+"""
+Move a file in the safest way possible::
+
+ >>> from django.core.files.move import file_move_save
+ >>> file_move_save("/tmp/old_file", "/tmp/new_file")
+"""
+
+import os
+from django.core.files import locks
+
+__all__ = ['file_move_safe']
+
+try:
+ import shutil
+ file_move = shutil.move
+except ImportError:
+ file_move = os.rename
+
+def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_overwrite=False):
+ """
+ Moves a file from one location to another in the safest way possible.
+
+ First, try using ``shutils.move``, which is OS-dependent but doesn't break
+ if moving across filesystems. Then, try ``os.rename``, which will break
+ across filesystems. Finally, streams manually from one file to another in
+ pure Python.
+
+ If the destination file exists and ``allow_overwrite`` is ``False``, this
+ function will throw an ``IOError``.
+ """
+
+ # There's no reason to move if we don't have to.
+ if old_file_name == new_file_name:
+ return
+
+ if not allow_overwrite and os.path.exists(new_file_name):
+ raise IOError("Cannot overwrite existing file '%s'." % new_file_name)
+
+ try:
+ file_move(old_file_name, new_file_name)
+ return
+ except OSError:
+ # This will happen with os.rename if moving to another filesystem
+ pass
+
+ # If the built-in didn't work, do it the hard way.
+ new_file = open(new_file_name, 'wb')
+ locks.lock(new_file, locks.LOCK_EX)
+ old_file = open(old_file_name, 'rb')
+ current_chunk = None
+
+ while current_chunk != '':
+ current_chunk = old_file.read(chunk_size)
+ new_file.write(current_chunk)
+
+ new_file.close()
+ old_file.close()
+
+ os.remove(old_file_name)
diff --git a/django/core/files/uploadedfile.py b/django/core/files/uploadedfile.py
new file mode 100644
index 0000000000..637609d085
--- /dev/null
+++ b/django/core/files/uploadedfile.py
@@ -0,0 +1,190 @@
+"""
+Classes representing uploaded files.
+"""
+
+import os
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile')
+
+class UploadedFile(object):
+ """
+ A abstract uploaded file (``TemporaryUploadedFile`` and
+ ``InMemoryUploadedFile`` are the built-in concrete subclasses).
+
+ An ``UploadedFile`` object behaves somewhat like a file object and
+ represents some file data that the user submitted with a form.
+ """
+ DEFAULT_CHUNK_SIZE = 64 * 2**10
+
+ def __init__(self, file_name=None, content_type=None, file_size=None, charset=None):
+ self.file_name = file_name
+ self.file_size = file_size
+ self.content_type = content_type
+ self.charset = charset
+
+ def __repr__(self):
+ return "<%s: %s (%s)>" % (self.__class__.__name__, self.file_name, self.content_type)
+
+ def _set_file_name(self, name):
+ # Sanitize the file name so that it can't be dangerous.
+ if name is not None:
+ # Just use the basename of the file -- anything else is dangerous.
+ name = os.path.basename(name)
+
+ # File names longer than 255 characters can cause problems on older OSes.
+ if len(name) > 255:
+ name, ext = os.path.splitext(name)
+ name = name[:255 - len(ext)] + ext
+
+ self._file_name = name
+
+ def _get_file_name(self):
+ return self._file_name
+
+ file_name = property(_get_file_name, _set_file_name)
+
+ def chunk(self, chunk_size=None):
+ """
+ Read the file and yield chucks of ``chunk_size`` bytes (defaults to
+ ``UploadedFile.DEFAULT_CHUNK_SIZE``).
+ """
+ if not chunk_size:
+ chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
+
+ if hasattr(self, 'seek'):
+ self.seek(0)
+ # Assume the pointer is at zero...
+ counter = self.file_size
+
+ while counter > 0:
+ yield self.read(chunk_size)
+ counter -= chunk_size
+
+ def multiple_chunks(self, chunk_size=None):
+ """
+ Returns ``True`` if you can expect multiple chunks.
+
+ NB: If a particular file representation is in memory, subclasses should
+ always return ``False`` -- there's no good reason to read from memory in
+ chunks.
+ """
+ if not chunk_size:
+ chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
+ return self.file_size < chunk_size
+
+ # Abstract methods; subclasses *must* default read() and probably should
+ # define open/close.
+ def read(self, num_bytes=None):
+ raise NotImplementedError()
+
+ def open(self):
+ pass
+
+ def close(self):
+ pass
+
+ # Backwards-compatible support for uploaded-files-as-dictionaries.
+ def __getitem__(self, key):
+ import warnings
+ warnings.warn(
+ message = "The dictionary access of uploaded file objects is deprecated. Use the new object interface instead.",
+ category = DeprecationWarning,
+ stacklevel = 2
+ )
+ backwards_translate = {
+ 'filename': 'file_name',
+ 'content-type': 'content_type',
+ }
+
+ if key == 'content':
+ return self.read()
+ elif key == 'filename':
+ return self.file_name
+ elif key == 'content-type':
+ return self.content_type
+ else:
+ return getattr(self, key)
+
+class TemporaryUploadedFile(UploadedFile):
+ """
+ A file uploaded to a temporary location (i.e. stream-to-disk).
+ """
+
+ def __init__(self, file, file_name, content_type, file_size, charset):
+ super(TemporaryUploadedFile, self).__init__(file_name, content_type, file_size, charset)
+ self.file = file
+ self.path = file.name
+ self.file.seek(0)
+
+ def temporary_file_path(self):
+ """
+ Returns the full path of this file.
+ """
+ return self.path
+
+ def read(self, *args, **kwargs):
+ return self.file.read(*args, **kwargs)
+
+ def open(self):
+ self.seek(0)
+
+ def seek(self, *args, **kwargs):
+ self.file.seek(*args, **kwargs)
+
+class InMemoryUploadedFile(UploadedFile):
+ """
+ A file uploaded into memory (i.e. stream-to-memory).
+ """
+ def __init__(self, file, field_name, file_name, content_type, file_size, charset):
+ super(InMemoryUploadedFile, self).__init__(file_name, content_type, file_size, charset)
+ self.file = file
+ self.field_name = field_name
+ self.file.seek(0)
+
+ def seek(self, *args, **kwargs):
+ self.file.seek(*args, **kwargs)
+
+ def open(self):
+ self.seek(0)
+
+ def read(self, *args, **kwargs):
+ return self.file.read(*args, **kwargs)
+
+ def chunk(self, chunk_size=None):
+ self.file.seek(0)
+ yield self.read()
+
+ def multiple_chunks(self, chunk_size=None):
+ # Since it's in memory, we'll never have multiple chunks.
+ return False
+
+class SimpleUploadedFile(InMemoryUploadedFile):
+ """
+ A simple representation of a file, which just has content, size, and a name.
+ """
+ def __init__(self, name, content, content_type='text/plain'):
+ self.file = StringIO(content or '')
+ self.file_name = name
+ self.field_name = None
+ self.file_size = len(content or '')
+ self.content_type = content_type
+ self.charset = None
+ self.file.seek(0)
+
+ def from_dict(cls, file_dict):
+ """
+ Creates a SimpleUploadedFile object from
+ a dictionary object with the following keys:
+ - filename
+ - content-type
+ - content
+ """
+ return cls(file_dict['filename'],
+ file_dict['content'],
+ file_dict.get('content-type', 'text/plain'))
+
+ from_dict = classmethod(from_dict)
diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py
new file mode 100644
index 0000000000..ab587769f7
--- /dev/null
+++ b/django/core/files/uploadhandler.py
@@ -0,0 +1,245 @@
+"""
+Base file upload handler classes, and the built-in concrete subclasses
+"""
+import os
+import tempfile
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile
+
+__all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler',
+ 'TemporaryFileUploadHandler', 'MemoryFileUploadHandler',
+ 'load_handler']
+
+class UploadFileException(Exception):
+ """
+ Any error having to do with uploading files.
+ """
+ pass
+
+class StopUpload(UploadFileException):
+ """
+ This exception is raised when an upload must abort.
+ """
+ def __init__(self, connection_reset=False):
+ """
+ If ``connection_reset`` is ``True``, Django knows will halt the upload
+ without consuming the rest of the upload. This will cause the browser to
+ show a "connection reset" error.
+ """
+ self.connection_reset = connection_reset
+
+ def __unicode__(self):
+ if self.connection_reset:
+ return u'StopUpload: Halt current upload.'
+ else:
+ return u'StopUpload: Consume request data, then halt.'
+
+class SkipFile(UploadFileException):
+ """
+ This exception is raised by an upload handler that wants to skip a given file.
+ """
+ pass
+
+class StopFutureHandlers(UploadFileException):
+ """
+ Upload handers that have handled a file and do not want future handlers to
+ run should raise this exception instead of returning None.
+ """
+ pass
+
+class FileUploadHandler(object):
+ """
+ Base class for streaming upload handlers.
+ """
+ chunk_size = 64 * 2 ** 10 #: The default chunk size is 64 KB.
+
+ def __init__(self, request=None):
+ self.file_name = None
+ self.content_type = None
+ self.content_length = None
+ self.charset = None
+ self.request = request
+
+ def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
+ """
+ Handle the raw input from the client.
+
+ Parameters:
+
+ :input_data:
+ An object that supports reading via .read().
+ :META:
+ ``request.META``.
+ :content_length:
+ The (integer) value of the Content-Length header from the
+ client.
+ :boundary: The boundary from the Content-Type header. Be sure to
+ prepend two '--'.
+ """
+ pass
+
+ def new_file(self, field_name, file_name, content_type, content_length, charset=None):
+ """
+ Signal that a new file has been started.
+
+ Warning: As with any data from the client, you should not trust
+ content_length (and sometimes won't even get it).
+ """
+ self.field_name = field_name
+ self.file_name = file_name
+ self.content_type = content_type
+ self.content_length = content_length
+ self.charset = charset
+
+ def receive_data_chunk(self, raw_data, start):
+ """
+ Receive data from the streamed upload parser. ``start`` is the position
+ in the file of the chunk.
+ """
+ raise NotImplementedError()
+
+ def file_complete(self, file_size):
+ """
+ Signal that a file has completed. File size corresponds to the actual
+ size accumulated by all the chunks.
+
+ Subclasses must should return a valid ``UploadedFile`` object.
+ """
+ raise NotImplementedError()
+
+ def upload_complete(self):
+ """
+ Signal that the upload is complete. Subclasses should perform cleanup
+ that is necessary for this handler.
+ """
+ pass
+
+class TemporaryFileUploadHandler(FileUploadHandler):
+ """
+ Upload handler that streams data into a temporary file.
+ """
+ def __init__(self, *args, **kwargs):
+ super(TemporaryFileUploadHandler, self).__init__(*args, **kwargs)
+
+ def new_file(self, file_name, *args, **kwargs):
+ """
+ Create the file object to append to as data is coming in.
+ """
+ super(TemporaryFileUploadHandler, self).new_file(file_name, *args, **kwargs)
+ self.file = TemporaryFile(settings.FILE_UPLOAD_TEMP_DIR)
+ self.write = self.file.write
+
+ def receive_data_chunk(self, raw_data, start):
+ self.write(raw_data)
+
+ def file_complete(self, file_size):
+ self.file.seek(0)
+ return TemporaryUploadedFile(
+ file = self.file,
+ file_name = self.file_name,
+ content_type = self.content_type,
+ file_size = file_size,
+ charset = self.charset
+ )
+
+class MemoryFileUploadHandler(FileUploadHandler):
+ """
+ File upload handler to stream uploads into memory (used for small files).
+ """
+
+ def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
+ """
+ Use the content_length to signal whether or not this handler should be in use.
+ """
+ # Check the content-length header to see if we should
+ # If the the post is too large, we cannot use the Memory handler.
+ if content_length > settings.FILE_UPLOAD_MAX_MEMORY_SIZE:
+ self.activated = False
+ else:
+ self.activated = True
+
+ def new_file(self, *args, **kwargs):
+ super(MemoryFileUploadHandler, self).new_file(*args, **kwargs)
+ if self.activated:
+ self.file = StringIO()
+ raise StopFutureHandlers()
+
+ def receive_data_chunk(self, raw_data, start):
+ """
+ Add the data to the StringIO file.
+ """
+ if self.activated:
+ self.file.write(raw_data)
+ else:
+ return raw_data
+
+ def file_complete(self, file_size):
+ """
+ Return a file object if we're activated.
+ """
+ if not self.activated:
+ return
+
+ return InMemoryUploadedFile(
+ file = self.file,
+ field_name = self.field_name,
+ file_name = self.file_name,
+ content_type = self.content_type,
+ file_size = file_size,
+ charset = self.charset
+ )
+
+class TemporaryFile(object):
+ """
+ A temporary file that tries to delete itself when garbage collected.
+ """
+ def __init__(self, dir):
+ if not dir:
+ dir = tempfile.gettempdir()
+ try:
+ (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir)
+ self.file = os.fdopen(fd, 'w+b')
+ except (OSError, IOError):
+ raise OSError("Could not create temporary file for uploading, have you set settings.FILE_UPLOAD_TEMP_DIR correctly?")
+ self.name = name
+
+ def __getattr__(self, name):
+ a = getattr(self.__dict__['file'], name)
+ if type(a) != type(0):
+ setattr(self, name, a)
+ return a
+
+ def __del__(self):
+ try:
+ os.unlink(self.name)
+ except OSError:
+ pass
+
+def load_handler(path, *args, **kwargs):
+ """
+ Given a path to a handler, return an instance of that handler.
+
+ E.g.::
+ >>> load_handler('django.core.files.uploadhandler.TemporaryFileUploadHandler', request)
+ <TemporaryFileUploadHandler object at 0x...>
+
+ """
+ i = path.rfind('.')
+ module, attr = path[:i], path[i+1:]
+ try:
+ mod = __import__(module, {}, {}, [attr])
+ except ImportError, e:
+ raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e))
+ except ValueError, e:
+ raise ImproperlyConfigured('Error importing upload handler module. Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?')
+ try:
+ cls = getattr(mod, attr)
+ except AttributeError:
+ raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr))
+ return cls(*args, **kwargs)
diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py
index abab399009..332df6f54c 100644
--- a/django/core/handlers/modpython.py
+++ b/django/core/handlers/modpython.py
@@ -53,7 +53,8 @@ class ModPythonRequest(http.HttpRequest):
def _load_post_and_files(self):
"Populates self._post and self._files"
if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
- self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
+ self._raw_post_data = ''
+ self._post, self._files = self.parse_file_upload(self.META, self._req)
else:
self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index df2ba19b65..795f139042 100644
--- a/django/core/handlers/wsgi.py
+++ b/django/core/handlers/wsgi.py
@@ -112,9 +112,8 @@ class WSGIRequest(http.HttpRequest):
# Populates self._post and self._files
if self.method == 'POST':
if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
- header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')])
- header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
- self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
+ self._raw_post_data = ''
+ self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input'])
else:
self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
else:
diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py
index c8df8d04f7..098bca793f 100644
--- a/django/core/management/commands/createcachetable.py
+++ b/django/core/management/commands/createcachetable.py
@@ -21,10 +21,10 @@ class Command(LabelCommand):
for f in fields:
field_output = [qn(f.name), f.db_type()]
field_output.append("%sNULL" % (not f.null and "NOT " or ""))
- if f.unique:
- field_output.append("UNIQUE")
if f.primary_key:
field_output.append("PRIMARY KEY")
+ elif f.unique:
+ field_output.append("UNIQUE")
if f.db_index:
unique = f.unique and "UNIQUE " or ""
index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \
diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py
index 688cd58e2c..0ccc38ba8d 100644
--- a/django/core/management/commands/loaddata.py
+++ b/django/core/management/commands/loaddata.py
@@ -162,3 +162,9 @@ class Command(BaseCommand):
else:
if verbosity > 0:
print "Installed %d object(s) from %d fixture(s)" % (object_count, fixture_count)
+
+ # Close the DB connection. This is required as a workaround for an
+ # edge case in MySQL: if the same connection is used to
+ # create tables, load data, and query, the query can return
+ # incorrect results. See Django #7572, MySQL #37735.
+ connection.close()
diff --git a/django/core/management/sql.py b/django/core/management/sql.py
index dfe97cdd0c..0b01edcfc9 100644
--- a/django/core/management/sql.py
+++ b/django/core/management/sql.py
@@ -268,11 +268,11 @@ def sql_model_create(model, style, known_models=set()):
field_output = [style.SQL_FIELD(qn(f.column)),
style.SQL_COLTYPE(col_type)]
field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
- if f.unique and (not f.primary_key or connection.features.allows_unique_and_pk):
- field_output.append(style.SQL_KEYWORD('UNIQUE'))
if f.primary_key:
field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
- if tablespace and connection.features.supports_tablespaces and (f.unique or f.primary_key) and connection.features.autoindexes_primary_keys:
+ elif f.unique:
+ field_output.append(style.SQL_KEYWORD('UNIQUE'))
+ if tablespace and connection.features.supports_tablespaces and f.unique:
# We must specify the index tablespace inline, because we
# won't be generating a CREATE INDEX statement for this field.
field_output.append(connection.ops.tablespace_sql(tablespace, inline=True))
@@ -355,7 +355,7 @@ def many_to_many_sql_for_model(model, style):
for f in opts.local_many_to_many:
if not isinstance(f.rel, generic.GenericRel):
tablespace = f.db_tablespace or opts.db_tablespace
- if tablespace and connection.features.supports_tablespaces and connection.features.autoindexes_primary_keys:
+ if tablespace and connection.features.supports_tablespaces:
tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True)
else:
tablespace_sql = ''
@@ -468,15 +468,14 @@ def sql_indexes_for_model(model, style):
qn = connection.ops.quote_name
for f in model._meta.local_fields:
- if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys):
- unique = f.unique and 'UNIQUE ' or ''
+ if f.db_index and not f.unique:
tablespace = f.db_tablespace or model._meta.db_tablespace
if tablespace and connection.features.supports_tablespaces:
tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace)
else:
tablespace_sql = ''
output.append(
- style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
+ style.SQL_KEYWORD('CREATE INDEX') + ' ' + \
style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \
style.SQL_KEYWORD('ON') + ' ' + \
style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + \
diff --git a/django/core/paginator.py b/django/core/paginator.py
index 04cc4bf481..5fc6c80812 100644
--- a/django/core/paginator.py
+++ b/django/core/paginator.py
@@ -173,7 +173,10 @@ class ObjectPaginator(Paginator):
if self._count is None:
try:
self._count = self.object_list.count()
- except TypeError:
+ except (AttributeError, TypeError):
+ # AttributeError if object_list has no count() method.
+ # TypeError if object_list.count() requires arguments
+ # (i.e. is of type list).
self._count = len(self.object_list)
return self._count
count = property(_get_count)
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
index 5bdf08b8ca..f88f8c70bb 100644
--- a/django/core/servers/basehttp.py
+++ b/django/core/servers/basehttp.py
@@ -551,6 +551,9 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
def __init__(self, *args, **kwargs):
from django.conf import settings
self.admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
+ # We set self.path to avoid crashes in log_message() on unsupported
+ # requests (like "OPTIONS").
+ self.path = ''
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
def get_environ(self):
diff --git a/django/core/servers/fastcgi.py b/django/core/servers/fastcgi.py
index d7145e15ec..dc4c35b08d 100644
--- a/django/core/servers/fastcgi.py
+++ b/django/core/servers/fastcgi.py
@@ -40,6 +40,7 @@ Optional Fcgi settings: (setting=value)
workdir=DIRECTORY change to this directory when daemonizing.
outlog=FILE write stdout to this file.
errlog=FILE write stderr to this file.
+ umask=UMASK umask to use when daemonizing (default 022).
Examples:
Run a "standard" fastcgi process on a file-descriptor
@@ -73,6 +74,7 @@ FASTCGI_OPTIONS = {
'maxrequests': 0,
'outlog': None,
'errlog': None,
+ 'umask': None,
}
def fastcgi_help(message=None):
@@ -159,6 +161,8 @@ def runfastcgi(argset=[], **kwargs):
daemon_kwargs['out_log'] = options['outlog']
if options['errlog']:
daemon_kwargs['err_log'] = options['errlog']
+ if options['umask']:
+ daemon_kwargs['umask'] = int(options['umask'])
if daemonize:
from django.utils.daemonize import become_daemon
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index 2ad63bfc7d..ff0bcbcfea 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -296,3 +296,8 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None):
kwargs = kwargs or {}
return iri_to_uri(u'/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs))
+def clear_url_caches():
+ global _resolver_cache
+ global _callable_cache
+ _resolver_cache.clear()
+ _callable_cache.clear()
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 7a4e46a7d7..36a2339638 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -41,8 +41,6 @@ class BaseDatabaseWrapper(local):
class BaseDatabaseFeatures(object):
allows_group_by_ordinal = True
- allows_unique_and_pk = True
- autoindexes_primary_keys = True
inline_fk_references = True
needs_datetime_string_cast = True
supports_constraints = True
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index 23735a023b..336ad89504 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -60,7 +60,6 @@ server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
# TRADITIONAL will automatically cause most warnings to be treated as errors.
class DatabaseFeatures(BaseDatabaseFeatures):
- autoindexes_primary_keys = False
inline_fk_references = False
empty_fetchmany_value = ()
update_can_self_select = False
@@ -136,7 +135,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures()
ops = DatabaseOperations()
operators = {
- 'exact': '= %s',
+ 'exact': '= BINARY %s',
'iexact': 'LIKE %s',
'contains': 'LIKE BINARY %s',
'icontains': 'LIKE %s',
diff --git a/django/db/backends/mysql_old/base.py b/django/db/backends/mysql_old/base.py
index e57aee6a04..48b6b6958a 100644
--- a/django/db/backends/mysql_old/base.py
+++ b/django/db/backends/mysql_old/base.py
@@ -64,7 +64,6 @@ class MysqlDebugWrapper:
return getattr(self.cursor, attr)
class DatabaseFeatures(BaseDatabaseFeatures):
- autoindexes_primary_keys = False
inline_fk_references = False
empty_fetchmany_value = ()
update_can_self_select = False
@@ -140,7 +139,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures()
ops = DatabaseOperations()
operators = {
- 'exact': '= %s',
+ 'exact': '= BINARY %s',
'iexact': 'LIKE %s',
'contains': 'LIKE BINARY %s',
'icontains': 'LIKE %s',
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 56ae051de8..0b408990fb 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -24,7 +24,6 @@ IntegrityError = Database.IntegrityError
class DatabaseFeatures(BaseDatabaseFeatures):
allows_group_by_ordinal = False
- allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259)
empty_fetchmany_value = ()
needs_datetime_string_cast = False
supports_tablespaces = True
@@ -147,11 +146,11 @@ class DatabaseOperations(BaseDatabaseOperations):
# Since we've just deleted all the rows, running our sequence
# ALTER code will reset the sequence to 0.
for sequence_info in sequences:
- table_name = sequence_info['table']
- seq_name = get_sequence_name(table_name)
+ sequence_name = get_sequence_name(sequence_info['table'])
+ table_name = self.quote_name(sequence_info['table'])
column_name = self.quote_name(sequence_info['column'] or 'id')
- query = _get_sequence_reset_sql() % {'sequence': seq_name,
- 'table': self.quote_name(table_name),
+ query = _get_sequence_reset_sql() % {'sequence': sequence_name,
+ 'table': table_name,
'column': column_name}
sql.append(query)
return sql
@@ -163,19 +162,22 @@ class DatabaseOperations(BaseDatabaseOperations):
output = []
query = _get_sequence_reset_sql()
for model in model_list:
- for f in model._meta.fields:
+ for f in model._meta.local_fields:
if isinstance(f, models.AutoField):
+ table_name = self.quote_name(model._meta.db_table)
sequence_name = get_sequence_name(model._meta.db_table)
- column_name = self.quote_name(f.db_column or f.name)
+ column_name = self.quote_name(f.column)
output.append(query % {'sequence': sequence_name,
- 'table': model._meta.db_table,
+ 'table': table_name,
'column': column_name})
break # Only one AutoField is allowed per model, so don't bother continuing.
for f in model._meta.many_to_many:
+ table_name = self.quote_name(f.m2m_db_table())
sequence_name = get_sequence_name(f.m2m_db_table())
+ column_name = self.quote_name('id')
output.append(query % {'sequence': sequence_name,
- 'table': f.m2m_db_table(),
- 'column': self.quote_name('id')})
+ 'table': table_name,
+ 'column': column_name})
return output
def start_transaction_sql(self):
diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py
index 14bc8faf86..a106df47a8 100644
--- a/django/db/backends/oracle/creation.py
+++ b/django/db/backends/oracle/creation.py
@@ -23,7 +23,7 @@ DATA_TYPES = {
'ImageField': 'NVARCHAR2(%(max_length)s)',
'IntegerField': 'NUMBER(11)',
'IPAddressField': 'VARCHAR2(15)',
- 'NullBooleanField': 'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(column)s IS NULL))',
+ 'NullBooleanField': 'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
'OneToOneField': 'NUMBER(11)',
'PhoneNumberField': 'VARCHAR2(20)',
'PositiveIntegerField': 'NUMBER(11) CHECK (%(qn_column)s >= 0)',
diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py
index 7e27b23f72..ba6e3235c2 100644
--- a/django/db/backends/postgresql/operations.py
+++ b/django/db/backends/postgresql/operations.py
@@ -97,7 +97,7 @@ class DatabaseOperations(BaseDatabaseOperations):
# Use `coalesce` to set the sequence for each model to the max pk value if there are records,
# or 1 if there are none. Set the `is_called` property (the third argument to `setval`) to true
# if there are records (as the max pk value is already in use), otherwise set it to false.
- for f in model._meta.fields:
+ for f in model._meta.local_fields:
if isinstance(f, models.AutoField):
output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
(style.SQL_KEYWORD('SELECT'),
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 14a48044df..5669694a1b 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -19,6 +19,8 @@ from django.dispatch import dispatcher
from django.utils.datastructures import SortedDict
from django.utils.functional import curry
from django.utils.encoding import smart_str, force_unicode, smart_unicode
+from django.core.files.move import file_move_safe
+from django.core.files import locks
from django.conf import settings
try:
@@ -50,7 +52,15 @@ class ModelBase(type):
meta = attr_meta
base_meta = getattr(new_class, '_meta', None)
- new_class.add_to_class('_meta', Options(meta))
+ if getattr(meta, 'app_label', None) is None:
+ # Figure out the app_label by looking one level up.
+ # For 'django.contrib.sites.models', this would be 'sites'.
+ model_module = sys.modules[new_class.__module__]
+ kwargs = {"app_label": model_module.__name__.split('.')[-2]}
+ else:
+ kwargs = {}
+
+ new_class.add_to_class('_meta', Options(meta, **kwargs))
if not abstract:
new_class.add_to_class('DoesNotExist',
subclass_exception('DoesNotExist', ObjectDoesNotExist, module))
@@ -71,11 +81,6 @@ class ModelBase(type):
if new_class._default_manager.model._meta.abstract:
old_default_mgr = new_class._default_manager
new_class._default_manager = None
- if getattr(new_class._meta, 'app_label', None) is None:
- # Figure out the app_label by looking one level up.
- # 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]
# Bail out early if we have already created this class.
m = get_model(new_class._meta.app_label, name, False)
@@ -392,6 +397,21 @@ class Model(object):
for sub_obj in getattr(self, rel_opts_name).all():
sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)
+ # Handle any ancestors (for the model-inheritance case). We do this by
+ # traversing to the most remote parent classes -- those with no parents
+ # themselves -- and then adding those instances to the collection. That
+ # will include all the child instances down to "self".
+ parent_stack = self._meta.parents.values()
+ while parent_stack:
+ link = parent_stack.pop()
+ parent_obj = getattr(self, link.name)
+ if parent_obj._meta.parents:
+ parent_stack.extend(parent_obj._meta.parents.values())
+ continue
+ # At this point, parent_obj is base class (no ancestor models). So
+ # delete it and all its descendents.
+ parent_obj._collect_sub_objects(seen_objs)
+
def delete(self):
assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
@@ -439,7 +459,7 @@ class Model(object):
def _get_FIELD_filename(self, field):
if getattr(self, field.attname): # value is not blank
- return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))
+ return os.path.normpath(os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname)))
return ''
def _get_FIELD_url(self, field):
@@ -451,16 +471,51 @@ class Model(object):
def _get_FIELD_size(self, field):
return os.path.getsize(self._get_FIELD_filename(field))
- def _save_FIELD_file(self, field, filename, raw_contents, save=True):
+ def _save_FIELD_file(self, field, filename, raw_field, save=True):
directory = field.get_directory_name()
try: # Create the date-based directory if it doesn't exist.
os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
except OSError: # Directory probably already exists.
pass
+
+ #
+ # Check for old-style usage (files-as-dictionaries). Warn here first
+ # since there are multiple locations where we need to support both new
+ # and old usage.
+ #
+ if isinstance(raw_field, dict):
+ import warnings
+ warnings.warn(
+ message = "Representing uploaded files as dictionaries is"\
+ " deprected. Use django.core.files.SimpleUploadedFile"\
+ " instead.",
+ category = DeprecationWarning,
+ stacklevel = 2
+ )
+ from django.core.files.uploadedfile import SimpleUploadedFile
+ raw_field = SimpleUploadedFile.from_dict(raw_field)
+
+ elif isinstance(raw_field, basestring):
+ import warnings
+ warnings.warn(
+ message = "Representing uploaded files as strings is "\
+ " deprecated. Use django.core.files.SimpleUploadedFile "\
+ " instead.",
+ category = DeprecationWarning,
+ stacklevel = 2
+ )
+ from django.core.files.uploadedfile import SimpleUploadedFile
+ raw_field = SimpleUploadedFile(filename, raw_field)
+
+ if filename is None:
+ filename = raw_field.file_name
+
filename = field.get_filename(filename)
+ #
# If the filename already exists, keep adding an underscore to the name of
# the file until the filename doesn't exist.
+ #
while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):
try:
dot_index = filename.rindex('.')
@@ -468,14 +523,27 @@ class Model(object):
filename += '_'
else:
filename = filename[:dot_index] + '_' + filename[dot_index:]
+ #
+ # Save the file name on the object and write the file to disk
+ #
- # Write the file to disk.
setattr(self, field.attname, filename)
full_filename = self._get_FIELD_filename(field)
- fp = open(full_filename, 'wb')
- fp.write(raw_contents)
- fp.close()
+
+ if hasattr(raw_field, 'temporary_file_path'):
+ # This file has a file path that we can move.
+ raw_field.close()
+ file_move_safe(raw_field.temporary_file_path(), full_filename)
+
+ else:
+ # This is a normal uploadedfile that we can stream.
+ fp = open(full_filename, 'wb')
+ locks.lock(fp, locks.LOCK_EX)
+ for chunk in raw_field.chunk():
+ fp.write(chunk)
+ locks.unlock(fp)
+ fp.close()
# Save the width and/or height, if applicable.
if isinstance(field, ImageField) and (field.width_field or field.height_field):
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 33a941ff01..0a2549437b 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -91,7 +91,7 @@ class Field(object):
self.name = name
self.verbose_name = verbose_name
self.primary_key = primary_key
- self.max_length, self.unique = max_length, unique
+ self.max_length, self._unique = max_length, unique
self.blank, self.null = blank, null
# Oracle treats the empty string ('') as null, so coerce the null
# option whenever '' is a possible value.
@@ -168,6 +168,10 @@ class Field(object):
except KeyError:
return None
+ def unique(self):
+ return self._unique or self.primary_key
+ unique = property(unique)
+
def validate_full(self, field_data, all_data):
"""
Returns a list of errors for this field. This is the main interface,
@@ -695,7 +699,7 @@ class DecimalField(Field):
_("This value must be a decimal number."))
def _format(self, value):
- if isinstance(value, basestring):
+ if isinstance(value, basestring) or value is None:
return value
else:
return self.format_number(value)
@@ -716,8 +720,7 @@ class DecimalField(Field):
return u"%.*f" % (self.decimal_places, value)
def get_db_prep_save(self, value):
- if value is not None:
- value = self._format(value)
+ value = self._format(value)
return super(DecimalField, self).get_db_prep_save(value)
def get_db_prep_lookup(self, lookup_type, value):
@@ -812,7 +815,7 @@ class FileField(Field):
setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self))
setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
- setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save))
+ setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save))
dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
def delete_file(self, instance):
@@ -835,9 +838,19 @@ class FileField(Field):
if new_data.get(upload_field_name, False):
func = getattr(new_object, 'save_%s_file' % self.name)
if rel:
- func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save)
+ file = new_data[upload_field_name][0]
else:
- func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save)
+ file = new_data[upload_field_name]
+
+ # Backwards-compatible support for files-as-dictionaries.
+ # We don't need to raise a warning because Model._save_FIELD_file will
+ # do so for us.
+ try:
+ file_name = file.file_name
+ except AttributeError:
+ file_name = file['filename']
+
+ func(file_name, file, save)
def get_directory_name(self):
return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
@@ -850,7 +863,7 @@ class FileField(Field):
def save_form_data(self, instance, data):
from django.newforms.fields import UploadedFile
if data and isinstance(data, UploadedFile):
- getattr(instance, "save_%s_file" % self.name)(data.filename, data.content, save=False)
+ getattr(instance, "save_%s_file" % self.name)(data.filename, data.data, save=False)
def formfield(self, **kwargs):
defaults = {'form_class': forms.FileField}
@@ -1166,12 +1179,3 @@ class XMLField(TextField):
def get_manipulator_field_objs(self):
return [curry(oldforms.XMLLargeTextField, schema_path=self.schema_path)]
-class OrderingField(IntegerField):
- empty_strings_allowed=False
- def __init__(self, with_respect_to, **kwargs):
- self.wrt = with_respect_to
- kwargs['null'] = True
- IntegerField.__init__(self, **kwargs )
-
- def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
- return [oldforms.HiddenField(name_prefix + self.name)]
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 9d5886d426..9ef46df575 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -185,11 +185,11 @@ class SingleRelatedObjectDescriptor(object):
def __set__(self, instance, value):
if instance is None:
raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
-
- # The similarity of the code below to the code in
+
+ # The similarity of the code below to the code in
# ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch
# of small differences that would make a common base class convoluted.
-
+
# If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class.
if value is None and self.related.field.null == False:
@@ -197,14 +197,14 @@ class SingleRelatedObjectDescriptor(object):
(instance._meta.object_name, self.related.get_accessor_name()))
elif value is not None and not isinstance(value, self.related.model):
raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
- (value, instance._meta.object_name,
+ (value, instance._meta.object_name,
self.related.get_accessor_name(), self.related.opts.object_name))
-
+
# Set the value of the related field
setattr(value, self.related.field.rel.get_related_field().attname, instance)
# Since we already know what the related object is, seed the related
- # object caches now, too. This avoids another db hit if you get the
+ # object caches now, too. This avoids another db hit if you get the
# object you just set.
setattr(instance, self.cache_name, value)
setattr(value, self.related.field.get_cache_name(), instance)
@@ -243,7 +243,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
def __set__(self, instance, value):
if instance is None:
raise AttributeError, "%s must be accessed via instance" % self._field.name
-
+
# If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class.
if value is None and self.field.null == False:
@@ -251,9 +251,9 @@ class ReverseSingleRelatedObjectDescriptor(object):
(instance._meta.object_name, self.field.name))
elif value is not None and not isinstance(value, self.field.rel.to):
raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
- (value, instance._meta.object_name,
+ (value, instance._meta.object_name,
self.field.name, self.field.rel.to._meta.object_name))
-
+
# Set the value of the related field
try:
val = getattr(value, self.field.rel.get_related_field().attname)
@@ -262,7 +262,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
setattr(instance, self.field.attname, val)
# Since we already know what the related object is, seed the related
- # object cache now, too. This avoids another db hit if you get the
+ # object cache now, too. This avoids another db hit if you get the
# object you just set.
setattr(instance, self.field.get_cache_name(), value)
@@ -322,7 +322,9 @@ class ForeignRelatedObjectsDescriptor(object):
clear.alters_data = True
manager = RelatedManager()
- manager.core_filters = {'%s__pk' % rel_field.name: getattr(instance, rel_field.rel.get_related_field().attname)}
+ attname = rel_field.rel.get_related_field().name
+ manager.core_filters = {'%s__%s' % (rel_field.name, attname):
+ getattr(instance, attname)}
manager.model = self.related.model
return manager
@@ -692,6 +694,11 @@ class ForeignKey(RelatedField, Field):
def contribute_to_class(self, cls, name):
super(ForeignKey, self).contribute_to_class(cls, name)
setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
+ if isinstance(self.rel.to, basestring):
+ target = self.rel.to
+ else:
+ target = self.rel.to._meta.db_table
+ cls._meta.duplicate_targets[self.column] = (target, "o2m")
def contribute_to_related_class(self, cls, related):
setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
@@ -829,6 +836,12 @@ class ManyToManyField(RelatedField, Field):
# Set up the accessor for the m2m table name for the relation
self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
+ if isinstance(self.rel.to, basestring):
+ target = self.rel.to
+ else:
+ target = self.rel.to._meta.db_table
+ cls._meta.duplicate_targets[self.column] = (target, "m2m")
+
def contribute_to_related_class(self, cls, related):
# m2m relations to self do not have a ManyRelatedObjectsDescriptor,
# as it would be redundant - unless the field is non-symmetrical.
diff --git a/django/db/models/options.py b/django/db/models/options.py
index c2313f221f..a81a34d722 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -25,7 +25,7 @@ DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
'abstract')
class Options(object):
- def __init__(self, meta):
+ def __init__(self, meta, app_label=None):
self.local_fields, self.local_many_to_many = [], []
self.module_name, self.verbose_name = None, None
self.verbose_name_plural = None
@@ -33,7 +33,7 @@ class Options(object):
self.ordering = []
self.unique_together = []
self.permissions = []
- self.object_name, self.app_label = None, None
+ self.object_name, self.app_label = None, app_label
self.get_latest_by = None
self.order_with_respect_to = None
self.db_tablespace = settings.DEFAULT_TABLESPACE
@@ -44,8 +44,12 @@ class Options(object):
self.one_to_one_field = None
self.abstract = False
self.parents = SortedDict()
+ self.duplicate_targets = {}
def contribute_to_class(self, cls, name):
+ from django.db import connection
+ from django.db.backends.util import truncate_name
+
cls._meta = self
self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
# First, construct the default values for these options.
@@ -87,9 +91,13 @@ class Options(object):
self.verbose_name_plural = string_concat(self.verbose_name, 's')
del self.meta
+ # If the db_table wasn't provided, use the app_label + module_name.
+ if not self.db_table:
+ self.db_table = "%s_%s" % (self.app_label, self.module_name)
+ self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
+
+
def _prepare(self, model):
- from django.db import connection
- from django.db.backends.util import truncate_name
if self.order_with_respect_to:
self.order_with_respect_to = self.get_field(self.order_with_respect_to)
self.ordering = ('_order',)
@@ -108,10 +116,23 @@ class Options(object):
auto_created=True)
model.add_to_class('id', auto)
- # If the db_table wasn't provided, use the app_label + module_name.
- if not self.db_table:
- self.db_table = "%s_%s" % (self.app_label, self.module_name)
- self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
+ # Determine any sets of fields that are pointing to the same targets
+ # (e.g. two ForeignKeys to the same remote model). The query
+ # construction code needs to know this. At the end of this,
+ # self.duplicate_targets will map each duplicate field column to the
+ # columns it duplicates.
+ collections = {}
+ for column, target in self.duplicate_targets.iteritems():
+ try:
+ collections[target].add(column)
+ except KeyError:
+ collections[target] = set([column])
+ self.duplicate_targets = {}
+ for elt in collections.itervalues():
+ if len(elt) == 1:
+ continue
+ for column in elt:
+ self.duplicate_targets[column] = elt.difference(set([column]))
def add_field(self, field):
# Insert the given field in the order in which it was created, using
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 98caaf004c..e92f6c4275 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -3,7 +3,7 @@ import warnings
from django.conf import settings
from django.db import connection, transaction, IntegrityError
from django.db.models.fields import DateField, FieldDoesNotExist
-from django.db.models.query_utils import Q
+from django.db.models.query_utils import Q, select_related_descend
from django.db.models import signals, sql
from django.dispatch import dispatcher
from django.utils.datastructures import SortedDict
@@ -761,8 +761,7 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
index_end = index_start + len(klass._meta.fields)
obj = klass(*row[index_start:index_end])
for f in klass._meta.fields:
- if (not f.rel or (not restricted and f.null) or
- (restricted and f.name not in requested) or f.rel.parent_link):
+ if not select_related_descend(f, restricted, requested):
continue
if restricted:
next = requested[f.name]
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
index 0ce7900c74..8dbb1ec667 100644
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -48,3 +48,20 @@ class Q(tree.Node):
obj.negate()
return obj
+def select_related_descend(field, restricted, requested):
+ """
+ Returns True if this field should be used to descend deeper for
+ select_related() purposes. Used by both the query construction code
+ (sql.query.fill_related_selections()) and the model instance creation code
+ (query.get_cached_row()).
+ """
+ if not field.rel:
+ return False
+ if field.rel.parent_link:
+ return False
+ if restricted and field.name not in requested:
+ return False
+ if not restricted and field.null:
+ return False
+ return True
+
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index e8d10bc55b..7944d0358f 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -7,6 +7,7 @@ databases). The abstraction barrier only works one way: this module has to know
all about the internals of models in order to get the information it needs.
"""
+import datetime
from copy import deepcopy
from django.utils.tree import Node
@@ -14,9 +15,10 @@ from django.utils.datastructures import SortedDict
from django.dispatch import dispatcher
from django.db import connection
from django.db.models import signals
+from django.db.models.fields import FieldDoesNotExist
+from django.db.models.query_utils import select_related_descend
from django.db.models.sql.where import WhereNode, EverythingNode, AND, OR
from django.db.models.sql.datastructures import Count
-from django.db.models.fields import FieldDoesNotExist
from django.core.exceptions import FieldError
from datastructures import EmptyResultSet, Empty, MultiJoin
from constants import *
@@ -56,6 +58,7 @@ class Query(object):
self.start_meta = None
self.select_fields = []
self.related_select_fields = []
+ self.dupe_avoidance = {}
# SQL-related attributes
self.select = []
@@ -164,6 +167,7 @@ class Query(object):
obj.start_meta = self.start_meta
obj.select_fields = self.select_fields[:]
obj.related_select_fields = self.related_select_fields[:]
+ obj.dupe_avoidance = self.dupe_avoidance.copy()
obj.select = self.select[:]
obj.tables = self.tables[:]
obj.where = deepcopy(self.where)
@@ -214,7 +218,7 @@ class Query(object):
obj.select_related = False
obj.related_select_cols = []
obj.related_select_fields = []
- if obj.distinct and len(obj.select) > 1:
+ if len(obj.select) > 1:
obj = self.clone(CountQuery, _query=obj, where=self.where_class(),
distinct=False)
obj.select = []
@@ -362,10 +366,21 @@ class Query(object):
item.relabel_aliases(change_map)
self.select.append(item)
self.select_fields = rhs.select_fields[:]
- self.extra_select = rhs.extra_select.copy()
- self.extra_tables = rhs.extra_tables
- self.extra_where = rhs.extra_where
- self.extra_params = rhs.extra_params
+
+ if connector == OR:
+ # It would be nice to be able to handle this, but the queries don't
+ # really make sense (or return consistent value sets). Not worth
+ # the extra complexity when you can write a real query instead.
+ if self.extra_select and rhs.extra_select:
+ raise ValueError("When merging querysets using 'or', you "
+ "cannot have extra(select=...) on both sides.")
+ if self.extra_where and rhs.extra_where:
+ raise ValueError("When merging querysets using 'or', you "
+ "cannot have extra(where=...) on both sides.")
+ self.extra_select.update(rhs.extra_select)
+ self.extra_tables += rhs.extra_tables
+ self.extra_where += rhs.extra_where
+ self.extra_params += rhs.extra_params
# Ordering uses the 'rhs' ordering, unless it has none, in which case
# the current ordering is used.
@@ -439,28 +454,39 @@ class Query(object):
self._select_aliases = aliases
return result
- def get_default_columns(self, with_aliases=False, col_aliases=None):
+ def get_default_columns(self, with_aliases=False, col_aliases=None,
+ start_alias=None, opts=None, as_pairs=False):
"""
Computes the default columns for selecting every field in the base
model.
Returns a list of strings, quoted appropriately for use in SQL
- directly, as well as a set of aliases used in the select statement.
+ directly, as well as a set of aliases used in the select statement (if
+ 'as_pairs' is True, returns a list of (alias, col_name) pairs instead
+ of strings as the first component and None as the second component).
"""
result = []
- table_alias = self.tables[0]
- root_pk = self.model._meta.pk.column
+ if opts is None:
+ opts = self.model._meta
+ if start_alias:
+ table_alias = start_alias
+ else:
+ table_alias = self.tables[0]
+ root_pk = opts.pk.column
seen = {None: table_alias}
qn = self.quote_name_unless_alias
qn2 = self.connection.ops.quote_name
aliases = set()
- for field, model in self.model._meta.get_fields_with_model():
+ for field, model in opts.get_fields_with_model():
try:
alias = seen[model]
except KeyError:
alias = self.join((table_alias, model._meta.db_table,
root_pk, model._meta.pk.column))
seen[model] = alias
+ if as_pairs:
+ result.append((alias, field.column))
+ continue
if with_aliases and field.column in col_aliases:
c_alias = 'Col%d' % len(col_aliases)
result.append('%s.%s AS %s' % (qn(alias),
@@ -473,6 +499,8 @@ class Query(object):
aliases.add(r)
if with_aliases:
col_aliases.add(field.column)
+ if as_pairs:
+ return result, None
return result, aliases
def get_from_clause(self):
@@ -609,6 +637,11 @@ class Query(object):
alias, False)
alias = joins[-1]
col = target.column
+ if not field.rel:
+ # To avoid inadvertent trimming of a necessary alias, use the
+ # refcount to show that we are referencing a non-relation field on
+ # the model.
+ self.ref_alias(alias)
# Must use left outer joins for nullable fields.
for join in joins:
@@ -829,8 +862,8 @@ class Query(object):
if reuse and always_create and table in self.table_map:
# Convert the 'reuse' to case to be "exclude everything but the
- # reusable set for this table".
- exclusions = set(self.table_map[table]).difference(reuse)
+ # reusable set, minus exclusions, for this table".
+ exclusions = set(self.table_map[table]).difference(reuse).union(set(exclusions))
always_create = False
t_ident = (lhs_table, table, lhs_col, col)
if not always_create:
@@ -865,7 +898,8 @@ class Query(object):
return alias
def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1,
- used=None, requested=None, restricted=None, nullable=None):
+ used=None, requested=None, restricted=None, nullable=None,
+ dupe_set=None):
"""
Fill in the information needed for a select_related query. The current
depth is measured as the number of connections away from the root model
@@ -875,6 +909,7 @@ class Query(object):
if not restricted and self.max_depth and cur_depth > self.max_depth:
# We've recursed far enough; bail out.
return
+
if not opts:
opts = self.get_meta()
root_alias = self.get_initial_alias()
@@ -882,6 +917,10 @@ class Query(object):
self.related_select_fields = []
if not used:
used = set()
+ if dupe_set is None:
+ dupe_set = set()
+ orig_dupe_set = dupe_set
+ orig_used = used
# Setup for the case when only particular related fields should be
# included in the related selection.
@@ -893,9 +932,10 @@ class Query(object):
restricted = False
for f, model in opts.get_fields_with_model():
- if (not f.rel or (restricted and f.name not in requested) or
- (not restricted and f.null) or f.rel.parent_link):
+ if not select_related_descend(f, restricted, requested):
continue
+ dupe_set = orig_dupe_set.copy()
+ used = orig_used.copy()
table = f.rel.to._meta.db_table
if nullable or f.null:
promote = True
@@ -906,18 +946,32 @@ class Query(object):
alias = root_alias
for int_model in opts.get_base_chain(model):
lhs_col = int_opts.parents[int_model].column
+ dedupe = lhs_col in opts.duplicate_targets
+ if dedupe:
+ used.update(self.dupe_avoidance.get(id(opts), lhs_col),
+ ())
+ dupe_set.add((opts, lhs_col))
int_opts = int_model._meta
alias = self.join((alias, int_opts.db_table, lhs_col,
int_opts.pk.column), exclusions=used,
promote=promote)
+ for (dupe_opts, dupe_col) in dupe_set:
+ self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
else:
alias = root_alias
+
+ dedupe = f.column in opts.duplicate_targets
+ if dupe_set or dedupe:
+ used.update(self.dupe_avoidance.get((id(opts), f.column), ()))
+ if dedupe:
+ dupe_set.add((opts, f.column))
+
alias = self.join((alias, table, f.column,
f.rel.get_related_field().column), exclusions=used,
promote=promote)
used.add(alias)
- self.related_select_cols.extend([(alias, f2.column)
- for f2 in f.rel.to._meta.fields])
+ self.related_select_cols.extend(self.get_default_columns(
+ start_alias=alias, opts=f.rel.to._meta, as_pairs=True)[0])
self.related_select_fields.extend(f.rel.to._meta.fields)
if restricted:
next = requested.get(f.name, {})
@@ -927,8 +981,10 @@ class Query(object):
new_nullable = f.null
else:
new_nullable = None
+ for dupe_opts, dupe_col in dupe_set:
+ self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1,
- used, next, restricted, new_nullable)
+ used, next, restricted, new_nullable, dupe_set)
def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
can_reuse=None):
@@ -1058,15 +1114,17 @@ class Query(object):
for alias in join_list:
if self.alias_map[alias][JOIN_TYPE] == self.LOUTER:
j_col = self.alias_map[alias][RHS_JOIN_COL]
- entry = Node([(alias, j_col, None, 'isnull', True)])
+ entry = self.where_class()
+ entry.add((alias, j_col, None, 'isnull', True), AND)
entry.negate()
self.where.add(entry, AND)
break
- elif not (lookup_type == 'in' and not value):
+ elif not (lookup_type == 'in' and not value) and field.null:
# Leaky abstraction artifact: We have to specifically
# exclude the "foo__in=[]" case from this handling, because
# it's short-circuited in the Where class.
- entry = Node([(alias, col, field, 'isnull', True)])
+ entry = self.where_class()
+ entry.add((alias, col, None, 'isnull', True), AND)
entry.negate()
self.where.add(entry, AND)
@@ -1114,7 +1172,9 @@ class Query(object):
(which gives the table we are joining to), 'alias' is the alias for the
table we are joining to. If dupe_multis is True, any many-to-many or
many-to-one joins will always create a new alias (necessary for
- disjunctive filters).
+ disjunctive filters). If can_reuse is not None, it's a list of aliases
+ that can be reused in these joins (nothing else can be reused in this
+ case).
Returns the final field involved in the join, the target database
column (used for any 'where' constraint), the final 'opts' value and the
@@ -1122,7 +1182,14 @@ class Query(object):
"""
joins = [alias]
last = [0]
+ dupe_set = set()
+ exclusions = set()
for pos, name in enumerate(names):
+ try:
+ exclusions.add(int_alias)
+ except NameError:
+ pass
+ exclusions.add(alias)
last.append(len(joins))
if name == 'pk':
name = opts.pk.name
@@ -1141,6 +1208,7 @@ class Query(object):
names = opts.get_all_field_names()
raise FieldError("Cannot resolve keyword %r into field. "
"Choices are: %s" % (name, ", ".join(names)))
+
if not allow_many and (m2m or not direct):
for alias in joins:
self.unref_alias(alias)
@@ -1150,12 +1218,27 @@ class Query(object):
alias_list = []
for int_model in opts.get_base_chain(model):
lhs_col = opts.parents[int_model].column
+ dedupe = lhs_col in opts.duplicate_targets
+ if dedupe:
+ exclusions.update(self.dupe_avoidance.get(
+ (id(opts), lhs_col), ()))
+ dupe_set.add((opts, lhs_col))
opts = int_model._meta
alias = self.join((alias, opts.db_table, lhs_col,
- opts.pk.column), exclusions=joins)
+ opts.pk.column), exclusions=exclusions)
joins.append(alias)
+ exclusions.add(alias)
+ for (dupe_opts, dupe_col) in dupe_set:
+ self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
cached_data = opts._join_cache.get(name)
orig_opts = opts
+ dupe_col = direct and field.column or field.field.column
+ dedupe = dupe_col in opts.duplicate_targets
+ if dupe_set or dedupe:
+ if dedupe:
+ dupe_set.add((opts, dupe_col))
+ exclusions.update(self.dupe_avoidance.get((id(opts), dupe_col),
+ ()))
if direct:
if m2m:
@@ -1177,9 +1260,11 @@ class Query(object):
target)
int_alias = self.join((alias, table1, from_col1, to_col1),
- dupe_multis, joins, nullable=True, reuse=can_reuse)
+ dupe_multis, exclusions, nullable=True,
+ reuse=can_reuse)
alias = self.join((int_alias, table2, from_col2, to_col2),
- dupe_multis, joins, nullable=True, reuse=can_reuse)
+ dupe_multis, exclusions, nullable=True,
+ reuse=can_reuse)
joins.extend([int_alias, alias])
elif field.rel:
# One-to-one or many-to-one field
@@ -1195,7 +1280,7 @@ class Query(object):
opts, target)
alias = self.join((alias, table, from_col, to_col),
- exclusions=joins, nullable=field.null)
+ exclusions=exclusions, nullable=field.null)
joins.append(alias)
else:
# Non-relation fields.
@@ -1223,9 +1308,11 @@ class Query(object):
target)
int_alias = self.join((alias, table1, from_col1, to_col1),
- dupe_multis, joins, nullable=True, reuse=can_reuse)
+ dupe_multis, exclusions, nullable=True,
+ reuse=can_reuse)
alias = self.join((int_alias, table2, from_col2, to_col2),
- dupe_multis, joins, nullable=True, reuse=can_reuse)
+ dupe_multis, exclusions, nullable=True,
+ reuse=can_reuse)
joins.extend([int_alias, alias])
else:
# One-to-many field (ForeignKey defined on the target model)
@@ -1243,14 +1330,34 @@ class Query(object):
opts, target)
alias = self.join((alias, table, from_col, to_col),
- dupe_multis, joins, nullable=True, reuse=can_reuse)
+ dupe_multis, exclusions, nullable=True,
+ reuse=can_reuse)
joins.append(alias)
+ for (dupe_opts, dupe_col) in dupe_set:
+ try:
+ self.update_dupe_avoidance(dupe_opts, dupe_col, int_alias)
+ except NameError:
+ self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
+
if pos != len(names) - 1:
raise FieldError("Join on field %r not permitted." % name)
return field, target, opts, joins, last
+ def update_dupe_avoidance(self, opts, col, alias):
+ """
+ For a column that is one of multiple pointing to the same table, update
+ the internal data structures to note that this alias shouldn't be used
+ for those other columns.
+ """
+ ident = id(opts)
+ for name in opts.duplicate_targets[col]:
+ try:
+ self.dupe_avoidance[ident, name].add(alias)
+ except KeyError:
+ self.dupe_avoidance[ident, name] = set([alias])
+
def split_exclude(self, filter_expr, prefix):
"""
When doing an exclude against any kind of N-to-many relation, we need
diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
index 14e54487a3..18e4bf2f7e 100644
--- a/django/db/models/sql/where.py
+++ b/django/db/models/sql/where.py
@@ -21,12 +21,42 @@ class WhereNode(tree.Node):
the correct SQL).
The children in this tree are usually either Q-like objects or lists of
- [table_alias, field_name, field_class, lookup_type, value]. However, a
- child could also be any class with as_sql() and relabel_aliases() methods.
+ [table_alias, field_name, db_type, lookup_type, value_annotation,
+ params]. However, a child could also be any class with as_sql() and
+ relabel_aliases() methods.
"""
default = AND
- def as_sql(self, node=None, qn=None):
+ def add(self, data, connector):
+ """
+ Add a node to the where-tree. If the data is a list or tuple, it is
+ expected to be of the form (alias, col_name, field_obj, lookup_type,
+ value), which is then slightly munged before being stored (to avoid
+ storing any reference to field objects). Otherwise, the 'data' is
+ stored unchanged and can be anything with an 'as_sql()' method.
+ """
+ if not isinstance(data, (list, tuple)):
+ super(WhereNode, self).add(data, connector)
+ return
+
+ alias, col, field, lookup_type, value = data
+ if field:
+ params = field.get_db_prep_lookup(lookup_type, value)
+ db_type = field.db_type()
+ else:
+ # This is possible when we add a comparison to NULL sometimes (we
+ # don't really need to waste time looking up the associated field
+ # object).
+ params = Field().get_db_prep_lookup(lookup_type, value)
+ db_type = None
+ if isinstance(value, datetime.datetime):
+ annotation = datetime.datetime
+ else:
+ annotation = bool(value)
+ super(WhereNode, self).add((alias, col, db_type, lookup_type,
+ annotation, params), connector)
+
+ def as_sql(self, qn=None):
"""
Returns the SQL version of the where clause and the value to be
substituted in. Returns None, None if this node is empty.
@@ -35,82 +65,73 @@ class WhereNode(tree.Node):
(generally not needed except by the internal implementation for
recursion).
"""
- if node is None:
- node = self
if not qn:
qn = connection.ops.quote_name
- if not node.children:
+ if not self.children:
return None, []
result = []
result_params = []
empty = True
- for child in node.children:
+ for child in self.children:
try:
if hasattr(child, 'as_sql'):
sql, params = child.as_sql(qn=qn)
- format = '(%s)'
- elif isinstance(child, tree.Node):
- sql, params = self.as_sql(child, qn)
- if child.negated:
- format = 'NOT (%s)'
- elif len(child.children) == 1:
- format = '%s'
- else:
- format = '(%s)'
else:
+ # A leaf node in the tree.
sql, params = self.make_atom(child, qn)
- format = '%s'
except EmptyResultSet:
- if node.connector == AND and not node.negated:
+ if self.connector == AND and not self.negated:
# We can bail out early in this particular case (only).
raise
- elif node.negated:
+ elif self.negated:
empty = False
continue
except FullResultSet:
if self.connector == OR:
- if node.negated:
+ if self.negated:
empty = True
break
# We match everything. No need for any constraints.
return '', []
- if node.negated:
+ if self.negated:
empty = True
continue
empty = False
if sql:
- result.append(format % sql)
+ result.append(sql)
result_params.extend(params)
if empty:
raise EmptyResultSet
- conn = ' %s ' % node.connector
- return conn.join(result), result_params
+
+ conn = ' %s ' % self.connector
+ sql_string = conn.join(result)
+ if sql_string:
+ if self.negated:
+ sql_string = 'NOT (%s)' % sql_string
+ elif len(self.children) != 1:
+ sql_string = '(%s)' % sql_string
+ return sql_string, result_params
def make_atom(self, child, qn):
"""
- Turn a tuple (table_alias, field_name, field_class, lookup_type, value)
- into valid SQL.
+ Turn a tuple (table_alias, column_name, db_type, lookup_type,
+ value_annot, params) into valid SQL.
Returns the string for the SQL fragment and the parameters to use for
it.
"""
- table_alias, name, field, lookup_type, value = child
+ table_alias, name, db_type, lookup_type, value_annot, params = child
if table_alias:
lhs = '%s.%s' % (qn(table_alias), qn(name))
else:
lhs = qn(name)
- db_type = field and field.db_type() or None
field_sql = connection.ops.field_cast_sql(db_type) % lhs
- if isinstance(value, datetime.datetime):
+ if value_annot is datetime.datetime:
cast_sql = connection.ops.datetime_cast_sql()
else:
cast_sql = '%s'
- if field:
- params = field.get_db_prep_lookup(lookup_type, value)
- else:
- params = Field().get_db_prep_lookup(lookup_type, value)
if isinstance(params, QueryWrapper):
extra, params = params.data
else:
@@ -123,11 +144,11 @@ class WhereNode(tree.Node):
connection.operators[lookup_type] % cast_sql), params)
if lookup_type == 'in':
- if not value:
+ if not value_annot:
raise EmptyResultSet
if extra:
return ('%s IN %s' % (field_sql, extra), params)
- return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(value))),
+ return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(params))),
params)
elif lookup_type in ('range', 'year'):
return ('%s BETWEEN %%s and %%s' % field_sql, params)
@@ -135,8 +156,8 @@ class WhereNode(tree.Node):
return ('%s = %%s' % connection.ops.date_extract_sql(lookup_type,
field_sql), params)
elif lookup_type == 'isnull':
- return ('%s IS %sNULL' % (field_sql, (not value and 'NOT ' or '')),
- params)
+ return ('%s IS %sNULL' % (field_sql,
+ (not value_annot and 'NOT ' or '')), ())
elif lookup_type == 'search':
return (connection.ops.fulltext_search_sql(field_sql), params)
elif lookup_type in ('regex', 'iregex'):
diff --git a/django/db/transaction.py b/django/db/transaction.py
index c0c3779f6a..cd27cf6044 100644
--- a/django/db/transaction.py
+++ b/django/db/transaction.py
@@ -196,7 +196,10 @@ def commit_on_success(func):
managed(True)
try:
res = func(*args, **kw)
- except Exception, e:
+ except (Exception, KeyboardInterrupt, SystemExit):
+ # (We handle KeyboardInterrupt and SystemExit specially, since
+ # they don't inherit from Exception in Python 2.5, but we
+ # should treat them uniformly here.)
if is_dirty():
rollback()
raise
diff --git a/django/http/__init__.py b/django/http/__init__.py
index 7faa3c875e..ef15479983 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -9,14 +9,15 @@ try:
except ImportError:
from cgi import parse_qsl
-from django.utils.datastructures import MultiValueDict, FileDict
+from django.utils.datastructures import MultiValueDict, ImmutableList
from django.utils.encoding import smart_str, iri_to_uri, force_unicode
-
+from django.http.multipartparser import MultiPartParser
+from django.conf import settings
+from django.core.files import uploadhandler
from utils import *
RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
-
class Http404(Exception):
pass
@@ -25,6 +26,7 @@ class HttpRequest(object):
# The encoding used in GET/POST dicts. None means use default setting.
_encoding = None
+ _upload_handlers = []
def __init__(self):
self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
@@ -102,39 +104,31 @@ class HttpRequest(object):
encoding = property(_get_encoding, _set_encoding)
-def parse_file_upload(header_dict, post_data):
- """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
- import email, email.Message
- from cgi import parse_header
- raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
- raw_message += '\r\n\r\n' + post_data
- msg = email.message_from_string(raw_message)
- POST = QueryDict('', mutable=True)
- FILES = MultiValueDict()
- for submessage in msg.get_payload():
- if submessage and isinstance(submessage, email.Message.Message):
- name_dict = parse_header(submessage['Content-Disposition'])[1]
- # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
- # or {'name': 'blah'} for POST fields
- # We assume all uploaded files have a 'filename' set.
- if 'filename' in name_dict:
- assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
- if not name_dict['filename'].strip():
- continue
- # IE submits the full path, so trim everything but the basename.
- # (We can't use os.path.basename because that uses the server's
- # directory separator, which may not be the same as the
- # client's one.)
- filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
- FILES.appendlist(name_dict['name'], FileDict({
- 'filename': filename,
- 'content-type': 'Content-Type' in submessage and submessage['Content-Type'] or None,
- 'content': submessage.get_payload(),
- }))
- else:
- POST.appendlist(name_dict['name'], submessage.get_payload())
- return POST, FILES
+ def _initialize_handlers(self):
+ self._upload_handlers = [uploadhandler.load_handler(handler, self)
+ for handler in settings.FILE_UPLOAD_HANDLERS]
+
+ def _set_upload_handlers(self, upload_handlers):
+ if hasattr(self, '_files'):
+ raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
+ self._upload_handlers = upload_handlers
+
+ def _get_upload_handlers(self):
+ if not self._upload_handlers:
+ # If thre are no upload handlers defined, initialize them from settings.
+ self._initialize_handlers()
+ return self._upload_handlers
+
+ upload_handlers = property(_get_upload_handlers, _set_upload_handlers)
+ def parse_file_upload(self, META, post_data):
+ """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
+ self.upload_handlers = ImmutableList(
+ self.upload_handlers,
+ warning = "You cannot alter upload handlers after the upload has been processed."
+ )
+ parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
+ return parser.parse()
class QueryDict(MultiValueDict):
"""
diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
new file mode 100644
index 0000000000..8bed5681cf
--- /dev/null
+++ b/django/http/multipartparser.py
@@ -0,0 +1,658 @@
+"""
+Multi-part parsing for file uploads.
+
+Exposes one class, ``MultiPartParser``, which feeds chunks of uploaded data to
+file upload handlers for processing.
+"""
+import cgi
+from django.conf import settings
+from django.core.exceptions import SuspiciousOperation
+from django.utils.datastructures import MultiValueDict
+from django.utils.encoding import force_unicode
+from django.utils.text import unescape_entities
+from django.core.files.uploadhandler import StopUpload, SkipFile, StopFutureHandlers
+
+__all__ = ('MultiPartParser','MultiPartParserError','InputStreamExhausted')
+
+class MultiPartParserError(Exception):
+ pass
+
+class InputStreamExhausted(Exception):
+ """
+ No more reads are allowed from this device.
+ """
+ pass
+
+RAW = "raw"
+FILE = "file"
+FIELD = "field"
+
+class MultiPartParser(object):
+ """
+ A rfc2388 multipart/form-data parser.
+
+ ``MultiValueDict.parse()`` reads the input stream in ``chunk_size`` chunks
+ and returns a tuple of ``(MultiValueDict(POST), MultiValueDict(FILES))``. If
+ ``file_upload_dir`` is defined files will be streamed to temporary files in
+ that directory.
+ """
+ def __init__(self, META, input_data, upload_handlers, encoding=None):
+ """
+ Initialize the MultiPartParser object.
+
+ :META:
+ The standard ``META`` dictionary in Django request objects.
+ :input_data:
+ The raw post data, as a bytestring.
+ :upload_handler:
+ An UploadHandler instance that performs operations on the uploaded
+ data.
+ :encoding:
+ The encoding with which to treat the incoming data.
+ """
+
+ #
+ # Content-Type should containt multipart and the boundary information.
+ #
+
+ content_type = META.get('HTTP_CONTENT_TYPE', META.get('CONTENT_TYPE', ''))
+ if not content_type.startswith('multipart/'):
+ raise MultiPartParserError('Invalid Content-Type: %s' % content_type)
+
+ # Parse the header to get the boundary to split the parts.
+ ctypes, opts = parse_header(content_type)
+ boundary = opts.get('boundary')
+ if not boundary or not cgi.valid_boundary(boundary):
+ raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary)
+
+
+ #
+ # Content-Length should contain the length of the body we are about
+ # to receive.
+ #
+ try:
+ content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH',0)))
+ except (ValueError, TypeError):
+ # For now set it to 0; we'll try again later on down.
+ content_length = 0
+
+ if content_length <= 0:
+ # This means we shouldn't continue...raise an error.
+ raise MultiPartParserError("Invalid content length: %r" % content_length)
+
+ self._boundary = boundary
+ self._input_data = input_data
+
+ # For compatibility with low-level network APIs (with 32-bit integers),
+ # the chunk size should be < 2^31, but still divisible by 4.
+ self._chunk_size = min(2**31-4, *[x.chunk_size for x in upload_handlers if x.chunk_size])
+
+ self._meta = META
+ self._encoding = encoding or settings.DEFAULT_CHARSET
+ self._content_length = content_length
+ self._upload_handlers = upload_handlers
+
+ def parse(self):
+ """
+ Parse the POST data and break it into a FILES MultiValueDict and a POST
+ MultiValueDict.
+
+ Returns a tuple containing the POST and FILES dictionary, respectively.
+ """
+ # We have to import QueryDict down here to avoid a circular import.
+ from django.http import QueryDict
+
+ encoding = self._encoding
+ handlers = self._upload_handlers
+
+ limited_input_data = LimitBytes(self._input_data, self._content_length)
+
+ # See if the handler will want to take care of the parsing.
+ # This allows overriding everything if somebody wants it.
+ for handler in handlers:
+ result = handler.handle_raw_input(limited_input_data,
+ self._meta,
+ self._content_length,
+ self._boundary,
+ encoding)
+ if result is not None:
+ return result[0], result[1]
+
+ # Create the data structures to be used later.
+ self._post = QueryDict('', mutable=True)
+ self._files = MultiValueDict()
+
+ # Instantiate the parser and stream:
+ stream = LazyStream(ChunkIter(limited_input_data, self._chunk_size))
+
+ # Whether or not to signal a file-completion at the beginning of the loop.
+ old_field_name = None
+ counters = [0] * len(handlers)
+
+ try:
+ for item_type, meta_data, field_stream in Parser(stream, self._boundary):
+ if old_field_name:
+ # We run this at the beginning of the next loop
+ # since we cannot be sure a file is complete until
+ # we hit the next boundary/part of the multipart content.
+ self.handle_file_complete(old_field_name, counters)
+
+ try:
+ disposition = meta_data['content-disposition'][1]
+ field_name = disposition['name'].strip()
+ except (KeyError, IndexError, AttributeError):
+ continue
+
+ transfer_encoding = meta_data.get('content-transfer-encoding')
+ field_name = force_unicode(field_name, encoding, errors='replace')
+
+ if item_type == FIELD:
+ # This is a post field, we can just set it in the post
+ if transfer_encoding == 'base64':
+ raw_data = field_stream.read()
+ try:
+ data = str(raw_data).decode('base64')
+ except:
+ data = raw_data
+ else:
+ data = field_stream.read()
+
+ self._post.appendlist(field_name,
+ force_unicode(data, encoding, errors='replace'))
+ elif item_type == FILE:
+ # This is a file, use the handler...
+ file_successful = True
+ file_name = disposition.get('filename')
+ if not file_name:
+ continue
+ file_name = force_unicode(file_name, encoding, errors='replace')
+ file_name = self.IE_sanitize(unescape_entities(file_name))
+
+ content_type = meta_data.get('content-type', ('',))[0].strip()
+ try:
+ charset = meta_data.get('content-type', (0,{}))[1].get('charset', None)
+ except:
+ charset = None
+
+ try:
+ content_length = int(meta_data.get('content-length')[0])
+ except (IndexError, TypeError, ValueError):
+ content_length = None
+
+ counters = [0] * len(handlers)
+ try:
+ for handler in handlers:
+ try:
+ handler.new_file(field_name, file_name,
+ content_type, content_length,
+ charset)
+ except StopFutureHandlers:
+ break
+
+ for chunk in field_stream:
+ if transfer_encoding == 'base64':
+ # We only special-case base64 transfer encoding
+ try:
+ chunk = str(chunk).decode('base64')
+ except Exception, e:
+ # Since this is only a chunk, any error is an unfixable error.
+ raise MultiPartParserError("Could not decode base64 data: %r" % e)
+
+ for i, handler in enumerate(handlers):
+ chunk_length = len(chunk)
+ chunk = handler.receive_data_chunk(chunk,
+ counters[i])
+ counters[i] += chunk_length
+ if chunk is None:
+ # If the chunk received by the handler is None, then don't continue.
+ break
+
+ except SkipFile, e:
+ file_successful = False
+ # Just use up the rest of this file...
+ exhaust(field_stream)
+ else:
+ # Handle file upload completions on next iteration.
+ old_field_name = field_name
+ else:
+ # If this is neither a FIELD or a FILE, just exhaust the stream.
+ exhaust(stream)
+ except StopUpload, e:
+ if not e.connection_reset:
+ exhaust(limited_input_data)
+ else:
+ # Make sure that the request data is all fed
+ exhaust(limited_input_data)
+
+ # Signal that the upload has completed.
+ for handler in handlers:
+ retval = handler.upload_complete()
+ if retval:
+ break
+
+ return self._post, self._files
+
+ def handle_file_complete(self, old_field_name, counters):
+ """
+ Handle all the signalling that takes place when a file is complete.
+ """
+ for i, handler in enumerate(self._upload_handlers):
+ file_obj = handler.file_complete(counters[i])
+ if file_obj:
+ # If it returns a file object, then set the files dict.
+ self._files.appendlist(force_unicode(old_field_name,
+ self._encoding,
+ errors='replace'),
+ file_obj)
+ break
+
+ def IE_sanitize(self, filename):
+ """Cleanup filename from Internet Explorer full paths."""
+ return filename and filename[filename.rfind("\\")+1:].strip()
+
+class LazyStream(object):
+ """
+ The LazyStream wrapper allows one to get and "unget" bytes from a stream.
+
+ Given a producer object (an iterator that yields bytestrings), the
+ LazyStream object will support iteration, reading, and keeping a "look-back"
+ variable in case you need to "unget" some bytes.
+ """
+ def __init__(self, producer, length=None):
+ """
+ Every LazyStream must have a producer when instantiated.
+
+ A producer is an iterable that returns a string each time it
+ is called.
+ """
+ self._producer = producer
+ self._empty = False
+ self._leftover = ''
+ self.length = length
+ self._position = 0
+ self._remaining = length
+
+ # These fields are to do sanity checking to make sure we don't
+ # have infinite loops getting/ungetting from the stream. The
+ # purpose overall is to raise an exception if we perform lots
+ # of stream get/unget gymnastics without getting
+ # anywhere. Naturally this is not sound, but most probably
+ # would indicate a bug if the exception is raised.
+
+ # largest position tell us how far this lazystream has ever
+ # been advanced
+ self._largest_position = 0
+
+ # "modifications since" will start at zero and increment every
+ # time the position is modified but a new largest position is
+ # not achieved.
+ self._modifications_since = 0
+
+ def tell(self):
+ return self.position
+
+ def read(self, size=None):
+ def parts():
+ remaining = (size is not None and [size] or [self._remaining])[0]
+ # do the whole thing in one shot if no limit was provided.
+ if remaining is None:
+ yield ''.join(self)
+ return
+
+ # otherwise do some bookkeeping to return exactly enough
+ # of the stream and stashing any extra content we get from
+ # the producer
+ while remaining != 0:
+ assert remaining > 0, 'remaining bytes to read should never go negative'
+
+ chunk = self.next()
+
+ emitting = chunk[:remaining]
+ self.unget(chunk[remaining:])
+ remaining -= len(emitting)
+ yield emitting
+
+ out = ''.join(parts())
+ return out
+
+ def next(self):
+ """
+ Used when the exact number of bytes to read is unimportant.
+
+ This procedure just returns whatever is chunk is conveniently returned
+ from the iterator instead. Useful to avoid unnecessary bookkeeping if
+ performance is an issue.
+ """
+ if self._leftover:
+ output = self._leftover
+ self._leftover = ''
+ else:
+ output = self._producer.next()
+ self.position += len(output)
+ return output
+
+ def close(self):
+ """
+ Used to invalidate/disable this lazy stream.
+
+ Replaces the producer with an empty list. Any leftover bytes that have
+ already been read will still be reported upon read() and/or next().
+ """
+ self._producer = []
+
+ def __iter__(self):
+ return self
+
+ def unget(self, bytes):
+ """
+ Places bytes back onto the front of the lazy stream.
+
+ Future calls to read() will return those bytes first. The
+ stream position and thus tell() will be rewound.
+ """
+ self.position -= len(bytes)
+ self._leftover = ''.join([bytes, self._leftover])
+
+ def _set_position(self, value):
+ if value > self._largest_position:
+ self._modifications_since = 0
+ self._largest_position = value
+ else:
+ self._modifications_since += 1
+ if self._modifications_since > 500:
+ raise SuspiciousOperation(
+ "The multipart parser got stuck, which shouldn't happen with"
+ " normal uploaded files. Check for malicious upload activity;"
+ " if there is none, report this to the Django developers."
+ )
+
+ self._position = value
+
+ position = property(lambda self: self._position, _set_position)
+
+class ChunkIter(object):
+ """
+ An iterable that will yield chunks of data. Given a file-like object as the
+ constructor, this object will yield chunks of read operations from that
+ object.
+ """
+ def __init__(self, flo, chunk_size=64 * 1024):
+ self.flo = flo
+ self.chunk_size = chunk_size
+
+ def next(self):
+ try:
+ data = self.flo.read(self.chunk_size)
+ except InputStreamExhausted:
+ raise StopIteration()
+ if data:
+ return data
+ else:
+ raise StopIteration()
+
+ def __iter__(self):
+ return self
+
+class LimitBytes(object):
+ """ Limit bytes for a file object. """
+ def __init__(self, fileobject, length):
+ self._file = fileobject
+ self.remaining = length
+
+ def read(self, num_bytes=None):
+ """
+ Read data from the underlying file.
+ If you ask for too much or there isn't anything left,
+ this will raise an InputStreamExhausted error.
+ """
+ if self.remaining <= 0:
+ raise InputStreamExhausted()
+ if num_bytes is None:
+ num_bytes = self.remaining
+ else:
+ num_bytes = min(num_bytes, self.remaining)
+ self.remaining -= num_bytes
+ return self._file.read(num_bytes)
+
+class InterBoundaryIter(object):
+ """
+ A Producer that will iterate over boundaries.
+ """
+ def __init__(self, stream, boundary):
+ self._stream = stream
+ self._boundary = boundary
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ try:
+ return LazyStream(BoundaryIter(self._stream, self._boundary))
+ except InputStreamExhausted:
+ raise StopIteration()
+
+class BoundaryIter(object):
+ """
+ A Producer that is sensitive to boundaries.
+
+ Will happily yield bytes until a boundary is found. Will yield the bytes
+ before the boundary, throw away the boundary bytes themselves, and push the
+ post-boundary bytes back on the stream.
+
+ The future calls to .next() after locating the boundary will raise a
+ StopIteration exception.
+ """
+
+ def __init__(self, stream, boundary):
+ self._stream = stream
+ self._boundary = boundary
+ self._done = False
+ # rollback an additional six bytes because the format is like
+ # this: CRLF<boundary>[--CRLF]
+ self._rollback = len(boundary) + 6
+
+ # Try to use mx fast string search if available. Otherwise
+ # use Python find. Wrap the latter for consistency.
+ unused_char = self._stream.read(1)
+ if not unused_char:
+ raise InputStreamExhausted()
+ self._stream.unget(unused_char)
+ try:
+ from mx.TextTools import FS
+ self._fs = FS(boundary).find
+ except ImportError:
+ self._fs = lambda data: data.find(boundary)
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ if self._done:
+ raise StopIteration()
+
+ stream = self._stream
+ rollback = self._rollback
+
+ bytes_read = 0
+ chunks = []
+ for bytes in stream:
+ bytes_read += len(bytes)
+ chunks.append(bytes)
+ if bytes_read > rollback:
+ break
+ if not bytes:
+ break
+ else:
+ self._done = True
+
+ if not chunks:
+ raise StopIteration()
+
+ chunk = ''.join(chunks)
+ boundary = self._find_boundary(chunk, len(chunk) < self._rollback)
+
+ if boundary:
+ end, next = boundary
+ stream.unget(chunk[next:])
+ self._done = True
+ return chunk[:end]
+ else:
+ # make sure we dont treat a partial boundary (and
+ # its separators) as data
+ if not chunk[:-rollback]:# and len(chunk) >= (len(self._boundary) + 6):
+ # There's nothing left, we should just return and mark as done.
+ self._done = True
+ return chunk
+ else:
+ stream.unget(chunk[-rollback:])
+ return chunk[:-rollback]
+
+ def _find_boundary(self, data, eof = False):
+ """
+ Finds a multipart boundary in data.
+
+ Should no boundry exist in the data None is returned instead. Otherwise
+ a tuple containing the indices of the following are returned:
+
+ * the end of current encapsulation
+ * the start of the next encapsulation
+ """
+ index = self._fs(data)
+ if index < 0:
+ return None
+ else:
+ end = index
+ next = index + len(self._boundary)
+ data_len = len(data) - 1
+ # backup over CRLF
+ if data[max(0,end-1)] == '\n':
+ end -= 1
+ if data[max(0,end-1)] == '\r':
+ end -= 1
+ # skip over --CRLF
+ #if data[min(data_len,next)] == '-':
+ # next += 1
+ #if data[min(data_len,next)] == '-':
+ # next += 1
+ #if data[min(data_len,next)] == '\r':
+ # next += 1
+ #if data[min(data_len,next)] == '\n':
+ # next += 1
+ return end, next
+
+def exhaust(stream_or_iterable):
+ """
+ Completely exhausts an iterator or stream.
+
+ Raise a MultiPartParserError if the argument is not a stream or an iterable.
+ """
+ iterator = None
+ try:
+ iterator = iter(stream_or_iterable)
+ except TypeError:
+ iterator = ChunkIter(stream_or_iterable, 16384)
+
+ if iterator is None:
+ raise MultiPartParserError('multipartparser.exhaust() was passed a non-iterable or stream parameter')
+
+ for __ in iterator:
+ pass
+
+def parse_boundary_stream(stream, max_header_size):
+ """
+ Parses one and exactly one stream that encapsulates a boundary.
+ """
+ # Stream at beginning of header, look for end of header
+ # and parse it if found. The header must fit within one
+ # chunk.
+ chunk = stream.read(max_header_size)
+
+ # 'find' returns the top of these four bytes, so we'll
+ # need to munch them later to prevent them from polluting
+ # the payload.
+ header_end = chunk.find('\r\n\r\n')
+
+ def _parse_header(line):
+ main_value_pair, params = parse_header(line)
+ try:
+ name, value = main_value_pair.split(':', 1)
+ except:
+ raise ValueError("Invalid header: %r" % line)
+ return name, (value, params)
+
+ if header_end == -1:
+ # we find no header, so we just mark this fact and pass on
+ # the stream verbatim
+ stream.unget(chunk)
+ return (RAW, {}, stream)
+
+ header = chunk[:header_end]
+
+ # here we place any excess chunk back onto the stream, as
+ # well as throwing away the CRLFCRLF bytes from above.
+ stream.unget(chunk[header_end + 4:])
+
+ TYPE = RAW
+ outdict = {}
+
+ # Eliminate blank lines
+ for line in header.split('\r\n'):
+ # This terminology ("main value" and "dictionary of
+ # parameters") is from the Python docs.
+ try:
+ name, (value, params) = _parse_header(line)
+ except:
+ continue
+
+ if name == 'content-disposition':
+ TYPE = FIELD
+ if params.get('filename'):
+ TYPE = FILE
+
+ outdict[name] = value, params
+
+ if TYPE == RAW:
+ stream.unget(chunk)
+
+ return (TYPE, outdict, stream)
+
+class Parser(object):
+ def __init__(self, stream, boundary):
+ self._stream = stream
+ self._separator = '--' + boundary
+
+ def __iter__(self):
+ boundarystream = InterBoundaryIter(self._stream, self._separator)
+ for sub_stream in boundarystream:
+ # Iterate over each part
+ yield parse_boundary_stream(sub_stream, 1024)
+
+def parse_header(line):
+ """ Parse the header into a key-value. """
+ plist = _parse_header_params(';' + line)
+ key = plist.pop(0).lower()
+ pdict = {}
+ for p in plist:
+ i = p.find('=')
+ if i >= 0:
+ name = p[:i].strip().lower()
+ value = p[i+1:].strip()
+ if len(value) >= 2 and value[0] == value[-1] == '"':
+ value = value[1:-1]
+ value = value.replace('\\\\', '\\').replace('\\"', '"')
+ pdict[name] = value
+ return key, pdict
+
+def _parse_header_params(s):
+ plist = []
+ while s[:1] == ';':
+ s = s[1:]
+ end = s.find(';')
+ while end > 0 and s.count('"', 0, end) % 2:
+ end = s.find(';', end + 1)
+ if end < 0:
+ end = len(s)
+ f = s[:end]
+ plist.append(f.strip())
+ s = s[end:]
+ return plist
diff --git a/django/middleware/http.py b/django/middleware/http.py
index 2ef46c6b61..53b65c1034 100644
--- a/django/middleware/http.py
+++ b/django/middleware/http.py
@@ -19,14 +19,14 @@ class ConditionalGetMiddleware(object):
# Setting the status is enough here. The response handling path
# automatically removes content for this status code (in
# http.conditional_content_removal()).
- response.status = 304
+ response.status_code = 304
if response.has_header('Last-Modified'):
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None)
if if_modified_since == response['Last-Modified']:
# Setting the status code is enough here (same reasons as
# above).
- response.status = 304
+ response.status_code = 304
return response
diff --git a/django/newforms/fields.py b/django/newforms/fields.py
index dfe46a2e3e..1feef31ee0 100644
--- a/django/newforms/fields.py
+++ b/django/newforms/fields.py
@@ -7,6 +7,11 @@ import datetime
import os
import re
import time
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
# Python 2.3 fallbacks
try:
from decimal import Decimal, DecimalException
@@ -416,9 +421,9 @@ except ImportError:
class UploadedFile(StrAndUnicode):
"A wrapper for files uploaded in a FileField"
- def __init__(self, filename, content):
+ def __init__(self, filename, data):
self.filename = filename
- self.content = content
+ self.data = data
def __unicode__(self):
"""
@@ -444,15 +449,34 @@ class FileField(Field):
return None
elif not data and initial:
return initial
+
+ if isinstance(data, dict):
+ # We warn once, then support both ways below.
+ import warnings
+ warnings.warn(
+ message = "Representing uploaded files as dictionaries is"\
+ " deprecated. Use django.core.files.SimpleUploadedFile "\
+ " instead.",
+ category = DeprecationWarning,
+ stacklevel = 2
+ )
+
try:
- f = UploadedFile(data['filename'], data['content'])
- except TypeError:
+ file_name = data.file_name
+ file_size = data.file_size
+ except AttributeError:
+ try:
+ file_name = data.get('filename')
+ file_size = bool(data['content'])
+ except (AttributeError, KeyError):
+ raise ValidationError(self.error_messages['invalid'])
+
+ if not file_name:
raise ValidationError(self.error_messages['invalid'])
- except KeyError:
- raise ValidationError(self.error_messages['missing'])
- if not f.content:
+ if not file_size:
raise ValidationError(self.error_messages['empty'])
- return f
+
+ return UploadedFile(file_name, data)
class ImageField(FileField):
default_error_messages = {
@@ -470,15 +494,31 @@ class ImageField(FileField):
elif not data and initial:
return initial
from PIL import Image
- from cStringIO import StringIO
+
+ # We need to get a file object for PIL. We might have a path or we might
+ # have to read the data into memory.
+ if hasattr(data, 'temporary_file_path'):
+ file = data.temporary_file_path()
+ else:
+ if hasattr(data, 'read'):
+ file = StringIO(data.read())
+ else:
+ file = StringIO(data['content'])
+
try:
# load() is the only method that can spot a truncated JPEG,
# but it cannot be called sanely after verify()
- trial_image = Image.open(StringIO(f.content))
+ trial_image = Image.open(file)
trial_image.load()
+
+ # Since we're about to use the file again we have to reset the
+ # file object if possible.
+ if hasattr(file, 'reset'):
+ file.reset()
+
# verify() is the only method that can spot a corrupt PNG,
# but it must be called immediately after the constructor
- trial_image = Image.open(StringIO(f.content))
+ trial_image = Image.open(file)
trial_image.verify()
except Exception: # Python Imaging Library doesn't recognize it as an image
raise ValidationError(self.error_messages['invalid_image'])
@@ -535,13 +575,17 @@ class BooleanField(Field):
def clean(self, value):
"""Returns a Python boolean object."""
- super(BooleanField, self).clean(value)
# Explicitly check for the string 'False', which is what a hidden field
# will submit for False. Because bool("True") == True, we don't need to
# handle that explicitly.
if value == 'False':
- return False
- return bool(value)
+ value = False
+ else:
+ value = bool(value)
+ super(BooleanField, self).clean(value)
+ if not value and self.required:
+ raise ValidationError(self.error_messages['required'])
+ return value
class NullBooleanField(BooleanField):
"""
diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
index fc8727185f..ee838d234a 100644
--- a/django/oldforms/__init__.py
+++ b/django/oldforms/__init__.py
@@ -680,18 +680,27 @@ class FileUploadField(FormField):
self.field_name, self.is_required = field_name, is_required
self.validator_list = [self.isNonEmptyFile] + validator_list
- def isNonEmptyFile(self, field_data, all_data):
+ def isNonEmptyFile(self, new_data, all_data):
+ if hasattr(new_data, 'upload_errors'):
+ upload_errors = new_data.upload_errors()
+ if upload_errors:
+ raise validators.CriticalValidationError, upload_errors
try:
- content = field_data['content']
- except TypeError:
- raise validators.CriticalValidationError, ugettext("No file was submitted. Check the encoding type on the form.")
- if not content:
+ file_size = new_data.file_size
+ except AttributeError:
+ file_size = len(new_data['content'])
+ if not file_size:
raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
def render(self, data):
return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \
(self.get_id(), self.__class__.__name__, self.field_name))
+ def prepare(self, new_data):
+ if hasattr(new_data, 'upload_errors'):
+ upload_errors = new_data.upload_errors()
+ new_data[self.field_name] = { '_file_upload_error': upload_errors }
+
def html2python(data):
if data is None:
raise EmptyValue
diff --git a/django/test/client.py b/django/test/client.py
index a15876e6f9..87731043a7 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -1,7 +1,11 @@
import urllib
import sys
import os
-from cStringIO import StringIO
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
from django.conf import settings
from django.contrib.auth import authenticate, login
from django.core.handlers.base import BaseHandler
@@ -19,6 +23,27 @@ from django.utils.itercompat import is_iterable
BOUNDARY = 'BoUnDaRyStRiNg'
MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
+
+class FakePayload(object):
+ """
+ A wrapper around StringIO that restricts what can be read since data from
+ the network can't be seeked and cannot be read outside of its content
+ length. This makes sure that views can't do anything under the test client
+ that wouldn't work in Real Life.
+ """
+ def __init__(self, content):
+ self.__content = StringIO(content)
+ self.__len = len(content)
+
+ def read(self, num_bytes=None):
+ if num_bytes is None:
+ num_bytes = self.__len or 1
+ assert self.__len >= num_bytes, "Cannot read more than the available bytes from the HTTP incoming data."
+ content = self.__content.read(num_bytes)
+ self.__len -= num_bytes
+ return content
+
+
class ClientHandler(BaseHandler):
"""
A HTTP Handler that can be used for testing purposes.
@@ -39,7 +64,7 @@ class ClientHandler(BaseHandler):
request = WSGIRequest(environ)
response = self.get_response(request)
- # Apply response middleware
+ # Apply response middleware.
for middleware_method in self._response_middleware:
response = middleware_method(request, response)
response = self.apply_response_fixes(request, response)
@@ -49,14 +74,15 @@ class ClientHandler(BaseHandler):
return response
def store_rendered_templates(store, signal, sender, template, context):
- "A utility function for storing templates and contexts that are rendered"
+ """
+ Stores templates and contexts that are rendered.
+ """
store.setdefault('template',[]).append(template)
store.setdefault('context',[]).append(context)
def encode_multipart(boundary, data):
"""
- A simple method for encoding multipart POST data from a dictionary of
- form values.
+ Encodes multipart POST data from a dictionary of form values.
The key will be used as the form data name; the value will be transmitted
as content. If the value is a file, the contents of the file will be sent
@@ -68,7 +94,8 @@ def encode_multipart(boundary, data):
if isinstance(value, file):
lines.extend([
'--' + boundary,
- 'Content-Disposition: form-data; name="%s"; filename="%s"' % (to_str(key), to_str(os.path.basename(value.name))),
+ 'Content-Disposition: form-data; name="%s"; filename="%s"' \
+ % (to_str(key), to_str(os.path.basename(value.name))),
'Content-Type: application/octet-stream',
'',
value.read()
@@ -122,13 +149,14 @@ class Client:
def store_exc_info(self, *args, **kwargs):
"""
- Utility method that can be used to store exceptions when they are
- generated by a view.
+ Stores exceptions when they are generated by a view.
"""
self.exc_info = sys.exc_info()
def _session(self):
- "Obtain the current session variables"
+ """
+ Obtains the current session variables.
+ """
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
@@ -144,7 +172,6 @@ class Client:
Assumes defaults for the query environment, which can be overridden
using the arguments to the request.
"""
-
environ = {
'HTTP_COOKIE': self.cookies,
'PATH_INFO': '/',
@@ -158,13 +185,13 @@ class Client:
environ.update(self.defaults)
environ.update(request)
- # Curry a data dictionary into an instance of
- # the template renderer callback function
+ # Curry a data dictionary into an instance of the template renderer
+ # callback function.
data = {}
on_template_render = curry(store_rendered_templates, data)
dispatcher.connect(on_template_render, signal=signals.template_rendered)
- # Capture exceptions created by the handler
+ # Capture exceptions created by the handler.
dispatcher.connect(self.store_exc_info, signal=got_request_exception)
try:
@@ -187,14 +214,14 @@ class Client:
exc_info = self.exc_info
self.exc_info = None
raise exc_info[1], None, exc_info[2]
-
- # Save the client and request that stimulated the response
+
+ # Save the client and request that stimulated the response.
response.client = self
response.request = request
- # Add any rendered template detail to the response
+ # Add any rendered template detail to the response.
# If there was only one template rendered (the most likely case),
- # flatten the list to a single element
+ # flatten the list to a single element.
for detail in ('template', 'context'):
if data.get(detail):
if len(data[detail]) == 1:
@@ -204,14 +231,16 @@ class Client:
else:
setattr(response, detail, None)
- # Update persistent cookie data
+ # Update persistent cookie data.
if response.cookies:
self.cookies.update(response.cookies)
return response
def get(self, path, data={}, **extra):
- "Request a response from the server using GET."
+ """
+ Requests a response from the server using GET.
+ """
r = {
'CONTENT_LENGTH': None,
'CONTENT_TYPE': 'text/html; charset=utf-8',
@@ -224,8 +253,9 @@ class Client:
return self.request(**r)
def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra):
- "Request a response from the server using POST."
-
+ """
+ Requests a response from the server using POST.
+ """
if content_type is MULTIPART_CONTENT:
post_data = encode_multipart(BOUNDARY, data)
else:
@@ -236,37 +266,43 @@ class Client:
'CONTENT_TYPE': content_type,
'PATH_INFO': urllib.unquote(path),
'REQUEST_METHOD': 'POST',
- 'wsgi.input': StringIO(post_data),
+ 'wsgi.input': FakePayload(post_data),
}
r.update(extra)
return self.request(**r)
def login(self, **credentials):
- """Set the Client to appear as if it has sucessfully logged into a site.
+ """
+ Sets the Client to appear as if it has successfully logged into a site.
Returns True if login is possible; False if the provided credentials
are incorrect, or the user is inactive, or if the sessions framework is
not available.
"""
user = authenticate(**credentials)
- if user and user.is_active and 'django.contrib.sessions' in settings.INSTALLED_APPS:
+ if user and user.is_active \
+ and 'django.contrib.sessions' in settings.INSTALLED_APPS:
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
- # Create a fake request to store login details
+ # Create a fake request to store login details.
request = HttpRequest()
request.session = engine.SessionStore()
login(request, user)
- # Set the cookie to represent the session
- self.cookies[settings.SESSION_COOKIE_NAME] = request.session.session_key
- self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
- self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
- self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
- self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
- self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
+ # Set the cookie to represent the session.
+ session_cookie = settings.SESSION_COOKIE_NAME
+ self.cookies[session_cookie] = request.session.session_key
+ cookie_data = {
+ 'max-age': None,
+ 'path': '/',
+ 'domain': settings.SESSION_COOKIE_DOMAIN,
+ 'secure': settings.SESSION_COOKIE_SECURE or None,
+ 'expires': None,
+ }
+ self.cookies[session_cookie].update(cookie_data)
- # Save the session values
+ # Save the session values.
request.session.save()
return True
@@ -274,7 +310,8 @@ class Client:
return False
def logout(self):
- """Removes the authenticated user's cookies.
+ """
+ Removes the authenticated user's cookies.
Causes the authenticated user to be logged out.
"""
diff --git a/django/test/testcases.py b/django/test/testcases.py
index ee83b960a6..3bad3995bb 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -4,10 +4,12 @@ from urlparse import urlsplit, urlunsplit
from django.http import QueryDict
from django.db import transaction
+from django.conf import settings
from django.core import mail
from django.core.management import call_command
from django.test import _doctest as doctest
from django.test.client import Client
+from django.core.urlresolvers import clear_url_caches
normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
@@ -54,6 +56,8 @@ class TestCase(unittest.TestCase):
* Flushing the database.
* If the Test Case class has a 'fixtures' member, installing the
named fixtures.
+ * If the Test Case class has a 'urls' member, replace the
+ ROOT_URLCONF with it.
* Clearing the mail test outbox.
"""
call_command('flush', verbosity=0, interactive=False)
@@ -61,6 +65,10 @@ class TestCase(unittest.TestCase):
# We have to use this slightly awkward syntax due to the fact
# that we're using *args and **kwargs together.
call_command('loaddata', *self.fixtures, **{'verbosity': 0})
+ if hasattr(self, 'urls'):
+ self._old_root_urlconf = settings.ROOT_URLCONF
+ settings.ROOT_URLCONF = self.urls
+ clear_url_caches()
mail.outbox = []
def __call__(self, result=None):
@@ -79,6 +87,23 @@ class TestCase(unittest.TestCase):
result.addError(self, sys.exc_info())
return
super(TestCase, self).__call__(result)
+ try:
+ self._post_teardown()
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except Exception:
+ import sys
+ result.addError(self, sys.exc_info())
+ return
+
+ def _post_teardown(self):
+ """ Performs any post-test things. This includes:
+
+ * Putting back the original ROOT_URLCONF if it was changed.
+ """
+ if hasattr(self, '_old_root_urlconf'):
+ settings.ROOT_URLCONF = self._old_root_urlconf
+ clear_url_caches()
def assertRedirects(self, response, expected_url, status_code=302,
target_status_code=200, host=None):
diff --git a/django/utils/daemonize.py b/django/utils/daemonize.py
index f0c2f684bd..68e5392d89 100644
--- a/django/utils/daemonize.py
+++ b/django/utils/daemonize.py
@@ -2,7 +2,8 @@ import os
import sys
if os.name == 'posix':
- def become_daemon(our_home_dir='.', out_log='/dev/null', err_log='/dev/null'):
+ def become_daemon(our_home_dir='.', out_log='/dev/null',
+ err_log='/dev/null', umask=022):
"Robustly turn into a UNIX daemon, running in our_home_dir."
# First fork
try:
@@ -13,7 +14,7 @@ if os.name == 'posix':
sys.exit(1)
os.setsid()
os.chdir(our_home_dir)
- os.umask(0)
+ os.umask(umask)
# Second fork
try:
@@ -32,13 +33,13 @@ if os.name == 'posix':
# Set custom file descriptors so that they get proper buffering.
sys.stdout, sys.stderr = so, se
else:
- def become_daemon(our_home_dir='.', out_log=None, err_log=None):
+ def become_daemon(our_home_dir='.', out_log=None, err_log=None, umask=022):
"""
If we're not running under a POSIX system, just simulate the daemon
mode by doing redirections and directory changing.
"""
os.chdir(our_home_dir)
- os.umask(0)
+ os.umask(umask)
sys.stdin.close()
sys.stdout.close()
sys.stderr.close()
diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py
index 21a72f2d1e..f27bc1cfff 100644
--- a/django/utils/datastructures.py
+++ b/django/utils/datastructures.py
@@ -332,17 +332,49 @@ class DotExpandedDict(dict):
except TypeError: # Special-case if current isn't a dict.
current = {bits[-1]: v}
-class FileDict(dict):
+class ImmutableList(tuple):
"""
- A dictionary used to hold uploaded file contents. The only special feature
- here is that repr() of this object won't dump the entire contents of the
- file to the output. A handy safeguard for a large file upload.
+ A tuple-like object that raises useful errors when it is asked to mutate.
+
+ Example::
+
+ >>> a = ImmutableList(range(5), warning="You cannot mutate this.")
+ >>> a[3] = '4'
+ Traceback (most recent call last):
+ ...
+ AttributeError: You cannot mutate this.
"""
- def __repr__(self):
- if 'content' in self:
- d = dict(self, content='<omitted>')
- return dict.__repr__(d)
- return dict.__repr__(self)
+
+ def __new__(cls, *args, **kwargs):
+ if 'warning' in kwargs:
+ warning = kwargs['warning']
+ del kwargs['warning']
+ else:
+ warning = 'ImmutableList object is immutable.'
+ self = tuple.__new__(cls, *args, **kwargs)
+ self.warning = warning
+ return self
+
+ def complain(self, *wargs, **kwargs):
+ if isinstance(self.warning, Exception):
+ raise self.warning
+ else:
+ raise AttributeError, self.warning
+
+ # All list mutation functions complain.
+ __delitem__ = complain
+ __delslice__ = complain
+ __iadd__ = complain
+ __imul__ = complain
+ __setitem__ = complain
+ __setslice__ = complain
+ append = complain
+ extend = complain
+ insert = complain
+ pop = complain
+ remove = complain
+ sort = complain
+ reverse = complain
class DictWrapper(dict):
"""
diff --git a/django/utils/text.py b/django/utils/text.py
index aa190c8c4f..3686a454a8 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -3,6 +3,7 @@ from django.conf import settings
from django.utils.encoding import force_unicode
from django.utils.functional import allow_lazy
from django.utils.translation import ugettext_lazy
+from htmlentitydefs import name2codepoint
# Capitalizes the first letter of a string.
capfirst = lambda x: x and force_unicode(x)[0].upper() + force_unicode(x)[1:]
@@ -222,3 +223,26 @@ def smart_split(text):
yield bit
smart_split = allow_lazy(smart_split, unicode)
+def _replace_entity(match):
+ text = match.group(1)
+ if text[0] == u'#':
+ text = text[1:]
+ try:
+ if text[0] in u'xX':
+ c = int(text[1:], 16)
+ else:
+ c = int(text)
+ return unichr(c)
+ except ValueError:
+ return match.group(0)
+ else:
+ try:
+ return unichr(name2codepoint[text])
+ except (ValueError, KeyError):
+ return match.group(0)
+
+_entity_re = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
+
+def unescape_entities(text):
+ return _entity_re.sub(_replace_entity, text)
+unescape_entities = allow_lazy(unescape_entities, unicode)
diff --git a/django/utils/tree.py b/django/utils/tree.py
index a62a4ae6c3..5dd9f56799 100644
--- a/django/utils/tree.py
+++ b/django/utils/tree.py
@@ -29,6 +29,22 @@ class Node(object):
self.subtree_parents = []
self.negated = negated
+ # We need this because of django.db.models.query_utils.Q. Q. __init__() is
+ # problematic, but it is a natural Node subclass in all other respects.
+ def _new_instance(cls, children=None, connector=None, negated=False):
+ """
+ This is called to create a new instance of this class when we need new
+ Nodes (or subclasses) in the internal code in this class. Normally, it
+ just shadows __init__(). However, subclasses with an __init__ signature
+ that is not an extension of Node.__init__ might need to implement this
+ method to allow a Node to create a new instance of them (if they have
+ any extra setting up to do).
+ """
+ obj = Node(children, connector, negated)
+ obj.__class__ = cls
+ return obj
+ _new_instance = classmethod(_new_instance)
+
def __str__(self):
if self.negated:
return '(NOT (%s: %s))' % (self.connector, ', '.join([str(c) for c
@@ -82,7 +98,8 @@ class Node(object):
else:
self.children.append(node)
else:
- obj = Node(self.children, self.connector, self.negated)
+ obj = self._new_instance(self.children, self.connector,
+ self.negated)
self.connector = conn_type
self.children = [obj, node]
@@ -96,7 +113,8 @@ class Node(object):
Interpreting the meaning of this negate is up to client code. This
method is useful for implementing "not" arrangements.
"""
- self.children = [Node(self.children, self.connector, not self.negated)]
+ self.children = [self._new_instance(self.children, self.connector,
+ not self.negated)]
self.connector = self.default
def start_subtree(self, conn_type):
@@ -108,12 +126,13 @@ class Node(object):
if len(self.children) == 1:
self.connector = conn_type
elif self.connector != conn_type:
- self.children = [Node(self.children, self.connector, self.negated)]
+ self.children = [self._new_instance(self.children, self.connector,
+ self.negated)]
self.connector = conn_type
self.negated = False
- self.subtree_parents.append(Node(self.children, self.connector,
- self.negated))
+ self.subtree_parents.append(self.__class__(self.children,
+ self.connector, self.negated))
self.connector = self.default
self.negated = False
self.children = []
@@ -126,7 +145,7 @@ class Node(object):
the current instances state to be the parent.
"""
obj = self.subtree_parents.pop()
- node = Node(self.children, self.connector)
+ node = self.__class__(self.children, self.connector)
self.connector = obj.connector
self.negated = obj.negated
self.children = obj.children
diff --git a/docs/apache_auth.txt b/docs/apache_auth.txt
index 859050e716..62fd191896 100644
--- a/docs/apache_auth.txt
+++ b/docs/apache_auth.txt
@@ -39,9 +39,10 @@ with the standard ``Auth*`` and ``Require`` directives::
example at the bottom of this note).
You'll also need to insert configuration directives that prevent Apache
- from trying to use other authentication modules. Depending on which other
- authentication modules you have loaded, you might need one or more of
- the following directives::
+ from trying to use other authentication modules, as well as specifying
+ the ``AuthUserFile`` directive and pointing it to ``/dev/null``. Depending
+ on which other authentication modules you have loaded, you might need one
+ or more of the following directives::
AuthBasicAuthoritative Off
AuthDefaultAuthoritative Off
@@ -65,6 +66,7 @@ with the standard ``Auth*`` and ``Require`` directives::
<Location /example/>
AuthType Basic
AuthName "example.com"
+ **AuthUserFile /dev/null**
**AuthBasicAuthoritative Off**
Require valid-user
diff --git a/docs/db-api.txt b/docs/db-api.txt
index f80d63797a..9a604bf320 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -443,6 +443,31 @@ This is roughly equivalent to::
Note, however, that the first of these will raise ``IndexError`` while the
second will raise ``DoesNotExist`` if no objects match the given criteria.
+Combining QuerySets
+-------------------
+
+If you have two ``QuerySet`` instances that act on the same model, you can
+combine them using ``&`` and ``|`` to get the items that are in both result
+sets or in either results set, respectively. For example::
+
+ Entry.objects.filter(pubdate__gte=date1) & \
+ Entry.objects.filter(headline__startswith="What")
+
+will combine the two queries into a single SQL query. Of course, in this case
+you could have achieved the same result using multiple filters on the same
+``QuerySet``, but sometimes the ability to combine individual ``QuerySet``
+instance is useful.
+
+Be careful, if you are using ``extra()`` to add custom handling to your
+``QuerySet`` however. All the ``extra()`` components are merged and the result
+may or may not make sense. If you are using custom SQL fragments in your
+``extra()`` calls, Django will not inspect these fragments to see if they need
+to be rewritten because of changes in the merged query. So test the effects
+carefully. Also realise that if you are combining two ``QuerySets`` with
+``|``, you cannot use ``extra(select=...)`` or ``extra(where=...)`` on *both*
+``QuerySets``. You can only use those calls on one or the other (Django will
+raise a ``ValueError`` if you try to use this incorrectly).
+
QuerySet methods that return new QuerySets
------------------------------------------
diff --git a/docs/flatpages.txt b/docs/flatpages.txt
index a538217642..f1a354c653 100644
--- a/docs/flatpages.txt
+++ b/docs/flatpages.txt
@@ -14,9 +14,14 @@ custom Django application.
A flatpage can use a custom template or a default, systemwide flatpage
template. It can be associated with one, or multiple, sites.
+**New in Django development version**
+
+The content field may optionally be left blank if you prefer to put your
+content in a custom template.
+
Here are some examples of flatpages on Django-powered sites:
- * http://www.chicagocrime.org/about/
+ * http://www.everyblock.com/about/
* http://www.lawrence.com/about/contact/
Installation
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 1bed4ee818..4e7b5c3096 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -2155,7 +2155,7 @@ still only creating one database table per child model at the database level.
When an abstract base class is created, Django makes any ``Meta`` inner class
you declared on the base class available as an attribute. If a child class
-does not declared its own ``Meta`` class, it will inherit the parent's
+does not declare its own ``Meta`` class, it will inherit the parent's
``Meta``. If the child wants to extend the parent's ``Meta`` class, it can
subclass it. For example::
diff --git a/docs/newforms.txt b/docs/newforms.txt
index 04e4c1aaa0..296fc04c85 100644
--- a/docs/newforms.txt
+++ b/docs/newforms.txt
@@ -805,12 +805,12 @@ ContactForm to include an ``ImageField`` called ``mugshot``, we
need to bind the file data containing the mugshot image::
# Bound form with an image field
+ >>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
- >>> file_data = {'mugshot': {'filename':'face.jpg'
- ... 'content': <file data>}}
+ >>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)}
>>> f = ContactFormWithMugshot(data, file_data)
In practice, you will usually specify ``request.FILES`` as the source
diff --git a/docs/request_response.txt b/docs/request_response.txt
index 866a697e31..54fc24df9e 100644
--- a/docs/request_response.txt
+++ b/docs/request_response.txt
@@ -80,19 +80,36 @@ All attributes except ``session`` should be considered read-only.
strings.
``FILES``
+
+ .. admonition:: Changed in Django development version
+
+ In previous versions of Django, ``request.FILES`` contained
+ simple ``dict`` objects representing uploaded files. This is
+ no longer true -- files are represented by ``UploadedFile``
+ objects as described below.
+
+ These ``UploadedFile`` objects will emulate the old-style ``dict``
+ interface, but this is deprecated and will be removed in the next
+ release of Django.
+
A dictionary-like object containing all uploaded files. Each key in
``FILES`` is the ``name`` from the ``<input type="file" name="" />``. Each
- value in ``FILES`` is a standard Python dictionary with the following three
- keys:
+ value in ``FILES`` is an ``UploadedFile`` object containing the following
+ attributes:
- * ``filename`` -- The name of the uploaded file, as a Python string.
- * ``content-type`` -- The content type of the uploaded file.
- * ``content`` -- The raw content of the uploaded file.
+ * ``read(num_bytes=None)`` -- Read a number of bytes from the file.
+ * ``file_name`` -- The name of the uploaded file.
+ * ``file_size`` -- The size, in bytes, of the uploaded file.
+ * ``chunk()`` -- A generator that yields sequential chunks of data.
+ See `File Uploads`_ for more information.
+
Note that ``FILES`` will only contain data if the request method was POST
and the ``<form>`` that posted to the request had
``enctype="multipart/form-data"``. Otherwise, ``FILES`` will be a blank
dictionary-like object.
+
+ .. _File Uploads: ../upload_handling/
``META``
A standard Python dictionary containing all available HTTP headers.
diff --git a/docs/settings.txt b/docs/settings.txt
index 3fe999d0d2..a68d2ff92f 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -279,7 +279,7 @@ Default: ``''`` (Empty string)
The database backend to use. The build-in database backends are
``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``,
-``'sqlite3'`` and ``'oracle'``.
+``'sqlite3'``, ``'oracle'``, and ``'oracle'``.
In the Django development version, you can use a database backend that doesn't
ship with Django by setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.
@@ -530,6 +530,43 @@ Default: ``'utf-8'``
The character encoding used to decode any files read from disk. This includes
template files and initial SQL data files.
+FILE_UPLOAD_HANDLERS
+--------------------
+
+**New in Django development version**
+
+Default::
+
+ ("django.core.files.fileuploadhandler.MemoryFileUploadHandler",
+ "django.core.files.fileuploadhandler.TemporaryFileUploadHandler",)
+
+A tuple of handlers to use for uploading. See `file uploads`_ for details.
+
+.. _file uploads: ../upload_handling/
+
+FILE_UPLOAD_MAX_MEMORY_SIZE
+---------------------------
+
+**New in Django development version**
+
+Default: ``2621440`` (i.e. 2.5 MB).
+
+The maximum size (in bytes) that an upload will be before it gets streamed to
+the file system. See `file uploads`_ for details.
+
+FILE_UPLOAD_TEMP_DIR
+--------------------
+
+**New in Django development version**
+
+Default: ``None``
+
+The directory to store data temporarily while uploading files. If ``None``,
+Django will use the standard temporary directory for the operating system. For
+example, this will default to '/tmp' on *nix-style operating systems.
+
+See `file uploads`_ for details.
+
FIXTURE_DIRS
-------------
diff --git a/docs/sitemaps.txt b/docs/sitemaps.txt
index eb749dda2f..6a16e61879 100644
--- a/docs/sitemaps.txt
+++ b/docs/sitemaps.txt
@@ -317,3 +317,14 @@ A more efficient solution, however, would be to call ``ping_google()`` from a
cron script, or some other scheduled task. The function makes an HTTP request
to Google's servers, so you may not want to introduce that network overhead
each time you call ``save()``.
+
+Pinging Google via `manage.py`
+------------------------------
+
+**New in Django development version**
+
+Once the sitemaps application is added to your project, you may also
+ping the Google server's through the command line manage.py interface::
+
+ python manage.py ping_google [/sitemap.xml]
+
diff --git a/docs/templates.txt b/docs/templates.txt
index 3a557c1476..04a46580c5 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -477,11 +477,11 @@ escaped when the template is written.
This means you would write ::
- {{ data|default:"3 &gt; 2" }}
+ {{ data|default:"3 &lt; 2" }}
...rather than ::
- {{ data|default:"3 > 2" }} <-- Bad! Don't do this.
+ {{ data|default:"3 < 2" }} <-- Bad! Don't do this.
This doesn't affect what happens to data coming from the variable itself.
The variable's contents are still automatically escaped, if necessary, because
diff --git a/docs/testing.txt b/docs/testing.txt
index befa6979af..0b18545efb 100644
--- a/docs/testing.txt
+++ b/docs/testing.txt
@@ -797,6 +797,37 @@ another test, or by the order of test execution.
.. _dumpdata documentation: ../django-admin/#dumpdata-appname-appname
.. _loaddata documentation: ../django-admin/#loaddata-fixture-fixture
+URLconf configuration
+~~~~~~~~~~~~~~~~~~~~~
+
+**New in Django development version**
+
+If your application provides views, you may want to include tests that
+use the test client to exercise those views. However, an end user is free
+to deploy the views in your application at any URL of their choosing.
+This means that your tests can't rely upon the fact that your views will
+be available at a particular URL.
+
+In order to provide a reliable URL space for your test,
+``django.test.TestCase`` provides the ability to customize the URLconf
+configuration for the duration of the execution of a test suite.
+If your ``TestCase`` instance defines an ``urls`` attribute, the
+``TestCase`` will use the value of that attribute as the ``ROOT_URLCONF``
+for the duration of that test.
+
+For example::
+
+ from django.test import TestCase
+
+ class TestMyViews(TestCase):
+ urls = 'myapp.test_urls'
+
+ def testIndexPageView(self):
+ # Here you'd test your view using ``Client``.
+
+This test case will use the contents of ``myapp.test_urls`` as the
+URLconf for the duration of the test case.
+
Emptying the test outbox
~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/upload_handling.txt b/docs/upload_handling.txt
new file mode 100644
index 0000000000..34cd085ac9
--- /dev/null
+++ b/docs/upload_handling.txt
@@ -0,0 +1,346 @@
+============
+File Uploads
+============
+
+**New in Django development version**
+
+Most Web sites wouldn't be complete without a way to upload files. When Django
+handles a file upload, the file data ends up placed in ``request.FILES`` (for
+more on the ``request`` object see the documentation for `request and response
+objects`_). This document explains how files are stored on disk and in memory,
+and how to customize the default behavior.
+
+.. _request and response objects: ../request_response/#attributes
+
+Basic file uploads
+==================
+
+Consider a simple form containing a ``FileField``::
+
+ from django import newforms as forms
+
+ class UploadFileForm(forms.Form):
+ title = forms.CharField(max_length=50)
+ file = forms.FileField()
+
+A view handling this form will receive the file data in ``request.FILES``, which
+is a dictionary containing a key for each ``FileField`` (or ``ImageField``, or
+other ``FileField`` subclass) in the form. So the data from the above form would
+be accessible as ``request.FILES['file']``.
+
+Most of the time, you'll simply pass the file data from ``request`` into the
+form as described in `binding uploaded files to a form`_. This would look
+something like::
+
+ from django.http import HttpResponseRedirect
+ from django.shortcuts import render_to_response
+
+ # Imaginary function to handle an uploaded file.
+ from somewhere import handle_uploaded_file
+
+ def upload_file(request):
+ if request.method == 'POST':
+ form = UploadFileForm(request.POST, request.FILES)
+ if form.is_valid():
+ handle_uploaded_file(request.FILES['file'])
+ return HttpResponseRedirect('/success/url/')
+ else:
+ form = UploadFileForm()
+ return render_to_response('upload.html', {'form': form})
+
+.. _binding uploaded files to a form: ../newforms/#binding-uploaded-files-to-a- form
+
+Notice that we have to pass ``request.FILES`` into the form's constructor; this
+is how file data gets bound into a form.
+
+Handling uploaded files
+-----------------------
+
+The final piece of the puzzle is handling the actual file data from
+``request.FILES``. Each entry in this dictionary is an ``UploadedFile`` object
+-- a simple wrapper around an uploaded file. You'll usually use one of these
+methods to access the uploaded content:
+
+ ``UploadedFile.read()``
+ Read the entire uploaded data from the file. Be careful with this
+ method: if the uploaded file is huge it can overwhelm your system if you
+ try to read it into memory. You'll probably want to use ``chunk()``
+ instead; see below.
+
+ ``UploadedFile.multiple_chunks()``
+ Returns ``True`` if the uploaded file is big enough to require
+ reading in multiple chunks. By default this will be any file
+ larger than 2.5 megabytes, but that's configurable; see below.
+
+ ``UploadedFile.chunk()``
+ A generator returning chunks of the file. If ``multiple_chunks()`` is
+ ``True``, you should use this method in a loop instead of ``read()``.
+
+ In practice, it's often easiest simply to use ``chunks()`` all the time;
+ see the example below.
+
+ ``UploadedFile.file_name``
+ The name of the uploaded file (e.g. ``my_file.txt``).
+
+ ``UploadedFile.file_size``
+ The size, in bytes, of the uploaded file.
+
+There are a few other methods and attributes available on ``UploadedFile``
+objects; see `UploadedFile objects`_ for a complete reference.
+
+Putting it all together, here's a common way you might handle an uploaded file::
+
+ def handle_uploaded_file(f):
+ destination = open('some/file/name.txt', 'wb')
+ for chunk in f.chunks():
+ destination.write(chunk)
+
+Looping over ``UploadedFile.chunks()`` instead of using ``read()`` ensures that
+large files don't overwhelm your system's memory.
+
+Where uploaded data is stored
+-----------------------------
+
+Before you save uploaded files, the data needs to be stored somewhere.
+
+By default, if an uploaded file is smaller than 2.5 megabytes, Django will hold
+the entire contents of the upload in memory. This means that saving the file
+involves only a read from memory and a write to disk and thus is very fast.
+
+However, if an uploaded file is too large, Django will write the uploaded file
+to a temporary file stored in your system's temporary directory. On a Unix-like
+platform this means you can expect Django to generate a file called something
+like ``/tmp/tmpzfp6I6.upload``. If an upload is large enough, you can watch this
+file grow in size as Django streams the data onto disk.
+
+These specifics -- 2.5 megabytes; ``/tmp``; etc. -- are simply "reasonable
+defaults". Read on for details on how you can customize or completely replace
+upload behavior.
+
+Changing upload handler behavior
+--------------------------------
+
+Three `settings`_ control Django's file upload behavior:
+
+ ``FILE_UPLOAD_MAX_MEMORY_SIZE``
+ The maximum size, in bytes, for files that will be uploaded
+ into memory. Files larger than ``FILE_UPLOAD_MAX_MEMORY_SIZE``
+ will be streamed to disk.
+
+ Defaults to 2.5 megabytes.
+
+ ``FILE_UPLOAD_TEMP_DIR``
+ The directory where uploaded files larger than ``FILE_UPLOAD_TEMP_DIR``
+ will be stored.
+
+ Defaults to your system's standard temporary directory (i.e. ``/tmp`` on
+ most Unix-like systems).
+
+ ``FILE_UPLOAD_HANDLERS``
+ The actual handlers for uploaded files. Changing this setting
+ allows complete customization -- even replacement -- of
+ Django's upload process. See `upload handlers`_, below,
+ for details.
+
+ Defaults to::
+
+ ("django.core.files.uploadhandler.MemoryFileUploadHandler",
+ "django.core.files.uploadhandler.TemporaryFileUploadHandler",)
+
+ Which means "try to upload to memory first, then fall back to temporary
+ files."
+
+.. _settings: ../settings/
+
+``UploadedFile`` objects
+========================
+
+All ``UploadedFile`` objects define the following methods/attributes:
+
+ ``UploadedFile.read(self, num_bytes=None)``
+ Returns a byte string of length ``num_bytes``, or the complete file if
+ ``num_bytes`` is ``None``.
+
+ ``UploadedFile.chunk(self, chunk_size=None)``
+ A generator yielding small chunks from the file. If ``chunk_size`` isn't
+ given, chunks will be 64 kb.
+
+ ``UploadedFile.multiple_chunks(self, chunk_size=None)``
+ Returns ``True`` if you can expect more than one chunk when calling
+ ``UploadedFile.chunk(self, chunk_size)``.
+
+ ``UploadedFile.file_size``
+ The size, in bytes, of the uploaded file.
+
+ ``UploadedFile.file_name``
+ The name of the uploaded file as provided by the user.
+
+ ``UploadedFile.content_type``
+ The content-type header uploaded with the file (e.g. ``text/plain`` or
+ ``application/pdf``). Like any data supplied by the user, you shouldn't
+ trust that the uploaded file is actually this type. You'll still need to
+ validate that the file contains the content that the content-type header
+ claims -- "trust but verify."
+
+ ``UploadedFile.charset``
+ For ``text/*`` content-types, the character set (i.e. ``utf8``) supplied
+ by the browser. Again, "trust but verify" is the best policy here.
+
+ ``UploadedFile.temporary_file_path()``
+ Only files uploaded onto disk will have this method; it returns the full
+ path to the temporary uploaded file.
+
+Upload Handlers
+===============
+
+When a user uploads a file, Django passes off the file data to an *upload
+handler* -- a small class that handles file data as it gets uploaded. Upload
+handlers are initially defined in the ``FILE_UPLOAD_HANDLERS`` setting, which
+defaults to::
+
+ ("django.core.files.uploadhandler.MemoryFileUploadHandler",
+ "django.core.files.uploadhandler.TemporaryFileUploadHandler",)
+
+Together the ``MemoryFileUploadHandler`` and ``TemporaryFileUploadHandler``
+provide Django's default file upload behavior of reading small files into memory
+and large ones onto disk.
+
+You can write custom handlers that customize how Django handles files. You
+could, for example, use custom handlers to enforce user-level quotas, compress
+data on the fly, render progress bars, and even send data to another storage
+location directly without storing it locally.
+
+Modifying upload handlers on the fly
+------------------------------------
+
+Sometimes particular views require different upload behavior. In these cases,
+you can override upload handlers on a per-request basis by modifying
+``request.upload_handlers``. By default, this list will contain the upload
+handlers given by ``FILE_UPLOAD_HANDLERS``, but you can modify the list as you
+would any other list.
+
+For instance, suppose you've written a ``ProgressBarUploadHandler`` that
+provides feedback on upload progress to some sort of AJAX widget. You'd add this
+handler to your upload handers like this::
+
+ request.upload_handlers.insert(0, ProgressBarUploadHandler())
+
+You'd probably want to use ``list.insert()`` in this case (instead of
+``append()``) because a progress bar handler would need to run *before* any
+other handlers. Remember, the upload handlers are processed in order.
+
+If you want to replace the upload handlers completely, you can just assign a new
+list::
+
+ request.upload_handlers = [ProgressBarUploadHandler()]
+
+.. note::
+
+ You can only modify upload handlers *before* accessing ``request.FILES`` --
+ it doesn't make sense to change upload handlers after upload handling has
+ already started. If you try to modify ``request.upload_handlers`` after
+ reading from ``request.FILES`` Django will throw an error.
+
+ Thus, you should always modify uploading handlers as early in your view as
+ possible.
+
+Writing custom upload handlers
+------------------------------
+
+All file upload handlers should be subclasses of
+``django.core.files.uploadhandler.FileUploadHandler``. You can define upload
+handlers wherever you wish.
+
+Required methods
+~~~~~~~~~~~~~~~~
+
+Custom file upload handlers **must** define the following methods:
+
+ ``FileUploadHandler.receive_data_chunk(self, raw_data, start)``
+ Receives a "chunk" of data from the file upload.
+
+ ``raw_data`` is a byte string containing the uploaded data.
+
+ ``start`` is the position in the file where this ``raw_data`` chunk
+ begins.
+
+ The data you return will get fed into the subsequent upload handlers'
+ ``receive_data_chunk`` methods. In this way, one handler can be a
+ "filter" for other handlers.
+
+ Return ``None`` from ``receive_data_chunk`` to sort-circuit remaining
+ upload handlers from getting this chunk.. This is useful if you're
+ storing the uploaded data yourself and don't want future handlers to
+ store a copy of the data.
+
+ If you raise a ``StopUpload`` or a ``SkipFile`` exception, the upload
+ will abort or the file will be completely skipped.
+
+ ``FileUploadHandler.file_complete(self, file_size)``
+ Called when a file has finished uploading.
+
+ The handler should return an ``UploadedFile`` object that will be stored
+ in ``request.FILES``. Handlers may also return ``None`` to indicate that
+ the ``UploadedFile`` object should come from subsequent upload handlers.
+
+Optional methods
+~~~~~~~~~~~~~~~~
+
+Custom upload handlers may also define any of the following optional methods or
+attributes:
+
+ ``FileUploadHandler.chunk_size``
+ Size, in bytes, of the "chunks" Django should store into memory and feed
+ into the handler. That is, this attribute controls the size of chunks
+ fed into ``FileUploadHandler.receive_data_chunk``.
+
+ For maximum performance the chunk sizes should be divisible by ``4`` and
+ should not exceed 2 GB (2\ :sup:`31` bytes) in size. When there are
+ multiple chunk sizes provided by multiple handlers, Django will use the
+ smallest chunk size defined by any handler.
+
+ The default is 64*2\ :sup:`10` bytes, or 64 KB.
+
+ ``FileUploadHandler.new_file(self, field_name, file_name, content_type, content_length, charset)``
+ Callback signaling that a new file upload is starting. This is called
+ before any data has been fed to any upload handlers.
+
+ ``field_name`` is a string name of the file ``<input>`` field.
+
+ ``file_name`` is the unicode filename that was provided by the browser.
+
+ ``content_type`` is the MIME type provided by the browser -- E.g.
+ ``'image/jpeg'``.
+
+ ``content_length`` is the length of the image given by the browser.
+ Sometimes this won't be provided and will be ``None``., ``None``
+ otherwise.
+
+ ``charset`` is the character set (i.e. ``utf8``) given by the browser.
+ Like ``content_length``, this sometimes won't be provided.
+
+ This method may raise a ``StopFutureHandlers`` exception to prevent
+ future handlers from handling this file.
+
+ ``FileUploadHandler.upload_complete(self)``
+ Callback signaling that the entire upload (all files) has completed.
+
+ ``FileUploadHandler.handle_raw_input(self, input_data, META, content_length, boundary, encoding)``
+ Allows the handler to completely override the parsing of the raw
+ HTTP input.
+
+ ``input_data`` is a file-like object that supports ``read()``-ing.
+
+ ``META`` is the same object as ``request.META``.
+
+ ``content_length`` is the length of the data in ``input_data``. Don't
+ read more than ``content_length`` bytes from ``input_data``.
+
+ ``boundary`` is the MIME boundary for this request.
+
+ ``encoding`` is the encoding of the request.
+
+ Return ``None`` if you want upload handling to continue, or a tuple of
+ ``(POST, FILES)`` if you want to return the new data structures suitable
+ for the request directly.
+
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
index 470312f5ca..c856720a74 100644
--- a/tests/modeltests/model_forms/models.py
+++ b/tests/modeltests/model_forms/models.py
@@ -67,7 +67,13 @@ class TextFile(models.Model):
class ImageFile(models.Model):
description = models.CharField(max_length=20)
- image = models.FileField(upload_to=tempfile.gettempdir())
+ try:
+ # If PIL is available, try testing PIL.
+ # Otherwise, it's equivalent to TextFile above.
+ import Image
+ image = models.ImageField(upload_to=tempfile.gettempdir())
+ except ImportError:
+ image = models.FileField(upload_to=tempfile.gettempdir())
def __unicode__(self):
return self.description
@@ -75,6 +81,7 @@ class ImageFile(models.Model):
__test__ = {'API_TESTS': """
>>> from django import newforms as forms
>>> from django.newforms.models import ModelForm
+>>> from django.core.files.uploadedfile import SimpleUploadedFile
The bare bones, absolutely nothing custom, basic case.
@@ -792,7 +799,18 @@ False
# Upload a file and ensure it all works as expected.
->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test1.txt', 'content': 'hello world'}})
+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')})
+>>> f.is_valid()
+True
+>>> type(f.cleaned_data['file'])
+<class 'django.newforms.fields.UploadedFile'>
+>>> instance = f.save()
+>>> instance.file
+u'...test1.txt'
+
+>>> os.unlink(instance.get_file_filename())
+
+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')})
>>> f.is_valid()
True
>>> type(f.cleaned_data['file'])
@@ -814,18 +832,30 @@ u'...test1.txt'
u'...test1.txt'
# Delete the current file since this is not done by Django.
-
>>> os.unlink(instance.get_file_filename())
# Override the file by uploading a new one.
->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test2.txt', 'content': 'hello world'}}, instance=instance)
+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}, instance=instance)
>>> f.is_valid()
True
>>> instance = f.save()
>>> instance.file
u'...test2.txt'
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_file_filename())
+
+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')})
+>>> f.is_valid()
+True
+>>> instance = f.save()
+>>> instance.file
+u'...test2.txt'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_file_filename())
+
>>> instance.delete()
# Test the non-required FileField
@@ -838,12 +868,26 @@ True
>>> instance.file
''
->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': {'filename': 'test3.txt', 'content': 'hello world'}}, instance=instance)
+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance)
>>> f.is_valid()
True
>>> instance = f.save()
>>> instance.file
u'...test3.txt'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_file_filename())
+>>> instance.delete()
+
+>>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')})
+>>> f.is_valid()
+True
+>>> instance = f.save()
+>>> instance.file
+u'...test3.txt'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_file_filename())
>>> instance.delete()
# ImageField ###################################################################
@@ -858,7 +902,19 @@ u'...test3.txt'
>>> image_data = open(os.path.join(os.path.dirname(__file__), "test.png")).read()
->>> f = ImageFileForm(data={'description': u'An image'}, files={'image': {'filename': 'test.png', 'content': image_data}})
+>>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)})
+>>> f.is_valid()
+True
+>>> type(f.cleaned_data['image'])
+<class 'django.newforms.fields.UploadedFile'>
+>>> instance = f.save()
+>>> instance.image
+u'...test.png'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_image_filename())
+
+>>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)})
>>> f.is_valid()
True
>>> type(f.cleaned_data['image'])
@@ -885,13 +941,26 @@ u'...test.png'
# Override the file by uploading a new one.
->>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': {'filename': 'test2.png', 'content': image_data}}, instance=instance)
+>>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data)}, instance=instance)
+>>> f.is_valid()
+True
+>>> instance = f.save()
+>>> instance.image
+u'...test2.png'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_image_filename())
+>>> instance.delete()
+
+>>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data)})
>>> f.is_valid()
True
>>> instance = f.save()
>>> instance.image
u'...test2.png'
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_image_filename())
>>> instance.delete()
# Test the non-required ImageField
@@ -904,7 +973,18 @@ True
>>> instance.image
''
->>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': {'filename': 'test3.png', 'content': image_data}}, instance=instance)
+>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance)
+>>> f.is_valid()
+True
+>>> instance = f.save()
+>>> instance.image
+u'...test3.png'
+
+# Delete the current file since this is not done by Django.
+>>> os.unlink(instance.get_image_filename())
+>>> instance.delete()
+
+>>> f = ImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)})
>>> f.is_valid()
True
>>> instance = f.save()
diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py
index 277c5961e3..1f08a32903 100644
--- a/tests/modeltests/pagination/models.py
+++ b/tests/modeltests/pagination/models.py
@@ -200,6 +200,29 @@ InvalidPage: ...
>>> paginator.page_range
[1]
+# ObjectPaginator can be passed lists too.
+>>> paginator = ObjectPaginator([1, 2, 3], 5)
+>>> paginator.hits
+3
+>>> paginator.pages
+1
+>>> paginator.page_range
+[1]
+
+
+# ObjectPaginator can be passed other objects with a count() method.
+>>> class Container:
+... def __len__(self):
+... return 42
+>>> paginator = ObjectPaginator(Container(), 10)
+>>> paginator.hits
+42
+>>> paginator.pages
+5
+>>> paginator.page_range
+[1, 2, 3, 4, 5]
+
+
##################
# Orphan support #
##################
diff --git a/tests/regressiontests/bug639/tests.py b/tests/regressiontests/bug639/tests.py
index f9596d06cb..2726dec897 100644
--- a/tests/regressiontests/bug639/tests.py
+++ b/tests/regressiontests/bug639/tests.py
@@ -9,6 +9,7 @@ import unittest
from regressiontests.bug639.models import Photo
from django.http import QueryDict
from django.utils.datastructures import MultiValueDict
+from django.core.files.uploadedfile import SimpleUploadedFile
class Bug639Test(unittest.TestCase):
@@ -21,12 +22,8 @@ class Bug639Test(unittest.TestCase):
# Fake a request query dict with the file
qd = QueryDict("title=Testing&image=", mutable=True)
- qd["image_file"] = {
- "filename" : "test.jpg",
- "content-type" : "image/jpeg",
- "content" : img
- }
-
+ qd["image_file"] = SimpleUploadedFile('test.jpg', img, 'image/jpeg')
+
manip = Photo.AddManipulator()
manip.do_html2python(qd)
p = manip.save(qd)
@@ -39,4 +36,4 @@ class Bug639Test(unittest.TestCase):
Make sure to delete the "uploaded" file to avoid clogging /tmp.
"""
p = Photo.objects.get()
- os.unlink(p.get_image_filename()) \ No newline at end of file
+ os.unlink(p.get_image_filename())
diff --git a/tests/regressiontests/datastructures/tests.py b/tests/regressiontests/datastructures/tests.py
index d6141b09ce..62c57bc019 100644
--- a/tests/regressiontests/datastructures/tests.py
+++ b/tests/regressiontests/datastructures/tests.py
@@ -117,14 +117,25 @@ Init from sequence of tuples
>>> d['person']['2']['firstname']
['Adrian']
-### FileDict ################################################################
-
->>> d = FileDict({'content': 'once upon a time...'})
->>> repr(d)
-"{'content': '<omitted>'}"
->>> d = FileDict({'other-key': 'once upon a time...'})
+### ImmutableList ################################################################
+>>> d = ImmutableList(range(10))
+>>> d.sort()
+Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain
+ raise AttributeError, self.warning
+AttributeError: ImmutableList object is immutable.
>>> repr(d)
-"{'other-key': 'once upon a time...'}"
+'(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)'
+>>> d = ImmutableList(range(10), warning="Object is immutable!")
+>>> d[1]
+1
+>>> d[1] = 'test'
+Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain
+ raise AttributeError, self.warning
+AttributeError: Object is immutable!
### DictWrapper #############################################################
diff --git a/tests/regressiontests/extra_regress/__init__.py b/tests/regressiontests/extra_regress/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/extra_regress/__init__.py
diff --git a/tests/regressiontests/extra_regress/models.py b/tests/regressiontests/extra_regress/models.py
new file mode 100644
index 0000000000..e6665222bf
--- /dev/null
+++ b/tests/regressiontests/extra_regress/models.py
@@ -0,0 +1,55 @@
+import copy
+
+from django.db import models
+from django.db.models.query import Q
+
+
+class RevisionableModel(models.Model):
+ base = models.ForeignKey('self', null=True)
+ title = models.CharField(blank=True, max_length=255)
+
+ def __unicode__(self):
+ return u"%s (%s, %s)" % (self.title, self.id, self.base.id)
+
+ def save(self):
+ super(RevisionableModel, self).save()
+ if not self.base:
+ self.base = self
+ super(RevisionableModel, self).save()
+
+ def new_revision(self):
+ new_revision = copy.copy(self)
+ new_revision.pk = None
+ return new_revision
+
+__test__ = {"API_TESTS": """
+### Regression tests for #7314 and #7372
+
+>>> rm = RevisionableModel.objects.create(title='First Revision')
+>>> rm.pk, rm.base.pk
+(1, 1)
+
+>>> rm2 = rm.new_revision()
+>>> rm2.title = "Second Revision"
+>>> rm2.save()
+>>> print u"%s of %s" % (rm2.title, rm2.base.title)
+Second Revision of First Revision
+
+>>> rm2.pk, rm2.base.pk
+(2, 1)
+
+Queryset to match most recent revision:
+>>> qs = RevisionableModel.objects.extra(where=["%(table)s.id IN (SELECT MAX(rev.id) FROM %(table)s rev GROUP BY rev.base_id)" % {'table': RevisionableModel._meta.db_table,}],)
+>>> qs
+[<RevisionableModel: Second Revision (2, 1)>]
+
+Queryset to search for string in title:
+>>> qs2 = RevisionableModel.objects.filter(title__contains="Revision")
+>>> qs2
+[<RevisionableModel: First Revision (1, 1)>, <RevisionableModel: Second Revision (2, 1)>]
+
+Following queryset should return the most recent revision:
+>>> qs & qs2
+[<RevisionableModel: Second Revision (2, 1)>]
+
+"""}
diff --git a/tests/regressiontests/file_uploads/__init__.py b/tests/regressiontests/file_uploads/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/file_uploads/__init__.py
diff --git a/tests/regressiontests/file_uploads/models.py b/tests/regressiontests/file_uploads/models.py
new file mode 100644
index 0000000000..2d5607b2a7
--- /dev/null
+++ b/tests/regressiontests/file_uploads/models.py
@@ -0,0 +1,2 @@
+# This file unintentionally left blank.
+# Oops. \ No newline at end of file
diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py
new file mode 100644
index 0000000000..8992298470
--- /dev/null
+++ b/tests/regressiontests/file_uploads/tests.py
@@ -0,0 +1,158 @@
+import os
+import sha
+import tempfile
+from django.test import TestCase, client
+from django.utils import simplejson
+
+class FileUploadTests(TestCase):
+ def test_simple_upload(self):
+ post_data = {
+ 'name': 'Ringo',
+ 'file_field': open(__file__),
+ }
+ response = self.client.post('/file_uploads/upload/', post_data)
+ self.assertEqual(response.status_code, 200)
+
+ def test_large_upload(self):
+ tdir = tempfile.gettempdir()
+
+ file1 = tempfile.NamedTemporaryFile(suffix=".file1", dir=tdir)
+ file1.write('a' * (2 ** 21))
+ file1.seek(0)
+
+ file2 = tempfile.NamedTemporaryFile(suffix=".file2", dir=tdir)
+ file2.write('a' * (10 * 2 ** 20))
+ file2.seek(0)
+
+ # This file contains chinese symbols for a name.
+ file3 = open(os.path.join(tdir, u'test_&#20013;&#25991;_Orl\u00e9ans.jpg'), 'w+b')
+ file3.write('b' * (2 ** 10))
+ file3.seek(0)
+
+ post_data = {
+ 'name': 'Ringo',
+ 'file_field1': open(file1.name),
+ 'file_field2': open(file2.name),
+ 'file_unicode': file3,
+ }
+
+ for key in post_data.keys():
+ try:
+ post_data[key + '_hash'] = sha.new(post_data[key].read()).hexdigest()
+ post_data[key].seek(0)
+ except AttributeError:
+ post_data[key + '_hash'] = sha.new(post_data[key]).hexdigest()
+
+ response = self.client.post('/file_uploads/verify/', post_data)
+
+ try:
+ os.unlink(file3.name)
+ except:
+ pass
+
+ self.assertEqual(response.status_code, 200)
+
+ def test_dangerous_file_names(self):
+ """Uploaded file names should be sanitized before ever reaching the view."""
+ # This test simulates possible directory traversal attacks by a
+ # malicious uploader We have to do some monkeybusiness here to construct
+ # a malicious payload with an invalid file name (containing os.sep or
+ # os.pardir). This similar to what an attacker would need to do when
+ # trying such an attack.
+ scary_file_names = [
+ "/tmp/hax0rd.txt", # Absolute path, *nix-style.
+ "C:\\Windows\\hax0rd.txt", # Absolute path, win-syle.
+ "C:/Windows/hax0rd.txt", # Absolute path, broken-style.
+ "\\tmp\\hax0rd.txt", # Absolute path, broken in a different way.
+ "/tmp\\hax0rd.txt", # Absolute path, broken by mixing.
+ "subdir/hax0rd.txt", # Descendant path, *nix-style.
+ "subdir\\hax0rd.txt", # Descendant path, win-style.
+ "sub/dir\\hax0rd.txt", # Descendant path, mixed.
+ "../../hax0rd.txt", # Relative path, *nix-style.
+ "..\\..\\hax0rd.txt", # Relative path, win-style.
+ "../..\\hax0rd.txt" # Relative path, mixed.
+ ]
+
+ payload = []
+ for i, name in enumerate(scary_file_names):
+ payload.extend([
+ '--' + client.BOUNDARY,
+ 'Content-Disposition: form-data; name="file%s"; filename="%s"' % (i, name),
+ 'Content-Type: application/octet-stream',
+ '',
+ 'You got pwnd.'
+ ])
+ payload.extend([
+ '--' + client.BOUNDARY + '--',
+ '',
+ ])
+
+ payload = "\r\n".join(payload)
+ r = {
+ 'CONTENT_LENGTH': len(payload),
+ 'CONTENT_TYPE': client.MULTIPART_CONTENT,
+ 'PATH_INFO': "/file_uploads/echo/",
+ 'REQUEST_METHOD': 'POST',
+ 'wsgi.input': client.FakePayload(payload),
+ }
+ response = self.client.request(**r)
+
+ # The filenames should have been sanitized by the time it got to the view.
+ recieved = simplejson.loads(response.content)
+ for i, name in enumerate(scary_file_names):
+ got = recieved["file%s" % i]
+ self.assertEqual(got, "hax0rd.txt")
+
+ def test_filename_overflow(self):
+ """File names over 256 characters (dangerous on some platforms) get fixed up."""
+ name = "%s.txt" % ("f"*500)
+ payload = "\r\n".join([
+ '--' + client.BOUNDARY,
+ 'Content-Disposition: form-data; name="file"; filename="%s"' % name,
+ 'Content-Type: application/octet-stream',
+ '',
+ 'Oops.'
+ '--' + client.BOUNDARY + '--',
+ '',
+ ])
+ r = {
+ 'CONTENT_LENGTH': len(payload),
+ 'CONTENT_TYPE': client.MULTIPART_CONTENT,
+ 'PATH_INFO': "/file_uploads/echo/",
+ 'REQUEST_METHOD': 'POST',
+ 'wsgi.input': client.FakePayload(payload),
+ }
+ got = simplejson.loads(self.client.request(**r).content)
+ self.assert_(len(got['file']) < 256, "Got a long file name (%s characters)." % len(got['file']))
+
+ def test_custom_upload_handler(self):
+ # A small file (under the 5M quota)
+ smallfile = tempfile.NamedTemporaryFile()
+ smallfile.write('a' * (2 ** 21))
+
+ # A big file (over the quota)
+ bigfile = tempfile.NamedTemporaryFile()
+ bigfile.write('a' * (10 * 2 ** 20))
+
+ # Small file posting should work.
+ response = self.client.post('/file_uploads/quota/', {'f': open(smallfile.name)})
+ got = simplejson.loads(response.content)
+ self.assert_('f' in got)
+
+ # Large files don't go through.
+ response = self.client.post("/file_uploads/quota/", {'f': open(bigfile.name)})
+ got = simplejson.loads(response.content)
+ self.assert_('f' not in got)
+
+ def test_broken_custom_upload_handler(self):
+ f = tempfile.NamedTemporaryFile()
+ f.write('a' * (2 ** 21))
+
+ # AttributeError: You cannot alter upload handlers after the upload has been processed.
+ self.assertRaises(
+ AttributeError,
+ self.client.post,
+ '/file_uploads/quota/broken/',
+ {'f': open(f.name)}
+ )
+ \ No newline at end of file
diff --git a/tests/regressiontests/file_uploads/uploadhandler.py b/tests/regressiontests/file_uploads/uploadhandler.py
new file mode 100644
index 0000000000..54f82f626c
--- /dev/null
+++ b/tests/regressiontests/file_uploads/uploadhandler.py
@@ -0,0 +1,26 @@
+"""
+Upload handlers to test the upload API.
+"""
+
+from django.core.files.uploadhandler import FileUploadHandler, StopUpload
+
+class QuotaUploadHandler(FileUploadHandler):
+ """
+ This test upload handler terminates the connection if more than a quota
+ (5MB) is uploaded.
+ """
+
+ QUOTA = 5 * 2**20 # 5 MB
+
+ def __init__(self, request=None):
+ super(QuotaUploadHandler, self).__init__(request)
+ self.total_upload = 0
+
+ def receive_data_chunk(self, raw_data, start):
+ self.total_upload += len(raw_data)
+ if self.total_upload >= self.QUOTA:
+ raise StopUpload(connection_reset=True)
+ return raw_data
+
+ def file_complete(self, file_size):
+ return None \ No newline at end of file
diff --git a/tests/regressiontests/file_uploads/urls.py b/tests/regressiontests/file_uploads/urls.py
new file mode 100644
index 0000000000..529bee312d
--- /dev/null
+++ b/tests/regressiontests/file_uploads/urls.py
@@ -0,0 +1,10 @@
+from django.conf.urls.defaults import *
+import views
+
+urlpatterns = patterns('',
+ (r'^upload/$', views.file_upload_view),
+ (r'^verify/$', views.file_upload_view_verify),
+ (r'^echo/$', views.file_upload_echo),
+ (r'^quota/$', views.file_upload_quota),
+ (r'^quota/broken/$', views.file_upload_quota_broken),
+)
diff --git a/tests/regressiontests/file_uploads/views.py b/tests/regressiontests/file_uploads/views.py
new file mode 100644
index 0000000000..833cf90531
--- /dev/null
+++ b/tests/regressiontests/file_uploads/views.py
@@ -0,0 +1,70 @@
+import os
+import sha
+from django.core.files.uploadedfile import UploadedFile
+from django.http import HttpResponse, HttpResponseServerError
+from django.utils import simplejson
+from uploadhandler import QuotaUploadHandler
+
+def file_upload_view(request):
+ """
+ Check that a file upload can be updated into the POST dictionary without
+ going pear-shaped.
+ """
+ form_data = request.POST.copy()
+ form_data.update(request.FILES)
+ if isinstance(form_data.get('file_field'), UploadedFile) and isinstance(form_data['name'], unicode):
+ # If a file is posted, the dummy client should only post the file name,
+ # not the full path.
+ if os.path.dirname(form_data['file_field'].file_name) != '':
+ return HttpResponseServerError()
+ return HttpResponse('')
+ else:
+ return HttpResponseServerError()
+
+def file_upload_view_verify(request):
+ """
+ Use the sha digest hash to verify the uploaded contents.
+ """
+ form_data = request.POST.copy()
+ form_data.update(request.FILES)
+
+ # Check to see if unicode names worked out.
+ if not request.FILES['file_unicode'].file_name.endswith(u'test_\u4e2d\u6587_Orl\xe9ans.jpg'):
+ return HttpResponseServerError()
+
+ for key, value in form_data.items():
+ if key.endswith('_hash'):
+ continue
+ if key + '_hash' not in form_data:
+ continue
+ submitted_hash = form_data[key + '_hash']
+ if isinstance(value, UploadedFile):
+ new_hash = sha.new(value.read()).hexdigest()
+ else:
+ new_hash = sha.new(value).hexdigest()
+ if new_hash != submitted_hash:
+ return HttpResponseServerError()
+
+ return HttpResponse('')
+
+def file_upload_echo(request):
+ """
+ Simple view to echo back info about uploaded files for tests.
+ """
+ r = dict([(k, f.file_name) for k, f in request.FILES.items()])
+ return HttpResponse(simplejson.dumps(r))
+
+def file_upload_quota(request):
+ """
+ Dynamically add in an upload handler.
+ """
+ request.upload_handlers.insert(0, QuotaUploadHandler())
+ return file_upload_echo(request)
+
+def file_upload_quota_broken(request):
+ """
+ You can't change handlers after reading FILES; this view shouldn't work.
+ """
+ response = file_upload_echo(request)
+ request.upload_handlers.insert(0, QuotaUploadHandler())
+ return response \ No newline at end of file
diff --git a/tests/regressiontests/fixtures_regress/fixtures/big-fixture.json b/tests/regressiontests/fixtures_regress/fixtures/big-fixture.json
new file mode 100644
index 0000000000..e655fbbf3f
--- /dev/null
+++ b/tests/regressiontests/fixtures_regress/fixtures/big-fixture.json
@@ -0,0 +1,83 @@
+[
+ {
+ "pk": 6,
+ "model": "fixtures_regress.channel",
+ "fields": {
+ "name": "Business"
+ }
+ },
+
+ {
+ "pk": 1,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 1",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 2,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 2",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 3,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 3",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 4,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 4",
+ "channels": [6]
+ }
+ },
+
+ {
+ "pk": 5,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 5",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 6,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 6",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 7,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 7",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 8,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Article Title 8",
+ "channels": [6]
+ }
+ },
+ {
+ "pk": 9,
+ "model": "fixtures_regress.article",
+ "fields": {
+ "title": "Yet Another Article",
+ "channels": [6]
+ }
+ }
+] \ No newline at end of file
diff --git a/tests/regressiontests/fixtures_regress/fixtures/model-inheritance.json b/tests/regressiontests/fixtures_regress/fixtures/model-inheritance.json
new file mode 100644
index 0000000000..00c482b3dd
--- /dev/null
+++ b/tests/regressiontests/fixtures_regress/fixtures/model-inheritance.json
@@ -0,0 +1,4 @@
+[
+ {"pk": 1, "model": "fixtures_regress.parent", "fields": {"name": "fred"}},
+ {"pk": 1, "model": "fixtures_regress.child", "fields": {"data": "apple"}}
+]
diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py
index 59fc167d50..2c048dc0b8 100644
--- a/tests/regressiontests/fixtures_regress/models.py
+++ b/tests/regressiontests/fixtures_regress/models.py
@@ -20,7 +20,7 @@ class Plant(models.Model):
class Stuff(models.Model):
name = models.CharField(max_length=20, null=True)
owner = models.ForeignKey(User, null=True)
-
+
def __unicode__(self):
# Oracle doesn't distinguish between None and the empty string.
# This hack makes the test case pass using Oracle.
@@ -38,13 +38,29 @@ class Absolute(models.Model):
super(Absolute, self).__init__(*args, **kwargs)
Absolute.load_count += 1
+class Parent(models.Model):
+ name = models.CharField(max_length=10)
+
+class Child(Parent):
+ data = models.CharField(max_length=10)
+
+# Models to regresison check #7572
+class Channel(models.Model):
+ name = models.CharField(max_length=255)
+
+class Article(models.Model):
+ title = models.CharField(max_length=255)
+ channels = models.ManyToManyField(Channel)
+
+ class Meta:
+ ordering = ('id',)
__test__ = {'API_TESTS':"""
>>> from django.core import management
# Load a fixture that uses PK=1
>>> management.call_command('loaddata', 'sequence', verbosity=0)
-
+
# Create a new animal. Without a sequence reset, this new object
# will take a PK of 1 (on Postgres), and the save will fail.
# This is a regression test for ticket #3790.
@@ -61,9 +77,9 @@ __test__ = {'API_TESTS':"""
[<Stuff: None is owned by None>]
###############################################
-# Regression test for ticket #6436 --
+# Regression test for ticket #6436 --
# os.path.join will throw away the initial parts of a path if it encounters
-# an absolute path. This means that if a fixture is specified as an absolute path,
+# an absolute path. This means that if a fixture is specified as an absolute path,
# we need to make sure we don't discover the absolute path in every fixture directory.
>>> load_absolute_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'absolute.json')
@@ -94,4 +110,28 @@ No fixture data found for 'bad_fixture2'. (File format may be invalid.)
>>> sys.stderr = savestderr
+###############################################
+# Test for ticket #7565 -- PostgreSQL sequence resetting checks shouldn't
+# ascend to parent models when inheritance is used (since they are treated
+# individually).
+
+>>> management.call_command('loaddata', 'model-inheritance.json', verbosity=0)
+
+###############################################
+# Test for ticket #7572 -- MySQL has a problem if the same connection is
+# used to create tables, load data, and then query over that data.
+# To compensate, we close the connection after running loaddata.
+# This ensures that a new connection is opened when test queries are issued.
+
+>>> management.call_command('loaddata', 'big-fixture.json', verbosity=0)
+
+>>> articles = Article.objects.exclude(id=9)
+>>> articles.values_list('id', flat=True)
+[1, 2, 3, 4, 5, 6, 7, 8]
+
+# Just for good measure, run the same query again. Under the influence of
+# ticket #7572, this will give a different result to the previous call.
+>>> articles.values_list('id', flat=True)
+[1, 2, 3, 4, 5, 6, 7, 8]
+
"""}
diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py
index 9f972f5b90..580326f894 100644
--- a/tests/regressiontests/forms/error_messages.py
+++ b/tests/regressiontests/forms/error_messages.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
tests = r"""
>>> from django.newforms import *
+>>> from django.core.files.uploadedfile import SimpleUploadedFile
# CharField ###################################################################
@@ -214,11 +215,11 @@ ValidationError: [u'REQUIRED']
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
->>> f.clean({})
+>>> f.clean(SimpleUploadedFile('name', None))
Traceback (most recent call last):
...
-ValidationError: [u'MISSING']
->>> f.clean({'filename': 'name', 'content':''})
+ValidationError: [u'EMPTY FILE']
+>>> f.clean(SimpleUploadedFile('name', ''))
Traceback (most recent call last):
...
ValidationError: [u'EMPTY FILE']
@@ -237,7 +238,7 @@ ValidationError: [u'REQUIRED']
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
->>> f.clean('http://www.jfoiwjfoi23jfoijoaijfoiwjofiwjefewl.com')
+>>> f.clean('http://www.broken.djangoproject.com')
Traceback (most recent call last):
...
ValidationError: [u'INVALID LINK']
diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py
index f3b6a96a1e..4725c3ecf3 100644
--- a/tests/regressiontests/forms/fields.py
+++ b/tests/regressiontests/forms/fields.py
@@ -2,6 +2,7 @@
tests = r"""
>>> from django.newforms import *
>>> from django.newforms.widgets import RadioFieldRenderer
+>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> import datetime
>>> import time
>>> import re
@@ -770,17 +771,17 @@ ValidationError: [u'This field is required.']
>>> f.clean(None, 'files/test2.pdf')
'files/test2.pdf'
->>> f.clean({})
+>>> f.clean(SimpleUploadedFile('', ''))
Traceback (most recent call last):
...
-ValidationError: [u'No file was submitted.']
+ValidationError: [u'No file was submitted. Check the encoding type on the form.']
->>> f.clean({}, '')
+>>> f.clean(SimpleUploadedFile('', ''), '')
Traceback (most recent call last):
...
-ValidationError: [u'No file was submitted.']
+ValidationError: [u'No file was submitted. Check the encoding type on the form.']
->>> f.clean({}, 'files/test3.pdf')
+>>> f.clean(None, 'files/test3.pdf')
'files/test3.pdf'
>>> f.clean('some content that is not a file')
@@ -788,20 +789,20 @@ Traceback (most recent call last):
...
ValidationError: [u'No file was submitted. Check the encoding type on the form.']
->>> f.clean({'filename': 'name', 'content': None})
+>>> f.clean(SimpleUploadedFile('name', None))
Traceback (most recent call last):
...
ValidationError: [u'The submitted file is empty.']
->>> f.clean({'filename': 'name', 'content': ''})
+>>> f.clean(SimpleUploadedFile('name', ''))
Traceback (most recent call last):
...
ValidationError: [u'The submitted file is empty.']
->>> type(f.clean({'filename': 'name', 'content': 'Some File Content'}))
+>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content')))
<class 'django.newforms.fields.UploadedFile'>
->>> type(f.clean({'filename': 'name', 'content': 'Some File Content'}, 'files/test4.pdf'))
+>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf'))
<class 'django.newforms.fields.UploadedFile'>
# URLField ##################################################################
@@ -887,7 +888,7 @@ u'http://www.google.com'
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://www.jfoiwjfoi23jfoijoaijfoiwjofiwjefewl.com') # bad domain
+>>> f.clean('http://www.broken.djangoproject.com') # bad domain
Traceback (most recent call last):
...
ValidationError: [u'This URL appears to be a broken link.']
@@ -937,18 +938,24 @@ ValidationError: [u'This field is required.']
>>> f.clean(True)
True
>>> f.clean(False)
-False
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
>>> f.clean(1)
True
>>> f.clean(0)
-False
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
>>> f.clean('Django rocks')
True
>>> f.clean('True')
True
>>> f.clean('False')
-False
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
>>> f = BooleanField(required=False)
>>> f.clean('')
diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py
index 7fc206de4c..041fa4054c 100644
--- a/tests/regressiontests/forms/forms.py
+++ b/tests/regressiontests/forms/forms.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
tests = r"""
>>> from django.newforms import *
+>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> import datetime
>>> import time
>>> import re
@@ -1465,7 +1466,7 @@ not request.POST.
>>> print f
<tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr>
->>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':''}}, auto_id=False)
+>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', '')}, auto_id=False)
>>> print f
<tr><th>File1:</th><td><ul class="errorlist"><li>The submitted file is empty.</li></ul><input type="file" name="file1" /></td></tr>
@@ -1473,7 +1474,7 @@ not request.POST.
>>> print f
<tr><th>File1:</th><td><ul class="errorlist"><li>No file was submitted. Check the encoding type on the form.</li></ul><input type="file" name="file1" /></td></tr>
->>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content'}}, auto_id=False)
+>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', 'some content')}, auto_id=False)
>>> print f
<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
>>> f.is_valid()
diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py
index 4e49df1555..429bdd7558 100644
--- a/tests/regressiontests/many_to_one_regress/models.py
+++ b/tests/regressiontests/many_to_one_regress/models.py
@@ -28,6 +28,24 @@ class Child(models.Model):
parent = models.ForeignKey(Parent)
+# Multiple paths to the same model (#7110, #7125)
+class Category(models.Model):
+ name = models.CharField(max_length=20)
+
+ def __unicode__(self):
+ return self.name
+
+class Record(models.Model):
+ category = models.ForeignKey(Category)
+
+class Relation(models.Model):
+ left = models.ForeignKey(Record, related_name='left_set')
+ right = models.ForeignKey(Record, related_name='right_set')
+
+ def __unicode__(self):
+ return u"%s - %s" % (self.left.category.name, self.right.category.name)
+
+
__test__ = {'API_TESTS':"""
>>> Third.objects.create(id='3', name='An example')
<Third: Third object>
@@ -73,4 +91,26 @@ Traceback (most recent call last):
...
ValueError: Cannot assign "<First: First object>": "Child.parent" must be a "Parent" instance.
+# Test of multiple ForeignKeys to the same model (bug #7125)
+
+>>> c1 = Category.objects.create(name='First')
+>>> c2 = Category.objects.create(name='Second')
+>>> c3 = Category.objects.create(name='Third')
+>>> r1 = Record.objects.create(category=c1)
+>>> r2 = Record.objects.create(category=c1)
+>>> r3 = Record.objects.create(category=c2)
+>>> r4 = Record.objects.create(category=c2)
+>>> r5 = Record.objects.create(category=c3)
+>>> r = Relation.objects.create(left=r1, right=r2)
+>>> r = Relation.objects.create(left=r3, right=r4)
+>>> r = Relation.objects.create(left=r1, right=r3)
+>>> r = Relation.objects.create(left=r5, right=r2)
+>>> r = Relation.objects.create(left=r3, right=r2)
+
+>>> Relation.objects.filter(left__category__name__in=['First'], right__category__name__in=['Second'])
+[<Relation: First - Second>]
+
+>>> Category.objects.filter(record__left_set__right__category__name='Second').order_by('name')
+[<Category: First>, <Category: Second>]
+
"""}
diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py
index e279a0669f..c2ba9ee008 100644
--- a/tests/regressiontests/model_fields/tests.py
+++ b/tests/regressiontests/model_fields/tests.py
@@ -15,4 +15,21 @@ Decimal("3.14")
Traceback (most recent call last):
...
ValidationError: [u'This value must be a decimal number.']
+
+>>> f = DecimalField(max_digits=5, decimal_places=1)
+>>> x = f.to_python(2)
+>>> y = f.to_python('2.6')
+
+>>> f.get_db_prep_save(x)
+u'2.0'
+>>> f.get_db_prep_save(y)
+u'2.6'
+>>> f.get_db_prep_save(None)
+>>> f.get_db_prep_lookup('exact', x)
+[u'2.0']
+>>> f.get_db_prep_lookup('exact', y)
+[u'2.6']
+>>> f.get_db_prep_lookup('exact', None)
+[None]
+
"""
diff --git a/tests/regressiontests/model_inheritance_regress/models.py b/tests/regressiontests/model_inheritance_regress/models.py
index 33e2e0e4f6..24d6186150 100644
--- a/tests/regressiontests/model_inheritance_regress/models.py
+++ b/tests/regressiontests/model_inheritance_regress/models.py
@@ -131,4 +131,26 @@ __test__ = {'API_TESTS':"""
>>> Child.objects.dates('created', 'month')
[datetime.datetime(2008, 6, 1, 0, 0)]
+# Regression test for #7276: calling delete() on a model with multi-table
+# inheritance should delete the associated rows from any ancestor tables, as
+# well as any descendent objects.
+
+>>> ident = ItalianRestaurant.objects.all()[0].id
+>>> Place.objects.get(pk=ident)
+<Place: Guido's All New House of Pasta the place>
+>>> xx = Restaurant.objects.create(name='a', address='xx', serves_hot_dogs=True, serves_pizza=False)
+
+# This should delete both Restuarants, plus the related places, plus the ItalianRestaurant.
+>>> Restaurant.objects.all().delete()
+
+>>> Place.objects.get(pk=ident)
+Traceback (most recent call last):
+...
+DoesNotExist: Place matching query does not exist.
+
+>>> ItalianRestaurant.objects.get(pk=ident)
+Traceback (most recent call last):
+...
+DoesNotExist: ItalianRestaurant matching query does not exist.
+
"""}
diff --git a/tests/regressiontests/model_inheritance_select_related/__init__.py b/tests/regressiontests/model_inheritance_select_related/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/model_inheritance_select_related/__init__.py
diff --git a/tests/regressiontests/model_inheritance_select_related/models.py b/tests/regressiontests/model_inheritance_select_related/models.py
new file mode 100644
index 0000000000..a97ecaadea
--- /dev/null
+++ b/tests/regressiontests/model_inheritance_select_related/models.py
@@ -0,0 +1,47 @@
+"""
+Regression tests for the interaction between model inheritance and
+select_related().
+"""
+
+from django.db import models
+
+class Place(models.Model):
+ name = models.CharField(max_length=50)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return u"%s the place" % self.name
+
+class Restaurant(Place):
+ serves_sushi = models.BooleanField()
+ serves_steak = models.BooleanField()
+
+ def __unicode__(self):
+ return u"%s the restaurant" % self.name
+
+class Person(models.Model):
+ name = models.CharField(max_length=50)
+ favorite_restaurant = models.ForeignKey(Restaurant)
+
+ def __unicode__(self):
+ return self.name
+
+__test__ = {'API_TESTS':"""
+Regression test for #7246
+
+>>> r1 = Restaurant.objects.create(name="Nobu", serves_sushi=True, serves_steak=False)
+>>> r2 = Restaurant.objects.create(name="Craft", serves_sushi=False, serves_steak=True)
+>>> p1 = Person.objects.create(name="John", favorite_restaurant=r1)
+>>> p2 = Person.objects.create(name="Jane", favorite_restaurant=r2)
+
+>>> Person.objects.order_by('name').select_related()
+[<Person: Jane>, <Person: John>]
+
+>>> jane = Person.objects.order_by('name').select_related('favorite_restaurant')[0]
+>>> jane.favorite_restaurant.name
+u'Craft'
+
+"""}
+
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
index c02ad73998..566411e513 100644
--- a/tests/regressiontests/queries/models.py
+++ b/tests/regressiontests/queries/models.py
@@ -3,13 +3,15 @@ Various complex queries that have been problematic in the past.
"""
import datetime
+import pickle
from django.db import models
from django.db.models.query import Q
class Tag(models.Model):
name = models.CharField(max_length=10)
- parent = models.ForeignKey('self', blank=True, null=True)
+ parent = models.ForeignKey('self', blank=True, null=True,
+ related_name='children')
def __unicode__(self):
return self.name
@@ -24,6 +26,14 @@ class Note(models.Model):
def __unicode__(self):
return self.note
+class Annotation(models.Model):
+ name = models.CharField(max_length=10)
+ tag = models.ForeignKey(Tag)
+ notes = models.ManyToManyField(Note)
+
+ def __unicode__(self):
+ return self.name
+
class ExtraInfo(models.Model):
info = models.CharField(max_length=100)
note = models.ForeignKey(Note)
@@ -162,85 +172,67 @@ class Child(models.Model):
person = models.OneToOneField(Member, primary_key=True)
parent = models.ForeignKey(Member, related_name="children")
+# Custom primary keys interfered with ordering in the past.
+class CustomPk(models.Model):
+ name = models.CharField(max_length=10, primary_key=True)
+ extra = models.CharField(max_length=10)
+
+ class Meta:
+ ordering = ['name', 'extra']
+
+class Related(models.Model):
+ custom = models.ForeignKey(CustomPk)
+
__test__ = {'API_TESTS':"""
->>> t1 = Tag(name='t1')
->>> t1.save()
->>> t2 = Tag(name='t2', parent=t1)
->>> t2.save()
->>> t3 = Tag(name='t3', parent=t1)
->>> t3.save()
->>> t4 = Tag(name='t4', parent=t3)
->>> t4.save()
->>> t5 = Tag(name='t5', parent=t3)
->>> t5.save()
+>>> t1 = Tag.objects.create(name='t1')
+>>> t2 = Tag.objects.create(name='t2', parent=t1)
+>>> t3 = Tag.objects.create(name='t3', parent=t1)
+>>> t4 = Tag.objects.create(name='t4', parent=t3)
+>>> t5 = Tag.objects.create(name='t5', parent=t3)
->>> n1 = Note(note='n1', misc='foo')
->>> n1.save()
->>> n2 = Note(note='n2', misc='bar')
->>> n2.save()
->>> n3 = Note(note='n3', misc='foo')
->>> n3.save()
+>>> n1 = Note.objects.create(note='n1', misc='foo')
+>>> n2 = Note.objects.create(note='n2', misc='bar')
+>>> n3 = Note.objects.create(note='n3', misc='foo')
Create these out of order so that sorting by 'id' will be different to sorting
by 'info'. Helps detect some problems later.
->>> e2 = ExtraInfo(info='e2', note=n2)
->>> e2.save()
->>> e1 = ExtraInfo(info='e1', note=n1)
->>> e1.save()
+>>> e2 = ExtraInfo.objects.create(info='e2', note=n2)
+>>> e1 = ExtraInfo.objects.create(info='e1', note=n1)
->>> a1 = Author(name='a1', num=1001, extra=e1)
->>> a1.save()
->>> a2 = Author(name='a2', num=2002, extra=e1)
->>> a2.save()
->>> a3 = Author(name='a3', num=3003, extra=e2)
->>> a3.save()
->>> a4 = Author(name='a4', num=4004, extra=e2)
->>> a4.save()
+>>> a1 = Author.objects.create(name='a1', num=1001, extra=e1)
+>>> a2 = Author.objects.create(name='a2', num=2002, extra=e1)
+>>> a3 = Author.objects.create(name='a3', num=3003, extra=e2)
+>>> a4 = Author.objects.create(name='a4', num=4004, extra=e2)
>>> time1 = datetime.datetime(2007, 12, 19, 22, 25, 0)
>>> time2 = datetime.datetime(2007, 12, 19, 21, 0, 0)
>>> time3 = datetime.datetime(2007, 12, 20, 22, 25, 0)
>>> time4 = datetime.datetime(2007, 12, 20, 21, 0, 0)
->>> i1 = Item(name='one', created=time1, modified=time1, creator=a1, note=n3)
->>> i1.save()
+>>> i1 = Item.objects.create(name='one', created=time1, modified=time1, creator=a1, note=n3)
>>> i1.tags = [t1, t2]
->>> i2 = Item(name='two', created=time2, creator=a2, note=n2)
->>> i2.save()
+>>> i2 = Item.objects.create(name='two', created=time2, creator=a2, note=n2)
>>> i2.tags = [t1, t3]
->>> i3 = Item(name='three', created=time3, creator=a2, note=n3)
->>> i3.save()
->>> i4 = Item(name='four', created=time4, creator=a4, note=n3)
->>> i4.save()
+>>> i3 = Item.objects.create(name='three', created=time3, creator=a2, note=n3)
+>>> i4 = Item.objects.create(name='four', created=time4, creator=a4, note=n3)
>>> i4.tags = [t4]
->>> r1 = Report(name='r1', creator=a1)
->>> r1.save()
->>> r2 = Report(name='r2', creator=a3)
->>> r2.save()
->>> r3 = Report(name='r3')
->>> r3.save()
+>>> r1 = Report.objects.create(name='r1', creator=a1)
+>>> r2 = Report.objects.create(name='r2', creator=a3)
+>>> r3 = Report.objects.create(name='r3')
Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the Meta.ordering
will be rank3, rank2, rank1.
->>> rank1 = Ranking(rank=2, author=a2)
->>> rank1.save()
->>> rank2 = Ranking(rank=1, author=a3)
->>> rank2.save()
->>> rank3 = Ranking(rank=3, author=a1)
->>> rank3.save()
+>>> rank1 = Ranking.objects.create(rank=2, author=a2)
+>>> rank2 = Ranking.objects.create(rank=1, author=a3)
+>>> rank3 = Ranking.objects.create(rank=3, author=a1)
->>> c1 = Cover(title="first", item=i4)
->>> c1.save()
->>> c2 = Cover(title="second", item=i2)
->>> c2.save()
+>>> c1 = Cover.objects.create(title="first", item=i4)
+>>> c2 = Cover.objects.create(title="second", item=i2)
->>> n1 = Number(num=4)
->>> n1.save()
->>> n2 = Number(num=8)
->>> n2.save()
->>> n3 = Number(num=12)
->>> n3.save()
+>>> num1 = Number.objects.create(num=4)
+>>> num2 = Number.objects.create(num=8)
+>>> num3 = Number.objects.create(num=12)
Bug #1050
>>> Item.objects.filter(tags__isnull=True)
@@ -346,6 +338,10 @@ Bug #1878, #2939
4
>>> xx.delete()
+Bug #7323
+>>> Item.objects.values('creator', 'name').count()
+4
+
Bug #2253
>>> q1 = Item.objects.order_by('name')
>>> q2 = Item.objects.filter(id=i1.id)
@@ -387,6 +383,10 @@ Bug #4510
>>> Author.objects.filter(report__name='r1')
[<Author: a1>]
+Bug #7378
+>>> a1.report_set.all()
+[<Report: r1>]
+
Bug #5324, #6704
>>> Item.objects.filter(tags__name='t4')
[<Item: four>]
@@ -791,5 +791,19 @@ Empty querysets can be merged with others.
>>> Note.objects.all() & Note.objects.none()
[]
+Bug #7204, #7506 -- make sure querysets with related fields can be pickled. If
+this doesn't crash, it's a Good Thing.
+>>> out = pickle.dumps(Item.objects.all())
+
+Bug #7277
+>>> ann1 = Annotation.objects.create(name='a1', tag=t1)
+>>> ann1.notes.add(n1)
+>>> n1.annotation_set.filter(Q(tag=t5) | Q(tag__children=t5) | Q(tag__children__children=t5))
+[<Annotation: a1>]
+
+Bug #7371
+>>> Related.objects.order_by('custom')
+[]
+
"""}
diff --git a/tests/regressiontests/select_related_regress/__init__.py b/tests/regressiontests/select_related_regress/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/select_related_regress/__init__.py
diff --git a/tests/regressiontests/select_related_regress/models.py b/tests/regressiontests/select_related_regress/models.py
new file mode 100644
index 0000000000..05b851829d
--- /dev/null
+++ b/tests/regressiontests/select_related_regress/models.py
@@ -0,0 +1,60 @@
+from django.db import models
+
+class Building(models.Model):
+ name = models.CharField(max_length=10)
+
+ def __unicode__(self):
+ return u"Building: %s" % self.name
+
+class Device(models.Model):
+ building = models.ForeignKey('Building')
+ name = models.CharField(max_length=10)
+
+ def __unicode__(self):
+ return u"device '%s' in building %s" % (self.name, self.building)
+
+class Port(models.Model):
+ device = models.ForeignKey('Device')
+ port_number = models.CharField(max_length=10)
+
+ def __unicode__(self):
+ return u"%s/%s" % (self.device.name, self.port_number)
+
+class Connection(models.Model):
+ start = models.ForeignKey(Port, related_name='connection_start',
+ unique=True)
+ end = models.ForeignKey(Port, related_name='connection_end', unique=True)
+
+ def __unicode__(self):
+ return u"%s to %s" % (self.start, self.end)
+
+__test__ = {'API_TESTS': """
+Regression test for bug #7110. When using select_related(), we must query the
+Device and Building tables using two different aliases (each) in order to
+differentiate the start and end Connection fields. The net result is that both
+the "connections = ..." queries here should give the same results.
+
+>>> b=Building.objects.create(name='101')
+>>> dev1=Device.objects.create(name="router", building=b)
+>>> dev2=Device.objects.create(name="switch", building=b)
+>>> dev3=Device.objects.create(name="server", building=b)
+>>> port1=Port.objects.create(port_number='4',device=dev1)
+>>> port2=Port.objects.create(port_number='7',device=dev2)
+>>> port3=Port.objects.create(port_number='1',device=dev3)
+>>> c1=Connection.objects.create(start=port1, end=port2)
+>>> c2=Connection.objects.create(start=port2, end=port3)
+
+>>> connections=Connection.objects.filter(start__device__building=b, end__device__building=b).order_by('id')
+>>> [(c.id, unicode(c.start), unicode(c.end)) for c in connections]
+[(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')]
+
+>>> connections=Connection.objects.filter(start__device__building=b, end__device__building=b).select_related().order_by('id')
+>>> [(c.id, unicode(c.start), unicode(c.end)) for c in connections]
+[(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')]
+
+# This final query should only join seven tables (port, device and building
+# twice each, plus connection once).
+>>> connections.query.count_active_tables()
+7
+
+"""}
diff --git a/tests/regressiontests/string_lookup/models.py b/tests/regressiontests/string_lookup/models.py
index 1bdb2d4452..39e7955592 100644
--- a/tests/regressiontests/string_lookup/models.py
+++ b/tests/regressiontests/string_lookup/models.py
@@ -97,6 +97,12 @@ __test__ = {'API_TESTS': ur"""
>>> Article.objects.get(text__exact='The quick brown fox jumps over the lazy dog.')
<Article: Article Test>
+# Regression tests for #2170: test case sensitiveness
+>>> Article.objects.filter(text__exact='tHe qUick bRown fOx jUmps over tHe lazy dog.')
+[]
+>>> Article.objects.filter(text__iexact='tHe qUick bRown fOx jUmps over tHe lazy dog.')
+[<Article: Article Test>]
+
>>> Article.objects.get(text__contains='quick brown fox')
<Article: Article Test>
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
index a204ec3e72..1eb55e312e 100644
--- a/tests/regressiontests/test_client_regress/models.py
+++ b/tests/regressiontests/test_client_regress/models.py
@@ -6,6 +6,7 @@ from django.test import Client, TestCase
from django.core.urlresolvers import reverse
from django.core.exceptions import SuspiciousOperation
import os
+import sha
class AssertContainsTests(TestCase):
def test_contains(self):
@@ -240,16 +241,6 @@ class AssertFormErrorTests(TestCase):
except AssertionError, e:
self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )")
-class FileUploadTests(TestCase):
- def test_simple_upload(self):
- fd = open(os.path.join(os.path.dirname(__file__), "views.py"))
- post_data = {
- 'name': 'Ringo',
- 'file_field': fd,
- }
- response = self.client.post('/test_client_regress/file_upload/', post_data)
- self.assertEqual(response.status_code, 200)
-
class LoginTests(TestCase):
fixtures = ['testdata']
@@ -269,7 +260,6 @@ class LoginTests(TestCase):
# default client.
self.assertRedirects(response, "http://testserver/test_client_regress/get_view/")
-
class URLEscapingTests(TestCase):
def test_simple_argument_get(self):
"Get a view that has a simple string argument"
@@ -318,3 +308,22 @@ class ExceptionTests(TestCase):
self.client.get("/test_client_regress/staff_only/")
except SuspiciousOperation:
self.fail("Staff should be able to visit this page")
+
+# We need two different tests to check URLconf subsitution - one to check
+# it was changed, and another one (without self.urls) to check it was reverted on
+# teardown. This pair of tests relies upon the alphabetical ordering of test execution.
+class UrlconfSubstitutionTests(TestCase):
+ urls = 'regressiontests.test_client_regress.urls'
+
+ def test_urlconf_was_changed(self):
+ "TestCase can enforce a custom URLConf on a per-test basis"
+ url = reverse('arg_view', args=['somename'])
+ self.assertEquals(url, '/arg_view/somename/')
+
+# This test needs to run *after* UrlconfSubstitutionTests; the zz prefix in the
+# name is to ensure alphabetical ordering.
+class zzUrlconfSubstitutionTests(TestCase):
+ def test_urlconf_was_reverted(self):
+ "URLconf is reverted to original value after modification in a TestCase"
+ url = reverse('arg_view', args=['somename'])
+ self.assertEquals(url, '/test_client_regress/arg_view/somename/')
diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py
index dc26d1260a..12f6afacf3 100644
--- a/tests/regressiontests/test_client_regress/urls.py
+++ b/tests/regressiontests/test_client_regress/urls.py
@@ -3,7 +3,6 @@ import views
urlpatterns = patterns('',
(r'^no_template_view/$', views.no_template_view),
- (r'^file_upload/$', views.file_upload_view),
(r'^staff_only/$', views.staff_only_view),
(r'^get_view/$', views.get_view),
url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'),
diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py
index 9632c17284..d703c82124 100644
--- a/tests/regressiontests/test_client_regress/views.py
+++ b/tests/regressiontests/test_client_regress/views.py
@@ -1,36 +1,18 @@
-import os
-
from django.contrib.auth.decorators import login_required
-from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError
+from django.http import HttpResponse, HttpResponseRedirect
from django.core.exceptions import SuspiciousOperation
def no_template_view(request):
"A simple view that expects a GET request, and returns a rendered template"
return HttpResponse("No template used. Sample content: twice once twice. Content ends.")
-def file_upload_view(request):
- """
- Check that a file upload can be updated into the POST dictionary without
- going pear-shaped.
- """
- form_data = request.POST.copy()
- form_data.update(request.FILES)
- if isinstance(form_data['file_field'], dict) and isinstance(form_data['name'], unicode):
- # If a file is posted, the dummy client should only post the file name,
- # not the full path.
- if os.path.dirname(form_data['file_field']['filename']) != '':
- return HttpResponseServerError()
- return HttpResponse('')
- else:
- return HttpResponseServerError()
-
def staff_only_view(request):
"A view that can only be visited by staff. Non staff members get an exception"
if request.user.is_staff:
return HttpResponse('')
else:
raise SuspiciousOperation()
-
+
def get_view(request):
"A simple login protected view"
return HttpResponse("Hello world")
@@ -51,4 +33,4 @@ def view_with_argument(request, name):
def login_protected_redirect_view(request):
"A view that redirects all requests to the GET view"
return HttpResponseRedirect('/test_client_regress/get_view/')
-login_protected_redirect_view = login_required(login_protected_redirect_view) \ No newline at end of file
+login_protected_redirect_view = login_required(login_protected_redirect_view)
diff --git a/tests/urls.py b/tests/urls.py
index dbdf9a8064..cea453ef37 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -5,6 +5,9 @@ urlpatterns = patterns('',
(r'^test_client/', include('modeltests.test_client.urls')),
(r'^test_client_regress/', include('regressiontests.test_client_regress.urls')),
+ # File upload test views
+ (r'^file_uploads/', include('regressiontests.file_uploads.urls')),
+
# Always provide the auth system login and logout views
(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
(r'^accounts/logout/$', 'django.contrib.auth.views.logout'),