diff options
| author | Joseph Kocherhans <joseph@jkocherhans.com> | 2006-11-06 21:11:49 +0000 |
|---|---|---|
| committer | Joseph Kocherhans <joseph@jkocherhans.com> | 2006-11-06 21:11:49 +0000 |
| commit | dc59c670b8cbe055ff3565f8d5a2f600c5ab1ba8 (patch) | |
| tree | 84a2d1a3e729a813cf692ebbe7404ba8e5687d22 | |
| parent | bf629e5a4d1a51f938c84d35d768830158ad5ebd (diff) | |
Merged to [3519]
git-svn-id: http://code.djangoproject.com/svn/django/branches/generic-auth@4024 bcc190cf-cafb-0310-a4f2-bffc1f526a37
207 files changed, 9922 insertions, 2403 deletions
@@ -16,12 +16,23 @@ before Simon departed and currently oversees things with Adrian. Wilson Miner <http://www.wilsonminer.com/>, who designed Django's admin interface, pretty error pages, official Web site (djangoproject.com) and has -made many other contributions. +made many other contributions. He makes us look good. + +Malcolm Tredinnick <http://www.pointy-stick.com/blog/>, who has made +significant contributions to all levels of the framework, from its database +layer to template system and documentation. Georg "Hugo" Bauer <http://hugo.muensterland.org/>, who added internationalization support, manages i18n contributions and has made a ton of excellent tweaks, feature additions and bug fixes. +Luke Plant <http://lukeplant.me.uk/>, who has contributed many excellent +improvements, including database-level improvements, the CSRF middleware and +unit tests. + +Russell Keith-Magee <freakboy@iinet.net.au>, who has contributed many excellent +improvements, including refactoring of the Django ORM code and unit tests. + Robert Wittams <http://robert.wittams.com/>, who majorly refactored the Django admin application to allow for easier reuse and has made a ton of excellent tweaks, feature additions and bug fixes. @@ -54,10 +65,13 @@ answer newbie questions, and generally made Django that much better: Jason Davies (Esaj) <http://www.jasondavies.com/> Alex Dedul deric@monowerks.com + dne@mayonnaise.net Jeremy Dunck <http://dunck.us/> Clint Ecker gandalf@owca.info Baishampayan Ghose + martin.glueck@gmail.com + Simon Greenhill <dev@simon.net.nz> Espen Grindhaug <http://grindhaug.org/> Brant Harris hipertracker@gmail.com @@ -69,8 +83,8 @@ answer newbie questions, and generally made Django that much better: Michael Josephson <http://www.sdjournal.com/> jpellerin@gmail.com junzhang.jn@gmail.com - Russell Keith-Magee <freakboy@iinet.net.au> Garth Kidd <http://www.deadlybloodyserious.com/> + kilian <kilian.cavalotti@lip6.fr> Sune Kirkeby <http://ibofobi.dk/> Cameron Knight (ckknight) Bruce Kroeze <http://coderseye.com/> @@ -96,6 +110,7 @@ answer newbie questions, and generally made Django that much better: Sam Newman <http://www.magpiebrain.com/> Neal Norwitz <nnorwitz@google.com> oggie rob <oz.robharvey@gmail.com> + Jay Parlar <parlar@gmail.com> pgross@thoughtworks.com phaedo <http://phaedo.cx/> phil@produxion.net @@ -116,7 +131,6 @@ answer newbie questions, and generally made Django that much better: Tom Tobin Tom Insam Joe Topjian <http://joe.terrarum.net/geek/code/python/django/> - Malcolm Tredinnick Amit Upadhyay Geert Vanderkelen Milton Waddams @@ -1,7 +1,22 @@ Thanks for downloading Django. -To install it, make sure you have Python 2.3 or greater installed. Then run this command: +To install it, make sure you have Python 2.3 or greater installed. Then run +this command from the command prompt: -python setup.py install + python setup.py install + +Note this requires a working Internet connection if you don't already have the +Python utility "setuptools" installed. + +AS AN ALTERNATIVE, you can just copy the entire "django" directory to Python's +site-packages directory, which is located wherever your Python installation +lives. Some places you might check are: + + /usr/lib/python2.4/site-packages (Unix, Python 2.4) + /usr/lib/python2.3/site-packages (Unix, Python 2.3) + C:\\PYTHON\site-packages (Windows) + +This second solution does not require a working Internet connection; it +bypasses "setuptools" entirely. For more detailed instructions, see docs/install.txt. diff --git a/django/__init__.py b/django/__init__.py index 00c6f82478..5d5461c867 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1 +1 @@ -VERSION = (0, 95, 'post-magic-removal') +VERSION = (0, 96, 'pre') diff --git a/django/bin/compile-messages.py b/django/bin/compile-messages.py index e33fdd780b..5f653df95d 100755 --- a/django/bin/compile-messages.py +++ b/django/bin/compile-messages.py @@ -2,7 +2,6 @@ import os import sys -import getopt def compile_messages(): basedir = None diff --git a/django/bin/make-messages.py b/django/bin/make-messages.py index 75b0bc0ca0..557cb5eeec 100755 --- a/django/bin/make-messages.py +++ b/django/bin/make-messages.py @@ -1,5 +1,9 @@ #!/usr/bin/env python +# Need to ensure that the i18n framework is enabled +from django.conf import settings +settings.configure(USE_I18N = True) + from django.utils.translation import templatize import re import os diff --git a/django/conf/__init__.py b/django/conf/__init__.py index d5477201d7..1a04bbfb02 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -7,7 +7,6 @@ a list of all possible variables. """ import os -import sys from django.conf import global_settings ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" @@ -116,7 +115,7 @@ class UserSettingsHolder(object): """ Holder for user configured settings. """ - # SETTINGS_MODULE does not really make sense in the manually configured + # SETTINGS_MODULE doesn't make much sense in the manually configured # (standalone) case. SETTINGS_MODULE = None @@ -135,6 +134,13 @@ class UserSettingsHolder(object): settings = LazySettings() -# install the translation machinery so that it is available -from django.utils import translation -translation.install() +# This function replaces itself with django.utils.translation.gettext() the +# first time it's run. This is necessary because the import of +# django.utils.translation requires a working settings module, and loading it +# from within this file would cause a circular import. +def first_time_gettext(*args): + from django.utils.translation import gettext + __builtins__['_'] = gettext + return gettext(*args) + +__builtins__['_'] = first_time_gettext diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 640b290a6f..a00e2ba4eb 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -1,7 +1,9 @@ # Default Django settings. Override these with settings in the module # pointed-to by the DJANGO_SETTINGS_MODULE environment variable. -from django.utils.translation import gettext_lazy as _ +# This is defined here as a do-nothing function because we can't import +# django.utils.translation -- that module depends on the settings. +gettext_noop = lambda s: s #################### # CORE # @@ -34,38 +36,44 @@ LANGUAGE_CODE = 'en-us' # Languages we provide translations for, out of the box. The language name # should be the utf-8 encoded local name for the language. LANGUAGES = ( - ('bn', _('Bengali')), - ('cs', _('Czech')), - ('cy', _('Welsh')), - ('da', _('Danish')), - ('de', _('German')), - ('el', _('Greek')), - ('en', _('English')), - ('es', _('Spanish')), - ('es_AR', _('Argentinean Spanish')), - ('fr', _('French')), - ('gl', _('Galician')), - ('hu', _('Hungarian')), - ('he', _('Hebrew')), - ('is', _('Icelandic')), - ('it', _('Italian')), - ('ja', _('Japanese')), - ('nl', _('Dutch')), - ('no', _('Norwegian')), - ('pt-br', _('Brazilian')), - ('ro', _('Romanian')), - ('ru', _('Russian')), - ('sk', _('Slovak')), - ('sl', _('Slovenian')), - ('sr', _('Serbian')), - ('sv', _('Swedish')), - ('uk', _('Ukrainian')), - ('zh-cn', _('Simplified Chinese')), - ('zh-tw', _('Traditional Chinese')), + ('ar', gettext_noop('Arabic')), + ('bn', gettext_noop('Bengali')), + ('cs', gettext_noop('Czech')), + ('cy', gettext_noop('Welsh')), + ('da', gettext_noop('Danish')), + ('de', gettext_noop('German')), + ('el', gettext_noop('Greek')), + ('en', gettext_noop('English')), + ('es', gettext_noop('Spanish')), + ('es_AR', gettext_noop('Argentinean Spanish')), + ('fr', gettext_noop('French')), + ('gl', gettext_noop('Galician')), + ('hu', gettext_noop('Hungarian')), + ('he', gettext_noop('Hebrew')), + ('is', gettext_noop('Icelandic')), + ('it', gettext_noop('Italian')), + ('ja', gettext_noop('Japanese')), + ('nl', gettext_noop('Dutch')), + ('no', gettext_noop('Norwegian')), + ('pt-br', gettext_noop('Brazilian')), + ('ro', gettext_noop('Romanian')), + ('ru', gettext_noop('Russian')), + ('sk', gettext_noop('Slovak')), + ('sl', gettext_noop('Slovenian')), + ('sr', gettext_noop('Serbian')), + ('sv', gettext_noop('Swedish')), + ('ta', gettext_noop('Tamil')), + ('uk', gettext_noop('Ukrainian')), + ('zh-cn', gettext_noop('Simplified Chinese')), + ('zh-tw', gettext_noop('Traditional Chinese')), ) # Languages using BiDi (right-to-left) layout -LANGUAGES_BIDI = ("he",) +LANGUAGES_BIDI = ("he", "ar") + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True # Not-necessarily-technical managers of the site. They get broken link # notifications and other various e-mails. @@ -281,3 +289,9 @@ COMMENTS_FIRST_FEW = 0 # A tuple of IP addresses that have been banned from participating in various # Django-powered features. BANNED_IPS = () + +################## +# AUTHENTICATION # +################## + +AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',) diff --git a/django/conf/locale/ar/LC_MESSAGES/django.mo b/django/conf/locale/ar/LC_MESSAGES/django.mo Binary files differnew file mode 100644 index 0000000000..323e53321d --- /dev/null +++ b/django/conf/locale/ar/LC_MESSAGES/django.mo diff --git a/django/conf/locale/ar/LC_MESSAGES/django.po b/django/conf/locale/ar/LC_MESSAGES/django.po new file mode 100644 index 0000000000..203d50d7de --- /dev/null +++ b/django/conf/locale/ar/LC_MESSAGES/django.po @@ -0,0 +1,1989 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the django package. +# Ahmad Alhashemi <trans@ahmadh.com>, 2006. +# +msgid "" +msgstr "" +"Project-Id-Version: Django SVN\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-07-03 19:22+0300\n" +"PO-Revision-Date: 2006-07-06 23:46+0300\n" +"Last-Translator: Ahmad Alhashemi <ahmad@ahmadh.com>\n" +"Language-Team: Ahmad Alhashemi <trans@ahmadh.com>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n == 1? 0 : (n == 2? 1 : (n <= 10? 2 : 3)));\n" +"X-Poedit-Language: Arabic\n" +"X-Poedit-Country: KUWAIT\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: .\conf\global_settings.py:37 +msgid "Bengali" +msgstr "بنغالي" + +#: .\conf\global_settings.py:38 +msgid "Czech" +msgstr "تشيكي" + +#: .\conf\global_settings.py:39 +msgid "Welsh" +msgstr "ويلزي" + +#: .\conf\global_settings.py:40 +msgid "Danish" +msgstr "دنماركي" + +#: .\conf\global_settings.py:41 +msgid "German" +msgstr "ألماني" + +#: .\conf\global_settings.py:42 +msgid "Greek" +msgstr "اغريقي" + +#: .\conf\global_settings.py:43 +msgid "English" +msgstr "انجليزي" + +#: .\conf\global_settings.py:44 +msgid "Spanish" +msgstr "اسباني" + +#: .\conf\global_settings.py:45 +msgid "Argentinean Spanish" +msgstr "اسباني أرجنتيني" + +#: .\conf\global_settings.py:46 +msgid "French" +msgstr "ÙØ±Ù†Ø³ÙŠ" + +#: .\conf\global_settings.py:47 +msgid "Galician" +msgstr "جاليسي" + +#: .\conf\global_settings.py:48 +msgid "Hungarian" +msgstr "هنغاري" + +#: .\conf\global_settings.py:49 +msgid "Hebrew" +msgstr "عبري" + +#: .\conf\global_settings.py:50 +msgid "Icelandic" +msgstr "آيسلندي" + +#: .\conf\global_settings.py:51 +msgid "Italian" +msgstr "ايطالي" + +#: .\conf\global_settings.py:52 +msgid "Japanese" +msgstr "ياباني" + +#: .\conf\global_settings.py:53 +msgid "Dutch" +msgstr "هولندي" + +#: .\conf\global_settings.py:54 +msgid "Norwegian" +msgstr "نرويجي" + +#: .\conf\global_settings.py:55 +msgid "Brazilian" +msgstr "برازيلي" + +#: .\conf\global_settings.py:56 +msgid "Romanian" +msgstr "روماني" + +#: .\conf\global_settings.py:57 +msgid "Russian" +msgstr "روسي" + +#: .\conf\global_settings.py:58 +msgid "Slovak" +msgstr "Ø³Ù„ÙˆÙØ§Ùƒ" + +#: .\conf\global_settings.py:59 +msgid "Slovenian" +msgstr "Ø³Ù„ÙˆÙØ§ØªÙŠ" + +#: .\conf\global_settings.py:60 +msgid "Serbian" +msgstr "صربي" + +#: .\conf\global_settings.py:61 +msgid "Swedish" +msgstr "سويدي" + +#: .\conf\global_settings.py:62 +msgid "Ukrainian" +msgstr "أكراني" + +#: .\conf\global_settings.py:63 +msgid "Simplified Chinese" +msgstr "صيني مبسط" + +#: .\conf\global_settings.py:64 +msgid "Traditional Chinese" +msgstr "صيني تقليدي" + +#: .\contrib\admin\filterspecs.py:40 +#, python-format +msgid "" +"<h3>By %s:</h3>\n" +"<ul>\n" +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 +msgid "All" +msgstr "ÙƒØ§ÙØ©" + +#: .\contrib\admin\filterspecs.py:109 +msgid "Any date" +msgstr "أي تاريخ" + +#: .\contrib\admin\filterspecs.py:110 +msgid "Today" +msgstr "اليوم" + +#: .\contrib\admin\filterspecs.py:113 +msgid "Past 7 days" +msgstr "الأيام 7 الماضية" + +#: .\contrib\admin\filterspecs.py:115 +msgid "This month" +msgstr "هذا الشهر" + +#: .\contrib\admin\filterspecs.py:117 +msgid "This year" +msgstr "هذه السنة" + +#: .\contrib\admin\filterspecs.py:143 +msgid "Yes" +msgstr "نعم" + +#: .\contrib\admin\filterspecs.py:143 +msgid "No" +msgstr "لا" + +#: .\contrib\admin\filterspecs.py:150 +msgid "Unknown" +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 "ممثل العنصر" + +#: .\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\templates\admin\404.html.py:4 +#: .\contrib\admin\templates\admin\404.html.py:8 +msgid "Page not found" +msgstr "تعذر العثور على Ø§Ù„ØµÙØØ©" + +#: .\contrib\admin\templates\admin\404.html.py:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Ù†ØÙ† آسÙون، لكننا لم نعثر على Ø§Ù„ØµÙØØ© المطلوبة." + +#: .\contrib\admin\templates\admin\500.html.py:4 +#: .\contrib\admin\templates\admin\base.html.py:29 +#: .\contrib\admin\templates\admin\change_form.html.py:13 +#: .\contrib\admin\templates\admin\change_list.html.py:6 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:6 +#: .\contrib\admin\templates\admin\invalid_setup.html.py:4 +#: .\contrib\admin\templates\admin\object_history.html.py:5 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:3 +#: .\contrib\admin\templates\registration\logged_out.html.py:4 +#: .\contrib\admin\templates\registration\password_change_done.html.py:4 +#: .\contrib\admin\templates\registration\password_change_form.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_done.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:4 +msgid "Home" +msgstr "الرئيسية" + +#: .\contrib\admin\templates\admin\500.html.py:4 +msgid "Server error" +msgstr "خطأ ÙÙŠ المزود" + +#: .\contrib\admin\templates\admin\500.html.py:6 +msgid "Server error (500)" +msgstr "خطأ ÙÙŠ المزود (500)" + +#: .\contrib\admin\templates\admin\500.html.py:9 +msgid "Server Error <em>(500)</em>" +msgstr "خطأ ÙÙŠ المزود <em>(500)</em>" + +#: .\contrib\admin\templates\admin\500.html.py: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\base.html.py:24 +msgid "Welcome," +msgstr "أهلا، " + +#: .\contrib\admin\templates\admin\base.html.py:24 +#: .\contrib\admin\templates\admin\change_form.html.py:10 +#: .\contrib\admin\templates\admin\change_list.html.py:5 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:3 +#: .\contrib\admin\templates\admin\object_history.html.py:3 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:3 +#: .\contrib\admin\templates\registration\password_change_done.html.py:3 +#: .\contrib\admin\templates\registration\password_change_form.html.py:3 +msgid "Documentation" +msgstr "التعليمات" + +#: .\contrib\admin\templates\admin\base.html.py:24 +#: .\contrib\admin\templates\admin\change_form.html.py:10 +#: .\contrib\admin\templates\admin\change_list.html.py:5 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:3 +#: .\contrib\admin\templates\admin\object_history.html.py:3 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:4 +#: .\contrib\admin\templates\admin_doc\index.html.py:4 +#: .\contrib\admin\templates\admin_doc\missing_docutils.html.py:4 +#: .\contrib\admin\templates\admin_doc\model_detail.html.py:3 +#: .\contrib\admin\templates\admin_doc\model_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\template_filter_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_tag_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\view_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\view_index.html.py:5 +#: .\contrib\admin\templates\registration\password_change_done.html.py:3 +#: .\contrib\admin\templates\registration\password_change_form.html.py:3 +msgid "Change password" +msgstr "تغيير كلمة المرور" + +#: .\contrib\admin\templates\admin\base.html.py:24 +#: .\contrib\admin\templates\admin\change_form.html.py:10 +#: .\contrib\admin\templates\admin\change_list.html.py:5 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:3 +#: .\contrib\admin\templates\admin\object_history.html.py:3 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:4 +#: .\contrib\admin\templates\admin_doc\index.html.py:4 +#: .\contrib\admin\templates\admin_doc\missing_docutils.html.py:4 +#: .\contrib\admin\templates\admin_doc\model_detail.html.py:3 +#: .\contrib\admin\templates\admin_doc\model_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\template_filter_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_tag_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\view_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\view_index.html.py:5 +#: .\contrib\admin\templates\registration\password_change_done.html.py:3 +#: .\contrib\admin\templates\registration\password_change_form.html.py:3 +#: .\contrib\comments\templates\comments\form.html.py:8 +msgid "Log out" +msgstr "خروج" + +#: .\contrib\admin\templates\admin\base_site.html.py:4 +msgid "Django site admin" +msgstr "إدارة موقع جاننغو" + +#: .\contrib\admin\templates\admin\base_site.html.py:7 +msgid "Django administration" +msgstr "إدارة جانغو" + +#: .\contrib\admin\templates\admin\change_form.html.py:15 +#: .\contrib\admin\templates\admin\index.html.py:28 +msgid "Add" +msgstr "Ø¥Ø¶Ø§ÙØ©" + +#: .\contrib\admin\templates\admin\change_form.html.py:20 +#: .\contrib\admin\templates\admin\object_history.html.py:5 +msgid "History" +msgstr "تاريخ" + +#: .\contrib\admin\templates\admin\change_form.html.py:21 +msgid "View on site" +msgstr "عرض على الموقع" + +#: .\contrib\admin\templates\admin\change_form.html.py:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "الرجاء Ø§ØµÙ„Ø§Ø Ø§Ù„Ø®Ø·Ø£ التالي." +msgstr[1] "الرجاء Ø§ØµÙ„Ø§Ø Ø§Ù„Ø®Ø·Ø¦ÙŠÙ† التاليين." +msgstr[2] "الرجاء Ø§ØµÙ„Ø§Ø Ø§Ù„Ø£Ø®Ø·Ø§Ø¡ التالية." +msgstr[3] "الرجاء Ø§ØµÙ„Ø§Ø Ø§Ù„Ø£Ø®Ø·Ø§Ø¡ التالية." + +#: .\contrib\admin\templates\admin\change_form.html.py:48 +msgid "Ordering" +msgstr "الترتيب" + +#: .\contrib\admin\templates\admin\change_form.html.py:51 +msgid "Order:" +msgstr "الترتيب" + +#: .\contrib\admin\templates\admin\change_list.html.py:11 +#, python-format +msgid "Add %(name)s" +msgstr "Ø§Ø¶Ø§ÙØ© %(name)s" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:9 +#: .\contrib\admin\templates\admin\submit_line.html.py:3 +msgid "Delete" +msgstr "ØØ°Ù" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:14 +#, python-format +msgid "Deleting the %(object_name)s '%(object)s' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:" +msgstr "ØØ°Ù %(object_name)s '%(object)s' سيؤدي إلى ØØ°Ù العناصر المتعلقة به، لكن هذا Ø§Ù„ØØ³Ø§Ø¨ لا يملك الصلاØÙŠØ© Ù„ØØ°Ù أنواع العناصر التالية:" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:21 +#, python-format +msgid "Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of the following related items will be deleted:" +msgstr "هل أنت متأكد من أنك تريد ØØ°Ù %(object_name)s \"%(object)s\"? ÙƒØ§ÙØ© العناصر التالية المتعلقة به سيتم ØØ°Ùها:" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:26 +msgid "Yes, I'm sure" +msgstr "نعم، أنا متأكد" + +#: .\contrib\admin\templates\admin\filter.html.py:2 +#, python-format +msgid " By %(title)s " +msgstr " بواسطة %(title)s " + +#: .\contrib\admin\templates\admin\filters.html.py:4 +msgid "Filter" +msgstr "مرشØ" + +#: .\contrib\admin\templates\admin\index.html.py:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "النماذج Ø§Ù„Ù…ØªÙˆÙØ±Ø© ÙÙŠ برنامج %(name)s." + +#: .\contrib\admin\templates\admin\index.html.py:34 +msgid "Change" +msgstr "تغيير" + +#: .\contrib\admin\templates\admin\index.html.py:44 +msgid "You don't have permission to edit anything." +msgstr "ليست لديك الصلاØÙŠØ© لتعديل أي شيء." + +#: .\contrib\admin\templates\admin\index.html.py:52 +msgid "Recent Actions" +msgstr "العمليات الأخيرة" + +#: .\contrib\admin\templates\admin\index.html.py:53 +msgid "My Actions" +msgstr "عملياتي" + +#: .\contrib\admin\templates\admin\index.html.py:57 +msgid "None available" +msgstr "لا يوجد" + +#: .\contrib\admin\templates\admin\invalid_setup.html.py: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\login.html.py:17 +#: .\contrib\comments\templates\comments\form.html.py:6 +#: .\contrib\comments\templates\comments\form.html.py:8 +msgid "Username:" +msgstr "اسم المستخدم:" + +#: .\contrib\admin\templates\admin\login.html.py:20 +#: .\contrib\comments\templates\comments\form.html.py:6 +msgid "Password:" +msgstr "كلمة المرور:" + +#: .\contrib\admin\templates\admin\login.html.py:22 +msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?" +msgstr "Have you <a href=\"/password_reset/\">forgotten your password</a>?" + +#: .\contrib\admin\templates\admin\login.html.py:25 +#: .\contrib\admin\views\decorators.py:24 +msgid "Log in" +msgstr "دخول" + +#: .\contrib\admin\templates\admin\object_history.html.py:18 +msgid "Date/time" +msgstr "التاريخ/الوقت" + +#: .\contrib\admin\templates\admin\object_history.html.py:19 +msgid "User" +msgstr "المستخدم" + +#: .\contrib\admin\templates\admin\object_history.html.py:20 +msgid "Action" +msgstr "العملية" + +#: .\contrib\admin\templates\admin\object_history.html.py:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "" + +#: .\contrib\admin\templates\admin\object_history.html.py:36 +msgid "This object doesn't have a change history. It probably wasn't added via this admin site." +msgstr "هذا العنصر لا يملك تاريخ تغييرات، على الأغلب أن هذا العنصر لم يتم انشاءه عبر نظام الإدارة هذا." + +#: .\contrib\admin\templates\admin\pagination.html.py:10 +msgid "Show all" +msgstr "عرض الكل" + +#: .\contrib\admin\templates\admin\search_form.html.py:8 +msgid "Go" +msgstr "انطلق" + +#: .\contrib\admin\templates\admin\search_form.html.py:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "Ù†ØªÙŠØØ© ÙˆØ§ØØ¯Ø©" +msgstr[1] "نتيجتان" +msgstr[2] "%(counter)s نتائج" +msgstr[3] "%(counter)s Ù†ØªÙŠØØ©" + +#: .\contrib\admin\templates\admin\search_form.html.py:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "المجموع %(full_result_count)s" + +#: .\contrib\admin\templates\admin\submit_line.html.py:4 +msgid "Save as new" +msgstr "ØÙظ كجديد" + +#: .\contrib\admin\templates\admin\submit_line.html.py:5 +msgid "Save and add another" +msgstr "ØÙظ ÙˆØ¥Ø¶Ø§ÙØ© آخر" + +#: .\contrib\admin\templates\admin\submit_line.html.py:6 +msgid "Save and continue editing" +msgstr "ØÙظ واستمرار التعديل" + +#: .\contrib\admin\templates\admin\submit_line.html.py:7 +msgid "Save" +msgstr "ØÙظ" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:3 +msgid "Bookmarklets" +msgstr "أوامر Ø§Ù„Ù…ÙØ¶Ù„Ø©" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:5 +msgid "Documentation bookmarklets" +msgstr "أوامر Ù…ÙØ¶Ù„Ø© التعليمات" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:9 +msgid "" +"\n" +"<p class=\"help\">To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").</p>\n" +msgstr "" +"\n" +"<p class=\"help\">لتركيب أوامر Ø§Ù„Ù…ÙØ¶Ù„ة، قم Ø¨Ø³ØØ¨ الوصلة إلى\n" +"شريط أدات Ø§Ù„Ù…ÙØ¶Ù„ات ÙÙŠ Ù…ØªØµÙØÙƒØŒ أو قم بالضغط عليها بالزر الأيمن وأضÙها إلى Ù…ÙØ¶Ù„اتك.\n" +"سيمكنك الآن أن اختيار أوامر Ø§Ù„Ù…ÙØ¶Ù„Ø© من أي ØµÙØØ© ÙÙŠ الموقع، Ù„Ø§ØØ¸ بأن بعض\n" +"أوامر Ø§Ù„Ù…ÙØ¶Ù„Ø© هذه معدة لتعمل على أجهزة كمبيوتر تعتبر على أنها \"داخلية\"\n" +"(ØªØØ¯Ø« إلى مسؤول النظم إذا لم تكن متأكدا ما إذا كان كمبيوتر يعتبر \"داخليا\").</p>\n" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:19 +msgid "Documentation for this page" +msgstr "التعليمات لهذه Ø§Ù„ØµÙØØ©" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:20 +msgid "Jumps you from any page to the documentation for the view that generates that page." +msgstr "ينتقل بك من أي ØµÙØØ© إلى تعليمات العرض الذي أنشأ هذه Ø§Ù„ØµÙØØ©." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:22 +msgid "Show object ID" +msgstr "عرض معر٠الكائن" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:23 +msgid "Shows the content-type and unique ID for pages that represent a single object." +msgstr "عرض نوع البيانات ÙˆØ§Ù„Ù…Ø¹Ø±Ù Ø§Ù„ÙØ±ÙŠØ¯ Ù„Ù„ØµÙØØ§Øª التي تمثل كائنا ÙˆØ§ØØ¯Ø§." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:25 +msgid "Edit this object (current window)" +msgstr "تعديل هذا الكائن (Ø§Ù„Ù†Ø§ÙØ°Ø© Ø§Ù„ØØ§Ù„ية)" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "ينتقل بك إلى ØµÙØØ© الإدارة Ù„Ù„ØµÙØØ§Øª التي تمثل كائنا ÙˆØ§ØØ¯Ø§." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:28 +msgid "Edit this object (new window)" +msgstr "تعديل هذا العنصر (Ù†Ø§ÙØ°Ø© جديدة)" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:29 +msgid "As above, but opens the admin page in a new window." +msgstr "كما سبق، لكن ÙŠÙØªØ ØµÙØØ© الإدارة ÙÙŠ Ù†Ø§ÙØ°Ø© جديدة." + +#: .\contrib\admin\templates\registration\logged_out.html.py:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "شكرا لك على قضائك بعض الوقت مع الموقع اليوم." + +#: .\contrib\admin\templates\registration\logged_out.html.py:10 +msgid "Log in again" +msgstr "دخول مرة أخرى" + +#: .\contrib\admin\templates\registration\password_change_done.html.py:4 +#: .\contrib\admin\templates\registration\password_change_form.html.py:4 +#: .\contrib\admin\templates\registration\password_change_form.html.py:6 +#: .\contrib\admin\templates\registration\password_change_form.html.py:10 +msgid "Password change" +msgstr "تغيير كلمة المرور" + +#: .\contrib\admin\templates\registration\password_change_done.html.py:6 +#: .\contrib\admin\templates\registration\password_change_done.html.py:10 +msgid "Password change successful" +msgstr "تم تغيير كلمة المرور بنجاØ" + +#: .\contrib\admin\templates\registration\password_change_done.html.py:12 +msgid "Your password was changed." +msgstr "لقد تغيرت كلمة مرورك." + +#: .\contrib\admin\templates\registration\password_change_form.html.py:12 +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.py:17 +msgid "Old password:" +msgstr "كلمة المرور القديمة:" + +#: .\contrib\admin\templates\registration\password_change_form.html.py:19 +msgid "New password:" +msgstr "كلمة المرور الجديدة:" + +#: .\contrib\admin\templates\registration\password_change_form.html.py:21 +msgid "Confirm password:" +msgstr "تأكيد كلمة المرور:" + +#: .\contrib\admin\templates\registration\password_change_form.html.py:23 +msgid "Change my password" +msgstr "تغيير كلمة المرور الخاصة بي" + +#: .\contrib\admin\templates\registration\password_reset_done.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:6 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:10 +msgid "Password reset" +msgstr "اعادة ضبط كلمة المرور" + +#: .\contrib\admin\templates\registration\password_reset_done.html.py:6 +#: .\contrib\admin\templates\registration\password_reset_done.html.py:10 +msgid "Password reset successful" +msgstr "تمت عملية اعادة ضبط كلمة المرور بنجاØ" + +#: .\contrib\admin\templates\registration\password_reset_done.html.py:12 +msgid "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_email.html.py:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "لقد وصلتك رسالة البريد الإلكتروني هذه لأنك طلبت إعادة ضبط كلمة المرور." + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "Ù„ØØ³Ø§Ø¨Ùƒ المستخدم الخاص بك ÙÙŠ %(site_name)s" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "كلمة مرورك الجديدة هي: %(new_password)s" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:7 +msgid "Feel free to change this password by going to this page:" +msgstr "يمكنك تغيير كلمة المرور هذه بالذهاب إلى هذه Ø§Ù„ØµÙØØ©:" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:11 +msgid "Your username, in case you've forgotten:" +msgstr "اسم المستخدم الخاص بك، ÙÙŠ ØØ§Ù„ كنت قد نسيته:" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:13 +msgid "Thanks for using our site!" +msgstr "شكرا لاستخدامك لموقعنا!" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "ÙØ±ÙŠÙ‚ %(site_name)s" + +#: .\contrib\admin\templates\registration\password_reset_form.html.py: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\password_reset_form.html.py:16 +msgid "E-mail address:" +msgstr "عنوان البريد الإلكتروني:" + +#: .\contrib\admin\templates\registration\password_reset_form.html.py:16 +msgid "Reset my password" +msgstr "اعادة ضبط كلمة المرور" + +#: .\contrib\admin\templates\widget\date_time.html.py:3 +msgid "Date:" +msgstr "التاريخ:" + +#: .\contrib\admin\templates\widget\date_time.html.py:4 +msgid "Time:" +msgstr "الوقت:" + +#: .\contrib\admin\templates\widget\file.html.py:2 +msgid "Currently:" +msgstr "ØØ§Ù„يا:" + +#: .\contrib\admin\templates\widget\file.html.py:3 +msgid "Change:" +msgstr "تغيير:" + +#: .\contrib\admin\templatetags\admin_list.py:230 +msgid "All dates" +msgstr "ÙƒØ§ÙØ© التواريخ" + +#: .\contrib\admin\views\decorators.py:10 +#: .\contrib\auth\forms.py:37 +msgid "Please enter a correct username and password. Note that both fields are case-sensitive." +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\views\decorators.py:83 +msgid "Usernames cannot contain the '@' character." +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\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 "أرقام صØÙŠØØ© Ù…ÙØµÙˆÙ„Ø© بÙواصل comma" + +#: .\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 "عنوان IP" + +#: .\contrib\admin\views\doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "ثنائي (إما ØµØ Ø£Ùˆ خطأ أو لاشيء)" + +#: .\contrib\admin\views\doc.py:307 +msgid "Relation to parent model" +msgstr "العلاقة بالنموذج الأب" + +#: .\contrib\admin\views\doc.py:308 +msgid "Phone number" +msgstr "رقم هاتÙ" + +#: .\contrib\admin\views\doc.py:313 +msgid "Text" +msgstr "نص" + +#: .\contrib\admin\views\doc.py:314 +msgid "Time" +msgstr "وقت" + +#: .\contrib\admin\views\doc.py:315 +#: .\contrib\flatpages\models.py:7 +msgid "URL" +msgstr "وصلة" + +#: .\contrib\admin\views\doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "ولاية أمريكية (ØØ±Ùان كبيران)" + +#: .\contrib\admin\views\doc.py:317 +msgid "XML text" +msgstr "نص XML" + +#: .\contrib\admin\views\main.py:226 +msgid "Site administration" +msgstr "إدارة الموقع" + +#: .\contrib\admin\views\main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "تم Ø§Ø¶Ø§ÙØ© %(name)s \"%(obj)s\" بنجاØ." + +#: .\contrib\admin\views\main.py:264 +#: .\contrib\admin\views\main.py:348 +msgid "You may edit it again below." +msgstr "يمكنك تعديله مجددا ÙÙŠ الأسÙÙ„." + +#: .\contrib\admin\views\main.py:272 +#: .\contrib\admin\views\main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "يمكنك Ø¥Ø¶Ø§ÙØ© %s آخر بالأسÙÙ„." + +#: .\contrib\admin\views\main.py:290 +#, python-format +msgid "Add %s" +msgstr "Ø§Ø¶Ø§ÙØ© %s" + +#: .\contrib\admin\views\main.py:336 +#, python-format +msgid "Added %s." +msgstr "اضا٠%s." + +#: .\contrib\admin\views\main.py:336 +#: .\contrib\admin\views\main.py:338 +#: .\contrib\admin\views\main.py:340 +msgid "and" +msgstr "Ùˆ" + +#: .\contrib\admin\views\main.py:338 +#, python-format +msgid "Changed %s." +msgstr "غير %s." + +#: .\contrib\admin\views\main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "ØØ°Ù %s." + +#: .\contrib\admin\views\main.py:343 +msgid "No fields changed." +msgstr "لم يتم تغيير أية ØÙ‚ول." + +#: .\contrib\admin\views\main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "تم تغيير %(name)s \"%(obj)s\" بنجاØ." + +#: .\contrib\admin\views\main.py:354 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "تم Ø§Ø¶Ø§ÙØ© %(name)s \"%(obj)s\" Ø¨Ù†Ø¬Ø§ØØŒ يمكنك تعديله مرة أخرى بالأسÙÙ„." + +#: .\contrib\admin\views\main.py:392 +#, python-format +msgid "Change %s" +msgstr "تغيير %s" + +#: .\contrib\admin\views\main.py:474 +#, 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:479 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "%(fieldname)s ÙˆØ§ØØ¯ أو أكثر ÙÙŠ %(name)s:" + +#: .\contrib\admin\views\main.py:512 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "تم ØØ°Ù %(name)s \"%(obj)s\" بنجاØ." + +#: .\contrib\admin\views\main.py:515 +msgid "Are you sure?" +msgstr "هل أنت متأكد؟" + +#: .\contrib\admin\views\main.py:537 +#, python-format +msgid "Change history: %s" +msgstr "تاريخ التغيير: %s" + +#: .\contrib\admin\views\main.py:571 +#, python-format +msgid "Select %s" +msgstr "اختر %s" + +#: .\contrib\admin\views\main.py:571 +#, python-format +msgid "Select %s to change" +msgstr "اختر %s لتغييره" + +#: .\contrib\admin\views\main.py:747 +msgid "Database error" +msgstr "خطـأ ÙÙŠ قاعدة البيانات" + +#: .\contrib\auth\forms.py:30 +msgid "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in." +msgstr "يبدو بأن الكوكيز غير Ù…ÙØ¹Ù„Ù‡ ÙÙŠ Ù…ØªØµÙØÙƒØŒ الكوكيز مطلوبة للتمكن من الدخول." + +#: .\contrib\auth\forms.py:39 +msgid "This account is inactive." +msgstr "هذا Ø§Ù„ØØ³Ø§Ø¨ غير ÙØ¹Ø§Ù„." + +#: .\contrib\auth\models.py:37 +#: .\contrib\auth\models.py:56 +msgid "name" +msgstr "الاسم" + +#: .\contrib\auth\models.py:39 +msgid "codename" +msgstr "الاسم الرمزي" + +#: .\contrib\auth\models.py:41 +msgid "permission" +msgstr "الصلاØÙŠØ©" + +#: .\contrib\auth\models.py:42 +#: .\contrib\auth\models.py:57 +msgid "permissions" +msgstr "الصلاØÙŠØ§Øª" + +#: .\contrib\auth\models.py:59 +msgid "group" +msgstr "المجموعة" + +#: .\contrib\auth\models.py:60 +#: .\contrib\auth\models.py:99 +msgid "groups" +msgstr "المجموعات" + +#: .\contrib\auth\models.py:89 +msgid "username" +msgstr "اسم المستخدم" + +#: .\contrib\auth\models.py:89 +msgid "Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)." +msgstr "مطلوب. 30 خانة أو أقل. خانات ØØ±Ù رقمية Ùقط (Ø£ØØ±ÙØŒ أرقام والشرطة السÙلية)." + +#: .\contrib\auth\models.py:90 +msgid "first name" +msgstr "الاسم الأول" + +#: .\contrib\auth\models.py:91 +msgid "last name" +msgstr "الاسم الأخير" + +#: .\contrib\auth\models.py:92 +msgid "e-mail address" +msgstr "عنوان البريد الإلكتروني" + +#: .\contrib\auth\models.py:93 +msgid "password" +msgstr "كلمة المرور" + +#: .\contrib\auth\models.py:93 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "استخدم '[algo]$[salt]$[hexdigest]'" + +#: .\contrib\auth\models.py:94 +msgid "staff status" +msgstr "ØØ§Ù„Ø© الطاقم" + +#: .\contrib\auth\models.py:94 +msgid "Designates whether the user can log into this admin site." +msgstr "ÙŠØØ¯Ø¯ ما إذا كان المستخدم يستطيع الدخول إلى موقع الإدارة هذا." + +#: .\contrib\auth\models.py:95 +msgid "active" +msgstr "ÙØ¹Ø§Ù„" + +#: .\contrib\auth\models.py:95 +msgid "Designates whether this user can log into the Django admin. Unselect this instead of deleting accounts." +msgstr "ÙŠØØ¯Ø¯ ما إذا كان المستخدم يستطيع الدخول إلى Ù„ÙˆØØ© تØÙƒÙ… جانغو، قم Ø¨ØªØØ¯ÙŠØ¯ هذا الخيار بدلا من ØØ°Ù ØØ³Ø§Ø¨Ø§Øª المستخدمين." + +#: .\contrib\auth\models.py:96 +msgid "superuser status" +msgstr "ØØ§Ù„Ø© المستخدم بالقوى الخارقة" + +#: .\contrib\auth\models.py:96 +msgid "Designates that this user has all permissions without explicitly assigning them." +msgstr "ØØ¯Ø¯ بأن هذا المستخدم يمتلك ÙƒØ§ÙØ© الصلاØÙŠØ§Øª دون Ø§Ù„ØØ§Ø¬Ø© Ù„ØªØØ¯ÙŠØ¯Ù‡Ø§ له ØªØµØ±ÙŠØØ§." + +#: .\contrib\auth\models.py:97 +msgid "last login" +msgstr "آخر عملية دخول" + +#: .\contrib\auth\models.py:98 +msgid "date joined" +msgstr "تاريخ الانضمام" + +#: .\contrib\auth\models.py:100 +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:101 +msgid "user permissions" +msgstr "صلاØÙŠØ§Øª المستخدم" + +#: .\contrib\auth\models.py:104 +msgid "user" +msgstr "المستخدم" + +#: .\contrib\auth\models.py:105 +msgid "users" +msgstr "المستخدمين" + +#: .\contrib\auth\models.py:110 +msgid "Personal info" +msgstr "المعلومات الشخصية" + +#: .\contrib\auth\models.py:111 +msgid "Permissions" +msgstr "الصلاØÙŠØ§Øª" + +#: .\contrib\auth\models.py:112 +msgid "Important dates" +msgstr "تواريخ مهمة" + +#: .\contrib\auth\models.py:113 +msgid "Groups" +msgstr "المجموعات" + +#: .\contrib\auth\models.py:250 +msgid "message" +msgstr "رسالة" + +#: .\contrib\auth\views.py:40 +msgid "Logged out" +msgstr "خروج" + +#: .\contrib\comments\models.py:67 +#: .\contrib\comments\models.py:166 +msgid "object ID" +msgstr "معر٠العنصر" + +#: .\contrib\comments\models.py:68 +msgid "headline" +msgstr "عنوان" + +#: .\contrib\comments\models.py:69 +#: .\contrib\comments\models.py:90 +#: .\contrib\comments\models.py:167 +msgid "comment" +msgstr "تعليق" + +#: .\contrib\comments\models.py:70 +msgid "rating #1" +msgstr "تقييم #1" + +#: .\contrib\comments\models.py:71 +msgid "rating #2" +msgstr "تقييم #2" + +#: .\contrib\comments\models.py:72 +msgid "rating #3" +msgstr "تقييم #3" + +#: .\contrib\comments\models.py:73 +msgid "rating #4" +msgstr "تقييم #4" + +#: .\contrib\comments\models.py:74 +msgid "rating #5" +msgstr "تقييم #5" + +#: .\contrib\comments\models.py:75 +msgid "rating #6" +msgstr "تقييم #8" + +#: .\contrib\comments\models.py:76 +msgid "rating #7" +msgstr "تقييم #7" + +#: .\contrib\comments\models.py:77 +msgid "rating #8" +msgstr "تقييم #8" + +#: .\contrib\comments\models.py:82 +msgid "is valid rating" +msgstr "تقييم صالØ" + +#: .\contrib\comments\models.py:83 +#: .\contrib\comments\models.py:169 +msgid "date/time submitted" +msgstr "تم ارسال التاريخ/الوقت" + +#: .\contrib\comments\models.py:84 +#: .\contrib\comments\models.py:170 +msgid "is public" +msgstr "عام" + +#: .\contrib\comments\models.py:86 +msgid "is removed" +msgstr "Ù…ØØ°ÙˆÙ" + +#: .\contrib\comments\models.py:86 +msgid "Check this box if the comment is inappropriate. A \"This comment has been removed\" message will be displayed instead." +msgstr "قم Ø¨ØªØØ¯ÙŠØ¯ هذا المربع إذا كان التعليق غير لائق، سيتم عرض الرسالة \"تم ØØ°Ù هذا التعليق\" بدلا منه." + +#: .\contrib\comments\models.py:91 +msgid "comments" +msgstr "تعليقات" + +#: .\contrib\comments\models.py:131 +#: .\contrib\comments\models.py:207 +msgid "Content object" +msgstr "عنصر Ù…ØØªÙˆÙ‰" + +#: .\contrib\comments\models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"أرسلت بواسطة %(user)s ÙÙŠ %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: .\contrib\comments\models.py:168 +msgid "person's name" +msgstr "اسم الشخص" + +#: .\contrib\comments\models.py:171 +msgid "ip address" +msgstr "عنوان ip" + +#: .\contrib\comments\models.py:173 +msgid "approved by staff" +msgstr "مواÙÙ‚ عليه من قبل الطاقم" + +#: .\contrib\comments\models.py:176 +msgid "free comment" +msgstr "تعليق ØØ±" + +#: .\contrib\comments\models.py:177 +msgid "free comments" +msgstr "تعليقات ØØ±Ø©" + +#: .\contrib\comments\models.py:233 +msgid "score" +msgstr "الدرجة" + +#: .\contrib\comments\models.py:234 +msgid "score date" +msgstr "تاريخ الدرجة" + +#: .\contrib\comments\models.py:237 +msgid "karma score" +msgstr "درجة الكارما" + +#: .\contrib\comments\models.py:238 +msgid "karma scores" +msgstr "درجات الكارما" + +#: .\contrib\comments\models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "تقييم %(score)d بواسطة %(user)s" + +#: .\contrib\comments\models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"هذا التعليق تم تعليمه بواسطة %(user)s:\n" +"\n" +"%(text)s" + +#: .\contrib\comments\models.py:265 +msgid "flag date" +msgstr "تاريخ التعليم" + +#: .\contrib\comments\models.py:268 +msgid "user flag" +msgstr "علامة مستخدم" + +#: .\contrib\comments\models.py:269 +msgid "user flags" +msgstr "علامات المستخدم" + +#: .\contrib\comments\models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "علامة بواسطة %r" + +#: .\contrib\comments\models.py:278 +msgid "deletion date" +msgstr "تاريخ Ø§Ù„ØØ°Ù" + +#: .\contrib\comments\models.py:280 +msgid "moderator deletion" +msgstr "ØØ°Ù المراقب" + +#: .\contrib\comments\models.py:281 +msgid "moderator deletions" +msgstr "ØØ°ÙˆÙات المراقب" + +#: .\contrib\comments\models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "ØØ°Ù المراقب بواسطة %r" + +#: .\contrib\comments\templates\comments\form.html.py:6 +msgid "Forgotten your password?" +msgstr "نسيت كلمة المرور؟" + +#: .\contrib\comments\templates\comments\form.html.py:12 +msgid "Ratings" +msgstr "التقييمات" + +#: .\contrib\comments\templates\comments\form.html.py:12 +#: .\contrib\comments\templates\comments\form.html.py:23 +msgid "Required" +msgstr "مطلوب" + +#: .\contrib\comments\templates\comments\form.html.py:12 +#: .\contrib\comments\templates\comments\form.html.py:23 +msgid "Optional" +msgstr "اختياري" + +#: .\contrib\comments\templates\comments\form.html.py:23 +msgid "Post a photo" +msgstr "ارسال صورة" + +#: .\contrib\comments\templates\comments\form.html.py:28 +#: .\contrib\comments\templates\comments\freeform.html.py:5 +msgid "Comment:" +msgstr "تعليق:" + +#: .\contrib\comments\templates\comments\form.html.py:34 +#: .\contrib\comments\templates\comments\freeform.html.py:9 +msgid "Preview comment" +msgstr "استعراض التعليق" + +#: .\contrib\comments\templates\comments\freeform.html.py:4 +msgid "Your name:" +msgstr "اسمك:" + +#: .\contrib\comments\views\comments.py:27 +msgid "This rating is required because you've entered at least one other rating." +msgstr "هذا التقييم مطلوب لأنك قمت بادخال تقييم ÙˆØ§ØØ¯ على الأقل." + +#: .\contrib\comments\views\comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"هذا التعليق كتب بواسطة شخص لديه أقل من تعليق ÙˆØ§ØØ¯:\n" +"\n" +"%(text)s" +msgstr[1] "" +"هذا التعليق كتب بواسطة شخص لديه أقل من تعليقان:\n" +"\n" +"%(text)s" +msgstr[2] "" +"هذا التعليق كتب بواسطة شخص لديه أقل من %(count)s تعليقات:\n" +"\n" +"%(text)s" +msgstr[3] "" +"هذا التعليق كتب بواسطة شخص لديه أقل من %(count)s تعليق:\n" +"\n" +"%(text)s" + +#: .\contrib\comments\views\comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"هذا التعليق كتب بواسطة عضو سطØÙŠ:\n" +"\n" +"%(text)s" + +#: .\contrib\comments\views\comments.py:188 +#: .\contrib\comments\views\comments.py:280 +msgid "Only POSTs are allowed" +msgstr "ÙŠØ³Ù…Ø Ø¨Ø§Ø³ØªØ®Ø¯Ø§Ù… POST Ùقط" + +#: .\contrib\comments\views\comments.py:192 +#: .\contrib\comments\views\comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "لم يتم ارسال ÙˆØ§ØØ¯ أو أكثر من الØÙ‚ول المطلوبة." + +#: .\contrib\comments\views\comments.py:196 +#: .\contrib\comments\views\comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "شخص ما قام بالتلاعب بنموذج التعليق (انتهاك أمني)" + +#: .\contrib\comments\views\comments.py:206 +#: .\contrib\comments\views\comments.py:292 +msgid "The comment form had an invalid 'target' parameter -- the object ID was invalid" +msgstr "نموذج التعليق Ø§ØØªÙˆÙ‰ 'هدÙ' غير صØÙŠØ -- معر٠الكائن كان غير صØÙŠØØ§" + +#: .\contrib\comments\views\comments.py:257 +#: .\contrib\comments\views\comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "نموذج التعليق لم ÙŠØØ¯Ø¯ أيا من 'استعراض' أو 'ارسال'" + +#: .\contrib\comments\views\karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "لا يمكن للمستخدمين المجهولين التصويت" + +#: .\contrib\comments\views\karma.py:23 +msgid "Invalid comment ID" +msgstr "معر٠التعليق غير صØÙŠØ" + +#: .\contrib\comments\views\karma.py:25 +msgid "No voting for yourself" +msgstr "لا يمكنك التصويت Ù„Ù†ÙØ³Ùƒ" + +#: .\contrib\contenttypes\models.py:20 +msgid "python model class name" +msgstr "اسم صن٠النموذج ÙÙŠ python" + +#: .\contrib\contenttypes\models.py:23 +msgid "content type" +msgstr "نوع البيانات" + +#: .\contrib\contenttypes\models.py:24 +msgid "content types" +msgstr "أنواع البيانات" + +#: .\contrib\flatpages\models.py:8 +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "مثال: '/about/contact/'. تأكد من وضع شرطات ÙÙŠ البداية والنهاية." + +#: .\contrib\flatpages\models.py:9 +msgid "title" +msgstr "العنوان" + +#: .\contrib\flatpages\models.py:10 +msgid "content" +msgstr "Ø§Ù„Ù…ØØªÙˆÙ‰" + +#: .\contrib\flatpages\models.py:11 +msgid "enable comments" +msgstr "Ø§Ù„Ø³Ù…Ø§Ø Ø¨Ø§Ù„ØªØ¹Ù„ÙŠÙ‚Ø§Øª" + +#: .\contrib\flatpages\models.py:12 +msgid "template name" +msgstr "اسم القالب" + +#: .\contrib\flatpages\models.py:13 +msgid "Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'." +msgstr "مثال: 'flatpages/contact_page'. إذا لم يتم ØªØØ¯ÙŠØ¯Ù‡ ÙØ¥Ù† النظام سيقوم باستخدام 'flatpages/default'." + +#: .\contrib\flatpages\models.py:14 +msgid "registration required" +msgstr "التسجيل مطلوب" + +#: .\contrib\flatpages\models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "إذا كان هذا الخيار Ù…ØØ¯Ø¯Ø§ØŒ ÙØ¥Ù† المستخدمين الداخلين Ùقط سيتمكنون من مشاهدة Ø§Ù„ØµÙØØ©." + +#: .\contrib\flatpages\models.py:18 +msgid "flat page" +msgstr "ØµÙØØ© Ù…Ø³Ø·ØØ©" + +#: .\contrib\flatpages\models.py:19 +msgid "flat pages" +msgstr "ØµÙØØ§Øª Ù…Ø³Ø·ØØ©" + +#: .\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 "يجب أن يكون هذا مسارا مطلقا وبدون اسم النطاق. مثال: '/events/search/'." + +#: .\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:42 +msgid "session data" +msgstr "بيانات الجلسة" + +#: .\contrib\sessions\models.py:43 +msgid "expire date" +msgstr "تاريخ الانتهاء" + +#: .\contrib\sessions\models.py:47 +msgid "session" +msgstr "جلسة" + +#: .\contrib\sessions\models.py:48 +msgid "sessions" +msgstr "جلسات" + +#: .\contrib\sites\models.py:10 +msgid "domain name" +msgstr "اسم النطاق" + +#: .\contrib\sites\models.py:11 +msgid "display name" +msgstr "اسم العرض" + +#: .\contrib\sites\models.py:15 +msgid "site" +msgstr "موقع" + +#: .\contrib\sites\models.py:16 +msgid "sites" +msgstr "مواقع" + +#: .\core\validators.py:63 +msgid "This value must contain only letters, numbers and underscores." +msgstr "هذه القيمة يجب أن ØªØØªÙˆÙŠ Ùقط على Ø§Ù„Ø£ØØ±Ù والأرقام والشرطة السÙلية." + +#: .\core\validators.py:67 +msgid "This value must contain only letters, numbers, underscores, dashes or slashes." +msgstr "هذه القيمة يجب أن ØªØØªÙˆÙŠ Ùقط على Ø§Ù„Ø£ØØ±Ù والأرقام والشرطات السÙلية والشرطة العادية والشرطات المائلة." + +#: .\core\validators.py:75 +msgid "Uppercase letters are not allowed here." +msgstr "Ø§Ù„ØØ±ÙˆÙ الكبيرة غير Ù…Ø³Ù…ÙˆØ Ø¨Ù‡Ø§ هنا." + +#: .\core\validators.py:79 +msgid "Lowercase letters are not allowed here." +msgstr "Ø§Ù„ØØ±ÙˆÙ الصغيرة غير Ù…Ø³Ù…ÙˆØ Ø¨Ù‡Ø§ هنا." + +#: .\core\validators.py:86 +msgid "Enter only digits separated by commas." +msgstr "أدخل أرقاما Ùقط Ù…ÙØµÙˆÙ„ بينها بÙواصل comma." + +#: .\core\validators.py:98 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "أدخل عناوين بريد إلكتروني ØµØ§Ù„ØØ© Ù…ÙØµÙˆÙ„ بينها بÙواصل comma." + +#: .\core\validators.py:102 +msgid "Please enter a valid IP address." +msgstr "أدخل عنوان IP ØµØ§Ù„Ø Ù…Ù† ÙØ¶Ù„Ùƒ." + +#: .\core\validators.py:106 +msgid "Empty values are not allowed here." +msgstr "القيم Ø§Ù„ÙØ§Ø±ØºØ© غير Ù…Ø³Ù…ÙˆØ Ø¨Ù‡Ø§ هنا." + +#: .\core\validators.py:110 +msgid "Non-numeric characters aren't allowed here." +msgstr "الخانات غير الرقمية غير Ù…Ø³Ù…ÙˆØ Ø¨Ù‡Ø§ هنا." + +#: .\core\validators.py:114 +msgid "This value can't be comprised solely of digits." +msgstr "لا يمكن أن تكون القيمة مكونة من الأرقام Ùقط." + +#: .\core\validators.py:119 +msgid "Enter a whole number." +msgstr "أدخل رقما صØÙŠØØ§." + +#: .\core\validators.py:123 +msgid "Only alphabetical characters are allowed here." +msgstr "Ùقط الخانات Ø§Ù„ØØ±Ùية Ù…Ø³Ù…ÙˆØ Ø¨Ù‡Ø§ هنا." + +#: .\core\validators.py:127 +#: .\db\models\fields\__init__.py:412 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "أدخل تاريخا صØÙŠØØ§ بتنسيق YYYY-MM-DD." + +#: .\core\validators.py:131 +msgid "Enter a valid time in HH:MM format." +msgstr "أدخل وقتا صØÙŠØØ§ بتنسيق HH:MM." + +#: .\core\validators.py:135 +#: .\db\models\fields\__init__.py:474 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "أدخل تاريخ/وقت صØÙŠØÙŠÙ† بتنسيق YYYY-MM-DD HH:MM." + +#: .\core\validators.py:139 +msgid "Enter a valid e-mail address." +msgstr "أدخل عنوان بريد إلكتروني صØÙŠØ." + +#: .\core\validators.py:151 +#: .\core\validators.py:379 +#: .\forms\__init__.py:659 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "لم يتم ارسال Ù…Ù„ÙØŒ الرجاء التأكد من نوع ترميز (encoding type) النموذج." + +#: .\core\validators.py:155 +msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image." +msgstr "قم Ø¨Ø±ÙØ¹ صورة ØµØ§Ù„ØØ©ØŒ المل٠الذي قمت Ø¨Ø±ÙØ¹Ù‡ إما أنه ليس Ù…Ù„ÙØ§ لصورة أو أنه مل٠معطوب." + +#: .\core\validators.py:162 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "العنوان %s لا ÙŠØØªÙˆÙŠ Ø¹Ù„Ù‰ صورة ØµØ§Ù„ØØ©." + +#: .\core\validators.py:166 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "رقم الهات٠يجب أن يكون بتنسيق XXX-XXX-XXXX. \"%s\" غير صالØ." + +#: .\core\validators.py:174 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "هذا العنوان %s لا يشير إلى مقطع Ùيديو QuickTime صالØ." + +#: .\core\validators.py:178 +msgid "A valid URL is required." +msgstr "يجب ادخال عنوان صالØ." + +#: .\core\validators.py:192 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"مطلوب Ø´ÙØ±Ø© HTML ØµØ§Ù„ØØ©ØŒ الأخطاء على وجه Ø§Ù„ØªØØ¯ÙŠØ¯ هي:\n" +"%s" + +#: .\core\validators.py:199 +#, python-format +msgid "Badly formed XML: %s" +msgstr "XML مهيئة بصورة سيئة: %s" + +#: .\core\validators.py:209 +#, python-format +msgid "Invalid URL: %s" +msgstr "وصلة غير ØµØ§Ù„ØØ©: %s" + +#: .\core\validators.py:213 +#: .\core\validators.py:215 +#, python-format +msgid "The URL %s is a broken link." +msgstr "الوصلة %s غير صØÙŠØØ©." + +#: .\core\validators.py:221 +msgid "Enter a valid U.S. state abbreviation." +msgstr "أدخل اختصار ولاية أمريكية صØÙŠØØ§." + +#: .\core\validators.py:236 +#, 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 غير Ù…Ø³Ù…ÙˆØ Ø¨Ù‡Ù…Ø§ هنا." +msgstr[2] "انتبه إلى ما تقول! الكلمات %s غير Ù…Ø³Ù…ÙˆØ Ø¨Ù‡Ø§ هنا." +msgstr[3] "انتبه إلى ما تقول! الكلمات %s غير Ù…Ø³Ù…ÙˆØ Ø¨Ù‡Ø§ هنا." + +#: .\core\validators.py:243 +#, python-format +msgid "This field must match the '%s' field." +msgstr "هذا الØÙ‚Ù„ يجب أن يطابق الØÙ‚Ù„ '%s'." + +#: .\core\validators.py:262 +msgid "Please enter something for at least one field." +msgstr "الرجاء ادخال شيء ما ÙÙŠ ØÙ‚Ù„ ÙˆØ§ØØ¯ على الأقل." + +#: .\core\validators.py:271 +#: .\core\validators.py:282 +msgid "Please enter both fields or leave them both empty." +msgstr "الرجاء ادخال كلا الØÙ‚لين أن ترك كلاهما ÙØ§Ø±ØºØ§." + +#: .\core\validators.py:289 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "هذا الØÙ‚Ù„ يجب أن يعطي إذا كان %(field)s %(value)s" + +#: .\core\validators.py:301 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "هذا الØÙ‚Ù„ يجب أن يعطى إذا لم يكن %(field)s %(value)s" + +#: .\core\validators.py:320 +msgid "Duplicate values are not allowed." +msgstr "القيم المكررة غير Ù…Ø³Ù…ÙˆØ Ø¨Ù‡Ø§." + +#: .\core\validators.py:343 +#, python-format +msgid "This value must be a power of %s." +msgstr "يجب أن تكون القيام من Ù…Ø¶Ø§Ø¹ÙØ§Øª %s." + +#: .\core\validators.py:354 +msgid "Please enter a valid decimal number." +msgstr "الرجاء ادخال رقم عشري صالØ." + +#: .\core\validators.py:356 +#, 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] "رجاء أدخل رقم عشري ØµØ§Ù„Ø Ù…ÙƒÙˆÙ† من خانة ÙˆØ§ØØ¯Ø© على الأكثر." +msgstr[1] "رجاء أدخل رقم عشري ØµØ§Ù„Ø Ù…ÙƒÙˆÙ† من خانتين على الأكثر." +msgstr[2] "رجاء أدخل رقم عشري ØµØ§Ù„Ø Ù…ÙƒÙˆÙ† من %s خانات على الأكثر." +msgstr[3] "رجاء أدخل رقم عشري ØµØ§Ù„Ø Ù…ÙƒÙˆÙ† من %s خانة على الأكثر." + +#: .\core\validators.py:359 +#, 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] "رجاء أدخل رقم عشري ØµØ§Ù„Ø ÙŠÙƒÙˆÙ† الجزء الصØÙŠØ منه مكونا من خانة ÙˆØ§ØØ¯Ø© على الأكثر." +msgstr[1] "رجاء أدخل رقم عشري ØµØ§Ù„Ø ÙŠÙƒÙˆÙ† الجزء الصØÙŠØ منه مكونا من خانتين على الأكثر." +msgstr[2] "رجاء أدخل رقم عشري ØµØ§Ù„Ø ÙŠÙƒÙˆÙ† الجزء الصØÙŠØ منه مكونا من %s خانات على الأكثر." +msgstr[3] "رجاء أدخل رقم عشري ØµØ§Ù„Ø ÙŠÙƒÙˆÙ† الجزء الصØÙŠØ منه مكونا من %s خانة على الأكثر." + +#: .\core\validators.py:362 +#, 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] "الرجاء ادخال رقم عشري ØµØ§Ù„Ø ØªÙƒÙˆÙ† Ùيه خانة عشرية ÙˆØ§ØØ¯Ø© على الأكثر." +msgstr[1] "الرجاء ادخال رقم عشري ØµØ§Ù„Ø ØªÙƒÙˆÙ† Ùيه خانتان عشريتان على الأكثر." +msgstr[2] "الرجاء ادخال رقم عشري ØµØ§Ù„Ø ØªÙƒÙˆÙ† Ùيه %s خانات عشرية على الأكثر." +msgstr[3] "الرجاء ادخال رقم عشري ØµØ§Ù„Ø ØªÙƒÙˆÙ† Ùيه %s خانة عشرية على الأكثر." + +#: .\core\validators.py:372 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "تأكد من أن ØØ¬Ù… المل٠الذي قمت Ø¨Ø±ÙØ¹Ù‡ لا يقل عن %s بايت." + +#: .\core\validators.py:373 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "تأكد من أن المل٠الذي قمت Ø¨Ø±ÙØ¹Ù‡ لا يزيد عن %s بايت." + +#: .\core\validators.py:390 +msgid "The format for this field is wrong." +msgstr "تنسيق هذا الØÙ‚Ù„ خاطئ." + +#: .\core\validators.py:405 +msgid "This field is invalid." +msgstr "هذا الØÙ‚Ù„ غير صØÙŠØ." + +#: .\core\validators.py:441 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "تعذر جلب أي شيء من %s." + +#: .\core\validators.py:444 +#, 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:477 +#, 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:481 +#, 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:486 +#, 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:491 +#, 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:495 +#, 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:500 +#, 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:302 +#, 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\related.py:51 +#, python-format +msgid "Please enter a valid %s." +msgstr "الرجاء ادخال %s صالØ." + +#: .\db\models\fields\related.py:618 +msgid "Separate multiple IDs with commas." +msgstr "Ø§ÙØµÙ„ بين Ø§Ù„Ù…Ø¹Ø±ÙØ§Øª بÙواصل comma." + +#: .\db\models\fields\related.py:620 +msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "اضغط زر التØÙƒÙ… \"Control\", أو \"Command\" على أجهزة Mac لاختيار أكثر من ÙˆØ§ØØ¯." + +#: .\db\models\fields\related.py:664 +#, 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 غير ØµØ§Ù„ØØ©." +msgstr[2] "الرجاء ادخال Ù…Ø¹Ø±ÙØ§Øª %(self)s ØµØ§Ù„ØØ©ØŒ القيم %(value)r غير ØµØ§Ù„ØØ©." +msgstr[3] "الرجاء ادخال Ù…Ø¹Ø±ÙØ§Øª %(self)s ØµØ§Ù„ØØ©ØŒ القيم %(value)r غير ØµØ§Ù„ØØ©." + +#: .\db\models\fields\__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s بالØÙ‚Ù„ %(fieldname)s موجود Ø¨Ø§Ù„ÙØ¹Ù„." + +#: .\db\models\fields\__init__.py:114 +#: .\db\models\fields\__init__.py:265 +#: .\db\models\fields\__init__.py:548 +#: .\db\models\fields\__init__.py:559 +#: .\forms\__init__.py:346 +msgid "This field is required." +msgstr "هذا الØÙ‚Ù„ مطلوب." + +#: .\db\models\fields\__init__.py:337 +msgid "This value must be an integer." +msgstr "هذه القيمة يجب أن تكون رقما صØÙŠØØ§." + +#: .\db\models\fields\__init__.py:369 +msgid "This value must be either True or False." +msgstr "هذه القيمة يجب أن تكون إما ØµØ Ø£Ùˆ خطأ." + +#: .\db\models\fields\__init__.py:385 +msgid "This field cannot be null." +msgstr "لا يمكن أن تكون قيمة هذا الØÙ‚Ù„ لا شيء." + +#: .\db\models\fields\__init__.py:568 +msgid "Enter a valid filename." +msgstr "أدخل اسم مل٠صالØ." + +#: .\forms\__init__.py:381 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "تأكد من أن النص الذي أدخلته أقل من خانة ÙˆØ§ØØ¯Ø©." +msgstr[1] "تأكد من أن النص الذي أدخلته أقل من خانتين." +msgstr[2] "تأكد من أن النص الذي أدخلته أقل من %s خانات." +msgstr[3] "تأكد من أن النص الذي أدخلته أقل من %s خانة." + +#: .\forms\__init__.py:386 +msgid "Line breaks are not allowed here." +msgstr "الأسطر الجديدة غير Ù…Ø³Ù…ÙˆØ Ù‡ØªØ§." + +#: .\forms\__init__.py:485 +#: .\forms\__init__.py:558 +#: .\forms\__init__.py:597 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "ØØ¯Ø¯ خيارا صØÙŠØØ§; '%(data)s' ليست ضمن %(choices)s." + +#: .\forms\__init__.py:661 +msgid "The submitted file is empty." +msgstr "المل٠الذي قمت بارساله ÙØ§Ø±Øº." + +#: .\forms\__init__.py:717 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "أدخل رقما صØÙŠØØ§ بين -32,768 Ùˆ 32,767." + +#: .\forms\__init__.py:727 +msgid "Enter a positive number." +msgstr "أدخل رقما موجبا." + +#: .\forms\__init__.py:737 +msgid "Enter a whole number between 0 and 32,767." +msgstr "أدخل رقما صØÙŠØØ§ بين 0 Ùˆ 32,767." + +#: .\template\defaultfilters.py:401 +msgid "yes,no,maybe" +msgstr "نعم،لا،ربما" + +#: .\utils\dates.py:6 +msgid "Monday" +msgstr "الاثنين" + +#: .\utils\dates.py:6 +msgid "Tuesday" +msgstr "الثلاثاء" + +#: .\utils\dates.py:6 +msgid "Wednesday" +msgstr "الأربعاء" + +#: .\utils\dates.py:6 +msgid "Thursday" +msgstr "الخميس" + +#: .\utils\dates.py:6 +msgid "Friday" +msgstr "الجمعة" + +#: .\utils\dates.py:7 +msgid "Saturday" +msgstr "السبت" + +#: .\utils\dates.py:7 +msgid "Sunday" +msgstr "Ø§Ù„Ø£ØØ¯" + +#: .\utils\dates.py:14 +msgid "January" +msgstr "يناير" + +#: .\utils\dates.py:14 +msgid "February" +msgstr "ÙØ¨Ø±Ø§ÙŠØ±" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "March" +msgstr "مارس" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "April" +msgstr "ابريل" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "May" +msgstr "مايو" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "June" +msgstr "يونيو" + +#: .\utils\dates.py:15 +#: .\utils\dates.py:27 +msgid "July" +msgstr "يوليو" + +#: .\utils\dates.py:15 +msgid "August" +msgstr "أغسطس" + +#: .\utils\dates.py:15 +msgid "September" +msgstr "سبتمبر" + +#: .\utils\dates.py:15 +msgid "October" +msgstr "أكتوبر" + +#: .\utils\dates.py:15 +msgid "November" +msgstr "نوÙمبر" + +#: .\utils\dates.py:16 +msgid "December" +msgstr "ديسمبر" + +#: .\utils\dates.py:19 +msgid "jan" +msgstr "" + +#: .\utils\dates.py:19 +msgid "feb" +msgstr "" + +#: .\utils\dates.py:19 +msgid "mar" +msgstr "" + +#: .\utils\dates.py:19 +msgid "apr" +msgstr "" + +#: .\utils\dates.py:19 +msgid "may" +msgstr "" + +#: .\utils\dates.py:19 +msgid "jun" +msgstr "" + +#: .\utils\dates.py:20 +msgid "jul" +msgstr "" + +#: .\utils\dates.py:20 +msgid "aug" +msgstr "" + +#: .\utils\dates.py:20 +msgid "sep" +msgstr "" + +#: .\utils\dates.py:20 +msgid "oct" +msgstr "" + +#: .\utils\dates.py:20 +msgid "nov" +msgstr "" + +#: .\utils\dates.py:20 +msgid "dec" +msgstr "" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "يناير" + +#: utils/dates.py:27 +msgid "Feb." +msgstr "ÙØ¨Ø±Ø§ÙŠØ±" + +#: utils/dates.py:28 +msgid "Aug." +msgstr "أغسطس" + +#: utils/dates.py:28 +msgid "Sept." +msgstr "سبتمبر" + +#: utils/dates.py:28 +msgid "Oct." +msgstr "أكتوبر" + +#: utils/dates.py:28 +msgid "Nov." +msgstr "نوÙمبر" + +#: utils/dates.py:28 +msgid "Dec." +msgstr "ديسمبر" + +#: .\utils\timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "سنة" +msgstr[1] "سنتان" +msgstr[2] "سنوات" +msgstr[3] "سنة" + +#: .\utils\timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "شهر" +msgstr[1] "شهران" +msgstr[2] "شهور" +msgstr[3] "شهر" + +#: .\utils\timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "أسبوع" +msgstr[1] "أسبوعان" +msgstr[2] "أسابيع" +msgstr[3] "أسبوع" + +#: .\utils\timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "يوم" +msgstr[1] "يومان" +msgstr[2] "أيام" +msgstr[3] "يوم" + +#: .\utils\timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "ساعة" +msgstr[1] "ساعتان" +msgstr[2] "ساعات" +msgstr[3] "ساعة" + +#: .\utils\timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "دقيقة" +msgstr[1] "دقيقتان" +msgstr[2] "دقائق" +msgstr[3] "دقيقة" + +#: .\utils\translation.py:363 +msgid "DATE_FORMAT" +msgstr "" + +#: .\utils\translation.py:364 +msgid "DATETIME_FORMAT" +msgstr "" + +#: .\utils\translation.py:365 +msgid "TIME_FORMAT" +msgstr "" + +#: .\utils\translation.py:381 +msgid "YEAR_MONTH_FORMAT" +msgstr "" + +#: .\utils\translation.py:382 +msgid "MONTH_DAY_FORMAT" +msgstr "" + diff --git a/django/conf/locale/ar/LC_MESSAGES/djangojs.mo b/django/conf/locale/ar/LC_MESSAGES/djangojs.mo Binary files differnew file mode 100644 index 0000000000..02c1d67b58 --- /dev/null +++ b/django/conf/locale/ar/LC_MESSAGES/djangojs.mo diff --git a/django/conf/locale/ar/LC_MESSAGES/djangojs.po b/django/conf/locale/ar/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000000..29b16aef26 --- /dev/null +++ b/django/conf/locale/ar/LC_MESSAGES/djangojs.po @@ -0,0 +1,110 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Django SVN\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: 2006-07-06 23:50+0300\n" +"Last-Translator: Ahmad Alhashemi <ahmad@ahmadh.com>\n" +"Language-Team: Ahmad Alhashemi <trans@ahmadh.com>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Arabic\n" +"X-Poedit-Country: Kuwait\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "%s Ù…ØªÙˆÙØ±Ø©" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "اختيار الكل" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Ø¥Ø¶Ø§ÙØ©" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "ØØ°Ù" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "%s اختيرت" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "ØØ¯Ø¯ خيارك أو خياراتك واضغط" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Ù…Ø³Ø Ø§Ù„ÙƒÙ„" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "January February March April May June July August September October November December" +msgstr "يناير ÙØ¨Ø±Ø§ÙŠØ± مارس إبريل مايو يونيو يوليو أغسطس سبتمبر أكتوبر نوÙمبر ديسمبر" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Ø§Ù„Ø£ØØ¯ الأثنين الثلاثاء الأربعاء الخميس الجمعة السبت" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "Ø£ Ø£ Ø« Ø£ Ø® ج س" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "الآن" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "الساعة" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "اختر وقتا ما" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "منتص٠الليل" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 ص." + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "الظهر" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "الغاء" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "اليوم" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "التقويم" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "يوم أمس" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "الغد" + diff --git a/django/conf/locale/es_AR/LC_MESSAGES/django.mo b/django/conf/locale/es_AR/LC_MESSAGES/django.mo Binary files differindex 463078025c..f550fca3db 100644 --- a/django/conf/locale/es_AR/LC_MESSAGES/django.mo +++ b/django/conf/locale/es_AR/LC_MESSAGES/django.mo diff --git a/django/conf/locale/es_AR/LC_MESSAGES/django.po b/django/conf/locale/es_AR/LC_MESSAGES/django.po index 8fbe5cb952..8af7c42e5e 100644 --- a/django/conf/locale/es_AR/LC_MESSAGES/django.po +++ b/django/conf/locale/es_AR/LC_MESSAGES/django.po @@ -8,10 +8,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2006-05-16 09:26-0300\n" +"POT-Creation-Date: 2006-06-19 11:19-0300\n" "PO-Revision-Date: 2006-05-16 10:05-0300\n" "Last-Translator: Ramiro Morales <rm0@gmx.net>\n" -"Language-Team: Spanish <en@li.org>\n" +"Language-Team: Spanish <es@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: 8bit\n" @@ -29,6 +29,10 @@ msgstr "tipo de contenido" msgid "content types" msgstr "tipos de contenido" +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "Sesión cerrada" + #: contrib/auth/models.py:13 contrib/auth/models.py:26 msgid "name" msgstr "nombre" @@ -63,7 +67,7 @@ msgstr "nombre" #: contrib/auth/models.py:57 msgid "last name" -msgstr "apellido(s)" +msgstr "apellido" #: contrib/auth/models.py:58 msgid "e-mail address" @@ -83,7 +87,7 @@ msgstr "es staff" #: contrib/auth/models.py:60 msgid "Designates whether the user can log into this admin site." -msgstr "Indica si el usuario puede entrar en este sitio de administración." +msgstr "Indica si el usuario puede ingresar a este sitio de administración." #: contrib/auth/models.py:61 msgid "active" @@ -149,7 +153,7 @@ msgstr "" "Su navegador Web aparenta no tener cookies activas. Las cookies son un " "requerimiento para poder ingresar." -#: contrib/auth/forms.py:36 contrib/auth/forms.py:41 +#: contrib/auth/forms.py:36 contrib/auth/forms.py:43 #: contrib/admin/views/decorators.py:9 msgid "" "Please enter a correct username and password. Note that both fields are case-" @@ -158,6 +162,10 @@ msgstr "" "Por favor ingrese un nombre de usuario y una contraseña correctos. Note que " "ambos campos son sensibles a mayúsculas/minúsculas." +#: contrib/auth/forms.py:45 +msgid "This account is inactive." +msgstr "Esta cuenta está inactiva" + #: contrib/redirects/models.py:7 msgid "redirect from" msgstr "redirigir desde" @@ -182,11 +190,11 @@ msgstr "" "Esto puede ser bien una ruta absoluta (como antes) o una URL completa que " "empiece con 'http://'." -#: contrib/redirects/models.py:12 +#: contrib/redirects/models.py:13 msgid "redirect" msgstr "redirección" -#: contrib/redirects/models.py:13 +#: contrib/redirects/models.py:14 msgid "redirects" msgstr "redirecciones" @@ -247,7 +255,7 @@ msgstr "fecha/hora de envío" msgid "is public" msgstr "es público" -#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:292 msgid "IP address" msgstr "Dirección IP" @@ -413,17 +421,20 @@ msgstr[1] "" "%(text)s" #: contrib/comments/views/comments.py:117 -#, python-format +#, fuzzy, python-format msgid "" "This comment was posted by a sketchy user:\n" "\n" "%(text)s" msgstr "" +"Este comentario ha sido enviado por un usuario 'sketcky':\n" +"\n" +"%(text)s" #: contrib/comments/views/comments.py:189 #: contrib/comments/views/comments.py:280 msgid "Only POSTs are allowed" -msgstr "Sólo se admite POST" +msgstr "Sólo se admiten POSTs" #: contrib/comments/views/comments.py:193 #: contrib/comments/views/comments.py:284 @@ -484,7 +495,7 @@ msgstr "Olvidó su contraseña?" #: contrib/admin/templates/admin/object_history.html:3 #: contrib/admin/templates/admin/change_list.html:5 #: contrib/admin/templates/admin/change_form.html:10 -#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/base.html:24 #: contrib/admin/templates/admin/delete_confirmation.html:3 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:3 @@ -499,7 +510,7 @@ msgstr "Olvidó su contraseña?" #: contrib/admin/templates/admin_doc/index.html:4 #: contrib/admin/templates/admin_doc/model_index.html:5 msgid "Log out" -msgstr "Terminar sesión" +msgstr "Cerrar sesión" #: contrib/comments/templates/comments/form.html:12 msgid "Ratings" @@ -519,7 +530,7 @@ msgstr "Opcional" msgid "Post a photo" msgstr "Enviar una foto" -#: contrib/flatpages/models.py:7 contrib/admin/views/doc.py:300 +#: contrib/flatpages/models.py:7 contrib/admin/views/doc.py:303 msgid "URL" msgstr "URL" @@ -596,7 +607,7 @@ msgstr "nombre de dominio" #: contrib/sites/models.py:11 msgid "display name" -msgstr "nombre para mostrar" +msgstr "nombre para visualizar" #: contrib/sites/models.py:15 msgid "site" @@ -616,9 +627,9 @@ msgstr "" "<ul>\n" #: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 -#: contrib/admin/filterspecs.py:143 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 msgid "All" -msgstr "Todo" +msgstr "Todos/as" #: contrib/admin/filterspecs.py:109 msgid "Any date" @@ -680,7 +691,7 @@ msgstr "entrada de registro" msgid "log entries" msgstr "entradas de registro" -#: contrib/admin/templatetags/admin_list.py:228 +#: contrib/admin/templatetags/admin_list.py:230 msgid "All dates" msgstr "Todas las fechas" @@ -752,12 +763,12 @@ msgstr "y" #: contrib/admin/views/main.py:338 #, python-format msgid "Changed %s." -msgstr "Modificado %s." +msgstr "Modifica %s." #: contrib/admin/views/main.py:340 #, python-format msgid "Deleted %s." -msgstr "Eliminado %s." +msgstr "Elimina %s." #: contrib/admin/views/main.py:343 msgid "No fields changed." @@ -802,82 +813,87 @@ msgstr "¿Está seguro?" #: contrib/admin/views/main.py:533 #, python-format msgid "Change history: %s" -msgstr "Modificar histórico: %s" +msgstr "Historia de modificaciones: %s" -#: contrib/admin/views/main.py:565 +#: contrib/admin/views/main.py:567 #, python-format msgid "Select %s" msgstr "Seleccione %s" -#: contrib/admin/views/main.py:565 +#: contrib/admin/views/main.py:567 #, python-format msgid "Select %s to change" -msgstr "Seleccione %s para modificar" +msgstr "Seleccione %s a modificar" + +#: contrib/admin/views/main.py:743 +msgid "Database error" +msgstr "Error de base de datos" -#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 -#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 -#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:289 +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:297 +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:300 msgid "Integer" msgstr "Entero" -#: contrib/admin/views/doc.py:278 +#: contrib/admin/views/doc.py:280 msgid "Boolean (Either True or False)" msgstr "Booleano (Verdadero o Falso)" -#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#: contrib/admin/views/doc.py:281 contrib/admin/views/doc.py:299 #, python-format msgid "String (up to %(maxlength)s)" msgstr "Cadena (máximo %(maxlength)s)" -#: contrib/admin/views/doc.py:280 +#: contrib/admin/views/doc.py:282 msgid "Comma-separated integers" msgstr "Enteros separados por comas" -#: contrib/admin/views/doc.py:281 +#: contrib/admin/views/doc.py:283 msgid "Date (without time)" msgstr "Fecha (sin hora)" -#: contrib/admin/views/doc.py:282 +#: contrib/admin/views/doc.py:284 msgid "Date (with time)" msgstr "Fecha (con hora)" -#: contrib/admin/views/doc.py:283 +#: contrib/admin/views/doc.py:285 msgid "E-mail address" msgstr "Dirección de correo electrónico" -#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +#: contrib/admin/views/doc.py:286 contrib/admin/views/doc.py:287 +#: contrib/admin/views/doc.py:290 msgid "File path" msgstr "Ruta de archivo" -#: contrib/admin/views/doc.py:285 +#: contrib/admin/views/doc.py:288 msgid "Decimal number" msgstr "Número decimal" -#: contrib/admin/views/doc.py:291 +#: contrib/admin/views/doc.py:294 msgid "Boolean (Either True, False or None)" msgstr "Booleano (Verdadero, Falso o Nulo)" -#: contrib/admin/views/doc.py:292 +#: contrib/admin/views/doc.py:295 msgid "Relation to parent model" msgstr "Relación con el modelo padre" -#: contrib/admin/views/doc.py:293 +#: contrib/admin/views/doc.py:296 msgid "Phone number" msgstr "Número de teléfono" -#: contrib/admin/views/doc.py:298 +#: contrib/admin/views/doc.py:301 msgid "Text" msgstr "Texto" -#: contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 msgid "Time" msgstr "Hora" -#: contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:304 msgid "U.S. state (two uppercase letters)" msgstr "Estado de los EEUU (dos letras mayúsculas)" -#: contrib/admin/views/doc.py:302 +#: contrib/admin/views/doc.py:305 msgid "XML text" msgstr "Texto XML" @@ -900,7 +916,7 @@ msgstr "Hora:" #: contrib/admin/templates/admin/object_history.html:3 #: contrib/admin/templates/admin/change_list.html:5 #: contrib/admin/templates/admin/change_form.html:10 -#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/base.html:24 #: contrib/admin/templates/admin/delete_confirmation.html:3 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:3 @@ -911,7 +927,7 @@ msgstr "Documentación" #: contrib/admin/templates/admin/object_history.html:3 #: contrib/admin/templates/admin/change_list.html:5 #: contrib/admin/templates/admin/change_form.html:10 -#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/base.html:24 #: contrib/admin/templates/admin/delete_confirmation.html:3 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:3 @@ -932,8 +948,9 @@ msgstr "Cambiar contraseña" #: contrib/admin/templates/admin/change_list.html:6 #: contrib/admin/templates/admin/500.html:4 #: contrib/admin/templates/admin/change_form.html:13 -#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/base.html:29 #: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/invalid_setup.html:4 #: contrib/admin/templates/registration/password_change_done.html:4 #: contrib/admin/templates/registration/password_reset_form.html:4 #: contrib/admin/templates/registration/logged_out.html:4 @@ -946,7 +963,7 @@ msgstr "Inicio" #: contrib/admin/templates/admin/object_history.html:5 #: contrib/admin/templates/admin/change_form.html:20 msgid "History" -msgstr "Histórico" +msgstr "Historia" #: contrib/admin/templates/admin/object_history.html:18 msgid "Date/time" @@ -962,15 +979,15 @@ msgstr "Acción" #: contrib/admin/templates/admin/object_history.html:26 msgid "DATE_WITH_TIME_FULL" -msgstr "" +msgstr "j M Y P" #: contrib/admin/templates/admin/object_history.html:36 msgid "" "This object doesn't have a change history. It probably wasn't added via this " "admin site." msgstr "" -"Este objeto no tiene histórico de cambios. Probablemente no fue añadido " -"usando este sitio de administración." +"Este objeto no tiene historia de modificaciones. Probablemente no fue " +"añadido usando este sitio de administración." #: contrib/admin/templates/admin/change_list.html:11 #, python-format @@ -1007,6 +1024,22 @@ msgstr "" msgid "Go" msgstr "Buscar" +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "un resultado" +msgstr[1] "%(counter)s resultados" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "total: %(full_result_count)s" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "Mostrar todos/as" + #: contrib/admin/templates/admin/base_site.html:4 msgid "Django site admin" msgstr "Sitio de administración de Django" @@ -1018,7 +1051,7 @@ msgstr "Administración de Django" #: contrib/admin/templates/admin/index.html:17 #, python-format msgid "Models available in the %(name)s application." -msgstr "Modelos disponibles en la aplciación %(name)s." +msgstr "Modelos disponibles en la aplicación %(name)s." #: contrib/admin/templates/admin/index.html:28 #: contrib/admin/templates/admin/change_form.html:15 @@ -1043,7 +1076,7 @@ msgstr "Mis acciones" #: contrib/admin/templates/admin/index.html:57 msgid "None available" -msgstr "Ninguno disponible" +msgstr "Ninguna disponible" #: contrib/admin/templates/admin/404.html:4 #: contrib/admin/templates/admin/404.html:8 @@ -1058,6 +1091,10 @@ msgstr "Lo sentimos, pero no se encuentra la página solicitada." msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?" msgstr "¿Ha <a href=\"/password_reset/\">olvidado su contraseña</a>?" +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "Filtrar" + #: contrib/admin/templates/admin/change_form.html:21 msgid "View on site" msgstr "Ver en el sitio" @@ -1076,7 +1113,7 @@ msgstr "Ordenación" msgid "Order:" msgstr "Orden:" -#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/base.html:24 msgid "Welcome," msgstr "Bienvenido," @@ -1125,6 +1162,16 @@ msgstr "Grabar y continuar editando" msgid "Save" msgstr "Grabar" +#: 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 "" +"hay algún problema con su instalación de base de datos. Asegúrese de que las " +"tablas de la misma hayan sido creadas, y asegúrese de que el usuario " +"apropiado tenga permisos de escritura en la base de datos." + #: contrib/admin/templates/registration/password_change_done.html:4 #: contrib/admin/templates/registration/password_change_form.html:4 #: contrib/admin/templates/registration/password_change_form.html:6 @@ -1312,17 +1359,25 @@ msgid "As above, but opens the admin page in a new window." msgstr "" "Como antes, pero abre la página de administración en una nueva ventana." -#: utils/translation.py:360 +#: utils/translation.py:363 msgid "DATE_FORMAT" -msgstr "" +msgstr "j N Y" -#: utils/translation.py:361 +#: utils/translation.py:364 msgid "DATETIME_FORMAT" -msgstr "" +msgstr "j N Y P" -#: utils/translation.py:362 +#: utils/translation.py:365 msgid "TIME_FORMAT" -msgstr "" +msgstr "P" + +#: utils/translation.py:381 +msgid "YEAR_MONTH_FORMAT" +msgstr "F Y" + +#: utils/translation.py:382 +msgid "MONTH_DAY_FORMAT" +msgstr "j \\de F" #: utils/dates.py:6 msgid "Monday" @@ -1386,7 +1441,7 @@ msgstr "Agosto" #: utils/dates.py:15 msgid "September" -msgstr "Septiembre" +msgstr "Setiembre" #: utils/dates.py:15 msgid "October" @@ -1430,11 +1485,11 @@ msgstr "" #: utils/dates.py:20 msgid "aug" -msgstr "" +msgstr "ago" #: utils/dates.py:20 msgid "sep" -msgstr "" +msgstr "set" #: utils/dates.py:20 msgid "oct" @@ -1450,11 +1505,11 @@ msgstr "dic" #: utils/dates.py:27 msgid "Jan." -msgstr "Ene." +msgstr "Enero" #: utils/dates.py:27 msgid "Feb." -msgstr "Feb." +msgstr "" #: utils/dates.py:28 msgid "Aug." @@ -1462,15 +1517,15 @@ msgstr "Ago." #: utils/dates.py:28 msgid "Sept." -msgstr "Sept." +msgstr "Set." #: utils/dates.py:28 msgid "Oct." -msgstr "Oct." +msgstr "" #: utils/dates.py:28 msgid "Nov." -msgstr "Nov." +msgstr "" #: utils/dates.py:28 msgid "Dec." @@ -1491,7 +1546,7 @@ msgstr[1] "meses" #: utils/timesince.py:14 msgid "week" msgid_plural "weeks" -msgstr[0] "semmana" +msgstr[0] "semana" msgstr[1] "semanas" #: utils/timesince.py:15 @@ -1546,7 +1601,7 @@ msgstr "Español" #: conf/global_settings.py:45 msgid "Argentinean Spanish" -msgstr "" +msgstr "Español Argentino" #: conf/global_settings.py:46 msgid "French" @@ -1558,7 +1613,7 @@ msgstr "Gallego" #: conf/global_settings.py:48 msgid "Hungarian" -msgstr "" +msgstr "Húngaro" #: conf/global_settings.py:49 msgid "Hebrew" @@ -1636,7 +1691,7 @@ msgid "%(optname)s with this %(fieldname)s already exists." msgstr "Ya existe %(optname)s con este %(fieldname)s." #: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 -#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: db/models/fields/__init__.py:545 db/models/fields/__init__.py:556 #: forms/__init__.py:346 msgid "This field is required." msgstr "Este campo es obligatorio." @@ -1653,11 +1708,11 @@ msgstr "Este valor debe ser True o False." msgid "This field cannot be null." msgstr "Este campo no puede ser nulo." -#: db/models/fields/__init__.py:468 core/validators.py:132 +#: db/models/fields/__init__.py:471 core/validators.py:135 msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." msgstr "Introduzca una fecha/hora válida en formato YYYY-MM-DD HH:MM." -#: db/models/fields/__init__.py:562 +#: db/models/fields/__init__.py:565 msgid "Enter a valid filename." msgstr "Introduzca un nombre de achivo válido" @@ -1688,43 +1743,48 @@ msgstr[1] "" "Por favor, introduzca IDs de %(self)s válidos. Los valores %(value)r no son " "válidos." -#: forms/__init__.py:380 +#: forms/__init__.py:381 #, python-format msgid "Ensure your text is less than %s character." msgid_plural "Ensure your text is less than %s characters." msgstr[0] "Asegúrese de que su texto tiene menos de %s carácter." msgstr[1] "Asegúrese de que su texto tiene menos de %s caracteres." -#: forms/__init__.py:385 +#: forms/__init__.py:386 msgid "Line breaks are not allowed here." msgstr "No se permiten saltos de línea." -#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#: forms/__init__.py:485 forms/__init__.py:558 forms/__init__.py:597 #, python-format msgid "Select a valid choice; '%(data)s' is not in %(choices)s." msgstr "Seleccione una opción válida; '%(data)s' no está en %(choices)s." -#: forms/__init__.py:645 +#: forms/__init__.py:659 core/validators.py:151 core/validators.py:379 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" +"No se envió un archivo. Verifique el tipo de codificación en el formulario." + +#: forms/__init__.py:661 msgid "The submitted file is empty." msgstr "El archivo enviado está vacío." -#: forms/__init__.py:699 +#: forms/__init__.py:717 msgid "Enter a whole number between -32,768 and 32,767." msgstr "Introduzca un número entero entre -32,768 y 32,767." -#: forms/__init__.py:708 +#: forms/__init__.py:727 msgid "Enter a positive number." msgstr "Introduzca un número positivo." -#: forms/__init__.py:717 +#: forms/__init__.py:737 msgid "Enter a whole number between 0 and 32,767." msgstr "Introduzca un número entero entre 0 y 32,767." -#: core/validators.py:60 +#: core/validators.py:63 msgid "This value must contain only letters, numbers and underscores." msgstr "Este valor debe contener sólo letras, números y guiones bajos." -#: core/validators.py:64 +#: core/validators.py:67 msgid "" "This value must contain only letters, numbers, underscores, dashes or " "slashes." @@ -1732,59 +1792,59 @@ msgstr "" "Este valor debe contener sólo letras, números, guiones bajos, barras (/) o " "slashes." -#: core/validators.py:72 +#: core/validators.py:75 msgid "Uppercase letters are not allowed here." msgstr "No se admiten letras mayúsculas." -#: core/validators.py:76 +#: core/validators.py:79 msgid "Lowercase letters are not allowed here." msgstr "No se admiten letras minúsculas." -#: core/validators.py:83 +#: core/validators.py:86 msgid "Enter only digits separated by commas." msgstr "Introduzca sólo dígitos separados por comas." -#: core/validators.py:95 +#: core/validators.py:98 msgid "Enter valid e-mail addresses separated by commas." msgstr "Introduzca direcciones de correo válidas separadas por comas." -#: core/validators.py:99 +#: core/validators.py:102 msgid "Please enter a valid IP address." msgstr "Por favor introduzca una dirección IP válida." -#: core/validators.py:103 +#: core/validators.py:106 msgid "Empty values are not allowed here." msgstr "No se admiten valores vacíos." -#: core/validators.py:107 +#: core/validators.py:110 msgid "Non-numeric characters aren't allowed here." msgstr "No se admiten caracteres no numéricos." -#: core/validators.py:111 +#: core/validators.py:114 msgid "This value can't be comprised solely of digits." msgstr "Este valor no puede estar formado sólo por dígitos." -#: core/validators.py:116 +#: core/validators.py:119 msgid "Enter a whole number." msgstr "Introduzca un número entero." -#: core/validators.py:120 +#: core/validators.py:123 msgid "Only alphabetical characters are allowed here." msgstr "Sólo se admiten caracteres alfabéticos." -#: core/validators.py:124 +#: core/validators.py:127 msgid "Enter a valid date in YYYY-MM-DD format." msgstr "Introduzca una fecha válida en formato AAAA-MM-DD." -#: core/validators.py:128 +#: core/validators.py:131 msgid "Enter a valid time in HH:MM format." msgstr "Introduzca una hora válida en formato HH:MM." -#: core/validators.py:136 +#: core/validators.py:139 msgid "Enter a valid e-mail address." msgstr "Introduzca una dirección de correo electrónico válida" -#: core/validators.py:148 +#: core/validators.py:155 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -1792,28 +1852,28 @@ msgstr "" "Envíe una imagen válida. El archivo que ha enviado no era una imagen o se " "trataba de una imagen corrupta." -#: core/validators.py:155 +#: core/validators.py:162 #, python-format msgid "The URL %s does not point to a valid image." msgstr "La URL %s no apunta a una imagen válida." -#: core/validators.py:159 +#: core/validators.py:166 #, python-format msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." msgstr "" "Los números de teléfono deben guardar el formato XXX-XXX-XXXX format. \"%s\" " "no es válido." -#: core/validators.py:167 +#: core/validators.py:174 #, python-format msgid "The URL %s does not point to a valid QuickTime video." msgstr "La URL %s no apunta a un vídeo QuickTime válido." -#: core/validators.py:171 +#: core/validators.py:178 msgid "A valid URL is required." msgstr "Se precisa una URL válida." -#: core/validators.py:185 +#: core/validators.py:192 #, python-format msgid "" "Valid HTML is required. Specific errors are:\n" @@ -1822,116 +1882,129 @@ msgstr "" "Se precisa HTML válido. Los errores específicos son:\n" "%s" -#: core/validators.py:192 +#: core/validators.py:199 #, python-format msgid "Badly formed XML: %s" msgstr "XML mal formado: %s" -#: core/validators.py:202 +#: core/validators.py:209 #, python-format msgid "Invalid URL: %s" msgstr "URL no válida: %s" -#: core/validators.py:206 core/validators.py:208 +#: core/validators.py:213 core/validators.py:215 #, python-format msgid "The URL %s is a broken link." msgstr "La URL %s es un enlace roto." -#: core/validators.py:214 +#: core/validators.py:221 msgid "Enter a valid U.S. state abbreviation." msgstr "Introduzca una abreviatura válida de estado de los EEUU." -#: core/validators.py:229 +#: core/validators.py:236 #, 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] "¡Vigila tu boca! Aquí no admitimos la palabra %s." msgstr[1] "¡Vigila tu boca! Aquí no admitimos las palabras %s." -#: core/validators.py:236 +#: core/validators.py:243 #, python-format msgid "This field must match the '%s' field." msgstr "Este campo debe concordar con el campo '%s'." -#: core/validators.py:255 +#: core/validators.py:262 msgid "Please enter something for at least one field." msgstr "Por favor, introduzca algo en al menos un campo." -#: core/validators.py:264 core/validators.py:275 +#: core/validators.py:271 core/validators.py:282 msgid "Please enter both fields or leave them both empty." msgstr "Por favor, rellene ambos campos o deje ambos vacíos." -#: core/validators.py:282 +#: core/validators.py:289 #, python-format msgid "This field must be given if %(field)s is %(value)s" msgstr "Se debe proporcionar este campo si %(field)s es %(value)s" -#: core/validators.py:294 +#: core/validators.py:301 #, python-format msgid "This field must be given if %(field)s is not %(value)s" msgstr "Se debe proporcionar este campo si %(field)s no es %(value)s" -#: core/validators.py:313 +#: core/validators.py:320 msgid "Duplicate values are not allowed." msgstr "No se admiten valores duplicados." -#: core/validators.py:336 +#: core/validators.py:343 #, python-format msgid "This value must be a power of %s." msgstr "Este valor debe ser una potencia de %s." -#: core/validators.py:347 +#: core/validators.py:354 msgid "Please enter a valid decimal number." msgstr "Por favor, introduzca un número decimal válido." -#: core/validators.py:349 +#: core/validators.py:356 #, 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] "" -"Por favor, introduzca un número decimal válido con con un máximo de %s " +"Por favor, introduzca un número decimal válido con con un máximo de un " "dígito en total." msgstr[1] "" "Por favor, introduzca un número decimal válido con un maximo de %s dígitos " "en total." -#: core/validators.py:352 +#: core/validators.py:359 +#, 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] "" +"Por favor, introduzca un número decimal válido con un dígito entero como " +"máximo." +msgstr[1] "" +"Por favor, introduzca un número decimal válido con un máximo de %s dígitos " +"enteros." + +#: core/validators.py:362 #, 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] "" -"Por favor, introduzca un número decimal válido con un máximo de %s " -"posición decimal." +"Por favor, introduzca un número decimal válido con un máximo de una posición " +"decimal." msgstr[1] "" "Por favor, introduzca un número decimal válido con un máximo de %s " "posiciones decimales." -#: core/validators.py:362 +#: core/validators.py:372 #, python-format msgid "Make sure your uploaded file is at least %s bytes big." msgstr "Asegúrese de que el archivo que envía tiene al menos %s bytes." -#: core/validators.py:363 +#: core/validators.py:373 #, python-format msgid "Make sure your uploaded file is at most %s bytes big." msgstr "Asegúrese de que el archivo que envía tiene como máximo %s bytes." -#: core/validators.py:376 +#: core/validators.py:390 msgid "The format for this field is wrong." msgstr "El formato de este campo es incorrecto." -#: core/validators.py:391 +#: core/validators.py:405 msgid "This field is invalid." msgstr "Este campo no es válido." -#: core/validators.py:426 +#: core/validators.py:441 #, python-format msgid "Could not retrieve anything from %s." msgstr "No pude obtener nada de %s." -#: core/validators.py:429 +#: core/validators.py:444 #, python-format msgid "" "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." @@ -1939,7 +2012,7 @@ msgstr "" "La URL %(url)s devolvió la cabecera Content-Type '%(contenttype)s', que no " "es válida." -#: core/validators.py:462 +#: core/validators.py:477 #, python-format msgid "" "Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " @@ -1948,7 +2021,7 @@ msgstr "" "Por favor, cierre la etiqueta %(tag)s de la línea %(line)s. (La línea " "empieza por \"%(start)s\".)" -#: core/validators.py:466 +#: core/validators.py:481 #, python-format msgid "" "Some text starting on line %(line)s is not allowed in that context. (Line " @@ -1957,7 +2030,7 @@ msgstr "" "Parte del texto que comienza en la línea %(line)s no está permitido en ese " "contexto. (La línea empieza por \"%(start)s\".)" -#: core/validators.py:471 +#: core/validators.py:486 #, python-format msgid "" "\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" @@ -1966,7 +2039,7 @@ msgstr "" "El \"%(attr)s\" de la línea %(line)s no es un atributo válido. (La línea " "empieza por \"%(start)s\".)" -#: core/validators.py:476 +#: core/validators.py:491 #, python-format msgid "" "\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" @@ -1975,7 +2048,7 @@ msgstr "" "La \"<%(tag)s>\" de la línea %(line)s no es una etiqueta válida. (La línea " "empieza por \"%(start)s\".)" -#: core/validators.py:480 +#: core/validators.py:495 #, python-format msgid "" "A tag on line %(line)s is missing one or more required attributes. (Line " @@ -1984,7 +2057,7 @@ msgstr "" "A una etiqueta de la línea %(line)s le faltan uno o más atributos " "requeridos. (La línea empieza por \"%(start)s\".)" -#: core/validators.py:485 +#: core/validators.py:500 #, python-format msgid "" "The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " @@ -1993,10 +2066,23 @@ msgstr "" "El atributo \"%(attr)s\" de la línea %(line)s tiene un valor que no es " "válido. (La línea empieza por \"%(start)s\".)" -#: template/defaultfilters.py:379 +#: template/defaultfilters.py:389 msgid "yes,no,maybe" msgstr "si,no,tal vez" +#, fuzzy +#~ msgid "%(content_type_name)s" +#~ msgstr "tipos de contenido" + +#, fuzzy +#~ msgid "%(myname)s" +#~ msgstr "Agregar %(name)s" + +#~ msgid "%(result_count)s result" +#~ msgid_plural "%(counter)s results" +#~ msgstr[0] "un resultado" +#~ msgstr[1] "%(counter)s resultados" + #~ msgid "Comment" #~ msgstr "Comentario" @@ -2014,16 +2100,3 @@ msgstr "si,no,tal vez" #~ msgid "packages" #~ msgstr "paquetes" - -#, fuzzy -#~ 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] "" -#~ "Por favor, introduzca un número decimal válido con a lo más %s dígito en " -#~ "total." -#~ msgstr[1] "" -#~ "Por favor, introduzca un número decimal válido con a lo más %s dígitos en " -#~ "total." diff --git a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo Binary files differindex e48292825b..f24c9a12d0 100644 --- a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo +++ b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo diff --git a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po index e9578701d5..49d3856d3c 100644 --- a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po +++ b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Django JavaScript 1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"POT-Creation-Date: 2006-06-19 12:15-0300\n" "PO-Revision-Date: 2006-05-16 10:20-0300\n" "Last-Translator: Ramiro Morales <rm0@gmx.net>\n" "MIME-Version: 1.0\n" @@ -22,7 +22,7 @@ msgstr "%s disponibles" #: contrib/admin/media/js/SelectFilter2.js:41 msgid "Choose all" -msgstr "Selecciona todos" +msgstr "Seleccionar todos" #: contrib/admin/media/js/SelectFilter2.js:46 msgid "Add" @@ -43,9 +43,9 @@ msgstr "Haga sus elecciones y haga click en " #: contrib/admin/media/js/SelectFilter2.js:59 msgid "Clear all" -msgstr "Elimina todos" +msgstr "Eliminar todos" -#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/dateparse.js:32 #: contrib/admin/media/js/calendar.js:24 msgid "" "January February March April May June July August September October November " @@ -54,7 +54,7 @@ msgstr "" "Enero Febrero Marzo Abril Mayo Junio Julio Agosto Septiembre Octubre " "Noviembre Diciembre" -#: contrib/admin/media/js/dateparse.js:27 +#: contrib/admin/media/js/dateparse.js:33 msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" msgstr "Domingo Lunes Martes Miércoles Jueves Viernes Sábado" @@ -62,8 +62,17 @@ msgstr "Domingo Lunes Martes Miércoles Jueves Viernes Sábado" msgid "S M T W T F S" msgstr "D L M M J V S" +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34 +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72 +msgid "Show" +msgstr "Mostrar" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63 +msgid "Hide" +msgstr "Ocultar" + #: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:89 msgid "Now" msgstr "Ahora" @@ -71,40 +80,40 @@ msgstr "Ahora" msgid "Clock" msgstr "Reloj" -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:86 msgid "Choose a time" msgstr "Elija una hora" -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:90 msgid "Midnight" msgstr "Medianoche" -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:91 msgid "6 a.m." msgstr "6 a.m." -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:92 msgid "Noon" msgstr "Mediodía" -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:96 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:187 msgid "Cancel" msgstr "Cancelar" -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:120 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:181 msgid "Today" msgstr "Hoy" -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:123 msgid "Calendar" msgstr "Calendario" -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179 msgid "Yesterday" msgstr "Ayer" -#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183 msgid "Tomorrow" msgstr "Mañana" diff --git a/django/conf/locale/fr/LC_MESSAGES/django.mo b/django/conf/locale/fr/LC_MESSAGES/django.mo Binary files differindex d678225f2f..7b2b1f6e9e 100644 --- a/django/conf/locale/fr/LC_MESSAGES/django.mo +++ b/django/conf/locale/fr/LC_MESSAGES/django.mo diff --git a/django/conf/locale/fr/LC_MESSAGES/django.po b/django/conf/locale/fr/LC_MESSAGES/django.po index 7cc5070840..104e9a2aa1 100644 --- a/django/conf/locale/fr/LC_MESSAGES/django.po +++ b/django/conf/locale/fr/LC_MESSAGES/django.po @@ -514,7 +514,7 @@ msgstr "Supprimé %s." #: contrib/admin/views/main.py:343 msgid "No fields changed." -msgstr "Aucun champs modifié." +msgstr "Aucun champ modifié." #: contrib/admin/views/main.py:346 #, python-format @@ -1906,7 +1906,7 @@ msgstr "" #: db/models/fields/__init__.py:40 #, python-format msgid "%(optname)s with this %(fieldname)s already exists." -msgstr "%(optname)s avec le champs %(fieldname)s existe déjà ." +msgstr "%(optname)s avec le champ %(fieldname)s existe déjà ." #: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 #: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 diff --git a/django/conf/locale/gl/LC_MESSAGES/django.mo b/django/conf/locale/gl/LC_MESSAGES/django.mo Binary files differindex a808d2c6fc..00beabebf8 100644 --- a/django/conf/locale/gl/LC_MESSAGES/django.mo +++ b/django/conf/locale/gl/LC_MESSAGES/django.mo diff --git a/django/conf/locale/gl/LC_MESSAGES/django.po b/django/conf/locale/gl/LC_MESSAGES/django.po index ca4f0f3f90..394f9cd016 100644 --- a/django/conf/locale/gl/LC_MESSAGES/django.po +++ b/django/conf/locale/gl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2006-05-16 10:11+0200\n" -"PO-Revision-Date: 2005-12-20 10:48+0100\n" +"PO-Revision-Date: 2006-07-03 14:06+0200\n" "Last-Translator: Afonso Fernández Nogueira <fonzzo.django@gmail.com>\n" "Language-Team: Galego\n" "MIME-Version: 1.0\n" @@ -91,9 +91,8 @@ msgstr "" "comentario foi borrado\" no canto do seu contido." #: contrib/comments/models.py:91 -#, fuzzy msgid "comments" -msgstr "comentario" +msgstr "comentarios" #: contrib/comments/models.py:131 contrib/comments/models.py:207 msgid "Content object" @@ -120,21 +119,19 @@ msgstr "nome da persoa" #: contrib/comments/models.py:171 msgid "ip address" -msgstr "Enderezo IP" +msgstr "enderezo IP" #: contrib/comments/models.py:173 msgid "approved by staff" msgstr "aprobado polos moderadores" #: contrib/comments/models.py:176 -#, fuzzy msgid "free comment" -msgstr "Comentario libre" +msgstr "comentario libre" #: contrib/comments/models.py:177 -#, fuzzy msgid "free comments" -msgstr "Comentarios libres" +msgstr "comentarios libres" #: contrib/comments/models.py:233 msgid "score" @@ -145,14 +142,12 @@ msgid "score date" msgstr "data da puntuación" #: contrib/comments/models.py:237 -#, fuzzy msgid "karma score" -msgstr "Puntuación de karma" +msgstr "puntos de karma" #: contrib/comments/models.py:238 -#, fuzzy msgid "karma scores" -msgstr "Puntuacións de karma" +msgstr "puntos de karma" #: contrib/comments/models.py:242 #, python-format @@ -175,14 +170,12 @@ msgid "flag date" msgstr "data da marca" #: contrib/comments/models.py:268 -#, fuzzy msgid "user flag" -msgstr "Marca de usuario" +msgstr "marca de usuario" #: contrib/comments/models.py:269 -#, fuzzy msgid "user flags" -msgstr "Marcas de usuario" +msgstr "marcas de usuario" #: contrib/comments/models.py:273 #, python-format @@ -194,19 +187,17 @@ msgid "deletion date" msgstr "data de borrado" #: contrib/comments/models.py:280 -#, fuzzy msgid "moderator deletion" -msgstr "Borrado de moderación" +msgstr "borrado de moderador" #: contrib/comments/models.py:281 -#, fuzzy msgid "moderator deletions" -msgstr "Borrados de moderación" +msgstr "borrados de moderador" #: contrib/comments/models.py:285 #, python-format msgid "Moderator deletion by %r" -msgstr "Borrado de moderación por %r" +msgstr "Borrado polo moderador %r" #: contrib/comments/views/karma.py:19 msgid "Anonymous users cannot vote" @@ -218,7 +209,7 @@ msgstr "ID de comentario non válida" #: contrib/comments/views/karma.py:25 msgid "No voting for yourself" -msgstr "Non se pode votar a si mesmo" +msgstr "Vostede non se pode votar a si mesmo" #: contrib/comments/views/comments.py:28 msgid "" @@ -297,9 +288,8 @@ msgid "Password:" msgstr "Contrasinal:" #: contrib/comments/templates/comments/form.html:6 -#, fuzzy msgid "Forgotten your password?" -msgstr "Cambiar o contrasinal" +msgstr "Esqueceu o contrasinal?" #: contrib/comments/templates/comments/form.html:8 #: contrib/admin/templates/admin/object_history.html:3 @@ -320,7 +310,7 @@ msgstr "Cambiar o contrasinal" #: contrib/admin/templates/admin_doc/index.html:4 #: contrib/admin/templates/admin_doc/model_index.html:5 msgid "Log out" -msgstr "SaÃr" +msgstr "Rematar sesión" #: contrib/comments/templates/comments/form.html:12 msgid "Ratings" @@ -329,33 +319,30 @@ msgstr "" #: contrib/comments/templates/comments/form.html:12 #: contrib/comments/templates/comments/form.html:23 msgid "Required" -msgstr "" +msgstr "Requirido" #: contrib/comments/templates/comments/form.html:12 #: contrib/comments/templates/comments/form.html:23 msgid "Optional" -msgstr "" +msgstr "Opcional" #: contrib/comments/templates/comments/form.html:23 msgid "Post a photo" -msgstr "" +msgstr "Publicar unha foto" #: contrib/comments/templates/comments/form.html:27 #: contrib/comments/templates/comments/freeform.html:5 -#, fuzzy msgid "Comment:" -msgstr "Comentario" +msgstr "Comentario:" #: contrib/comments/templates/comments/form.html:32 #: contrib/comments/templates/comments/freeform.html:9 -#, fuzzy msgid "Preview comment" -msgstr "Comentario libre" +msgstr "Previsualizar comentario" #: contrib/comments/templates/comments/freeform.html:4 -#, fuzzy msgid "Your name:" -msgstr "nome de usuario" +msgstr "Nome:" #: contrib/admin/filterspecs.py:40 #, python-format @@ -440,12 +427,13 @@ msgstr "Todas as datas" msgid "" "Please enter a correct username and password. Note that both fields are case-" "sensitive." -msgstr "" +msgstr "Insira un nome de usuario e un contrasinal correctos. Teña en conta que " +"nos dous campos se distingue entre maiúsculas e minúsculas." #: contrib/admin/views/decorators.py:23 #: contrib/admin/templates/admin/login.html:25 msgid "Log in" -msgstr "Entrar" +msgstr "Iniciar sesión" #: contrib/admin/views/decorators.py:61 msgid "" @@ -757,7 +745,7 @@ msgstr "SentÃmolo, pero non se atopou a páxina solicitada." #: contrib/admin/templates/admin/index.html:17 #, python-format msgid "Models available in the %(name)s application." -msgstr "" +msgstr "Modelos dispoñÃbeis na aplicación %(name)s." #: contrib/admin/templates/admin/index.html:28 #: contrib/admin/templates/admin/change_form.html:15 @@ -1061,12 +1049,11 @@ msgstr "Hora" #: contrib/admin/templates/widget/file.html:2 msgid "Currently:" -msgstr "" +msgstr "Agora:" #: contrib/admin/templates/widget/file.html:3 -#, fuzzy msgid "Change:" -msgstr "Modificar" +msgstr "Modificar:" #: contrib/redirects/models.py:7 msgid "redirect from" @@ -1155,24 +1142,20 @@ msgid "codename" msgstr "código" #: contrib/auth/models.py:17 -#, fuzzy msgid "permission" -msgstr "Permiso" +msgstr "permiso" #: contrib/auth/models.py:18 contrib/auth/models.py:27 -#, fuzzy msgid "permissions" -msgstr "Permisos" +msgstr "permisos" #: contrib/auth/models.py:29 -#, fuzzy msgid "group" -msgstr "Grupo" +msgstr "grupo" #: contrib/auth/models.py:30 contrib/auth/models.py:65 -#, fuzzy msgid "groups" -msgstr "Grupos" +msgstr "grupos" #: contrib/auth/models.py:55 msgid "username" @@ -1231,19 +1214,16 @@ msgstr "" "permisos concedidos a cada un dos grupos aos que pertence." #: contrib/auth/models.py:67 -#, fuzzy msgid "user permissions" -msgstr "Permisos" +msgstr "permisos de usuario" #: contrib/auth/models.py:70 -#, fuzzy msgid "user" -msgstr "Usuario" +msgstr "usuario" #: contrib/auth/models.py:71 -#, fuzzy msgid "users" -msgstr "Usuarios" +msgstr "usuarios" #: contrib/auth/models.py:76 msgid "Personal info" @@ -1262,18 +1242,17 @@ msgid "Groups" msgstr "Grupos" #: contrib/auth/models.py:219 -#, fuzzy msgid "message" -msgstr "Mensaxe" +msgstr "mensaxe" #: contrib/auth/forms.py:30 msgid "" "Your Web browser doesn't appear to have cookies enabled. Cookies are " "required for logging in." -msgstr "" +msgstr "Semella que o seu navegador non acepta 'cookies'. RequÃrense " +"'cookies' para iniciar sesión." #: contrib/contenttypes/models.py:25 -#, fuzzy msgid "python model class name" msgstr "nome do módulo Python" @@ -1410,54 +1389,52 @@ msgid "December" msgstr "decembro" #: utils/dates.py:19 -#, fuzzy msgid "jan" -msgstr "e" +msgstr "xan" #: utils/dates.py:19 msgid "feb" -msgstr "" +msgstr "feb" #: utils/dates.py:19 msgid "mar" -msgstr "" +msgstr "mar" #: utils/dates.py:19 msgid "apr" -msgstr "" +msgstr "abr" #: utils/dates.py:19 -#, fuzzy msgid "may" -msgstr "dÃa" +msgstr "mai" #: utils/dates.py:19 msgid "jun" -msgstr "" +msgstr "xuñ" #: utils/dates.py:20 msgid "jul" -msgstr "" +msgstr "xul" #: utils/dates.py:20 msgid "aug" -msgstr "" +msgstr "ago" #: utils/dates.py:20 msgid "sep" -msgstr "" +msgstr "set" #: utils/dates.py:20 msgid "oct" -msgstr "" +msgstr "out" #: utils/dates.py:20 msgid "nov" -msgstr "" +msgstr "nov" #: utils/dates.py:20 msgid "dec" -msgstr "" +msgstr "dec" #: utils/dates.py:27 msgid "Jan." @@ -1502,8 +1479,8 @@ msgstr[1] "meses" #: utils/timesince.py:14 msgid "week" msgid_plural "weeks" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "semana" +msgstr[1] "semanas" #: utils/timesince.py:15 msgid "day" @@ -1545,7 +1522,7 @@ msgstr "alemán" #: conf/global_settings.py:42 msgid "Greek" -msgstr "" +msgstr "grego" #: conf/global_settings.py:43 msgid "English" @@ -1565,11 +1542,11 @@ msgstr "galego" #: conf/global_settings.py:47 msgid "Hungarian" -msgstr "" +msgstr "húngaro" #: conf/global_settings.py:48 msgid "Hebrew" -msgstr "" +msgstr "hebreo" #: conf/global_settings.py:49 msgid "Icelandic" @@ -1581,11 +1558,11 @@ msgstr "italiano" #: conf/global_settings.py:51 msgid "Japanese" -msgstr "" +msgstr "xaponés" #: conf/global_settings.py:52 msgid "Dutch" -msgstr "" +msgstr "holandés" #: conf/global_settings.py:53 msgid "Norwegian" @@ -1608,9 +1585,8 @@ msgid "Slovak" msgstr "eslovaco" #: conf/global_settings.py:58 -#, fuzzy msgid "Slovenian" -msgstr "eslovaco" +msgstr "esloveno" #: conf/global_settings.py:59 msgid "Serbian" @@ -1621,9 +1597,8 @@ msgid "Swedish" msgstr "sueco" #: conf/global_settings.py:61 -#, fuzzy msgid "Ukrainian" -msgstr "brasileiro" +msgstr "ucraÃno" #: conf/global_settings.py:62 msgid "Simplified Chinese" @@ -1631,20 +1606,19 @@ msgstr "chinés simplificado" #: conf/global_settings.py:63 msgid "Traditional Chinese" -msgstr "" +msgstr "chinés tradicional" #: core/validators.py:60 msgid "This value must contain only letters, numbers and underscores." msgstr "Este valor soamente pode conter letras, números e guións baixos (_)." #: core/validators.py:64 -#, fuzzy msgid "" "This value must contain only letters, numbers, underscores, dashes or " "slashes." msgstr "" -"Este valor soamente pode conter letras, números, guións baixos (_) e barras " -"inclinadas." +"Este valor soamente pode conter letras, números, guións baixos (_), guións (-) e barras " +"inclinadas (/)." #: core/validators.py:72 msgid "Uppercase letters are not allowed here." @@ -1903,9 +1877,9 @@ msgstr "" "comeza con \"%(start)s\")." #: db/models/manipulators.py:302 -#, fuzzy, python-format +#, python-format msgid "%(object)s with this %(type)s already exists for the given %(field)s." -msgstr "Xa existe un/ha %(optname)s con este/a %(fieldname)s." +msgstr "Xa existe un obxecto %(object)s con este %(type)s para o campo %(field)s." #: db/models/fields/__init__.py:40 #, python-format @@ -1919,19 +1893,16 @@ msgid "This field is required." msgstr "RequÃrese este campo." #: db/models/fields/__init__.py:337 -#, fuzzy msgid "This value must be an integer." -msgstr "Este valor ten que ser unha potencia de %s." +msgstr "Este valor ten que ser un número enteiro." #: db/models/fields/__init__.py:369 -#, fuzzy msgid "This value must be either True or False." -msgstr "Este valor ten que ser unha potencia de %s." +msgstr "Este valor ten que verdadeiro ou falso." #: db/models/fields/__init__.py:385 -#, fuzzy msgid "This field cannot be null." -msgstr "Este campo non é válido." +msgstr "Este campo non pode ser nulo." #: db/models/fields/__init__.py:562 msgid "Enter a valid filename." @@ -1943,12 +1914,10 @@ msgid "Please enter a valid %s." msgstr "Insira un %s válido/a." #: db/models/fields/related.py:579 -#, fuzzy msgid "Separate multiple IDs with commas." -msgstr " Separe IDs múltiplas con comas." +msgstr "Separe IDs múltiplas con comas." #: db/models/fields/related.py:581 -#, fuzzy msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" diff --git a/django/conf/locale/gl/LC_MESSAGES/djangojs.mo b/django/conf/locale/gl/LC_MESSAGES/djangojs.mo Binary files differindex bedf6fd743..140f9a220e 100644 --- a/django/conf/locale/gl/LC_MESSAGES/djangojs.mo +++ b/django/conf/locale/gl/LC_MESSAGES/djangojs.mo diff --git a/django/conf/locale/gl/LC_MESSAGES/djangojs.po b/django/conf/locale/gl/LC_MESSAGES/djangojs.po index 4c0a53ec1a..2a8f284659 100644 --- a/django/conf/locale/gl/LC_MESSAGES/djangojs.po +++ b/django/conf/locale/gl/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2005-12-09 11:51+0100\n" -"PO-Revision-Date: 2005-12-08 15:28+0100\n" +"PO-Revision-Date: 2005-07-02 13:25+0200\n" "Last-Translator: Afonso Fernández Nogueira <fonzzo.django@gmail.com>\n" "Language-Team: Galego\n" "MIME-Version: 1.0\n" @@ -18,33 +18,32 @@ msgstr "" #: contrib/admin/media/js/SelectFilter2.js:33 #, perl-format msgid "Available %s" -msgstr "" +msgstr "%s dispoñÃbeis" #: contrib/admin/media/js/SelectFilter2.js:41 -#, fuzzy msgid "Choose all" -msgstr "Escolla unha hora" +msgstr "Escoller todo" #: contrib/admin/media/js/SelectFilter2.js:46 msgid "Add" -msgstr "" +msgstr "Engadir" #: contrib/admin/media/js/SelectFilter2.js:48 msgid "Remove" -msgstr "" +msgstr "Quitar" #: contrib/admin/media/js/SelectFilter2.js:53 #, perl-format msgid "Chosen %s" -msgstr "" +msgstr "%s escollido/a(s)" #: contrib/admin/media/js/SelectFilter2.js:54 msgid "Select your choice(s) and click " -msgstr "" +msgstr "Seleccione unha ou varias entrada e faga clic " #: contrib/admin/media/js/SelectFilter2.js:59 msgid "Clear all" -msgstr "" +msgstr "Quitar todo" #: contrib/admin/media/js/dateparse.js:26 #: contrib/admin/media/js/calendar.js:24 @@ -52,7 +51,7 @@ msgid "" "January February March April May June July August September October November " "December" msgstr "" -"xaneiro febeiro marzo abril maio xuño xullo agosto setembro outubro novembro " +"xaneiro febreiro marzo abril maio xuño xullo agosto setembro outubro novembro " "decembro" #: contrib/admin/media/js/dateparse.js:27 diff --git a/django/conf/locale/sl/LC_MESSAGES/django.mo b/django/conf/locale/sl/LC_MESSAGES/django.mo Binary files differindex a7d6152411..12253610c4 100644 --- a/django/conf/locale/sl/LC_MESSAGES/django.mo +++ b/django/conf/locale/sl/LC_MESSAGES/django.mo diff --git a/django/conf/locale/sl/LC_MESSAGES/django.po b/django/conf/locale/sl/LC_MESSAGES/django.po index 1d9e2f4028..5a9b6d6b46 100644 --- a/django/conf/locale/sl/LC_MESSAGES/django.po +++ b/django/conf/locale/sl/LC_MESSAGES/django.po @@ -1,21 +1,25 @@ -# SOME DESCRIPTIVE TITLE. +# translation of django.po to Slovenian +# Igor Kolar <ike@email.si), 2006. +# Nena Kojadin <nena@kiberpipa.org), 2006. +# Jure Cuhalev <gandalf@owca.info>, 2006. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# Igor Kolar <ike@email.si), 2006. -# Nena Kojadin <nena@kiberpipa.org), 2006 msgid "" msgstr "" -"Project-Id-Version: Django Slovenian 0.92+svn translation\n" +"Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2006-05-16 10:13+0200\n" -"PO-Revision-Date: 2006-03-19 17:30+0100\n" +"PO-Revision-Date: 2006-07-29 11:52+0100\n" "Last-Translator: Jure ÄŒuhalev <gandalf@owca.info>\n" -"Language-Team: Slovenian <sl@li.org>\n" +"Language-Team: Slovenian <lugos-slo@lugos.si>\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" +"Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.2\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" -#: contrib/comments/models.py:67 contrib/comments/models.py:166 +#: contrib/comments/models.py:67 +#: contrib/comments/models.py:166 msgid "object ID" msgstr "ID objekta" @@ -23,7 +27,8 @@ msgstr "ID objekta" msgid "headline" msgstr "naslov" -#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:69 +#: contrib/comments/models.py:90 #: contrib/comments/models.py:167 msgid "comment" msgstr "komentar" @@ -64,15 +69,18 @@ msgstr "rating #8" msgid "is valid rating" msgstr "je veljavni rating" -#: contrib/comments/models.py:83 contrib/comments/models.py:169 +#: contrib/comments/models.py:83 +#: contrib/comments/models.py:169 msgid "date/time submitted" -msgstr "podatek datum/Äas poslan" +msgstr "datum/Äas vnosa" -#: contrib/comments/models.py:84 contrib/comments/models.py:170 +#: contrib/comments/models.py:84 +#: contrib/comments/models.py:170 msgid "is public" msgstr "je javno" -#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +#: contrib/comments/models.py:85 +#: contrib/admin/views/doc.py:289 msgid "IP address" msgstr "IP naslov" @@ -81,21 +89,17 @@ msgid "is removed" msgstr "je odstranjen/-a" #: contrib/comments/models.py:86 -msgid "" -"Check this box if the comment is inappropriate. A \"This comment has been " -"removed\" message will be displayed instead." -msgstr "" -"Odkljukaj, Äe je komntar neprimeren. Namesto komentarja bo vidno obvestilo " -"\"Ta komentar je bil odstranjen\"." +msgid "Check this box if the comment is inappropriate. A \"This comment has been removed\" message will be displayed instead." +msgstr "Odkljukaj, Äe je komntar neprimeren. Namesto komentarja bo vidno obvestilo \"Ta komentar je bil odstranjen\"." #: contrib/comments/models.py:91 -#, fuzzy msgid "comments" -msgstr "komentar" +msgstr "komentarji" -#: contrib/comments/models.py:131 contrib/comments/models.py:207 +#: contrib/comments/models.py:131 +#: contrib/comments/models.py:207 msgid "Content object" -msgstr "Objekt s vsebino" +msgstr "Objekt z vsebino" #: contrib/comments/models.py:159 #, python-format @@ -125,14 +129,12 @@ msgid "approved by staff" msgstr "potrjeno s strani osebja" #: contrib/comments/models.py:176 -#, fuzzy msgid "free comment" -msgstr "Zastonj komentar" +msgstr "anonimen komentar" #: contrib/comments/models.py:177 -#, fuzzy msgid "free comments" -msgstr "Zastonj komentarji" +msgstr "anonimni komentarji" #: contrib/comments/models.py:233 msgid "score" @@ -143,14 +145,12 @@ msgid "score date" msgstr "datum ocene" #: contrib/comments/models.py:237 -#, fuzzy msgid "karma score" -msgstr "Karma" +msgstr "karma toÄke" #: contrib/comments/models.py:238 -#, fuzzy msgid "karma scores" -msgstr "Skrivnosti karme" +msgstr "karma toÄke" #: contrib/comments/models.py:242 #, python-format @@ -173,14 +173,12 @@ msgid "flag date" msgstr "datum oznaÄitve (zastavice)" #: contrib/comments/models.py:268 -#, fuzzy msgid "user flag" -msgstr "UporabniÅ¡ka zastavica" +msgstr "uporabnikova zastavica" #: contrib/comments/models.py:269 -#, fuzzy msgid "user flags" -msgstr "UporabniÅ¡ke zastavice" +msgstr "uporabniÅ¡ke zastavice" #: contrib/comments/models.py:273 #, python-format @@ -192,14 +190,12 @@ msgid "deletion date" msgstr "datum izbrisa" #: contrib/comments/models.py:280 -#, fuzzy msgid "moderator deletion" -msgstr "Izbris s strani moderatorja" +msgstr "izbris s strani moderatorja" #: contrib/comments/models.py:281 -#, fuzzy msgid "moderator deletions" -msgstr "Izbrisi s strani moderatorja" +msgstr "izbrisi s strani moderatorja" #: contrib/comments/models.py:285 #, python-format @@ -219,30 +215,33 @@ msgid "No voting for yourself" msgstr "Ni mogoÄe glasovati zase" #: contrib/comments/views/comments.py:28 -msgid "" -"This rating is required because you've entered at least one other rating." +msgid "This rating is required because you've entered at least one other rating." msgstr "MoraÅ¡ podati tole oceno, ker si podal vsaj Å¡e eno drugo oceno." #: contrib/comments/views/comments.py:112 #, python-format msgid "" -"This comment was posted by a user who has posted fewer than %(count)s " -"comment:\n" +"This comment was posted by a user who has posted fewer than %(count)s comment:\n" "\n" "%(text)s" msgid_plural "" -"This comment was posted by a user who has posted fewer than %(count)s " -"comments:\n" +"This comment was posted by a user who has posted fewer than %(count)s comments:\n" "\n" "%(text)s" msgstr[0] "" -"Ta komentar je poslal uporabnik, ki je do zdaj poslal manj kot %(count)s " -"komentarjev Komentar:\n" +"Ta komentar je poslal uporabnik, ki je do zdaj poslal manj kot %(count)s komentarjev:\n" "\n" "%(text)s" msgstr[1] "" -"Ta komentar je poslal uporabnik, ki je do zdaj poslal manj kot %(count)s " -"komentarjev Komentar:\n" +"Ta komentar je poslal uporabnik, ki je do zdaj poslal manj kot %(count)s komentar:\n" +"\n" +"%(text)s" +msgstr[2] "" +"Ta komentar je poslal uporabnik, ki je do zdaj poslal manj kot %(count)s komentarja:\n" +"\n" +"%(text)s" +msgstr[3] "" +"Ta komentar je poslal uporabnik, ki je do zdaj poslal manj kot %(count)s komentarje:\n" "\n" "%(text)s" @@ -260,12 +259,12 @@ msgstr "" #: contrib/comments/views/comments.py:189 #: contrib/comments/views/comments.py:280 msgid "Only POSTs are allowed" -msgstr "Dovoljena je le metoda HTTP POST" +msgstr "Dovoljena je le metoda POST" #: contrib/comments/views/comments.py:193 #: contrib/comments/views/comments.py:284 msgid "One or more of the required fields wasn't submitted" -msgstr "Eden ali veÄ obveznih polj ni vpisanih" +msgstr "Eno ali veÄ obveznih polj ni vpisanih" #: contrib/comments/views/comments.py:197 #: contrib/comments/views/comments.py:286 @@ -274,17 +273,13 @@ msgstr "Nekdo se je poigraval z obrazcem za komentarje (varnostna krÅ¡itev)" #: contrib/comments/views/comments.py:207 #: contrib/comments/views/comments.py:292 -msgid "" -"The comment form had an invalid 'target' parameter -- the object ID was " -"invalid" -msgstr "" -"Obrazec s komentarji ima neveljavni parameter 'target' -- ID objekta je " -"neveljaven." +msgid "The comment form had an invalid 'target' parameter -- the object ID was invalid" +msgstr "Obrazec s komentarji ima neveljavni parameter 'target' -- ID objekta je neveljaven." #: contrib/comments/views/comments.py:257 #: contrib/comments/views/comments.py:321 msgid "The comment form didn't provide either 'preview' or 'post'" -msgstr "Obrazec s komentarji ne zahteva niti 'preview' niti 'post' akcije." +msgstr "Obrazec s komentarji ni podal niti 'preview' niti 'post' akcije." #: contrib/comments/templates/comments/form.html:6 #: contrib/comments/templates/comments/form.html:8 @@ -363,7 +358,8 @@ msgstr "" "<h3>Avtor: %s</h3>\n" "<ul>\n" -#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:70 +#: contrib/admin/filterspecs.py:88 #: contrib/admin/filterspecs.py:143 msgid "All" msgstr "Vse" @@ -390,7 +386,7 @@ msgstr "Letos" #: contrib/admin/filterspecs.py:143 msgid "Yes" -msgstr "Ja" +msgstr "Da" #: contrib/admin/filterspecs.py:143 msgid "No" @@ -432,46 +428,33 @@ msgstr "vnosi v dnevniku" msgid "All dates" msgstr "Vsi datumi" -#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/admin/views/decorators.py:9 +#: contrib/auth/forms.py:36 #: contrib/auth/forms.py:41 -msgid "" -"Please enter a correct username and password. Note that both fields are case-" -"sensitive." -msgstr "" -"Prosimo, vnesite veljavno uporabniÅ¡ko ime in geslo. Opomba: obe polji sta " -"obÄutljivi na velikost Ärk" +msgid "Please enter a correct username and password. Note that both fields are case-sensitive." +msgstr "Prosimo, vnesite veljavno uporabniÅ¡ko ime in geslo. Opomba: obe polji sta obÄutljivi na velikost Ärk" #: contrib/admin/views/decorators.py:23 #: contrib/admin/templates/admin/login.html:25 msgid "Log in" -msgstr "Prijate se" +msgstr "Prijavite se" #: contrib/admin/views/decorators.py:61 -msgid "" -"Please log in again, because your session has expired. Don't worry: Your " -"submission has been saved." -msgstr "" -"VaÅ¡a seja je pretekla; prosimo, prijavite se znova. Opomba: Vse vaÅ¡e objave " -"so varno shranjene." +msgid "Please log in again, because your session has expired. Don't worry: Your submission has been saved." +msgstr "VaÅ¡a seja je pretekla; prosimo, prijavite se znova. Ne skrbite, vaÅ¡e objave so varno shranjene." #: contrib/admin/views/decorators.py:68 -msgid "" -"Looks like your browser isn't configured to accept cookies. Please enable " -"cookies, reload this page, and try again." -msgstr "" -"Izgleda, da vaÅ¡ brskalnik nima podpore za piÅ¡kotke. Prosimo, vkljuÄite " -"piÅ¡kotke, znova naložite to stran in poskusite Å¡e enkrat." +msgid "Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again." +msgstr "Izgleda, da vaÅ¡ brskalnik nima podpore za piÅ¡kotke. Prosimo, vkljuÄite piÅ¡kotke, znova naložite to stran in poskusite Å¡e enkrat." #: contrib/admin/views/decorators.py:82 msgid "Usernames cannot contain the '@' character." -msgstr "UporabniÅ¡ka imena ne smejo vsebovati znaka '@'" +msgstr "UporabniÅ¡ka imena ne smejo vsebovati znaka '@'." #: contrib/admin/views/decorators.py:84 #, python-format msgid "Your e-mail address is not your username. Try '%s' instead." -msgstr "" -"VaÅ¡ e-mail naslov ne morete uporabljati kot uporabniÅ¡ko ime. Namesto tega " -"uporabite '%s'" +msgstr "VaÅ¡ e-mail naslov ne morete uporabljati kot uporabniÅ¡ko ime. Namesto tega uporabite '%s'." #: contrib/admin/views/main.py:226 msgid "Site administration" @@ -482,11 +465,13 @@ msgstr "Administracija strani" msgid "The %(name)s \"%(obj)s\" was added successfully." msgstr "%(name)s \"%(obj)s\" je bil uspeÅ¡no dodan." -#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +#: contrib/admin/views/main.py:264 +#: contrib/admin/views/main.py:348 msgid "You may edit it again below." msgstr "Vsebino lahko znova uredite spodaj." -#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#: contrib/admin/views/main.py:272 +#: contrib/admin/views/main.py:357 #, python-format msgid "You may add another %s below." msgstr "Spodaj lahko dodate Å¡e en %s." @@ -501,7 +486,8 @@ msgstr "Dodaj %s" msgid "Added %s." msgstr "Dodal %s." -#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:336 +#: contrib/admin/views/main.py:338 #: contrib/admin/views/main.py:340 msgid "and" msgstr "in" @@ -509,7 +495,7 @@ msgstr "in" #: contrib/admin/views/main.py:338 #, python-format msgid "Changed %s." -msgstr "Spremenil %s" +msgstr "Spremenil %s." #: contrib/admin/views/main.py:340 #, python-format @@ -527,10 +513,8 @@ msgstr "%(name)s \"%(obj)s\" je bilo uspeÅ¡no spremenjeno." #: contrib/admin/views/main.py:354 #, python-format -msgid "" -"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." -msgstr "" -"%(name)s \"%(obj)s\" je bilo uspeÅ¡no dodano. Znova ga lahko urejate spodaj." +msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "%(name)s \"%(obj)s\" je bilo uspeÅ¡no dodano. Znova ga lahko urejate spodaj." #: contrib/admin/views/main.py:392 #, python-format @@ -564,24 +548,28 @@ msgstr "Zgodovina sprememb: %s" #: contrib/admin/views/main.py:565 #, python-format msgid "Select %s" -msgstr "Izberi %s" +msgstr "Izberite %s" #: contrib/admin/views/main.py:565 #, python-format msgid "Select %s to change" -msgstr "Izberi %s, ki ga želite spremeniti" +msgstr "Izberite %s, ki ga želite spremeniti" -#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 -#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 -#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +#: contrib/admin/views/doc.py:277 +#: contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 +#: contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 +#: contrib/admin/views/doc.py:297 msgid "Integer" -msgstr "Integer (Å¡tevilo)" +msgstr "Å tevilo (integer)" #: contrib/admin/views/doc.py:278 msgid "Boolean (Either True or False)" msgstr "Boolean (ali True ali False)" -#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#: contrib/admin/views/doc.py:279 +#: contrib/admin/views/doc.py:296 #, python-format msgid "String (up to %(maxlength)s)" msgstr "Niz (vse do %(maxlength)s)" @@ -602,7 +590,8 @@ msgstr "Datum (s Äasom)" msgid "E-mail address" msgstr "E-naslov" -#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +#: contrib/admin/views/doc.py:284 +#: contrib/admin/views/doc.py:287 msgid "File path" msgstr "Pot do datoteke" @@ -616,7 +605,7 @@ msgstr "Boolean (ali True ali False ali None)" #: contrib/admin/views/doc.py:292 msgid "Relation to parent model" -msgstr "Razmerje z starÅ¡evskim modelom" +msgstr "Razmerje s starÅ¡evskim modelom" #: contrib/admin/views/doc.py:293 msgid "Phone number" @@ -630,7 +619,8 @@ msgstr "Besedilo" msgid "Time" msgstr "ÄŒas" -#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +#: contrib/admin/views/doc.py:300 +#: contrib/flatpages/models.py:7 msgid "URL" msgstr "URL (spletni naslov)" @@ -707,15 +697,11 @@ msgstr "Dejanje" #: contrib/admin/templates/admin/object_history.html:26 msgid "DATE_WITH_TIME_FULL" -msgstr "N j, Y, P" +msgstr "N j, Y, H:i" #: contrib/admin/templates/admin/object_history.html:36 -msgid "" -"This object doesn't have a change history. It probably wasn't added via this " -"admin site." -msgstr "" -"Ta objekt nima zgodovine. Verjetno ni bil dodan preko te administratorske " -"strani." +msgid "This object doesn't have a change history. It probably wasn't added via this admin site." +msgstr "Ta objekt nima zgodovine. Verjetno ni bil dodan preko te administratorske strani." #: contrib/admin/templates/admin/base_site.html:4 msgid "Django site admin" @@ -738,12 +724,8 @@ msgid "Server Error <em>(500)</em>" msgstr "Napaka strežnika <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 "" -"PriÅ¡lo je do nepriÄakovane napake. Administratorji strani so že obveÅ¡Äeni " -"prekoe-poÅ¡te in naj bi jo v kratkem odpravili. Hvala za vaÅ¡e potrpljenje." +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 "PriÅ¡lo je do nepriÄakovane napake. Administratorji strani so že obveÅ¡Äeni prekoe-poÅ¡te in naj bi jo v kratkem odpravili. Hvala za vaÅ¡e potrpljenje." #: contrib/admin/templates/admin/404.html:4 #: contrib/admin/templates/admin/404.html:8 @@ -752,12 +734,12 @@ msgstr "Strani ni mogoÄe najti" #: contrib/admin/templates/admin/404.html:10 msgid "We're sorry, but the requested page could not be found." -msgstr "Se opraivÄujemo, a zahtevane strani ni mogoÄe najti." +msgstr "Se opraviÄujemo, a zahtevane strani ni mogoÄe najti." #: contrib/admin/templates/admin/index.html:17 #, python-format msgid "Models available in the %(name)s application." -msgstr "" +msgstr "Modeli na voljo v %(name)s aplikaciji" #: contrib/admin/templates/admin/index.html:28 #: contrib/admin/templates/admin/change_form.html:15 @@ -776,7 +758,7 @@ msgstr "Nimate dovoljenja za urejanje Äesar koli." msgid "Recent Actions" msgstr "Zadnja dejanja" -#: contrib/admin/templates/admin/index.html:53 +#: contrib/admin/tempalates/admin/index.html:53 msgid "My Actions" msgstr "Moja dejanja" @@ -804,22 +786,13 @@ msgstr "IzbriÅ¡i" #: contrib/admin/templates/admin/delete_confirmation.html:14 #, python-format -msgid "" -"Deleting the %(object_name)s '%(object)s' would result in deleting related " -"objects, but your account doesn't have permission to delete the following " -"types of objects:" -msgstr "" -"Izbris %(object_name)s '%(object)s' bi pomenil izbris povezanih objektov, " -"vendarvi nimate dovoljenja za izbris naslednjih tipov objektov:" +msgid "Deleting the %(object_name)s '%(object)s' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:" +msgstr "Izbris %(object_name)s '%(object)s' bi pomenil izbris povezanih objektov, vendarvi nimate dovoljenja za izbris naslednjih tipov objektov:" #: contrib/admin/templates/admin/delete_confirmation.html:21 #, python-format -msgid "" -"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " -"the following related items will be deleted:" -msgstr "" -"Ste prepriÄani, da želite izbrisati %(object_name)s \"%(object)s\"?Vsi " -"naslednji povezani elementi bodo izbrisani:" +msgid "Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of the following related items will be deleted:" +msgstr "Ste prepriÄani, da želite izbrisati %(object_name)s \"%(object)s\"?Vsi naslednji povezani elementi bodo izbrisani:" #: contrib/admin/templates/admin/delete_confirmation.html:26 msgid "Yes, I'm sure" @@ -843,6 +816,8 @@ msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Prosimo, odpravite sledeÄo napako." msgstr[1] "Prosimo, odpravite sledeÄe napake." +msgstr[2] "Prosimo, odpravite sledeÄi napaki." +msgstr[3] "Prosimo, odpravite sledeÄe napake." #: contrib/admin/templates/admin/change_form.html:48 msgid "Ordering" @@ -892,12 +867,8 @@ msgid "Password reset" msgstr "Obnova gesla" #: 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 "" -"Ste pozabili geslo? Vnesite vaÅ¡ e-naslov spodaj in mi vam bomo poslali novo " -"geslo." +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 "Ste pozabili geslo? Vnesite vaÅ¡ e-naslov spodaj in mi vam bomo poslali novo geslo." #: contrib/admin/templates/registration/password_reset_form.html:16 msgid "E-mail address:" @@ -921,18 +892,12 @@ msgid "Password reset successful" msgstr "Geslo je bilo uspeÅ¡no obnovljeno" #: contrib/admin/templates/registration/password_reset_done.html:12 -msgid "" -"We've e-mailed a new password to the e-mail address you submitted. You " -"should be receiving it shortly." +msgid "We've e-mailed a new password to the e-mail address you submitted. You should be receiving it shortly." msgstr "Po e-poÅ¡ti smo vam poslali novo geslo.Morali bi ga prejeti v kratkem" #: contrib/admin/templates/registration/password_change_form.html:12 -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 "" -"Prosim, vnesite vaÅ¡e staro geslo (zaradi varnosti) in nato Å¡e dvakrat novo" -"(da preverimo, da se niste zatipkali)" +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 "Prosim, vnesite vaÅ¡e staro geslo (zaradi varnosti) in nato Å¡e dvakrat novo(da preverimo, da se niste zatipkali)" #: contrib/admin/templates/registration/password_change_form.html:17 msgid "Old password:" @@ -1000,34 +965,24 @@ msgid "" "your computer is \"internal\").</p>\n" msgstr "" "\n" -"<p class=\"help\">Za inÅ¡talacijo zaznamkic povleÄite povezavo v orodno " -"vrstico\n" -"z zaznamki, ali kliknite z desno miÅ¡kino tipko na povezavo in jo dodajte med " -"zaznamkeZdaj lahko uporabite zaznamek s katere koli strani. Opomba: nekatere " -"teh stranilahko gledate le z internega raÄunalnika (preverite s sistemskim " -"administratorjem)</p>\n" +"<p class=\"help\">Za inÅ¡talacijo zaznamkic povleÄite povezavo v orodno vrstico\n" +"z zaznamki, ali kliknite z desno miÅ¡kino tipko na povezavo in jo dodajte med zaznamkeZdaj lahko uporabite zaznamek s katere koli strani. Opomba: nekatere teh stranilahko gledate le z internega raÄunalnika (preverite s sistemskim administratorjem)</p>\n" #: contrib/admin/templates/admin_doc/bookmarklets.html:19 msgid "Documentation for this page" msgstr "Dokumentacija za to stran" #: contrib/admin/templates/admin_doc/bookmarklets.html:20 -msgid "" -"Jumps you from any page to the documentation for the view that generates " -"that page." -msgstr "" -"Skok na stran z dokumentacijo za pogled (view), ki generira trenutno stran." +msgid "Jumps you from any page to the documentation for the view that generates that page." +msgstr "Skok na stran z dokumentacijo za pogled (view), ki generira trenutno stran." #: contrib/admin/templates/admin_doc/bookmarklets.html:22 msgid "Show object ID" msgstr "Pokaži ID objekta" #: contrib/admin/templates/admin_doc/bookmarklets.html:23 -msgid "" -"Shows the content-type and unique ID for pages that represent a single " -"object." -msgstr "" -"Pokaže content-type in unikatni ID za strani, ki predstavljajo en objekt." +msgid "Shows the content-type and unique ID for pages that represent a single object." +msgstr "Pokaže content-type in unikatni ID za strani, ki predstavljajo en objekt." #: contrib/admin/templates/admin_doc/bookmarklets.html:25 msgid "Edit this object (current window)" @@ -1035,8 +990,7 @@ msgstr "Uredi trenutni objekt (v trenutnem oknu)" #: contrib/admin/templates/admin_doc/bookmarklets.html:26 msgid "Jumps to the admin page for pages that represent a single object." -msgstr "" -"Skok na administracijsko stran za vse strani, ki predstavljajo en objekt." +msgstr "Skok na administracijsko stran za vse strani, ki predstavljajo en objekt." #: contrib/admin/templates/admin_doc/bookmarklets.html:28 msgid "Edit this object (new window)" @@ -1067,23 +1021,16 @@ msgid "redirect from" msgstr "preusmeritev iz" #: contrib/redirects/models.py:8 -msgid "" -"This should be an absolute path, excluding the domain name. Example: '/" -"events/search/'." -msgstr "" -"To mora biti absolutna pot, izkljuÄujoÄ domeno. Primer: '/events/search'-" +msgid "This should be an absolute path, excluding the domain name. Example: '/events/search/'." +msgstr "To mora biti absolutna pot, izkljuÄujoÄ domeno. Primer: '/events/search'-" #: contrib/redirects/models.py:9 msgid "redirect to" msgstr "preusmeri na" #: contrib/redirects/models.py:10 -msgid "" -"This can be either an absolute path (as above) or a full URL starting with " -"'http://'." -msgstr "" -"To je ali absolutna pot (kot zgoraj) ali popoln URL naslov (zaÄne se z " -"'http://')" +msgid "This can be either an absolute path (as above) or a full URL starting with 'http://'." +msgstr "To je ali absolutna pot (kot zgoraj) ali popoln URL naslov (zaÄne se z 'http://')" #: contrib/redirects/models.py:12 msgid "redirect" @@ -1094,10 +1041,8 @@ msgid "redirects" msgstr "preusmeritve" #: contrib/flatpages/models.py:8 -msgid "" -"Example: '/about/contact/'. Make sure to have leading and trailing slashes." -msgstr "" -"Primer: '/about/contact/'. Mora vsebovati / (poÅ¡evnico) na zaÄetku in koncu." +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Primer: '/about/contact/'. Mora vsebovati / (poÅ¡evnico) na zaÄetku in koncu." #: contrib/flatpages/models.py:9 msgid "title" @@ -1116,12 +1061,8 @@ msgid "template name" msgstr "ime predloge" #: contrib/flatpages/models.py:13 -msgid "" -"Example: 'flatpages/contact_page'. If this isn't provided, the system will " -"use 'flatpages/default'." -msgstr "" -"Primer: 'flatpages/contact_page'. ÄŒe to polje ni izpolnjeno, bo sistem " -"uporabil 'flatpages/default'." +msgid "Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'." +msgstr "Primer: 'flatpages/contact_page'. ÄŒe to polje ni izpolnjeno, bo sistem uporabil 'flatpages/default'." #: contrib/flatpages/models.py:14 msgid "registration required" @@ -1129,19 +1070,18 @@ msgstr "obvezna registracija" #: contrib/flatpages/models.py:14 msgid "If this is checked, only logged-in users will be able to view the page." -msgstr "" -"ÄŒe je to polje odkljukano, si lahko to stran ogledajo le registrirani " -"uporabniki." +msgstr "ÄŒe je to polje odkljukano, si lahko to stran ogledajo le registrirani uporabniki." #: contrib/flatpages/models.py:18 msgid "flat page" -msgstr "ploh stran :)" +msgstr "enostavna stran" #: contrib/flatpages/models.py:19 msgid "flat pages" -msgstr "ploh strani :)" +msgstr "enostavne strani" -#: contrib/auth/models.py:13 contrib/auth/models.py:26 +#: contrib/auth/models.py:13 +#: contrib/auth/models.py:26 msgid "name" msgstr "ime" @@ -1150,24 +1090,22 @@ msgid "codename" msgstr "kodno ime" #: contrib/auth/models.py:17 -#, fuzzy msgid "permission" -msgstr "Dovoljenje" +msgstr "dovoljenje" -#: contrib/auth/models.py:18 contrib/auth/models.py:27 -#, fuzzy +#: contrib/auth/models.py:18 +#: contrib/auth/models.py:27 msgid "permissions" -msgstr "Dovoljenja" +msgstr "dovoljenja" #: contrib/auth/models.py:29 -#, fuzzy msgid "group" -msgstr "Skupina" +msgstr "skupina" -#: contrib/auth/models.py:30 contrib/auth/models.py:65 -#, fuzzy +#: contrib/auth/models.py:30 +#: contrib/auth/models.py:65 msgid "groups" -msgstr "Skupine" +msgstr "skupine" #: contrib/auth/models.py:55 msgid "username" @@ -1218,27 +1156,20 @@ msgid "date joined" msgstr "Älan od" #: contrib/auth/models.py:66 -msgid "" -"In addition to the permissions manually assigned, this user will also get " -"all permissions granted to each group he/she is in." -msgstr "" -"Polek roÄno doloÄenih dovoljenj bo ta uporabnik dobil tudi vsa dovoljenja,ki " -"pripadajo vsem skupinah, v katerih je." +msgid "In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in." +msgstr "Polek roÄno doloÄenih dovoljenj bo ta uporabnik dobil tudi vsa dovoljenja,ki pripadajo vsem skupinah, v katerih je." #: contrib/auth/models.py:67 -#, fuzzy msgid "user permissions" -msgstr "Dovoljenja" +msgstr "uporabniÅ¡ka dovoljenja" #: contrib/auth/models.py:70 -#, fuzzy msgid "user" -msgstr "Uporabnik" +msgstr "uporabnik" #: contrib/auth/models.py:71 -#, fuzzy msgid "users" -msgstr "Uporabniki" +msgstr "uporabniki" #: contrib/auth/models.py:76 msgid "Personal info" @@ -1257,22 +1188,16 @@ msgid "Groups" msgstr "Skupine" #: contrib/auth/models.py:219 -#, fuzzy msgid "message" -msgstr "SporoÄilo" +msgstr "sporoÄilo" #: contrib/auth/forms.py:30 -msgid "" -"Your Web browser doesn't appear to have cookies enabled. Cookies are " -"required for logging in." -msgstr "" -"Izgleda, da vaÅ¡ brskalnik nima omogoÄenih piÅ¡kotkov. PiÅ¡kotki so potrebni za " -"prijavo." +msgid "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in." +msgstr "Izgleda, da vaÅ¡ brskalnik nima omogoÄenih piÅ¡kotkov. PiÅ¡kotki so potrebni za prijavo." #: contrib/contenttypes/models.py:25 -#, fuzzy msgid "python model class name" -msgstr "python ime modula" +msgstr "python ime razreda modela" #: contrib/contenttypes/models.py:28 msgid "content type" @@ -1366,23 +1291,28 @@ msgstr "januar" msgid "February" msgstr "februar" -#: utils/dates.py:14 utils/dates.py:27 +#: utils/dates.py:14 +#: utils/dates.py:27 msgid "March" msgstr "marec" -#: utils/dates.py:14 utils/dates.py:27 +#: utils/dates.py:14 +#: utils/dates.py:27 msgid "April" msgstr "april" -#: utils/dates.py:14 utils/dates.py:27 +#: utils/dates.py:14 +#: utils/dates.py:27 msgid "May" msgstr "maj" -#: utils/dates.py:14 utils/dates.py:27 +#: utils/dates.py:14 +#: utils/dates.py:27 msgid "June" msgstr "junij" -#: utils/dates.py:15 utils/dates.py:27 +#: utils/dates.py:15 +#: utils/dates.py:27 msgid "July" msgstr "julij" @@ -1407,62 +1337,58 @@ msgid "December" msgstr "december" #: utils/dates.py:19 -#, fuzzy msgid "jan" -msgstr "in" +msgstr "jan" #: utils/dates.py:19 -#, fuzzy msgid "feb" -msgstr "feb." +msgstr "feb" #: utils/dates.py:19 msgid "mar" -msgstr "" +msgstr "mar" #: utils/dates.py:19 msgid "apr" -msgstr "" +msgstr "apr" #: utils/dates.py:19 -#, fuzzy msgid "may" -msgstr "dan" +msgstr "maj" #: utils/dates.py:19 msgid "jun" -msgstr "" +msgstr "jun" #: utils/dates.py:20 msgid "jul" -msgstr "" +msgstr "jul" #: utils/dates.py:20 msgid "aug" -msgstr "" +msgstr "avg" #: utils/dates.py:20 msgid "sep" -msgstr "" +msgstr "sep" #: utils/dates.py:20 msgid "oct" -msgstr "" +msgstr "okt" #: utils/dates.py:20 msgid "nov" -msgstr "" +msgstr "nov" #: utils/dates.py:20 msgid "dec" -msgstr "" +msgstr "dec" #: utils/dates.py:27 msgid "Jan." msgstr "jan." #: utils/dates.py:27 -#, fuzzy msgid "Feb." msgstr "feb." @@ -1489,38 +1415,50 @@ msgstr "dec." #: utils/timesince.py:12 msgid "year" msgid_plural "years" -msgstr[0] "leto" -msgstr[1] "let" +msgstr[0] "let" +msgstr[1] "leto" +msgstr[2] "leti" +msgstr[3] "leta" #: utils/timesince.py:13 msgid "month" msgid_plural "months" -msgstr[0] "mesec" -msgstr[1] "mesecev" +msgstr[0] "mesecev" +msgstr[1] "mesec" +msgstr[2] "meseca" +msgstr[3] "meseci" #: utils/timesince.py:14 msgid "week" msgid_plural "weeks" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "tednov" +msgstr[1] "teden" +msgstr[2] "tedna" +msgstr[3] "tednov" #: utils/timesince.py:15 msgid "day" msgid_plural "days" -msgstr[0] "dan" -msgstr[1] "dni" +msgstr[0] "dni" +msgstr[1] "dan" +msgstr[2] "dneva" +msgstr[3] "dni" #: utils/timesince.py:16 msgid "hour" msgid_plural "hours" -msgstr[0] "ura" -msgstr[1] "ur" +msgstr[0] "ur" +msgstr[1] "ura" +msgstr[2] "uri" +msgstr[3] "ure" #: utils/timesince.py:17 msgid "minute" msgid_plural "minutes" -msgstr[0] "minuta" -msgstr[1] "minut" +msgstr[0] "minut" +msgstr[1] "minuta" +msgstr[2] "minuti" +msgstr[3] "minute" #: conf/global_settings.py:37 msgid "Bengali" @@ -1544,7 +1482,7 @@ msgstr "NemÅ¡ki" #: conf/global_settings.py:42 msgid "Greek" -msgstr "" +msgstr "GrÅ¡ki" #: conf/global_settings.py:43 msgid "English" @@ -1564,11 +1502,11 @@ msgstr "GaliÄanski" #: conf/global_settings.py:47 msgid "Hungarian" -msgstr "" +msgstr "Madžarski" #: conf/global_settings.py:48 msgid "Hebrew" -msgstr "" +msgstr "Hebrejski" #: conf/global_settings.py:49 msgid "Icelandic" @@ -1619,9 +1557,8 @@ msgid "Swedish" msgstr "Å vedski" #: conf/global_settings.py:61 -#, fuzzy msgid "Ukrainian" -msgstr "Brazilski" +msgstr "Ukrajinski" #: conf/global_settings.py:62 msgid "Simplified Chinese" @@ -1636,12 +1573,8 @@ msgid "This value must contain only letters, numbers and underscores." msgstr "To polje lahko vsebuje le Ärke, Å¡tevila in podÄrtaje (_)." #: core/validators.py:64 -#, fuzzy -msgid "" -"This value must contain only letters, numbers, underscores, dashes or " -"slashes." -msgstr "" -"To polje lahko vsebuje le Ärke, Å¡tevila, podÄrtaje (_) in poÅ¡evnice (/)." +msgid "This value must contain only letters, numbers, underscores, dashes or slashes." +msgstr "To polje lahko vsebuje le Ärke, Å¡tevila, podÄrtaje, poÅ¡evnice ali pomiÅ¡ljaje." #: core/validators.py:72 msgid "Uppercase letters are not allowed here." @@ -1691,23 +1624,18 @@ msgstr "Vnesite veljavni datum v zapisu YYYY-MM-DD (leto-mesec-dan)." msgid "Enter a valid time in HH:MM format." msgstr "Vnesite veljavni Äas v zapisu HH:MM (ura:minuta)." -#: core/validators.py:132 db/models/fields/__init__.py:468 +#: core/validators.py:132 +#: db/models/fields/__init__.py:468 msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." -msgstr "" -"Vnesite veljavni datum/Äas v zapisu YYYY-MM-DD HH:MM (leto-mesec-dan ura:" -"minuta)" +msgstr "Vnesite veljavni datum/Äas v zapisu YYYY-MM-DD HH:MM (leto-mesec-dan ura:minuta)" #: core/validators.py:136 msgid "Enter a valid e-mail address." msgstr "Vnesite veljavni e-naslov." #: core/validators.py:148 -msgid "" -"Upload a valid image. The file you uploaded was either not an image or a " -"corrupted image." -msgstr "" -"Uploadjate veljavno sliko. Trenutna datoteka ni bila niti slika niti " -"okvarjena slika." +msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image." +msgstr "Uploadjate veljavno sliko. Trenutna datoteka ni bila niti slika niti okvarjena slika." #: core/validators.py:155 #, python-format @@ -1747,7 +1675,8 @@ msgstr "Pokvarjen XML: %s" msgid "Invalid URL: %s" msgstr "Neveljavni URL naslov: %s" -#: core/validators.py:206 core/validators.py:208 +#: core/validators.py:206 +#: core/validators.py:208 #, python-format msgid "The URL %s is a broken link." msgstr "URL povezava %s je polomljena." @@ -1762,6 +1691,8 @@ 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] "Pazite na jezik! Beseda %s tu ni dovoljena." msgstr[1] "Pazite na jezik! Besede %s tu niso dovoljene." +msgstr[2] "Pazite na jezik! Besede %s tu niso dovoljene." +msgstr[3] "Pazite na jezik! Besede %s tu niso dovoljene." #: core/validators.py:236 #, python-format @@ -1772,7 +1703,8 @@ msgstr "To polje mora ustrezati polju '%s'." msgid "Please enter something for at least one field." msgstr "Prosim, vnesite nekaj v vsaj eno od polj." -#: core/validators.py:264 core/validators.py:275 +#: core/validators.py:264 +#: core/validators.py:275 msgid "Please enter both fields or leave them both empty." msgstr "Prosimo, izpolnite obe polji ali ju pustite obe prazni." @@ -1802,20 +1734,20 @@ msgstr "Prosim vnesite decimalno Å¡tevilo." #: core/validators.py:349 #, 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] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s Å¡tevkami." -msgstr[1] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s Å¡tevkami." +msgid_plural "Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s Å¡tevko." +msgstr[1] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s Å¡tevkama." +msgstr[2] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s Å¡tevkami." +msgstr[3] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s Å¡tevkami." #: core/validators.py:352 #, 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] "" -"Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s decimalnimi mesti." -msgstr[1] "" -"Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s decimalnimi mesti." +msgid_plural "Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s decimalnim mestom." +msgstr[1] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s decimalnimi mesti." +msgstr[2] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s decimalnimi mesti." +msgstr[3] "Prosimo, vnesite veljavno decimalno Å¡tevilo z najveÄ %s decimalnimi mesti." #: core/validators.py:362 #, python-format @@ -1842,94 +1774,68 @@ msgstr "Iz %s nisem mogel izloÄiti niÄesar." #: core/validators.py:429 #, python-format -msgid "" -"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." msgstr "URL %(url)s je vrnil neveljavni Content-Type '%(contenttype)s'." #: core/validators.py:462 #, python-format -msgid "" -"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " -"\"%(start)s\".)" -msgstr "" -"Prosimo, zaprite nezaprto %(tag)s oznako v vrstici %(line)s. (Vrstica se " -"zaÄne z \"%(start)s\".)" +msgid "Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with \"%(start)s\".)" +msgstr "Prosimo, zaprite nezaprto %(tag)s oznako v vrstici %(line)s. (Vrstica se zaÄne z \"%(start)s\".)" #: core/validators.py:466 #, python-format -msgid "" -"Some text starting on line %(line)s is not allowed in that context. (Line " -"starts with \"%(start)s\".)" -msgstr "" -"Tekst z zaÄetka vrstice %(line)s ni dovoljen v tem kontekstu. (Vrstica se " -"zaÄne z \"%(start)s\".)" +msgid "Some text starting on line %(line)s is not allowed in that context. (Line starts with \"%(start)s\".)" +msgstr "Tekst z zaÄetka vrstice %(line)s ni dovoljen v tem kontekstu. (Vrstica se zaÄne z \"%(start)s\".)" #: core/validators.py:471 -#, fuzzy, python-format -msgid "" -"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" -"(start)s\".)" -msgstr "" -"\"<%(tag)s>\" v vrstici %(line)s je neveljavna oznaka. (Vrstica se zaÄne z " -"\"%(start)s\".)" +#, python-format +msgid "\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%(start)s\".)" +msgstr "\"%(attr)s\" v vrstici %(line)s je neveljavna oznaka. (Vrstica se zaÄne z \"%(start)s\".)" #: core/validators.py:476 #, python-format -msgid "" -"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" -"(start)s\".)" -msgstr "" -"\"<%(tag)s>\" v vrstici %(line)s je neveljavna oznaka. (Vrstica se zaÄne z " -"\"%(start)s\".)" +msgid "\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%(start)s\".)" +msgstr "\"<%(tag)s>\" v vrstici %(line)s je neveljavna oznaka. (Vrstica se zaÄne z \"%(start)s\".)" #: core/validators.py:480 #, python-format -msgid "" -"A tag on line %(line)s is missing one or more required attributes. (Line " -"starts with \"%(start)s\".)" -msgstr "" -"Oznaki v vrstici %(line)s manjka eden ali veÄ nujnih atributov (Vrstica se " -"zaÄne z \"%(start)s\".)" +msgid "A tag on line %(line)s is missing one or more required attributes. (Line starts with \"%(start)s\".)" +msgstr "Oznaki na vrstici %(line)s manjka eden ali veÄ zahtevanih parametrov. (Vrstica se zaÄne z \"%(start)s\".)" #: core/validators.py:485 #, python-format -msgid "" -"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " -"starts with \"%(start)s\".)" -msgstr "" -"Atribut \"%(attr)s\" v vrstici %(line)s vsebuje neveljavno vrednost. " -"(Vrstica se zaÄne z \"%(start)s\".)" +msgid "The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line starts with \"%(start)s\".)" +msgstr "Atribut \"%(attr)s\" v vrstici %(line)s vsebuje neveljavno vrednost. (Vrstica se zaÄne z \"%(start)s\".)" #: db/models/manipulators.py:302 #, python-format msgid "%(object)s with this %(type)s already exists for the given %(field)s." -msgstr "%(object)s s tem %(type)s že obstaja za veljavno (%field)s." +msgstr "%(object)s s tem %(type)s že obstaja za dane %(field)s." #: db/models/fields/__init__.py:40 #, python-format msgid "%(optname)s with this %(fieldname)s already exists." msgstr "%(optname)s s tem %(fieldname)s že obstaja." -#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 -#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: db/models/fields/__init__.py:114 +#: db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 +#: db/models/fields/__init__.py:553 #: forms/__init__.py:346 msgid "This field is required." msgstr "To polje je obvezno" #: db/models/fields/__init__.py:337 -#, fuzzy msgid "This value must be an integer." -msgstr "Ta vrednost mora biti potenca od %s." +msgstr "Ta vrednost mora biti Å¡tevilo." #: db/models/fields/__init__.py:369 -#, fuzzy msgid "This value must be either True or False." -msgstr "Ta vrednost mora biti potenca od %s." +msgstr "Ta vrednost mora biti \"True\" ali \"False\"." #: db/models/fields/__init__.py:385 -#, fuzzy msgid "This field cannot be null." -msgstr "To polje ni veljavno." +msgstr "To polje ne more biti prazno." #: db/models/fields/__init__.py:562 msgid "Enter a valid filename." @@ -1941,39 +1847,38 @@ msgid "Please enter a valid %s." msgstr "Prosimo, vnesite veljaven %s." #: db/models/fields/related.py:579 -#, fuzzy msgid "Separate multiple IDs with commas." msgstr "VeÄ ID-jev loÄite z vejicami." #: db/models/fields/related.py:581 -#, fuzzy -msgid "" -"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." -msgstr "" -" Stisni \"Control\" (ali \"Command\" na Mac-u), da izbereÅ¡ veÄ kot enega." +msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Držite \"Control\" (ali \"Command\" na Mac-u), za izbiro veÄ kot enega." #: db/models/fields/related.py:625 #, 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] "" -"Prosimo, vnesite veljavni %(self)s ID-je. Vrednost %(value)r ni veljavna." -msgstr[1] "" -"Prosimo, vnesite veljavni %(self)s ID-je. Vrednosti %(value)r niso veljavne." +msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Prosimo, vnesite veljavne %(self)s ID-je. Vrednost %(value)r ni veljavna." +msgstr[1] "Prosimo, vnesite veljavni %(self)s ID. Vrednosti %(value)r niso veljavne." +msgstr[2] "Prosimo, vnesite veljavne %(self)s ID-je. Vrednosti %(value)r niso veljavne." +msgstr[3] "Prosimo, vnesite veljavne %(self)s ID-je. Vrednosti %(value)r niso veljavne." #: forms/__init__.py:380 #, python-format msgid "Ensure your text is less than %s character." msgid_plural "Ensure your text is less than %s characters." -msgstr[0] "Poskrbite, da bo tekst krajÅ¡i do %s znakov." -msgstr[1] "Poskrbite, da bo tekst krajÅ¡i od %s znakov." +msgstr[0] "Poskrbite, da bo tekst krajÅ¡i od %s znakov." +msgstr[1] "Poskrbite, da bo tekst krajÅ¡i od %s znaka." +msgstr[2] "Poskrbite, da bo tekst krajÅ¡i od %s znakov." +msgstr[3] "Poskrbite, da bo tekst krajÅ¡i od %s znakov." #: forms/__init__.py:385 msgid "Line breaks are not allowed here." msgstr "Prelomi vrstice tu niso dovoljeni." -#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#: forms/__init__.py:480 +#: forms/__init__.py:551 +#: forms/__init__.py:589 #, python-format msgid "Select a valid choice; '%(data)s' is not in %(choices)s." msgstr "Izberite veljavno možnost; '%(data)s' ni v %(choices)s." @@ -1998,23 +1903,24 @@ msgstr "Vnesite celo Å¡tevilo med 0 in 32,767." msgid "yes,no,maybe" msgstr "ja,ne,morda" -#~ msgid "Comment" -#~ msgstr "Komentar" +msgid "Comment" +msgstr "Komentar" + +msgid "Comments" +msgstr "Komentarji" -#~ msgid "Comments" -#~ msgstr "Komentarji" +msgid "String (up to 50)" +msgstr "Niz (do 50 znakov)" -#~ msgid "String (up to 50)" -#~ msgstr "Niz (do 50 znakov)" +msgid "label" +msgstr "oznaka" -#~ msgid "label" -#~ msgstr "oznaka" +msgid "package" +msgstr "paket" -#~ msgid "package" -#~ msgstr "paket" +msgid "packages" +msgstr "paketi" -#~ msgid "packages" -#~ msgstr "paketi" +msgid "Slovene" +msgstr "Slovensko" -#~ msgid "Slovene" -#~ msgstr "Slovenski" diff --git a/django/conf/locale/ta/LC_MESSAGES/django.mo b/django/conf/locale/ta/LC_MESSAGES/django.mo Binary files differnew file mode 100644 index 0000000000..c85327d06b --- /dev/null +++ b/django/conf/locale/ta/LC_MESSAGES/django.mo diff --git a/django/conf/locale/ta/LC_MESSAGES/django.po b/django/conf/locale/ta/LC_MESSAGES/django.po new file mode 100644 index 0000000000..7637bb9cdb --- /dev/null +++ b/django/conf/locale/ta/LC_MESSAGES/django.po @@ -0,0 +1,2111 @@ +# translation of django.po to +# translation of django_aa.po to +# translation of django_aa.po to tamil +# Parthan <parthan@au-kbc.org>, 2006. +# R Hariram Aatreya <rha@localhost.localdomain>, 2006. +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:14+0200\n" +"PO-Revision-Date: 2006-07-18 16:47+0530\n" +"Last-Translator: R Hariram Aatreya <rha@localhost.localdomain>\n" +"Language-Team: <en@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.9\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "அடையாளமà¯" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "தலையஙà¯à®•à®®à¯" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "கà¯à®±à®¿à®ªà¯à®ªà¯" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "#1 தரவரிசை" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "#2 தரவரிசை" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "#3 தரவரிசை" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "#4 தரவரிசை" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "#5 தரவரிசை" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "#6 தரவரிசை" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "#7 தரவரிசை" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "#8 தரவரிசை" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "à®…à®™à¯à®•ீகரிகà¯à®•பà¯à®ªà®Ÿà¯à®Ÿ தரவரிசை" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "தேதிநேரம௠சமரà¯à®ªà¯à®ªà®¿à®•à¯à®•பà¯à®ªà®Ÿà¯à®Ÿà¯à®³à¯à®³à®¤à¯" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "பொதà¯à®µà®¾à®©à®¤à¯" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +msgid "IP address" +msgstr "ip விலாசமà¯" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "நீகà¯à®•படà¯à®Ÿà®¤à¯" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "கà¯à®±à®¿à®ªà¯à®ªà¯ செரியாக இலà¯à®²à¯ˆà®¯à¯†à®©à¯à®±à®¾à®²à¯ இநà¯à®¤à¯ பெடà¯à®Ÿà®¿à®¯à®¿à®²à¯ கà¯à®±à®¿à®¯à®¿à®Ÿà®µà¯à®®à¯. இதறà¯à®•௠பதிலாக \"இநà¯à®¤ கà¯à®±à®¿à®ªà¯à®ªà¯ நீகà¯à®•படà¯à®Ÿà®¤à¯\" காணà¯à®ªà®¿à®•à¯à®•படà¯à®®à¯." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "கà¯à®±à®¿à®ªà¯à®ªà¯" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "பொரà¯à®³à¯ அடகà¯à®• object" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"%(user)s ஆல௠%(date)s இல௠அளிகà¯à®•பà¯à®ªà®Ÿà¯à®Ÿà®¤à¯ \n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "நபரின௠பெயரà¯" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "ip விலாசமà¯" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "பணியாளரà¯à®•ளால௠அனà¯à®®à®¤à®¿à®•à¯à®•பà¯à®ªà®Ÿà¯à®Ÿà®¤à¯" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "சà¯à®¤à®¨à¯à®¤à®°à®®à®¾à®© கà¯à®±à®¿à®ªà¯à®ªà¯" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "சà¯à®¤à®¨à¯à®¤à®°à®®à®¾à®© கà¯à®±à®¿à®ªà¯à®ªà¯" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "மதிபà¯à®ªà¯€à®Ÿà¯" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "மதிபà¯à®ªà¯€à®Ÿà¯ தேதி" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "கரà¯à®®à®¾ மதிபà¯à®ªà¯€à®Ÿà¯" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "கரà¯à®®à®¾ மதிபà¯à®ªà¯€à®Ÿà¯" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(user)s ஈடà¯à®Ÿà®¯ %(score)d " + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"இநà¯à®¤ கà¯à®±à®¿à®ªà¯à®ªà¯ %(user)s ஆல௠கà¯à®±à®¿à®•à¯à®•படà¯à®Ÿà®¤à¯:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "கà¯à®±à®¿à®¯à®¿à®©à¯ தேதி" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "பயனாளர௠கà¯à®±à®¿" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "பயனாளர௠கà¯à®±à®¿à®•ளà¯" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "%r ஆல௠கà¯à®±à®¿à®•à¯à®•பà¯à®ªà®Ÿà¯à®Ÿà®¤à¯" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "நீகà¯à®•பà¯à®ªà®Ÿà¯à®Ÿ தேதி" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "மடà¯à®Ÿà¯Šà®±à¯à®¤à¯à®¤à®¾à®²à¯ நீகà¯à®•பà¯à®ªà®Ÿà¯à®Ÿà®¤à¯" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "மடà¯à®Ÿà¯Šà®±à¯à®¤à¯à®¤à®¾à®²à¯ நீகà¯à®•பà¯à®ªà®Ÿà¯à®Ÿà®¤à¯" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "மடà¯à®Ÿà¯Šà®±à¯à®¤à¯à®¤à®¾à®²à¯ நீகà¯à®•பà¯à®ªà®Ÿà¯à®Ÿà®¤à¯ %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "அடயாளà¯à®®à¯ இலà¯à®²à®¾à®¤ பயனாளறால௠வாகà¯à®•ளிகà¯à®• à®®à¯à®Ÿà®¿à®¯à®¾à®¤à¯" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "செலà¯à®²à®¾à®¤ கà¯à®±à®¿à®ªà¯à®ªà¯ ID" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +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:112 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr "" +"%(count)s கà¯à®±à¯ˆà®µà®¾à®• அளிதà¯à®¤ பயனாளரால௠இநà¯à®¤ கà¯à®°à®¿à®ªà¯à®ªà¯ˆ அளà¯à®¤à¯à®¤à®ªà®Ÿà®¤à¯:\n" +"\n" +"%(text)s" +"%(count)s கà¯à®±à¯ˆà®µà®¾à®• அளிதà¯à®¤ பயனாளரால௠இநà¯à®¤ கà¯à®°à®¿à®ªà¯à®ªà¯ˆ அளà¯à®¤à¯à®¤à®ªà®Ÿà®¤à¯:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"à®®à¯à®´à¯à®®à¯ˆà®¯à®¾à®© விவரஙகளை அளிகà¯à®•ாத பயனாளறால௠கொடà¯à®•à¯à®•பà¯à®ªà®Ÿà¯à®Ÿà®¤à¯:\n" +"%(text)s" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "POSTகளà¯à®•à¯à®•௠மடà¯à®Ÿà¯à®®à¯ அனà¯à®®à®¤à®¿ உணà¯à®Ÿà¯" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "ஒனà¯à®±à¯ அலà¯à®²à®¤à¯ ஒனà¯à®±à®¿à®±à¯à®•௠மேறà¯à®ªà¯à®ªà®Ÿà¯à®Ÿ பà¯à®²à®™à¯à®•ள௠சமறà¯à®ªà®¿à®•à¯à®•பà¯à®ªà®Ÿà®µà®¿à®²à¯à®²à¯ˆ" + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "எவறோ கà¯à®°à®¿à®ªà¯à®ªà¯à®±à¯ˆà®¯à¯ˆ செதபà¯à®ªà®Ÿà¯à®¤à¯à®¤à®¿à®µà®¿à®Ÿà¯à®Ÿà®¾à®±à¯à®•ள௠(பாதà¯à®•ாபà¯à®ªà¯ மீறலà¯)" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "கà¯à®±à®¿à®ªà¯à®ªà¯à®±à¯ˆ படிவதà¯à®¤à®¿à®²à¯ à®®à¯à®±à¯ˆà®¯à®¾à®© இலகà¯à®•௠அளவà¯à®°à¯à®•à¯à®• இலà¯à®²à¯ˆ -- object ID à®®à¯à®±à¯ˆà®¯à®¾à®©à®¤à®¾à®• இலà¯à®²à¯ˆ" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "கà¯à®±à®¿à®ªà¯à®ªà¯ படிவம௠மà¯à®©à¯à®©à¯‹à®Ÿà¯à®Ÿà®®à¯ அலà¯à®²à®¤à¯ பிறà¯à®ªà®Ÿà¯à®Ÿà®¤à¯ˆ வழஙà¯à®•விலà¯à®²à¯ˆ." + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "பயணர௠பெயரà¯:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "கடவà¯à®šà¯à®šà¯†à®¾à®²à¯:" + +#: contrib/comments/templates/comments/form.html:6 +msgid "Forgotten your password?" +msgstr "கடவà¯à®šà¯à®šà¯Šà®²à¯à®²à¯ˆ மறநà¯à®¤à¯à®µà®¿à®Ÿà¯à®Ÿà¯€à®°à®¾?" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "வெளியேறà¯" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +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:27 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "விவரமà¯:" + +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# translation of django_ab.po to +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# R Hariram Aatreya <rha@localhost.localdomain>, 2006. +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +msgid "Preview comment" +msgstr "கà¯à®±à®¿à®ªà¯à®ªà¯ˆ à®®à¯à®©à¯à®©à¯‡à®±à¯à®±à®®à®¿à®Ÿà¯" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "உஙà¯à®•ளà¯à®ªà¯†à®¯à®°à¯:" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"<h3>By %s:</h3>\n" +"<ul>\n" +msgstr "" +"<h3> %s ஆலà¯:</h3>\n" +"<ul>\n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "அணைதà¯à®¤à¯à®®à¯" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "எநà¯à®¤ தேதியà¯à®®à¯" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "இணà¯à®±à¯" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "கடநà¯à®¤ 7 நாடà¯à®•ளிலà¯" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "இநà¯à®¤ மாதமà¯" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "இநà¯à®¤ வரà¯à®Ÿà®®à¯" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "ஆமà¯" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "இலà¯à®²à¯ˆ" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +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 "பொரà¯à®³à¯ உரà¯à®µà®•ிதà¯à®¤à®®à¯" + +#: 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/templatetags/admin_list.py:228 +msgid "All dates" +msgstr "அனைதà¯à®¤à¯ தேதியà¯à®®à¯" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:41 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "தயவà¯à®šà¯†à®¯à¯à®¤à¯ சரியான பயனரà¯à®ªà®ªà¯†à®¯à®°à¯ மறà¯à®±à¯à®®à¯ கடவà¯à®šà¯à®šà¯Šà®²à¯à®²à¯ˆ உளà¯à®³à¯à®³à®¿à®Ÿà®µà¯à®®à¯. இரணà¯à®Ÿà¯à®®à¯ எழà¯à®¤à¯à®¤à¯à®µà®•ையை சாரà¯à®¨à¯à®¤à®¤à¯." + +#: contrib/admin/views/decorators.py:23 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "உளà¯à®³à¯‡ போ" + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "தயவà¯à®šà¯†à®¯à¯à®¤à¯ மறà¯à®ªà®Ÿà®¿à®¯à¯à®®à¯ பà¯à®•à¯à®ªà®¤à®¿à®µà¯ செயà¯. à®à®©à¯†à®©à¯à®±à®¾à®³à¯ காலம௠மà¯à®Ÿà®¿à®µà®Ÿà¯ˆà®¨à¯à®¤à®µà¯. கவலை படவேணà¯à®Ÿà®¾à®®à¯: உஙà¯à®•ளà¯à®Ÿà¯ˆà®¯ அணà¯à®ªà¯à®ªà¯à®¤à®²à¯ சேமிகà¯à®•பà¯à®ªà®Ÿà¯à®Ÿà¯à®³à¯à®³à®¤à¯. " + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "உஙà¯à®•ளà¯à®Ÿà¯ˆà®¯ உலாவி தறà¯à®•ால நிரலà¯à®•ளை அணà¯à®®à®¤à®¿à®•à¯à®•ாதவாற௠உளà¯à®³à®®à¯ˆà®•à¯à®•ப௠படà¯à®Ÿà®µà®¾à®±à¯ தெரிகிறதà¯. தயவà¯à®šà¯†à®¯à¯à®¤à¯ தறà¯à®•ாலிக நிரலை செயலà¯à®ªà®Ÿ செயà¯à®¤à¯, பகà¯à®•தà¯à®¤à¯ˆ மறà¯à®ªà®Ÿà®¿ உளà¯à®³à¯à®µà®¾à®™à¯à®•வà¯à®®à¯." + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "பயனர௠பெயர௠'@' கà¯à®±à®¿à®¯à¯€à®Ÿà¯à®Ÿà¯ˆ கொணà¯à®Ÿà®¿à®°à¯à®•à¯à®• à®®à¯à®Ÿà®¿à®¯à®¾à®¤à¯." + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "உனà¯à®•ள௠மிணà¯à®…ஞà¯à®šà®³à¯ à®®à¯à®•வரிஉஙà¯à®•ள௠பயனர௠பெயர௠இலà¯à®²à¯ˆ. '%s' யை à®®à¯à®¯à®±à¯à®šà¯à®šà®¿ செயà¯à®¯à®µà¯à®®à¯. " + +#: contrib/admin/views/main.py:226 +msgid "Site administration" +msgstr "இணைய மேளானà¯à®®à¯ˆ" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" வெறà¯à®±à®¿à®•ரமாக சேரà¯à®•à¯à®•பà¯à®ªà®Ÿà¯à®Ÿà®¤à¯." + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "நீஙà¯à®•ள௠மறà¯à®ªà®Ÿà®¿à®¯à¯à®®à¯ தொகà¯à®•à¯à®• à®®à¯à®Ÿà®¿à®¯à¯à®®à¯. " + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "நீஙà¯à®•ள௠மறà¯à®± %s யை கீழே சேரà¯à®•à¯à®• à®®à¯à®Ÿà®¿à®¯à¯à®®à¯." + +#: contrib/admin/views/main.py:290 +#, python-format +msgid "Add %s" +msgstr "%s யை சேரà¯" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "%s சேரà¯à®•à¯à®•பà¯à®ªà®Ÿà¯à®Ÿà¯à®³à¯à®³à®¤à¯." + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "மறà¯à®±à¯à®®à¯" + +#: contrib/admin/views/main.py:338 +#, python-format +msgid "Changed %s." +msgstr "%s மாறà¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿà¯à®³à¯à®³à®¤à¯." + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "%s அழிகà¯à®•பà¯à®ªà®Ÿà¯à®Ÿà®¤à¯." + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "எநà¯à®¤ பà¯à®²à®®à¯à®®à¯ மாறவிலà¯à®²à¯ˆ." + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr " %(name)s \"%(obj)s\" வெறà¯à®±à®¿à®•ரமாக மாறà¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿà®¤à¯." + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "%(name)s \"%(obj)s\" வெறà¯à®±à®¿à®•ரமாக சேரà¯à®•à¯à®•பà¯à®ªà®Ÿà¯à®Ÿà¯à®³à¯à®³à®¤à¯. நீஙà¯à®•ள௠கீழே தொகà¯à®•à¯à®• à®®à¯à®Ÿà®¿à®¯à¯à®®à¯." + +#: contrib/admin/views/main.py:392 +#, python-format +msgid "Change %s" +msgstr "%s யை மாறà¯à®±à¯" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "%(name)s ல௠உளà¯à®³ %(fieldname)s: %(obj)s" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "%(name)s ல௠உளà¯à®³ %(fieldname)s:" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" வெறà¯à®±à®¿à®•ரமாக அழிகà¯à®•பà¯à®ªà®Ÿà¯à®Ÿà¯à®³à¯à®³à®¤à¯." + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "உறà¯à®¤à®¿à®¯à®¾à®• சொகிறீரà¯à®•ளா?" + +#: contrib/admin/views/main.py:533 +#, python-format +msgid "Change history: %s" +msgstr "வரலாறà¯à®±à¯ˆ மாறà¯à®±à¯: %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s" +msgstr "%s யை தேரà¯à®¨à¯à®¤à¯†à®Ÿà¯" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s to change" +msgstr "%s யை மாறà¯à®± தேரà¯à®¨à¯à®¤à¯†à®Ÿà¯" + +#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +msgid "Integer" +msgstr "à®®à¯à®´à¯ எணà¯" + +#: contrib/admin/views/doc.py:278 +msgid "Boolean (Either True or False)" +msgstr "பூலியன௠(சரி அலà¯à®²à®¤à¯ தவறà¯)" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "உரை (%(maxlength)s வரைகà¯à®•à¯à®®à¯)" + +#: contrib/admin/views/doc.py:280 +msgid "Comma-separated integers" +msgstr "கமாவாள௠பிரிகà¯à®•பà¯à®ªà®Ÿà¯à®Ÿ à®®à¯à®´à¯ எணà¯" + +#: contrib/admin/views/doc.py:281 +msgid "Date (without time)" +msgstr "தேதி (நேரமிலà¯à®²à®¾à®®à®²à¯)" + +#: contrib/admin/views/doc.py:282 +msgid "Date (with time)" +msgstr "தேதி (நேரமà¯à®Ÿà®©à¯)" + +#: contrib/admin/views/doc.py:283 +msgid "E-mail address" +msgstr "மின௠அஞà¯à®šà®²à¯" + +#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +msgid "File path" +msgstr "கோபà¯à®ªà¯à®ªà¯ பாதை" + +#: contrib/admin/views/doc.py:285 +msgid "Decimal number" +msgstr "பà¯à®³à¯à®³à®¿ எணà¯à®•ளà¯" + +#: contrib/admin/views/doc.py:291 +msgid "Boolean (Either True, False or None)" +msgstr "இலகà¯à®•௠மà¯à®±à¯ˆ (சரி, தவற௠அலà¯à®²à®¤à¯ ஒனà¯à®±à¯à®®à¯ இலà¯à®²à¯ˆ)" + +#: contrib/admin/views/doc.py:292 +msgid "Relation to parent model" +msgstr "ஆதி மாதிரிகà¯à®•௠தொடரà¯à®ªà¯à®Ÿà¯ˆà®¯à®¤à¯" + +#: contrib/admin/views/doc.py:293 +msgid "Phone number" +msgstr "தொலைபேசி எணà¯" + +#: contrib/admin/views/doc.py:298 +msgid "Text" +msgstr "உரை" + +#: contrib/admin/views/doc.py:299 +msgid "Time" +msgstr "நேரமà¯" + +#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:301 +msgid "U.S. state (two uppercase letters)" +msgstr "U.S. மாநிலம௠(இரணà¯à®Ÿà¯ மேல௠எழà¯à®¤à¯à®¤à¯à®µà®•ை எழà¯à®¤à¯à®¤à¯)" + +#: contrib/admin/views/doc.py:302 +msgid "XML text" +msgstr "XML உரை" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "ஆவணமாகà¯à®•à®®à¯" + +# translation of django_ac.po to +# translation of django_ac.po to tamil +# Ashwin <ashwin@au-kbc.org>, 2006. +# R Hariram Aatreya <rha@localhost.localdomain>, 2006. +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "கடவà¯à®šà¯à®šà¯†à®¾à®²à¯à®²à¯ˆ மாறà¯à®±à¯ " + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: 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/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "வீடà¯" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "வரலாறà¯" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "தேதி/நேரம௠" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "பயனரà¯" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "செயலà¯" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "தேதியà¯à®®à¯ à®®à¯à®´à¯ நேரமà¯à®®à¯" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"இநà¯à®¤ பொரà¯à®³à¯ மாறà¯à®± வரலாறà¯à®±à®¿à®²à¯ இலà¯à®²à¯ˆ" +"ஒர௠வேளை நிரà¯à®µà®¾à®•தà¯à®¤à®³à®¤à¯à®¤à®¿à®©à¯ மூலம௠சேரà¯à®•à¯à®•பà¯à®ªà®Ÿà®¾à®®à®²à®¿à®°à¯à®•à¯à®•லாமà¯" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "டிஜாஙà¯à®™à¯‹ தள நிரà¯à®µà®¾à®•ி" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +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/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/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "செயலியில௠கிடைகà¯à®•க௠கூடிய %(name)s மாதிரிகளà¯" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "சேரà¯" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "மாறà¯à®±à¯" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "உஙà¯à®•ளà¯à®•à¯à®•௠மாறà¯à®±à¯à®µà®¤à®±à¯à®•௠உரிமையிலà¯à®²à¯ˆ" + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "தறà¯à®ªà¯‹à®¤à¯ˆà®¯ செயலà¯à®•ளà¯" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "எனத௠செயலà¯à®•ளà¯" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "எதà¯à®µà¯à®®à¯ கிடைகà¯à®•விலà¯à®²à¯ˆ" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "%(name)s சேரà¯" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?" +msgstr "நீஙà¯à®•ள௠தஙà¯à®•ளத௠கடவà¯à®šà¯à®šà¯Šà®²à¯à®²à¯ˆ <a href=\"/password_reset/\"> மறநà¯à®¤à¯ விடà¯à®Ÿà¯€à®°à¯à®•ளா?" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "நலà¯à®µà®°à®µà¯," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "நீகà¯à®•à¯" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "இநà¯à®¤ '%(object)s' இல௠%(object_name)s நீகà¯à®•à¯à®µà®¤à¯ தொடரà¯à®ªà¯à®Ÿà¯ˆà®¯ மறà¯à®±à®µà®±à¯à®±à¯ˆà®¯à¯à®®à¯ நீகà¯à®•à¯à®®à¯. ஆனால௠அதறà¯à®•௠உஙà¯à®•ளà¯à®•à¯à®•௠உரிமையிலà¯à®²à¯ˆ" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" +"நீஙà¯à®•ள௠இநà¯à®¤ \"%(object)s\" %(object_name)s நீகà¯à®•à¯à®µà®¤à®¿à®²à¯ நிசà¯à®šà®¯à®®à®¾?" +"தொடரà¯à®ªà¯à®Ÿà¯ˆà®¯ மறà¯à®±à®µà¯ˆà®¯à¯à®®à¯ நீகà¯à®•பà¯à®ªà®Ÿà¯à®®à¯. " + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "ஆம௠எனகà¯à®•௠உறà¯à®¤à®¿ " + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr "%(title)s ஆல௠" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "செலà¯" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "தளà¯à®¤à¯à®¤à®¿à®²à¯ பார௠" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgstr "கீழே உளà¯à®³ தவறà¯à®•ளைதà¯à®¤à®¿à®°à¯à®¤à¯à®¤à¯" + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "வரிசைபà¯à®ªà®Ÿà¯à®¤à¯à®¤à¯à®¤à®²à¯" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "வரிசைபà¯à®ªà®Ÿà¯à®¤à¯à®¤à¯" + +#: 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/registration/password_change_done.html:4 +#: 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 +msgid "Password change" +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/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "உஙà¯à®•ள௠கடவà¯à®šà¯à®šà¯Šà®²à¯ மாறà¯à®±à®ªà¯ படà¯à®Ÿà¯à®³à¯à®³à®¤à¯" + +#: 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 +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/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/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "" +"வலைதà¯à®¤à®³à®¤à¯à®¤à®¿à®²à¯ உஙà¯à®•ளத௠பொனà¯à®©à®¾à®© நேரதà¯à®¤à¯ˆ " +"செலவழிதà¯à®¤à®®à¯ˆà®•à¯à®•௠மிகà¯à®¨à¯à®¤ நனà¯à®±à®¿" + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +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_done.html:12 +msgid "" +"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_change_form.html:12 +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:17 +msgid "Old password:" +msgstr "பழைய கடவà¯à®šà¯à®šà¯Šà®²à¯ :" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "பà¯à®¤à®¿à®¯ கடவà¯à®šà¯à®šà¯Šà®²à¯:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "கடவà¯à®šà¯à®šà¯Šà®²à¯ மாறà¯à®±à®¤à¯à®¤à¯ˆ உறà¯à®¤à®¿à®ªà¯à®ªà®Ÿà¯à®¤à¯à®¤à¯:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change 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" +msgstr "கடவà¯à®šà¯à®šà¯Šà®²à¯à®²à¯ˆ மாறà¯à®±à®¿à®¯à®®à¯ˆà®•à¯à®• நீஙà¯à®•ள௠கேடà¯à®Ÿà®¤à®©à®¾à®²à¯ உஙà¯à®•ளà¯à®•à¯à®•௠இநà¯à®¤ மினà¯à®©à®žà¯à®šà®²à¯ அனà¯à®ªà¯à®ªà®ªà¯à®ªà®Ÿà¯à®Ÿà¯à®³à¯à®³à®¤à¯" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "%(site_name)s -இல௠உளà¯à®³ உஙà¯à®•ளத௠பயனாளர௠கணகà¯à®•à¯" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "உஙà¯à®•ளத௠பà¯à®¤à®¿à®¯ கடவà¯à®šà¯à®šà¯Šà®²à¯ : %(new_password)s " + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "கடவà¯à®šà¯à®šà¯Šà®²à¯à®²à¯ˆ மாறà¯à®±à®¿à®¯à®®à¯ˆà®•à¯à®• நீஙà¯à®•ள௠இநà¯à®¤ பகà¯à®•தà¯à®¤à®¿à®±à¯à®•௠தாராளமாக போகலாமà¯." + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "உஙà¯à®•ளத௠பயனாளர௠பெயரà¯, (நீஙà¯à®•ள௠மறநà¯à®¤à®¿à®°à¯à®¨à¯à®¤à®¾à®²à¯ ): " + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "எஙà¯à®•ளத௠வலைதà¯à®¤à®³à®¤à¯à®¤à¯ˆ பயன௠படà¯à®¤à¯à®¤à®¿à®¯à®¤à®±à¯à®•௠மிகà¯à®¨à¯à®¤ நனà¯à®±à®¿" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "இநà¯à®¤ %(site_name)s -இன௠கà¯à®´à¯ " + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "பà¯à®¤à¯à®¤à®•கà¯à®•à¯à®±à®¿à®•ளà¯" + +# translation of django_ad.po to +# translation of django_ad.po to +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# aukbc-guest, 2006. +# R Hariram Aatreya <rha@localhost.localdomain>, 2006. +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "ஆவணமாகà¯à®•க௠கà¯à®±à®¿à®¯à¯€à®Ÿà¯" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"<p class=\"help\">To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").</p>\n" +msgstr "" +"\n" +"<p class=\"help\"> பà¯à®¤à¯à®¤à®• கà¯à®±à®¿à®¯à¯€à®Ÿà¯à®•ளை நிறà¯à®µ இநà¯à®¤ இணைபà¯à®ªà®¿à®©à¯ˆ பà¯à®¤à¯à®¤à®•கà¯à®•à¯à®±à®¿à®¯à¯€à®Ÿà¯à®Ÿà¯à®ªà¯ \n" +"படà¯à®Ÿà¯ˆà®•à¯à®•௠இழà¯à®•à¯à®•வà¯à®®à¯. அலà¯à®²à®¤à¯ வலத௠கிிளிக செயà¯à®¤à¯ பà¯à®¤à¯à®¤à®•கà¯à®•à¯à®±à®¿à®¯à¯€à®Ÿà¯à®•ளில௠சேரà¯à®•à¯à®•வà¯à®®à¯. \n" +" இனி தளதà¯à®¤à®¿à®²à¯ எநà¯à®¤à®ªà¯ பகà¯à®•தà¯à®¤à®¿à®²à¯ இரà¯à®¨à¯à®¤à¯à®®à¯ பà¯à®¤à¯à®¤à®•கà¯à®•à¯à®±à®¿à®¯à¯€à®Ÿà¯à®Ÿà®¿à®©à¯ˆ தேரà¯à®µà¯à®šà¯†à®¯à¯à®¯ à®®à¯à®Ÿà®¿à®¯à¯à®®à¯. \n" +" நீஙà¯à®•ள௠இநà¯à®¤ தளதà¯à®¤à¯ˆ \"internal\" என கà¯à¯à®±à®¿à®•à¯à®•பà¯à®ªà®Ÿà¯à®Ÿ கணிணியில௠இரà¯à®¨à¯à®¤à¯ மடà¯à®Ÿà¯à®®à¯‡ \n" +" à®’à®°à¯à®šà®¿à®² பà¯à®¤à¯à®¤à®•கà¯à®•à¯à®±à®¿à®•ளை செயலà¯à®ªà®Ÿà¯à®¤à¯à®¤à®®à¯à®Ÿà®¿à®¯à¯à®®à¯\n " +" உஙà¯à®•ளà¯à®•à¯à®•à¯, கணிணி \"internal\" என உறà¯à®¤à®¿ செயà¯à®¯ கணிணிமேளாலரை அணà¯à®•வà¯à®®à¯.</p>\n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "இநà¯à®¤ பகà¯à®•தà¯à®¤à®¿à®±à¯à®•ான ஆவணமà¯" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "எநà¯à®¤ ஒர௠பகà¯à®•தà¯à®¤à®¿à®²à®¿à®°à¯à®¨à¯à®¤à¯à®®à¯ ஆவணபà¯à®ªà®•à¯à®•தà¯à®¤à¯ˆ பாரà¯à®µà¯ˆà®¯à®¿à®Ÿà¯à®¤à®²à¯, அநà¯à®¤ பகà¯à®•தà¯à®¤à¯ˆ உரà¯à®µà®¾à®•à¯à®•à¯à®•ிறதà¯." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "object ID-஠காடà¯à®Ÿà¯" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "ஒரே object-஠கà¯à®±à®¿à®•à¯à®•à¯à®®à¯ பகà¯à®•à®™à¯à®•ளின௠பொரà¯à®³à®Ÿà®•à¯à®• வகை மறà¯à®±à¯à®®à¯ unique ID-஠காடà¯à®Ÿà¯à®•ிறதà¯." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "இதை திரà¯à®¤à¯à®¤à¯à®• (தறà¯à®ªà¯‹à®¤à¯ˆà®¯ சாளரமà¯)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "ஒரே object-஠கà¯à®±à®¿à®•à¯à®•à¯à®®à¯ பகà¯à®•à®™à¯à®•ளைக௠காண மேலாளர௠பகà¯à®•தà¯à®¤à®¿à®±à¯à®•௠செலà¯à®•." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "இதை திரà¯à®¤à¯à®¤à¯à®•. (பà¯à®¤à®¿à®¯ சாளரமà¯)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +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/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/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 "" +"இத௠ஒர௠மà¯à®´à¯à®®à¯ˆà®¯à®¾à®© பாதையாக இரà¯à®•à¯à®•வேணà¯à®Ÿà¯à®®à¯. " +"இணையதà¯à®¤à®³à®ªà¯à®ªà¯†à®¯à®°à®¾à®• இரà¯à®•à¯à®•கà¯à®•ூடாதà¯. உதாரணமà¯:'/" +"events/search/'." + +#: 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:12 +msgid "redirect" +msgstr "திரà¯à®®à¯à®ª அனà¯à®ªà¯à®ªà¯" + +#: contrib/redirects/models.py:13 +msgid "redirects" +msgstr "திரà¯à®®à¯à®ª அனà¯à®ªà¯à®ªà¯à®•ிறதà¯. " + +#: contrib/flatpages/models.py:8 +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "உதாரணமà¯: '/about/contact/'. à®®à¯à®©à¯à®©à¯à®®à¯ பினà¯à®©à¯à®®à¯ '/' உளà¯à®³à®¤à¯ˆ உறà¯à®¤à®¿ செயà¯à®•. " + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "தலைபà¯à®ªà¯" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "பொரà¯à®³à®Ÿà®•à¯à®•à®®à¯" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "விமரà¯à®šà®©à®™à¯à®•ளை செயலாகà¯à®•à¯" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "வாரà¯à®ªà¯à®ªà¯à®°à¯ பெயரà¯" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "உதாரணம௠'flatpages/contact_page'. இத௠இலà¯à®²à¯ˆà®¯à¯†à®©à®¿à®²à¯ 'flatpages/default' எனà¯à®ªà®¤à¯‡ பயனà¯à®ªà®Ÿà¯à®¤à¯à®¤à®ªà¯à®ªà®Ÿà¯à®®à¯.பà¯à®ªà®Ÿà¯à®®à¯." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "à®®à¯à®©à¯à®ªà®¤à®¿à®µà¯ தேவை" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "இத௠தெரிவ௠செயà¯à®¯à®ªà¯à®ªà®Ÿà¯à®Ÿà®¿à®°à¯à®¨à¯à®¤à®¾à®²à¯, உளà¯à®¨à¯à®´à¯ˆà®¨à¯à®¤ பயனரà¯à®•ள௠மடà¯à®Ÿà¯à®®à¯‡ இநà¯à®¤à®ªà¯ பகà¯à®•தà¯à®¤à¯ˆ பாரà¯à®•à¯à®• à®®à¯à®Ÿà®¿à®¯à¯à®®à¯." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "எளிய பகà¯à®•à®®à¯" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "எளிய பகà¯à®•à®™à¯à®•ளà¯" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "பெயரà¯" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "கà¯à®±à®¿à®®à¯à®±à¯ˆ பெயரà¯" + +#: contrib/auth/models.py:17 +msgid "permission" +msgstr "அனà¯à®®à®¤à®¿" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +msgid "permissions" +msgstr "அனà¯à®®à®¤à®¿à®•ளà¯" + +#: contrib/auth/models.py:29 +msgid "group" +msgstr "கà¯à®´à¯" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +msgid "groups" +msgstr "கà¯à®´à¯à®•à¯à®•ளà¯" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "பயனர௠பெயரà¯" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "à®®à¯à®¤à®²à¯ பெயரà¯" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "கடைசி பெயரà¯" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "மினà¯à®©à®žà¯à®šà®²à¯ à®®à¯à®•வரி" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "கடவà¯à®šà¯à®šà¯Šà®²à¯" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "பயனà¯à®ªà®Ÿà¯à®¤à¯à®¤à¯ '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr "பணியாளர௠நிலை" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "பயனரà¯, 'மேலாளலரà¯' பகà¯à®•தà¯à®¤à®¿à®²à¯ நà¯à®´à¯ˆà¯à®´à¯ˆà®µà®¤à¯ˆ à®®à¯à®Ÿà®¿à®µà¯ செயà¯à®•ிறத௠" + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "செயலà¯à®ªà®Ÿà¯à®®à¯" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "மேலாளர௠இரà¯à®ªà¯à®ªà¯ நிலை" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "கடைசி உளà¯à®¨à¯à®´à¯ˆà®µà¯" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "சேரà¯à®¨à¯à®¤ தேதி" + +#: contrib/auth/models.py:66 +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:67 +msgid "user permissions" +msgstr "பயனர௠அனà¯à®®à®¤à®¿à®•ளà¯" + +#: contrib/auth/models.py:70 +msgid "user" +msgstr "பயனரà¯" + +#: contrib/auth/models.py:71 +msgid "users" +msgstr "பயனரà¯à®•ளà¯" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "தனிபà¯à®ªà®Ÿà¯à®Ÿ விவரமà¯" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "அனà¯à®®à®¤à®¿à®•ளà¯" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "à®®à¯à®•à¯à®•ியமான தேதிகளà¯" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "கà¯à®´à¯à®•à¯à®•ளà¯" + +#: contrib/auth/models.py:219 +msgid "message" +msgstr "செயà¯à®¤à®¿" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr " உஙà¯à®•ள௠இணைய உலாவியில௠கà¯à®•à¯à®•ிகள௠செயலாகà¯à®•ம௠பெறவிலà¯à®²à¯ˆ. உளà¯à®¨à¯à®´à¯ˆà®µà®¤à®±à¯à®•à¯à®•௠கà¯à®•à¯à®•ிகள௠அவசியமà¯." + +#: contrib/contenttypes/models.py:25 +msgid "python model class name" +msgstr "python model class name" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "பொரà¯à®³à®Ÿà®•à¯à®• வகை " + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "பொரà¯à®³à®Ÿà®•à¯à®• வகைகளà¯" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "அமரà¯à®µà¯ கà¯à®±à®¿à®¯à¯€" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "அமரà¯à®µà¯ தகவலà¯" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "காலாவதியாகà¯à®®à¯ தேதி" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "அமரà¯à®µà¯" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "அமரà¯à®µà¯à®•ளà¯" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "களப௠பெயரà¯" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "காடà¯à®Ÿà¯à®®à¯ பெயரà¯" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "வலைதà¯à®¤à®³à®®à¯" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "வலைதà¯à®¤à®³à®™à¯à®•ளà¯" + +#: utils/translation.py:360 +msgid "DATE_FORMAT" +msgstr "தேதி வடிவமà¯" + +#: utils/translation.py:361 +msgid "DATETIME_FORMAT" +msgstr "தேதிநேர வடிவமà¯" + +# translation of django_ae.po to +# translation of django_ae.po to +# R Hariram Aatreya <rha@localhost.localdomain>, 2006. +#: utils/translation.py:362 +msgid "TIME_FORMAT" +msgstr "நேரதà¯à®¤à®¿à®©à¯ அமைபà¯à®ªà¯à®®à¯" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "திஙà¯à®•ளà¯" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "செவà¯à®µà®¾à®¯à¯" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "பà¯à®¤à®©à¯" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "வியாழனà¯" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "வெளà¯à®³à®¿" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "சனி" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "ஞாயிறà¯" + +#: utils/dates.py:14 +msgid "January" +msgstr "ஜனவரி" + +#: utils/dates.py:14 +msgid "February" +msgstr "பிபà¯à®°à®µà®°à®¿" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "மாரà¯à®šà¯" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "à®à®ªà¯à®°à®²à¯" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "மே" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "ஜூனà¯" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "ஜூலை" + +#: utils/dates.py:15 +msgid "August" +msgstr "ஆகஸà¯à®Ÿà¯" + +#: utils/dates.py:15 +msgid "September" +msgstr "செபà¯à®Ÿà®®à¯à®ªà®°à¯" + +#: utils/dates.py:15 +msgid "October" +msgstr "அகà¯à®Ÿà¯‹à®ªà®°à¯" + +#: utils/dates.py:15 +msgid "November" +msgstr "நவமà¯à®ªà®°à¯" + +#: utils/dates.py:16 +msgid "December" +msgstr "டிசமà¯à®ªà®°à¯" + +#: utils/dates.py:19 +msgid "jan" +msgstr "ஜன" + +#: utils/dates.py:19 +msgid "feb" +msgstr "பிபà¯" + +#: utils/dates.py:19 +msgid "mar" +msgstr "மாரà¯" + +#: utils/dates.py:19 +msgid "apr" +msgstr "à®à®ªà¯" + +#: utils/dates.py:19 +msgid "may" +msgstr "மே" + +#: utils/dates.py:19 +msgid "jun" +msgstr "ஜூனà¯" + +#: utils/dates.py:20 +msgid "jul" +msgstr "ஜூலை" + +#: utils/dates.py:20 +msgid "aug" +msgstr "ஆக" + +#: utils/dates.py:20 +msgid "sep" +msgstr "செபà¯" + +#: utils/dates.py:20 +msgid "oct" +msgstr "அகà¯" + +#: utils/dates.py:20 +msgid "nov" +msgstr "நவ" + +#: utils/dates.py:20 +msgid "dec" +msgstr "டிச" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "ஜன." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "பிபà¯." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "ஆக." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "செபà¯." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "அகà¯." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "நவ." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "டிச." + +#: utils/timesince.py:12 +msgid "year" +msgstr "வரà¯à®Ÿà®®à¯" + +#: utils/timesince.py:13 +msgid "month" +msgstr "மாதமà¯" + +#: utils/timesince.py:14 +msgid "week" +msgstr "வாரமà¯" + +#: utils/timesince.py:15 +msgid "day" +msgstr "நாளà¯" + +#: utils/timesince.py:16 +msgid "hour" +msgstr "மணி" + +#: utils/timesince.py:17 +msgid "minute" +msgstr "நிமிடமà¯" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "பெஙà¯à®•ாலி" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "செகà¯" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "வெலà¯à®¸à¯" + +#: conf/global_settings.py:40 +msgid "Danish" +msgstr "டேனிஷà¯" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "ஜெரà¯à®®à®©à¯" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "கிரேகà¯à®•à®®à¯" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "ஆஙà¯à®•ிலமà¯" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "ஸà¯à®ªà®¾à®©à®¿à®·à¯" + +#: conf/global_settings.py:45 +msgid "French" +msgstr "பà¯à®°à¯†à®©à¯à®šà¯" + +#: conf/global_settings.py:46 +msgid "Galician" +msgstr "கலீஷீயனà¯" + +#: conf/global_settings.py:47 +msgid "Hungarian" +msgstr "ஹஙà¯à®•ேரியனà¯" + +#: conf/global_settings.py:48 +msgid "Hebrew" +msgstr "ஹீபà¯à®°à¯" + +#: conf/global_settings.py:49 +msgid "Icelandic" +msgstr "à®à®¸à¯à®²à®¾à®©à¯à®Ÿà®¿à®•à¯" + +#: conf/global_settings.py:50 +msgid "Italian" +msgstr "இதà¯à®¤à®¾à®²à®¿à®¯à®©à¯" + +#: conf/global_settings.py:51 +msgid "Japanese" +msgstr "ஜபà¯à®ªà®¾à®©à®¿à®¯" + +#: conf/global_settings.py:52 +msgid "Dutch" +msgstr "டசà¯à®šà¯" + +#: conf/global_settings.py:53 +msgid "Norwegian" +msgstr "நாரà¯à®µà¯€à®šà®¿à®¯à®©à¯" + +#: conf/global_settings.py:54 +msgid "Brazilian" +msgstr "பிரேசிலியனà¯" + +#: conf/global_settings.py:55 +msgid "Romanian" +msgstr "ரோமானியனà¯" + +#: conf/global_settings.py:56 +msgid "Russian" +msgstr "à®°à®·à¯à®¯à®©à¯" + +#: conf/global_settings.py:57 +msgid "Slovak" +msgstr "சà¯à®²à¯‹à®µà®¾à®•à¯" + +#: conf/global_settings.py:58 +msgid "Slovenian" +msgstr "ஸà¯à®²à¯‹à®µà¯‡à®©à®¿à®¯à®©à¯" + +#: conf/global_settings.py:59 +msgid "Serbian" +msgstr "செரà¯à®ªà®¿à®¯à®©à¯" + +#: conf/global_settings.py:60 +msgid "Swedish" +msgstr "சà¯à®µà®¿à®Ÿà®¿à®·à¯" + +#: conf/global_settings.py:61 +msgid "Ukrainian" +msgstr "உகà¯à®°à¯‡à®©à®¿à®¯à®©à¯" + +#: conf/global_settings.py:62 +msgid "Simplified Chinese" +msgstr "எளிய சீன மொழி" + +#: conf/global_settings.py:63 +msgid "Traditional Chinese" +msgstr "மரப௠சீன மொழி" + +#: core/validators.py:60 +msgid "This value must contain only letters, numbers and underscores." +msgstr "இநà¯à®¤ மதிபà¯à®ªà¯ எழà¯à®¤à¯à®¤à¯à®•ள௠எணà¯à®•ள௠அனà¯à®Ÿà®°à¯à®¸à¯à®•ோர௠மறà¯à®±à¯à®®à¯ உளà¯à®³à®Ÿà®•à¯à®• வேணà¯à®Ÿà¯à®®à¯ " + +#: core/validators.py:64 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "இநà¯à®¤ மதிபà¯à®ªà¯ எழà¯à®¤à¯à®¤à¯à®•ள௠எணà¯à®•ள௠அனà¯à®Ÿà®°à¯à®¸à¯à®•ோர௠டஷ௠அலà¯à®²à®¤à¯ சலஷ௠மறà¯à®±à¯à®®à¯ உளà¯à®³à®Ÿà®•à¯à®• வேணà¯à®Ÿà¯à®®à¯" + +#: core/validators.py:72 +msgid "Uppercase letters are not allowed here." +msgstr "பெரிய எழà¯à®¤à¯à®¤à¯à®•ளà¯à®•à¯à®•௠இஙà¯à®•௠அனà¯à®®à®¤à®¿ இலà¯à®²à¯ˆ இலà¯à®²à¯ˆ." + +#: core/validators.py:76 +msgid "Lowercase letters are not allowed here." +msgstr "சிறிய ய எழà¯à®¤à¯à®¤à¯à®•ளà¯à®•à¯à®•௠இஙà¯à®•௠அனà¯à®®à®¤à®¿ இலà¯à®²à¯ˆ" + +# translation of django_af.po to +# translation of django_af.po to +# translation of django_af.po to +# translation of django_af.po to +# translation of django_af.po to +# translation of django_af.po to +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# , 2006. +# R Hariram Aatreya <rha@localhost.localdomain>, 2006. +#: core/validators.py:83 +msgid "Enter only digits separated by commas." +msgstr "காறà¯à®ªà¯à®³à¯à®³à®¿à®•ளால௠தனிமைபà¯à®ªà®Ÿà¯à®¤à¯à®¤à®¿à®¯ இலகà¯à®•ஙகள மடà¯à®Ÿà¯à®®à¯ எழà¯à®¤à®µà¯à®®à¯" + +#: core/validators.py:95 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "காறà¯à®ªà¯à®³à¯à®³à®¿à®•ளால௠தனிமைபà¯à®ªà®Ÿà¯à®¤à¯à®¤à®¿à®¯ à®®à¯à®±à¯ˆà®¯à®¾à®© e à®®à¯à®•வரிகளà¯à¯ மடà¯à®Ÿà¯à®®à¯ எழà¯à®¤à®µà¯à®®à¯" + +#: core/validators.py:99 +msgid "Please enter a valid IP address." +msgstr "தயவ௠செயà¯à®¤à¯ à®®à¯à®±à¯ˆà®¯à®¾à®© à®.பி à®®à¯à®•வறி மடà¯à®Ÿà¯à®®à¯ எழà¯à®¤à®µà¯à®®à¯" + +#: core/validators.py:103 +msgid "Empty values are not allowed here." +msgstr "காலியான மதிபà¯à®ªà¯à®•à¯à®•ள௠அனà¯à®®à®¤à®¿Â இலà¯à®²à¯ˆ" + +#: core/validators.py:107 +msgid "Non-numeric characters aren't allowed here." +msgstr "எண௠வடிவமிலà¯à®²à®¾à®¤ எழà¯à®¤à¯à®¤à¯à®•à¯à®•ள௠அனà¯à®®à®¤à®¿Â இலà¯à®²à¯ˆ" + +#: core/validators.py:111 +msgid "This value can't be comprised solely of digits." +msgstr "இநà¯à®¤ மதிபà¯à®ªà¯ இலகà¯à®•à®™à¯à®•ள௠மடà¯à®Ÿà¯à®®à¯‡ கொணà¯à®Ÿà®¤à®¾à®• இரà¯à®•à¯à®• கூடாதà¯" + +#: core/validators.py:116 +msgid "Enter a whole number." +msgstr "à®®à¯à®´à¯ எண௠மடà¯à®Ÿà¯à®®à¯‡ எழà¯à®¤à®µà¯à®®à¯" + +#: core/validators.py:120 +msgid "Only alphabetical characters are allowed here." +msgstr "அகர வரிசை எழà¯à®¤à¯à®¤à¯à®•à¯à®•ள௠மடà¯à®Ÿà¯à®®à¯‡ அனà¯à®®à®¤à®¿ உனà¯à®Ÿà¯" + +#: core/validators.py:124 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "வவவவ-மாமா-நாநா எனà¯à®± அமைபà¯à®ªà®¿à®²à¯ உளà¯à®³ à®®à¯à®±à¯ˆà®¯à®¾à®© தேதி மடà¯à®Ÿà¯à®®à¯‡ எழà¯à®¤à®µà¯à®®à¯" + +#: core/validators.py:128 +msgid "Enter a valid time in HH:MM format." +msgstr "மம-நிநி எனà¯à®± அமைபà¯à®ªà®¿à®²à¯ உளà¯à®³ à®®à¯à®±à¯ˆà®¯à®¾à®© நேரம௠மடà¯à®Ÿà¯à®®à¯‡ எழà¯à®¤à®µà¯à®®à¯" + +#: core/validators.py:132 db/models/fields/__init__.py:468 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "வவவவ-மாமா-நாநா மம-நிநி எனà¯à®± அமைபà¯à®ªà®¿à®²à¯ உளà¯à®³ à®®à¯à®±à¯ˆà®¯à®¾à®© தேதி/நேரம௠மடà¯à®Ÿà¯à®®à¯‡ எழà¯à®¤à®µà¯à®®à¯" + +#: core/validators.py:136 +msgid "Enter a valid e-mail address." +msgstr "à®®à¯à®±à¯ˆà®¯à®¾à®© e à®®à¯à®•வரிகளà¯à¯ மடà¯à®Ÿà¯à®®à¯ எழà¯à®¤à®µà¯à®®à¯" + +#: core/validators.py:148 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "à®®à¯à®±à¯ˆà®¯à®¾à®© படம௠மடà¯à®Ÿà¯à®®à¯‡ பதிவேறà¯à®±à®®à¯ செயà¯à®¯à®µà¯à®®à¯. நீஙà¯à®•ள௠பதிவேறà¯à®±à®®à¯ செயà¯à®¤ கோபà¯à®ªà¯ படம௠அளà¯à®³à®¾à®¤ அளà¯à®³à®¤à¯ கெடà¯à®Ÿà¯à®ªà¯à®ªà¯‹à®© கோபà¯à®ªà®¾à®•à¯à®®à¯" + +#: core/validators.py:155 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "%s எனà¯à®± இணையதள à®®à¯à®•வறி சறியான படதà¯à®¤à¯ˆà®šà¯ சà¯à®Ÿà¯à®Ÿà®µà®¿à®²à¯à®²à¯ˆ" + +#: core/validators.py:159 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "தொலைபேசி எணà¯à®•ள௠XXX-XXX-XXXX எனà¯à®± அமைபà¯à®ªà®¿à®²à¯ இரà¯à®•à¯à®• வேணà¯à®Ÿà¯à®®à¯. \"%s\" எனà¯à®ªà®¤à¯ à®®à¯à®±à¯ˆà®¯à®³à¯à®³" + +#: core/validators.py:167 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "%s எனà¯à®± இணையதள à®®à¯à®•வறி à®®à¯à®±à¯ˆà®¯à®¾à®© கà¯à®¯à®¿à®•௠டைம௠படகà¯à®•ாடà¯à®šà®¿à®¯à¯ˆà®šà¯ சà¯à®Ÿà¯à®Ÿà®µà®¿à®²à¯à®²à¯ˆ" + +#: core/validators.py:171 +msgid "A valid URL is required." +msgstr "à®®à¯à®±à¯ˆà®¯à®¾à®© இணையதள à®®à¯à®•வறி தேவை" + +#: core/validators.py:185 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"à®®à¯à®±à¯ˆà®¯à®¾à®© இணையதள à®®à¯à®•வறி தேவை. கà¯à®±à®¿à®ªà¯à®ªà®¿à®Ÿà®¤à¯à®¤à®•à¯à®•த௠தவறà¯à®•ளாவன:\n" +"%s" + +#: core/validators.py:192 +#, python-format +msgid "Badly formed XML: %s" +msgstr "à®®à¯à®±à¯ˆà®ªà¯à®ªà®Ÿà¯à®¤à¯à®¤à®ªà¯à®ªà®Ÿà®¾à®¤ XML: %s" + +#: core/validators.py:202 +#, python-format +msgid "Invalid URL: %s" +msgstr "à®®à¯à®±à¯ˆà®ªà¯à®ªà®Ÿà¯à®¤à¯à®¤à®ªà¯à®ªà®Ÿà®¾à®¤ இணையதள à®®à¯à®•வறி: %s" + +#: core/validators.py:206 core/validators.py:208 +#, python-format +msgid "The URL %s is a broken link." +msgstr "%s எனà¯à®± இணையதள à®®à¯à®•வறி உடைநà¯à®¤à¯à®³à¯à®³à®¤à¯" + +#: core/validators.py:214 +msgid "Enter a valid U.S. state abbreviation." +msgstr "à®®à¯à®±à¯ˆà®¯à®¾à®© U.S மாநில பெயர௠சà¯à®°à¯à®•à¯à®•ம௠எழà¯à®¤à®µà¯à®®à¯à®´à¯à®¤à®µà¯à®®à¯" + +#: core/validators.py:229 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgstr "வாரà¯à®¤à¯à®¤à¯ˆà®•ளை அளனà¯à®¤à¯ பேசà¯à®™à¯à®•ளà¯. %s எனà¯à®± வாரà¯à®¤à¯à®¤à¯ˆ இஙà¯à®•௠அனà¯à®®à®¤à®¿ இலà¯à®²à¯ˆ" + +#: core/validators.py:236 +#, python-format +msgid "This field must match the '%s' field." +msgstr "இநà¯à®¤ பà¯à®²à®®à¯ %s எனà¯à®± பà¯à®²à®¤à¯à®¤à¯à®Ÿà®©à¯ ஒதà¯à®¤à®¿à®±à¯à®•à¯à®• வேணà¯à®Ÿà¯à®®à¯" + +#: core/validators.py:255 +msgid "Please enter something for at least one field." +msgstr "தயவ௠செயà¯à®¤à¯ ஒர௠பà¯à®²à®¤à¯à®¤à®¿à®²à®¾à®µà®¤à¯ à®à®¤à®¾à®µà®¤à¯ எழà¯à®¤à®µà¯à®®à¯" + +#: core/validators.py:264 core/validators.py:275 +msgid "Please enter both fields or leave them both empty." +msgstr "தயவ௠செயà¯à®¤à¯ இரà¯à¯à®ªà¯à®²à®™à¯à®•லையà¯à®®à¯à¯à®®à¯ நிரபà¯à®ªà®µà¯à®®à¯; அலà¯à®²à®¤à¯ இரணà¯à®Ÿà¯ˆà®¯à¯à®®à¯ காலியாக விடவà¯à®®à¯" + +#: core/validators.py:282 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "%(field)s, %(value)s ஆக இரà¯à®¨à¯à®¤à®¾à®²à¯ இனà¯à®¤ பà¯à®²à®®à¯ இரà¯à®•à¯à®• வேணà¯à®Ÿà¯à®®à¯" + +#: core/validators.py:294 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "%(field)s, %(value)s ஆக இலà¯à®²à¯ˆ எனà¯à®±à®¾à®²à¯ இனà¯à®¤ பà¯à®²à®®à¯ இரà¯à®•à¯à®• வேணà¯à®Ÿà¯à®®à¯" + +#: core/validators.py:313 +msgid "Duplicate values are not allowed." +msgstr "போலியான மதிபà¯à®ªà¯à®•ள௠அனà¯à®®à®¤à®¿ இலà¯à®²à¯ˆ" + +#: core/validators.py:336 +#, python-format +msgid "This value must be a power of %s." +msgstr "இநà¯à®¤ மதிபà¯à®ªà¯ %s இன௠அடà¯à®•à¯à®•ாக இரà¯à®•à¯à®• வேனà¯à®Ÿà¯à®®à¯" + +#: core/validators.py:347 +msgid "Please enter a valid decimal number." +msgstr "தயவà¯à®šà¯†à®¯à¯à®¤à¯ à®®à¯à®±à¯ˆà®¯à®¾à®© பதினà¯à®® எணà¯à®£à¯ˆ நழைகà¯à®•cவà¯à®®à¯" + +#: core/validators.py:349 +#, python-format +msgid "" +"Please enter a valid decimal number with at most %s total digit." +"Please enter a valid decimal number with at most %s total digits." +msgstr "" +"அதிகபடà¯à®šà®®à¯ %s எணà¯à®£à¯ˆ உளà¯à®³ பதினà¯à®® எணà¯à®£à¯ˆ நà¯à®´à¯ˆ." +"அதிகபடà¯à®šà®®à¯ %s எணà¯à®•ள௠உளà¯à®³ பதினà¯à®® எணà¯à®£à¯ˆ நà¯à®´à¯ˆ." + +#: core/validators.py:352 +#, python-format +msgid "" +"Please enter a valid decimal number with at most %s decimal place." +"Please enter a valid decimal number with at most %s decimal places." +msgstr "" +"அதிகபடà¯à®šà®®à¯ %s பà¯à®³à¯à®³à®¿ இடம௠ள௠உளà¯à®³ பதினà¯à®® எணà¯à¯ˆ நà¯à®´à¯ˆ" +"அதிகபடà¯à®šà®®à¯ %s பà¯à®³à¯à®³à®¿ இடஙà¯à®•ள௠உளà¯à®³ பதினà¯à®® எணà¯à¯ˆ நà¯à®´à¯ˆ" + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "மேலà¯à®à®±à¯à®±à¯ செயà¯à®¯à®ªà¯à®ªà®Ÿà¯à®Ÿ கோபà¯à®ªà¯ கà¯à®±à¯ˆà®¨à¯à®¤à®ªà®Ÿà¯à®šà®®à¯ %s பைடà¯à®Ÿà¯à®•ள௠உளà¯à®³à®©à®µà®¾ என சரி பாரà¯à®•à¯à®•வà¯à®®à¯" + +#: core/validators.py:363 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "மேலà¯à®à®±à¯à®±à¯ செயà¯à®¯à®ªà¯à®ªà®Ÿà¯à®Ÿ கோபà¯à®ªà¯ அதிகபடà¯à®šà®®à¯ %s பைடà¯à®Ÿà¯à®•ள௠உளà¯à®³à®©à®µà®¾ என சரி பாரà¯à®•à¯à®•வà¯à®®à¯." + +#: core/validators.py:376 +msgid "The format for this field is wrong." +msgstr "பà¯à®²à®©à¯à®Ÿà¯ˆà®¯ அமைபà¯à®ªà¯ தவறà¯" + +#: core/validators.py:391 +msgid "This field is invalid." +msgstr "இநà¯à®¤ பà¯à®²à®®à¯ செலà¯à®²à®¾à®¤à¯." + +#: core/validators.py:426 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "%s இரà¯à®¨à¯à®¤à¯ எதà¯à®µà¯à®®à¯ எடà¯à®•à¯à®• à®®à¯à®Ÿà®¿à®¯à®µà®¿à®²à¯à®²à¯ˆ" + +#: core/validators.py:429 +#, python-format +msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "வலைமனை %(url)s எனà¯à®ªà®¤à¯ செலà¯à®²à®¾à®¤ உளà¯à®³à®Ÿà®•à¯à®•-வகை தலைபà¯à®ªà®¾à®© '%(contenttype)s' ஠திரà¯à®ªà¯à®ªà®¿ தநà¯à®¤à¯à®³à¯à®³à®¤à¯." + +#: core/validators.py:462 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr " %(line)s கோட௠லிரà¯à®¨à¯à®¤à¯ மூடாத %(tag)s டாகை மூடà¯. ( வரி,\"%(start)s\"வà¯à®Ÿà®©à¯ தà¯à®µà®™à¯à®•à¯à®•ினà¯à®±à®¤à¯)" + +#: core/validators.py:466 +#, 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:471 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "வரி %(line)s இல௠உளà¯à®³ \"%(attr)s\" எனà¯à®ªà®¤à¯ தவறான பணà¯à®ªà®¾à®•à¯à®®.( வரி,\"%(start)s\"வà¯à®Ÿà®©à¯ தà¯à®µà®™à¯à®•à¯à®•ினà¯à®±à®¤à¯)" + +#: core/validators.py:476 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "வரி %(line)s இல௠உளà¯à®³ \"<%(tag)s>\" எனà¯à®ªà®¤à¯ தவறான ஒடà¯à®Ÿà®¾à®•à¯à®®à¯ .( வரி,\"%(start)s\"வà¯à®Ÿà®©à¯ தà¯à®µà®™à¯à®•à¯à®•ினà¯à®±à®¤à¯)" + +#: core/validators.py:480 +#, 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:485 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "வரி %(line)s இல௠உளà¯à®³ \"%(attr)s\" பணà¯à®ªà®¿à®©à¯ மதிபà¯à®ªà¯ தவறானதà¯.(வரி \"%(start)s\" இரà¯à®¨à¯à®¤à¯ ஆரமà¯à®ªà®®à¯)" + +#: db/models/manipulators.py:302 +#, python-format +#, fuzzy +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "%(type)s உடன௠உளà¯à®³ %(object)s உளà¯à®³à®¤à¯" + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(fieldname)s உடன௠உளà¯à®³ %(optname)s உயிரà¯à®Ÿà®© உளà¯à®³à®¤à¯" + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "இநà¯à®¤ பà¯à®²à®¤à¯à®¤à®¿à®²à¯ மதிபà¯à®ªà¯ தேவை" + +#: db/models/fields/__init__.py:337 +msgid "This value must be an integer." +msgstr "இநà¯à®¤ மதிபà¯à®ªà¯ à®®à¯à®´à¯à®µà¯†à®£à¯à®£à®¾à®• இரà¯à®•à¯à®• வேணà¯à®Ÿà¯à®®" + +#: db/models/fields/__init__.py:369 +msgid "This value must be either True or False." +msgstr "இநà¯à®¤ மதிபà¯à®ªà¯ சரி அலà¯à®²à®¤à¯ தவறாக இரà¯à®•à¯à®• வேணà¯à®Ÿà¯à®®à¯" + +#: db/models/fields/__init__.py:385 +msgid "This field cannot be null." +msgstr "இநà¯à®¤ பà¯à®²à®®à¯ காலியாக இரà¯à®•à¯à®•க௠கூடாதà¯" + +#: db/models/fields/__init__.py:562 +msgid "Enter a valid filename." +msgstr "à®®à¯à®±à¯ˆà®¯à®¾à®© கோபà¯à®ªà¯à®ªà¯ பெயரை எழà¯à®¤à®µà¯à®®à¯" + +#: db/models/fields/related.py:43 +#, python-format +msgid "Please enter a valid %s." +msgstr "தயவ௠செயà¯à®¤à¯ à®®à¯à®±à¯ˆà®¯à®¾à®© %s எழà¯à®¤à®µà¯à®®à¯" + +#: db/models/fields/related.py:579 +msgid "Separate multiple IDs with commas." +msgstr "பனà¯à®®à¯ˆà®¯à®¿à®²à¯à®³à¯à®³ அடையாளஙà¯à®•ளை காறà¯à®ªà¯à®³à¯à®³à®¿à®•ளால௠பிரிகà¯à®•வà¯à®®à¯" + +#: db/models/fields/related.py:581 +msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Mac இலà¯, ஒனà¯à®±à¯à®•à¯à®•௠மேறà¯à®ªà®Ÿà¯à®Ÿà®µà®±à¯à®±à¯ˆ தேரà¯à®µà¯ செயà¯à®¯ \"Control\" அலà¯à®²à®¤à¯ \"Command\" à® à®…à®´à¯à®¤à¯à®¤à®µà¯à®®à¯" + +#: db/models/fields/related.py:625 +#, python-format +msgid "" +"Please enter valid %(self)s IDs. The value %(value)r is invalid." +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr "" +"தயவ௠செயà¯à®¤à¯ à®®à¯à®±à¯ˆà®¯à®¾à®© %(self)s அடையாளஙà¯à®•ளை எழà¯à®¤à®µà¯à®®à¯. %(value)r எனà¯à®± மதிபà¯à®ªà¯ à®®à¯à®±à¯ˆà®¯à®¾à®©à®¤à®²à¯à®². " +"தயவ௠செயà¯à®¤à¯ à®®à¯à®±à¯ˆà®¯à®¾à®© %(self)s அடையாளஙà¯à®•ளை எழà¯à®¤à®µà¯à®®à¯. %(value)r எனà¯à®± மதிபà¯à®ªà¯à®•ள௠மà¯à®±à¯ˆà®¯à®¾à®©à®¤à®²à¯à®². " + +#: forms/__init__.py:380 +#, python-format +msgid "Ensure your text is less than %s character." +msgstr "உஙà¯à®•ள௠உரை %s ஠விட கà¯à®±à¯ˆà®µà®¾à®© எழà¯à®¤à¯à®¤à¯à®•à¯à®•ளை உடையதென உறà¯à®¤à®¿ செயà¯à®¤à¯ கொளà¯à®³à¯à®™à¯à®•ளà¯" + +#: forms/__init__.py:385 +msgid "Line breaks are not allowed here." +msgstr "வரி உடைவà¯à®•ள௠அனà¯à®®à®¤à®¿ இலà¯à®²à¯ˆ" + +#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "à®®à¯à®±à¯ˆà®¯à®¾à®© விரà¯à®ªà¯à®ªà®¤à¯à®¤à¯ˆ தேரà¯à®µà¯ செயà¯à®¯à®µà¯à®®à¯; '%(data)s எனà¯à®ªà®¤à¯ %(choices)s இல௠இலà¯à®²à¯ˆ" + +#: forms/__init__.py:645 +msgid "The submitted file is empty." +msgstr "சமரà¯à®ªà®¿à®•à¯à®•ப௠படà¯à®Ÿ கோபà¯à®ªà¯ காலியாக உளà¯à®³à®¤à¯" + +#: forms/__init__.py:699 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "-32,768 மறà¯à®±à¯à®®à¯ 32,767 க௠நடà¯à®µà®¿à®²à¯ ஒர௠மà¯à®´à¯ எணà¯à®£à¯ˆ எழà¯à®¤à®µà¯à®®à¯" + +#: forms/__init__.py:708 +msgid "Enter a positive number." +msgstr "ஒர௠நேரà¯à®•à¯à®•à¯à®±à®¿Â எணà¯à®£à¯ˆ எழà¯à®¤à®µà¯à®®à¯" + +#: forms/__init__.py:717 +msgid "Enter a whole number between 0 and 32,767." +msgstr "0 மறà¯à®±à¯à®®à¯ 32,767 க௠நடà¯à®µà®¿à®²à¯ ஒர௠மà¯à®´à¯ எணà¯à®£à¯ˆ எழà¯à®¤à®µà¯à®®à¯" + +#: template/defaultfilters.py:379 +msgid "yes,no,maybe" +msgstr "ஆமà¯, இலà¯à®²à¯ˆ, இரà¯à®•à¯à®•லாமà¯" + diff --git a/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mo b/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mo Binary files differnew file mode 100644 index 0000000000..983a8a7154 --- /dev/null +++ b/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mo diff --git a/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po b/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000000..1e9e7dbeb7 --- /dev/null +++ b/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po @@ -0,0 +1,107 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-03-21 18:43+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <max@exoweb.net>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +msgid "Available %s" +msgstr "å¯è¡Œ %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "全选" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "å¢žåŠ " + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "移出" + +#: contrib/admin/media/js/SelectFilter2.js:53 +msgid "Chosen %s" +msgstr "选择 %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "æŒ‘é€‰ä½ çš„é€‰æ‹©å¹¶ä¸”ç‚¹å‡» " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "清除所有" + +#: contrib/admin/media/js/dateparse.js:32 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "一月 二月 三月 四月 五月 å…æœˆ å…æœˆ 七月 八月 乿œˆ åæœˆ å一月 å二月" + +#: contrib/admin/media/js/dateparse.js:33 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "星期天 星期一 星期二 星期三 星期四 星期五 星期å…" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "æ—¥ 月 ç« æ°´ 木 金 土" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "现在" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "æ—¶é’Ÿ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "选择一个时间" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "åˆå¤œ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "上åˆ6点" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "æ£åˆ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "å–æ¶ˆ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "今天" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "日历" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "昨天" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "明天" diff --git a/django/conf/urls/defaults.py b/django/conf/urls/defaults.py index 07611c5147..17fe603d96 100644 --- a/django/conf/urls/defaults.py +++ b/django/conf/urls/defaults.py @@ -10,8 +10,10 @@ include = lambda urlconf_module: [urlconf_module] def patterns(prefix, *tuples): pattern_list = [] for t in tuples: - if type(t[1]) == list: - pattern_list.append(RegexURLResolver(t[0], t[1][0])) + regex, view_or_include = t[:2] + default_kwargs = t[2:] + if type(view_or_include) == list: + pattern_list.append(RegexURLResolver(regex, view_or_include[0], *default_kwargs)) else: - pattern_list.append(RegexURLPattern(t[0], prefix and (prefix + '.' + t[1]) or t[1], *t[2:])) + pattern_list.append(RegexURLPattern(regex, prefix and (prefix + '.' + view_or_include) or view_or_include, *default_kwargs)) return pattern_list diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index 25376be12a..8c2b82147e 100644 --- a/django/contrib/admin/filterspecs.py +++ b/django/contrib/admin/filterspecs.py @@ -123,7 +123,7 @@ class DateFieldFilterSpec(FilterSpec): def choices(self, cl): for title, param_dict in self.links: yield {'selected': self.date_params == param_dict, - 'query_string': cl.get_query_string(param_dict, self.field_generic), + 'query_string': cl.get_query_string(param_dict, [self.field_generic]), 'display': title} FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec) diff --git a/django/contrib/admin/media/css/changelists.css b/django/contrib/admin/media/css/changelists.css index fbcbe56f06..4834be4685 100644 --- a/django/contrib/admin/media/css/changelists.css +++ b/django/contrib/admin/media/css/changelists.css @@ -42,9 +42,9 @@ /* PAGINATOR */ .paginator { font-size:11px; padding-top:10px; padding-bottom:10px; line-height:22px; margin:0; border-top:1px solid #ddd; } -.paginator a:link, .paginator a:visited { padding:2px 6px; border:solid 1px #ccc; background:white; text-decoration:none; } +.paginator a:link, .paginator a:visited { padding:2px 6px; border:solid 1px #ccc; background:white; text-decoration:none; } .paginator a.showall { padding:0 !important; border:none !important; } .paginator a.showall:hover { color:#036 !important; background:transparent !important; } -.paginator .end { border-width:2px !important; margin-right:6px; } +.paginator .end { border-width:2px !important; margin-right:6px; } .paginator .this-page { padding:2px 6px; font-weight:bold; font-size:13px; vertical-align:top; } .paginator a:hover { color:white; background:#5b80b2; border-color:#036; } diff --git a/django/contrib/admin/media/css/forms.css b/django/contrib/admin/media/css/forms.css index b66f268fec..468e06a8a2 100644 --- a/django/contrib/admin/media/css/forms.css +++ b/django/contrib/admin/media/css/forms.css @@ -7,10 +7,10 @@ form .form-row p { padding-left:0; font-size:11px; } /* FORM LABELS */ -form h4 { margin:0 !important; padding:0 !important; border:none !important; } +form h4 { margin:0 !important; padding:0 !important; border:none !important; } label { font-weight:normal !important; color:#666; font-size:12px; } label.inline { margin-left:20px; } -.required label, label.required { font-weight:bold !important; color:#333 !important; } +.required label, label.required { font-weight:bold !important; color:#333 !important; } /* RADIO BUTTONS */ form ul.radiolist li { list-style-type:none; } diff --git a/django/contrib/admin/media/css/global.css b/django/contrib/admin/media/css/global.css index a87931681c..16c582d578 100644 --- a/django/contrib/admin/media/css/global.css +++ b/django/contrib/admin/media/css/global.css @@ -31,7 +31,7 @@ fieldset { margin:0; padding:0; } blockquote { font-size:11px; color:#777; margin-left:2px; padding-left:10px; border-left:5px solid #ddd; } code, pre { font-family:"Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; background:inherit; color:#666; font-size:11px; } pre.literal-block { margin:10px; background:#eee; padding:6px 8px; } -code strong { color:#930; } +code strong { color:#930; } hr { clear:both; color:#eee; background-color:#eee; height:1px; border:none; margin:0; padding:0; font-size:1px; line-height:1px; } /* TEXT STYLES & MODIFIERS */ @@ -81,7 +81,7 @@ table.orderable tbody tr td:first-child { padding-left:14px; background-image:ur table.orderable-initalized .order-cell, body>tr>td.order-cell { display:none; } /* FORM DEFAULTS */ -input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; } +input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; } textarea { vertical-align:top !important; } input[type=text], input[type=password], textarea, select, .vTextField { border:1px solid #ccc; } @@ -92,7 +92,7 @@ input[type=submit].default, .submit-row input.default { border:2px solid #5b80b2 input[type=submit].default:active { background-image:url(../img/admin/default-bg-reverse.gif); background-position:top; } /* MODULES */ -.module { border:1px solid #ccc; margin-bottom:5px; background:white; } +.module { border:1px solid #ccc; margin-bottom:5px; background:white; } .module p, .module ul, .module h3, .module h4, .module dl, .module pre { padding-left:10px; padding-right:10px; } .module blockquote { margin-left:12px; } .module ul, .module ol { margin-left:1.5em; } diff --git a/django/contrib/admin/media/css/layout.css b/django/contrib/admin/media/css/layout.css index befe4fc1ca..17c52861ee 100644 --- a/django/contrib/admin/media/css/layout.css +++ b/django/contrib/admin/media/css/layout.css @@ -4,7 +4,7 @@ #header { width:100%; } #content-main { float:left; width:100%; } #content-related { float:right; width:18em; position:relative; margin-right:-19em; } -#footer { clear:both; padding:10px; } +#footer { clear:both; padding:10px; } /* COLUMN TYPES */ .colMS { margin-right:20em !important; } @@ -16,14 +16,14 @@ .dashboard #content { width:500px; } /* HEADER */ -#header { background:#417690; color:#ffc; overflow:hidden; } +#header { background:#417690; color:#ffc; overflow:hidden; } #header a:link, #header a:visited { color:white; } #header a:hover { text-decoration:underline; } #branding h1 { padding:0 10px; font-size:18px; margin:8px 0; font-weight:normal; color:#f4f379; } #branding h2 { padding:0 10px; font-size:14px; margin:-8px 0 8px 0; font-weight:normal; color:#ffc; } -#user-tools { position:absolute; top:0; right:0; padding:1.2em 10px; font-size:11px; text-align:right; } +#user-tools { position:absolute; top:0; right:0; padding:1.2em 10px; font-size:11px; text-align:right; } /* SIDEBAR */ #content-related h3 { font-size:12px; color:#666; margin-bottom:3px; } #content-related h4 { font-size:11px; } -#content-related .module h2 { background:#eee url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; }
\ No newline at end of file +#content-related .module h2 { background:#eee url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; }
\ No newline at end of file diff --git a/django/contrib/admin/media/css/rtl.css b/django/contrib/admin/media/css/rtl.css index c29391cabf..1974e7c2ec 100644 --- a/django/contrib/admin/media/css/rtl.css +++ b/django/contrib/admin/media/css/rtl.css @@ -16,7 +16,7 @@ th { text-align: right; } /* layout styles */ -#user-tools { right:auto; left:0; text-align:left; } +#user-tools { right:auto; left:0; text-align:left; } div.breadcrumbs { text-align:right; } #content-main { float:right;} #content-related { float:left; margin-left:-19em; margin-right:auto;} diff --git a/django/contrib/admin/media/js/admin/CollapsedFieldsets.js b/django/contrib/admin/media/js/admin/CollapsedFieldsets.js index 97f0a68d04..c8426db228 100644 --- a/django/contrib/admin/media/js/admin/CollapsedFieldsets.js +++ b/django/contrib/admin/media/js/admin/CollapsedFieldsets.js @@ -3,83 +3,83 @@ // link when the fieldset is visible. function findForm(node) { - // returns the node of the form containing the given node - if (node.tagName.toLowerCase() != 'form') { - return findForm(node.parentNode); - } - return node; + // returns the node of the form containing the given node + if (node.tagName.toLowerCase() != 'form') { + return findForm(node.parentNode); + } + return node; } var CollapsedFieldsets = { - collapse_re: /\bcollapse\b/, // Class of fieldsets that should be dealt with. - collapsed_re: /\bcollapsed\b/, // Class that fieldsets get when they're hidden. - collapsed_class: 'collapsed', - init: function() { - var fieldsets = document.getElementsByTagName('fieldset'); - var collapsed_seen = false; - for (var i = 0, fs; fs = fieldsets[i]; i++) { - // Collapse this fieldset if it has the correct class, and if it - // doesn't have any errors. (Collapsing shouldn't apply in the case - // of error messages.) - if (fs.className.match(CollapsedFieldsets.collapse_re) && !CollapsedFieldsets.fieldset_has_errors(fs)) { - collapsed_seen = true; - // Give it an additional class, used by CSS to hide it. - fs.className += ' ' + CollapsedFieldsets.collapsed_class; - // (<a id="fieldsetcollapser3" class="collapse-toggle" href="#">Show</a>) - var collapse_link = document.createElement('a'); - collapse_link.className = 'collapse-toggle'; - collapse_link.id = 'fieldsetcollapser' + i; - collapse_link.onclick = new Function('CollapsedFieldsets.show('+i+'); return false;'); - collapse_link.href = '#'; - collapse_link.innerHTML = gettext('Show'); - var h2 = fs.getElementsByTagName('h2')[0]; - h2.appendChild(document.createTextNode(' (')); - h2.appendChild(collapse_link); - h2.appendChild(document.createTextNode(')')); - } - } - if (collapsed_seen) { - // Expand all collapsed fieldsets when form is submitted. - addEvent(findForm(document.getElementsByTagName('fieldset')[0]), 'submit', function() { CollapsedFieldsets.uncollapse_all(); }); - } - }, - fieldset_has_errors: function(fs) { - // Returns true if any fields in the fieldset have validation errors. - var divs = fs.getElementsByTagName('div'); - for (var i=0; i<divs.length; i++) { - if (divs[i].className.match(/\berror\b/)) { - return true; - } - } - return false; - }, - show: function(fieldset_index) { - var fs = document.getElementsByTagName('fieldset')[fieldset_index]; - // Remove the class name that causes the "display: none". - fs.className = fs.className.replace(CollapsedFieldsets.collapsed_re, ''); - // Toggle the "Show" link to a "Hide" link - var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index); - collapse_link.onclick = new Function('CollapsedFieldsets.hide('+fieldset_index+'); return false;'); - collapse_link.innerHTML = gettext('Hide'); - }, - hide: function(fieldset_index) { - var fs = document.getElementsByTagName('fieldset')[fieldset_index]; - // Add the class name that causes the "display: none". - fs.className += ' ' + CollapsedFieldsets.collapsed_class; - // Toggle the "Hide" link to a "Show" link - var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index); + collapse_re: /\bcollapse\b/, // Class of fieldsets that should be dealt with. + collapsed_re: /\bcollapsed\b/, // Class that fieldsets get when they're hidden. + collapsed_class: 'collapsed', + init: function() { + var fieldsets = document.getElementsByTagName('fieldset'); + var collapsed_seen = false; + for (var i = 0, fs; fs = fieldsets[i]; i++) { + // Collapse this fieldset if it has the correct class, and if it + // doesn't have any errors. (Collapsing shouldn't apply in the case + // of error messages.) + if (fs.className.match(CollapsedFieldsets.collapse_re) && !CollapsedFieldsets.fieldset_has_errors(fs)) { + collapsed_seen = true; + // Give it an additional class, used by CSS to hide it. + fs.className += ' ' + CollapsedFieldsets.collapsed_class; + // (<a id="fieldsetcollapser3" class="collapse-toggle" href="#">Show</a>) + var collapse_link = document.createElement('a'); + collapse_link.className = 'collapse-toggle'; + collapse_link.id = 'fieldsetcollapser' + i; + collapse_link.onclick = new Function('CollapsedFieldsets.show('+i+'); return false;'); + collapse_link.href = '#'; + collapse_link.innerHTML = gettext('Show'); + var h2 = fs.getElementsByTagName('h2')[0]; + h2.appendChild(document.createTextNode(' (')); + h2.appendChild(collapse_link); + h2.appendChild(document.createTextNode(')')); + } + } + if (collapsed_seen) { + // Expand all collapsed fieldsets when form is submitted. + addEvent(findForm(document.getElementsByTagName('fieldset')[0]), 'submit', function() { CollapsedFieldsets.uncollapse_all(); }); + } + }, + fieldset_has_errors: function(fs) { + // Returns true if any fields in the fieldset have validation errors. + var divs = fs.getElementsByTagName('div'); + for (var i=0; i<divs.length; i++) { + if (divs[i].className.match(/\berror\b/)) { + return true; + } + } + return false; + }, + show: function(fieldset_index) { + var fs = document.getElementsByTagName('fieldset')[fieldset_index]; + // Remove the class name that causes the "display: none". + fs.className = fs.className.replace(CollapsedFieldsets.collapsed_re, ''); + // Toggle the "Show" link to a "Hide" link + var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index); + collapse_link.onclick = new Function('CollapsedFieldsets.hide('+fieldset_index+'); return false;'); + collapse_link.innerHTML = gettext('Hide'); + }, + hide: function(fieldset_index) { + var fs = document.getElementsByTagName('fieldset')[fieldset_index]; + // Add the class name that causes the "display: none". + fs.className += ' ' + CollapsedFieldsets.collapsed_class; + // Toggle the "Hide" link to a "Show" link + var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index); collapse_link.onclick = new Function('CollapsedFieldsets.show('+fieldset_index+'); return false;'); - collapse_link.innerHTML = gettext('Show'); - }, + collapse_link.innerHTML = gettext('Show'); + }, - uncollapse_all: function() { - var fieldsets = document.getElementsByTagName('fieldset'); - for (var i=0; i<fieldsets.length; i++) { - if (fieldsets[i].className.match(CollapsedFieldsets.collapsed_re)) { - CollapsedFieldsets.show(i); - } - } - } + uncollapse_all: function() { + var fieldsets = document.getElementsByTagName('fieldset'); + for (var i=0; i<fieldsets.length; i++) { + if (fieldsets[i].className.match(CollapsedFieldsets.collapsed_re)) { + CollapsedFieldsets.show(i); + } + } + } } addEvent(window, 'load', CollapsedFieldsets.init); diff --git a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js index ed99c6103d..3f6fb67bc7 100644 --- a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js +++ b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js @@ -8,7 +8,9 @@ var DateTimeShortcuts = { clockInputs: [], calendarDivName1: 'calendarbox', // name of calendar <div> that gets toggled calendarDivName2: 'calendarin', // name of <div> that contains calendar + calendarLinkName: 'calendarlink',// name of the link that is used to toggle clockDivName: 'clockbox', // name of clock <div> that gets toggled + clockLinkName: 'clocklink', // name of the link that is used to toggle admin_media_prefix: '', init: function() { // Deduce admin_media_prefix by looking at the <script>s in the @@ -46,6 +48,7 @@ var DateTimeShortcuts = { now_link.appendChild(document.createTextNode(gettext('Now'))); var clock_link = document.createElement('a'); clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');'); + clock_link.id = DateTimeShortcuts.clockLinkName + num; quickElement('img', clock_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_clock.gif', 'alt', gettext('Clock')); shortcuts_span.appendChild(document.createTextNode('\240')); shortcuts_span.appendChild(now_link); @@ -69,17 +72,6 @@ var DateTimeShortcuts = { var clock_box = document.createElement('div'); clock_box.style.display = 'none'; clock_box.style.position = 'absolute'; - if (getStyle(document.body,'direction')!='rtl') { - clock_box.style.left = findPosX(clock_link) + 17 + 'px'; - } - else { - // since style's width is in em, it'd be tough to calculate - // px value of it. let's use an estimated px for now - // TODO: IE returns wrong value for findPosX when in rtl mode - // (it returns as it was left aligned), needs to be fixed. - clock_box.style.left = findPosX(clock_link) - 110 + 'px'; - } - clock_box.style.top = findPosY(clock_link) - 30 + 'px'; clock_box.className = 'clockbox module'; clock_box.setAttribute('id', DateTimeShortcuts.clockDivName + num); document.body.appendChild(clock_box); @@ -98,7 +90,25 @@ var DateTimeShortcuts = { quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissClock(' + num + ');'); }, openClock: function(num) { - document.getElementById(DateTimeShortcuts.clockDivName + num).style.display = 'block'; + var clock_box = document.getElementById(DateTimeShortcuts.clockDivName+num) + var clock_link = document.getElementById(DateTimeShortcuts.clockLinkName+num) + + // Recalculate the clockbox position + // is it left-to-right or right-to-left layout ? + if (getStyle(document.body,'direction')!='rtl') { + clock_box.style.left = findPosX(clock_link) + 17 + 'px'; + } + else { + // since style's width is in em, it'd be tough to calculate + // px value of it. let's use an estimated px for now + // TODO: IE returns wrong value for findPosX when in rtl mode + // (it returns as it was left aligned), needs to be fixed. + clock_box.style.left = findPosX(clock_link) - 110 + 'px'; + } + clock_box.style.top = findPosY(clock_link) - 30 + 'px'; + + // Show the clock box + clock_box.style.display = 'block'; addEvent(window, 'click', function() { DateTimeShortcuts.dismissClock(num); return true; }); }, dismissClock: function(num) { @@ -123,6 +133,7 @@ var DateTimeShortcuts = { today_link.appendChild(document.createTextNode(gettext('Today'))); var cal_link = document.createElement('a'); cal_link.setAttribute('href', 'javascript:DateTimeShortcuts.openCalendar(' + num + ');'); + cal_link.id = DateTimeShortcuts.calendarLinkName + num; quickElement('img', cal_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_calendar.gif', 'alt', gettext('Calendar')); shortcuts_span.appendChild(document.createTextNode('\240')); shortcuts_span.appendChild(today_link); @@ -149,18 +160,6 @@ var DateTimeShortcuts = { var cal_box = document.createElement('div'); cal_box.style.display = 'none'; cal_box.style.position = 'absolute'; - // is it left-to-right or right-to-left layout ? - if (getStyle(document.body,'direction')!='rtl') { - cal_box.style.left = findPosX(cal_link) + 17 + 'px'; - } - else { - // since style's width is in em, it'd be tough to calculate - // px value of it. let's use an estimated px for now - // TODO: IE returns wrong value for findPosX when in rtl mode - // (it returns as it was left aligned), needs to be fixed. - cal_box.style.left = findPosX(cal_link) - 180 + 'px'; - } - cal_box.style.top = findPosY(cal_link) - 75 + 'px'; cal_box.className = 'calendarbox module'; cal_box.setAttribute('id', DateTimeShortcuts.calendarDivName1 + num); document.body.appendChild(cal_box); @@ -195,7 +194,24 @@ var DateTimeShortcuts = { quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissCalendar(' + num + ');'); }, openCalendar: function(num) { - document.getElementById(DateTimeShortcuts.calendarDivName1+num).style.display = 'block'; + var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1+num) + var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName+num) + + // Recalculate the clockbox position + // is it left-to-right or right-to-left layout ? + if (getStyle(document.body,'direction')!='rtl') { + cal_box.style.left = findPosX(cal_link) + 17 + 'px'; + } + else { + // since style's width is in em, it'd be tough to calculate + // px value of it. let's use an estimated px for now + // TODO: IE returns wrong value for findPosX when in rtl mode + // (it returns as it was left aligned), needs to be fixed. + cal_box.style.left = findPosX(cal_link) - 180 + 'px'; + } + cal_box.style.top = findPosY(cal_link) - 75 + 'px'; + + cal_box.style.display = 'block'; addEvent(window, 'click', function() { DateTimeShortcuts.dismissCalendar(num); return true; }); }, dismissCalendar: function(num) { @@ -217,7 +233,7 @@ var DateTimeShortcuts = { DateTimeShortcuts.dismissCalendar(num); }, cancelEventPropagation: function(e) { - if (!e) var e = window.event; + if (!e) e = window.event; e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); } diff --git a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js index cf57fc4dfb..db4ed1a9d1 100644 --- a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js +++ b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js @@ -11,7 +11,7 @@ function showRelatedObjectLookupPopup(triggeringLink) { } else { href = triggeringLink.href + '?pop=1'; } - var win = window.open(href, name, 'height=500,width=740,resizable=yes,scrollbars=yes'); + var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); win.focus(); return false; } diff --git a/django/contrib/admin/media/js/core.js b/django/contrib/admin/media/js/core.js index 8eba69c9bb..d35bd29c1c 100644 --- a/django/contrib/admin/media/js/core.js +++ b/django/contrib/admin/media/js/core.js @@ -19,6 +19,7 @@ function removeEvent(obj, evType, fn) { return true; } else if (obj.detachEvent) { obj.detachEvent("on" + evType, fn); + return true; } else { return false; } diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index e7f1c7e5a9..41514e6a81 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -20,30 +20,30 @@ <div id="branding"> {% block branding %}{% endblock %} </div> - {% if not user.is_anonymous %}{% if user.is_staff %} + {% if user.is_authenticated and user.is_staff %} <div id="user-tools">{% trans 'Welcome,' %} <strong>{% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}</strong>. {% block userlinks %}<a href="doc/">{% trans 'Documentation' %}</a> / <a href="password_change/">{% trans 'Change password' %}</a> / <a href="logout/">{% trans 'Log out' %}</a>{% endblock %}</div> - {% endif %}{% endif %} + {% endif %} {% block nav-global %}{% endblock %} </div> <!-- END Header --> - {% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans 'Home' %}</a>{% if title %} › {{ title }}{% endif %}</div>{% endblock %} + {% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans 'Home' %}</a>{% if title %} › {{ title|escape }}{% endif %}</div>{% endblock %} {% endif %} {% if messages %} - <ul class="messagelist">{% for message in messages %}<li>{{ message }}</li>{% endfor %}</ul> + <ul class="messagelist">{% for message in messages %}<li>{{ message|escape }}</li>{% endfor %}</ul> {% endif %} <!-- Content --> <div id="content" class="{% block coltype %}colM{% endblock %}"> {% block pretitle %}{% endblock %} - {% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %} + {% block content_title %}{% if title %}<h1>{{ title|escape }}</h1>{% endif %}{% endblock %} {% block content %}{{ content }}{% endblock %} {% block sidebar %}{% endblock %} <br class="clear" /> </div> <!-- END Content --> - <div id="footer"></div> + {% block footer %}<div id="footer"></div>{% endblock %} </div> <!-- END Container --> diff --git a/django/contrib/admin/templates/admin/base_site.html b/django/contrib/admin/templates/admin/base_site.html index b867bd29bd..2bc7310873 100644 --- a/django/contrib/admin/templates/admin/base_site.html +++ b/django/contrib/admin/templates/admin/base_site.html @@ -1,7 +1,7 @@ {% extends "admin/base.html" %} {% load i18n %} -{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %} +{% block title %}{{ title|escape }} | {% trans 'Django site admin' %}{% endblock %} {% block branding %} <h1 id="site-name">{% trans 'Django administration' %}</h1> diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index fa04969f01..e61eb5513b 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -11,8 +11,8 @@ {% block breadcrumbs %}{% if not is_popup %} <div class="breadcrumbs"> <a href="../../../">{% trans "Home" %}</a> › - <a href="../">{{ opts.verbose_name_plural|capfirst }}</a> › - {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18"|escape }}{% endif %} + <a href="../">{{ opts.verbose_name_plural|capfirst|escape }}</a> › + {% if add %}{% trans "Add" %} {{ opts.verbose_name|escape }}{% else %}{{ original|truncatewords:"18"|escape }}{% endif %} </div> {% endif %}{% endblock %} {% block content %}<div id="content-main"> diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index 5b54bfb8cc..bd2304bd52 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -3,12 +3,12 @@ {% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %} {% block bodyclass %}change-list{% endblock %} {% block userlinks %}<a href="../../doc/">{% trans 'Documentation' %}</a> / <a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %} -{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> › {{ cl.opts.verbose_name_plural|capfirst }}</div>{% endblock %}{% endif %} +{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> › {{ cl.opts.verbose_name_plural|capfirst|escape }}</div>{% endblock %}{% endif %} {% block coltype %}flex{% endblock %} {% block content %} <div id="content-main"> {% if has_add_permission %} -<ul class="object-tools"><li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}</a></li></ul> +<ul class="object-tools"><li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name|escape as name %}Add {{ name }}{% endblocktrans %}</a></li></ul> {% endif %} <div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist"> {% block search %}{% search_form cl %}{% endblock %} diff --git a/django/contrib/admin/templates/admin/date_hierarchy.html b/django/contrib/admin/templates/admin/date_hierarchy.html index a53d810f93..d2d69616c7 100644 --- a/django/contrib/admin/templates/admin/date_hierarchy.html +++ b/django/contrib/admin/templates/admin/date_hierarchy.html @@ -1,10 +1,10 @@ {% if show %} <div class="xfull"> <ul class="toplinks"> -{% if back %}<li class="date-back"><a href="{{ back.link }}">‹ {{ back.title }}</a></li>{% endif %} +{% if back %}<li class="date-back"><a href="{{ back.link }}">‹ {{ back.title|escape }}</a></li>{% endif %} {% for choice in choices %} -<li> {% if choice.link %}<a href="{{ choice.link }}">{% endif %}{{ choice.title }}{% if choice.link %}</a>{% endif %}</li> +<li> {% if choice.link %}<a href="{{ choice.link }}">{% endif %}{{ choice.title|escape }}{% if choice.link %}</a>{% endif %}</li> {% endfor %} </ul><br class="clear" /> </div> -{% endif %}
\ No newline at end of file +{% endif %} diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html index f907c18a16..3921ab69e3 100644 --- a/django/contrib/admin/templates/admin/delete_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_confirmation.html @@ -4,21 +4,21 @@ {% block breadcrumbs %} <div class="breadcrumbs"> <a href="../../../../">{% trans "Home" %}</a> › - <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> › - <a href="../">{{ object|striptags|truncatewords:"18" }}</a> › + <a href="../../">{{ opts.verbose_name_plural|capfirst|escape }}</a> › + <a href="../">{{ object|escape|truncatewords:"18" }}</a> › {% trans 'Delete' %} </div> {% endblock %} {% block content %} {% if perms_lacking %} - <p>{% blocktrans %}Deleting the {{ object_name }} '{{ object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p> + <p>{% blocktrans with object|escape as escaped_object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p> <ul> {% for obj in perms_lacking %} - <li>{{ obj }}</li> + <li>{{ obj|escape }}</li> {% endfor %} </ul> {% else %} - <p>{% blocktrans %}Are you sure you want to delete the {{ object_name }} "{{ object }}"? All of the following related items will be deleted:{% endblocktrans %}</p> + <p>{% blocktrans with object|escape as escaped_object %}Are you sure you want to delete the {{ object_name }} "{{ escaped_object }}"? All of the following related items will be deleted:{% endblocktrans %}</p> <ul>{{ deleted_objects|unordered_list }}</ul> <form action="" method="post"> <div> diff --git a/django/contrib/admin/templates/admin/edit_inline_stacked.html b/django/contrib/admin/templates/admin/edit_inline_stacked.html index 45aa0a4f58..48ecc698d9 100644 --- a/django/contrib/admin/templates/admin/edit_inline_stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline_stacked.html @@ -1,7 +1,7 @@ {% load admin_modify %} <fieldset class="module aligned"> {% for fcw in bound_related_object.form_field_collection_wrappers %} - <h2>{{ bound_related_object.relation.opts.verbose_name|capfirst }} #{{ forloop.counter }}</h2> + <h2>{{ bound_related_object.relation.opts.verbose_name|capfirst|escape }} #{{ forloop.counter }}</h2> {% if bound_related_object.show_url %}{% if fcw.obj.original %} <p><a href="/r/{{ fcw.obj.original.content_type_id }}/{{ fcw.obj.original.id }}/">View on site</a></p> {% endif %}{% endif %} diff --git a/django/contrib/admin/templates/admin/edit_inline_tabular.html b/django/contrib/admin/templates/admin/edit_inline_tabular.html index e9535df02c..13d528331b 100644 --- a/django/contrib/admin/templates/admin/edit_inline_tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline_tabular.html @@ -1,10 +1,10 @@ {% load admin_modify %} <fieldset class="module"> - <h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}</h2><table> + <h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst|escape }}</h2><table> <thead><tr> {% for fw in bound_related_object.field_wrapper_list %} {% if fw.needs_header %} - <th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst }}</th> + <th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst|escape }}</th> {% endif %} {% endfor %} {% for fcw in bound_related_object.form_field_collection_wrappers %} diff --git a/django/contrib/admin/templates/admin/filter.html b/django/contrib/admin/templates/admin/filter.html index 5b0e78b6fc..8b5b521437 100644 --- a/django/contrib/admin/templates/admin/filter.html +++ b/django/contrib/admin/templates/admin/filter.html @@ -1,5 +1,5 @@ {% load i18n %} -<h3>{% blocktrans %} By {{ title }} {% endblocktrans %}</h3> +<h3>{% blocktrans with title|escape as filter_title %} By {{ filter_title }} {% endblocktrans %}</h3> <ul> {% for choice in choices %} <li{% if choice.selected %} class="selected"{% endif %}> diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index 246086861b..aa63c14fce 100644 --- a/django/contrib/admin/templates/admin/index.html +++ b/django/contrib/admin/templates/admin/index.html @@ -15,13 +15,13 @@ {% for app in app_list %} <div class="module"> <table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}"> - <caption>{{ app.name }}</caption> + <caption>{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</caption> {% for model in app.models %} <tr> {% if model.perms.change %} - <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th> + <th scope="row"><a href="{{ model.admin_url }}">{{ model.name|escape }}</a></th> {% else %} - <th scope="row">{{ model.name }}</th> + <th scope="row">{{ model.name|escape }}</th> {% endif %} {% if model.perms.add %} @@ -58,7 +58,7 @@ {% else %} <ul class="actionlist"> {% for entry in admin_log %} - <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{{ entry.content_type.name|capfirst }}</span></li> + <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{{ entry.content_type.name|capfirst|escape }}</span></li> {% endfor %} </ul> {% endif %} diff --git a/django/contrib/admin/templates/admin/invalid_setup.html b/django/contrib/admin/templates/admin/invalid_setup.html index 1fa0d32358..1d7d61f0d2 100644 --- a/django/contrib/admin/templates/admin/invalid_setup.html +++ b/django/contrib/admin/templates/admin/invalid_setup.html @@ -1,7 +1,7 @@ {% extends "admin/base_site.html" %} {% load i18n %} -{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> › {{ title }}</div>{% endblock %} +{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> › {{ title|escape }}</div>{% endblock %} {% block content %} diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html index 5f338f703e..e5bda0f0e6 100644 --- a/django/contrib/admin/templates/admin/login.html +++ b/django/contrib/admin/templates/admin/login.html @@ -13,17 +13,17 @@ {% endif %} <div id="content-main"> <form action="{{ app_path }}" method="post" id="login-form"> - <div class="form-row"> - <label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" /> - </div> - <div class="form-row"> - <label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" /> - <input type="hidden" name="this_is_the_login_form" value="1" /> - <input type="hidden" name="post_data" value="{{ post_data }}" /> {% comment %}<span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %} - </div> - <div class="submit-row"> - <label> </label><input type="submit" value="{% trans 'Log in' %}" /> - </div> + <div class="form-row"> + <label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" /> + </div> + <div class="form-row"> + <label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" /> + <input type="hidden" name="this_is_the_login_form" value="1" /> + <input type="hidden" name="post_data" value="{{ post_data }}" /> {% comment %}<span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %} + </div> + <div class="submit-row"> + <label> </label><input type="submit" value="{% trans 'Log in' %}" /> + </div> </form> <script type="text/javascript"> diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html index fc568305ca..14a77b8a31 100644 --- a/django/contrib/admin/templates/admin/object_history.html +++ b/django/contrib/admin/templates/admin/object_history.html @@ -2,7 +2,7 @@ {% load i18n %} {% block userlinks %}<a href="../../../../doc/">{% trans 'Documentation' %}</a> / <a href="../../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../../logout/">{% trans 'Log out' %}</a>{% endblock %} {% block breadcrumbs %} -<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> › <a href="../../">{{ module_name }}</a> › <a href="../">{{ object|truncatewords:"18" }}</a> › {% trans 'History' %}</div> +<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> › <a href="../../">{{ module_name|escape }}</a> › <a href="../">{{ object|escape|truncatewords:"18" }}</a> › {% trans 'History' %}</div> {% endblock %} {% block content %} diff --git a/django/contrib/admin/templates/admin/pagination.html b/django/contrib/admin/templates/admin/pagination.html index 7694e4c5b0..e1c09b2932 100644 --- a/django/contrib/admin/templates/admin/pagination.html +++ b/django/contrib/admin/templates/admin/pagination.html @@ -6,6 +6,6 @@ {% paginator_number cl i %} {% endfor %} {% endif %} -{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %} +{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name|escape }}{% else %}{{ cl.opts.verbose_name_plural|escape }}{% endifequal %} {% if show_all_url %} <a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %} </p> diff --git a/django/contrib/admin/templates/admin_doc/index.html b/django/contrib/admin/templates/admin_doc/index.html index 331774d2b3..74f9f20916 100644 --- a/django/contrib/admin/templates/admin_doc/index.html +++ b/django/contrib/admin/templates/admin_doc/index.html @@ -9,17 +9,17 @@ <h1>Documentation</h1>
<div id="content-main">
- <h3><a href="tags/">Tags</a></h3>
- <p>List of all the template tags and their functions.</p>
+ <h3><a href="tags/">Tags</a></h3>
+ <p>List of all the template tags and their functions.</p>
- <h3><a href="filters/">Filters</a></h3>
- <p>Filters are actions which can be applied to variables in a template to alter the output.</p>
+ <h3><a href="filters/">Filters</a></h3>
+ <p>Filters are actions which can be applied to variables in a template to alter the output.</p>
- <h3><a href="models/">Models</a></h3>
- <p>Models are descriptions of all the objects in the system and their associated fields. Each model has a list of fields which can be accessed as template variables.</p>
+ <h3><a href="models/">Models</a></h3>
+ <p>Models are descriptions of all the objects in the system and their associated fields. Each model has a list of fields which can be accessed as template variables.</p>
- <h3><a href="views/">Views</a></h3>
- <p>Each page on the public site is generated by a view. The view defines which template is used to generate the page and which objects are available to that template.</p>
+ <h3><a href="views/">Views</a></h3>
+ <p>Each page on the public site is generated by a view. The view defines which template is used to generate the page and which objects are available to that template.</p>
<h3><a href="bookmarklets/">Bookmarklets</a></h3>
<p>Tools for your browser to quickly access admin functionality.</p>
diff --git a/django/contrib/admin/templates/admin_doc/missing_docutils.html b/django/contrib/admin/templates/admin_doc/missing_docutils.html index d0b571f957..cdeab0cdd8 100644 --- a/django/contrib/admin/templates/admin_doc/missing_docutils.html +++ b/django/contrib/admin/templates/admin_doc/missing_docutils.html @@ -9,9 +9,9 @@ <h1>Documentation</h1> <div id="content-main"> - <h3>The admin documentation system requires Python's <a href="http://docutils.sf.net/">docutils</a> library.</h3> + <h3>The admin documentation system requires Python's <a href="http://docutils.sf.net/">docutils</a> library.</h3> - <p>Please ask your administrators to install <a href="http://docutils.sf.net/">docutils</a>.</p> + <p>Please ask your administrators to install <a href="http://docutils.sf.net/">docutils</a>.</p> </div> {% endblock %} diff --git a/django/contrib/admin/templates/admin_doc/model_detail.html b/django/contrib/admin/templates/admin_doc/model_detail.html index 9ac56864fa..44fc43e704 100644 --- a/django/contrib/admin/templates/admin_doc/model_detail.html +++ b/django/contrib/admin/templates/admin_doc/model_detail.html @@ -9,13 +9,13 @@ </style> {% endblock %} -{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › <a href="../">Models</a> › {{ name }}</div>{% endblock %} +{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › <a href="../">Models</a> › {{ name|escape }}</div>{% endblock %} -{% block title %}Model: {{ name }}{% endblock %} +{% block title %}Model: {{ name|escape }}{% endblock %} {% block content %} <div id="content-main"> -<h1>{{ summary }}</h1> +<h1>{{ summary|escape }}</h1> {% if description %} <p>{% filter escape|linebreaksbr %}{% trans description %}{% endfilter %}</p> @@ -35,7 +35,7 @@ <tr> <td>{{ field.name }}</td> <td>{{ field.data_type }}</td> - <td>{% if field.verbose %}{{ field.verbose }}{% endif %}{% if field.help_text %} - {{ field.help_text }}{% endif %}</td> + <td>{% if field.verbose %}{{ field.verbose|escape }}{% endif %}{% if field.help_text %} - {{ field.help_text|escape }}{% endif %}</td> </tr> {% endfor %} </tbody> diff --git a/django/contrib/admin/templates/admin_doc/template_detail.html b/django/contrib/admin/templates/admin_doc/template_detail.html index df67f1856b..280ea912d0 100644 --- a/django/contrib/admin/templates/admin_doc/template_detail.html +++ b/django/contrib/admin/templates/admin_doc/template_detail.html @@ -1,19 +1,19 @@ {% extends "admin/base_site.html" %} {% load i18n %} -{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › Templates › {{ name }}</div>{% endblock %} +{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › Templates › {{ name|escape }}</div>{% endblock %} {% block userlinks %}<a href="../../../password_change/">{% trans 'Change password' %}</a> / <a href="../../../logout/">{% trans 'Log out' %}</a>{% endblock %} -{% block title %}Template: {{ name }}{% endblock %} +{% block title %}Template: {{ name|escape }}{% endblock %} {% block content %} -<h1>Template: "{{ name }}"</h1> +<h1>Template: "{{ name|escape }}"</h1> {% regroup templates|dictsort:"site_id" by site as templates_by_site %} {% for group in templates_by_site %} - <h2>Search path for template "{{ name }}" on {{ group.grouper }}:</h2> + <h2>Search path for template "{{ name|escape }}" on {{ group.grouper }}:</h2> <ol> {% for template in group.list|dictsort:"order" %} - <li><code>{{ template.file }}</code>{% if not template.exists %} <em>(does not exist)</em>{% endif %}</li> + <li><code>{{ template.file|escape }}</code>{% if not template.exists %} <em>(does not exist)</em>{% endif %}</li> {% endfor %} </ol> {% endfor %} diff --git a/django/contrib/admin/templates/admin_doc/view_detail.html b/django/contrib/admin/templates/admin_doc/view_detail.html index ba90399358..ed90657361 100644 --- a/django/contrib/admin/templates/admin_doc/view_detail.html +++ b/django/contrib/admin/templates/admin_doc/view_detail.html @@ -8,7 +8,7 @@ <h1>{{ name }}</h1> -<h2 class="subhead">{{ summary }}</h2> +<h2 class="subhead">{{ summary|escape }}</h2> <p>{{ body }}</p> diff --git a/django/contrib/admin/templates/widget/file.html b/django/contrib/admin/templates/widget/file.html index e4a0756211..e584abf956 100644 --- a/django/contrib/admin/templates/widget/file.html +++ b/django/contrib/admin/templates/widget/file.html @@ -1,4 +1,4 @@ {% load admin_modify i18n %}{% if bound_field.original_value %} -{% trans "Currently:" %} <a href="{{ bound_field.original_url }}" > {{ bound_field.original_value }} </a><br /> +{% trans "Currently:" %} <a href="{{ bound_field.original_url }}" > {{ bound_field.original_value|escape }} </a><br /> {% trans "Change:" %}{% output_all bound_field.form_fields %} {% else %} {% output_all bound_field.form_fields %} {% endif %} diff --git a/django/contrib/admin/templates/widget/foreign.html b/django/contrib/admin/templates/widget/foreign.html index 6b43d044bd..301f5214db 100644 --- a/django/contrib/admin/templates/widget/foreign.html +++ b/django/contrib/admin/templates/widget/foreign.html @@ -15,6 +15,6 @@ {{ bound_field.original_value }} {% endif %} {% if bound_field.raw_id_admin %} - {% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %} + {% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %} {% endif %} {% endif %} diff --git a/django/contrib/admin/templates/widget/one_to_one.html b/django/contrib/admin/templates/widget/one_to_one.html index a79a12314f..efd0117bf2 100644 --- a/django/contrib/admin/templates/widget/one_to_one.html +++ b/django/contrib/admin/templates/widget/one_to_one.html @@ -1,2 +1,2 @@ {% if add %}{% include "widget/foreign.html" %}{% endif %} -{% if change %}{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}{% endif %} +{% if change %}{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %}{% endif %} diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 0e550dd471..832b3562cd 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -1,8 +1,6 @@ -from django import template from django.conf import settings -from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, ALL_VAR +from django.contrib.admin.views.main import ALL_VAR, EMPTY_CHANGELIST_VALUE from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR -from django.contrib.admin.views.main import IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.utils import dateformat @@ -119,7 +117,7 @@ def items_for_result(cl, result): if callable(attr): attr = attr() result_repr = str(attr) - except AttributeError, ObjectDoesNotExist: + except (AttributeError, ObjectDoesNotExist): result_repr = EMPTY_CHANGELIST_VALUE else: # Strip HTML tags in the resulting text, except if the @@ -165,12 +163,14 @@ def items_for_result(cl, result): result_repr = escape(str(field_val)) if result_repr == '': result_repr = ' ' - if first: # First column is a special case + # If list_display_links not defined, add the link tag to the first field + if (first and not cl.lookup_opts.admin.list_display_links) or field_name in cl.lookup_opts.admin.list_display_links: + table_tag = {True:'th', False:'td'}[first] first = False url = cl.url_for_result(result) result_id = str(getattr(result, pk)) # str() is needed in case of 23L (long ints) - yield ('<th%s><a href="%s"%s>%s</a></th>' % \ - (row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr)) + yield ('<%s%s><a href="%s"%s>%s</a></%s>' % \ + (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr, table_tag)) else: yield ('<td%s>%s</td>' % (row_class, result_repr)) diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index 2d34452f52..7ba7bef74e 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -1,9 +1,7 @@ from django import template from django.contrib.admin.views.main import AdminBoundField from django.template import loader -from django.utils.html import escape from django.utils.text import capfirst -from django.utils.functional import curry from django.db import models from django.db.models.fields import Field from django.db.models.related import BoundRelatedObject diff --git a/django/contrib/admin/urls.py b/django/contrib/admin/urls.py index a2d3ccae48..bd894d8de1 100644 --- a/django/contrib/admin/urls.py +++ b/django/contrib/admin/urls.py @@ -1,9 +1,15 @@ +from django.conf import settings from django.conf.urls.defaults import * +if settings.USE_I18N: + i18n_view = 'django.views.i18n.javascript_catalog' +else: + i18n_view = 'django.views.i18n.null_javascript_catalog' + urlpatterns = patterns('', ('^$', 'django.contrib.admin.views.main.index'), ('^r/(\d+)/(.*)/$', 'django.views.defaults.shortcut'), - ('^jsi18n/$', 'django.views.i18n.javascript_catalog', {'packages': 'django.conf'}), + ('^jsi18n/$', i18n_view, {'packages': 'django.conf'}), ('^logout/$', 'django.contrib.auth.views.logout'), ('^password_change/$', 'django.contrib.auth.views.password_change'), ('^password_change/done/$', 'django.contrib.auth.views.password_change_done'), @@ -29,3 +35,5 @@ urlpatterns = patterns('', ('^([^/]+)/([^/]+)/(.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'), ('^([^/]+)/([^/]+)/(.+)/$', 'django.contrib.admin.views.main.change_stage'), ) + +del i18n_view diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py index 0242b2f32e..9adf09b6a5 100644 --- a/django/contrib/admin/utils.py +++ b/django/contrib/admin/utils.py @@ -3,7 +3,6 @@ import re from email.Parser import HeaderParser from email.Errors import HeaderParseError -from urlparse import urljoin try: import docutils.core import docutils.nodes diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py index d984077dfb..fce50909f0 100644 --- a/django/contrib/admin/views/decorators.py +++ b/django/contrib/admin/views/decorators.py @@ -1,6 +1,7 @@ from django import http, template from django.conf import settings -from django.contrib.auth.models import User, SESSION_KEY +from django.contrib.auth.models import User +from django.contrib.auth import authenticate, login from django.shortcuts import render_to_response from django.utils.translation import gettext_lazy import base64, datetime, md5 @@ -45,7 +46,7 @@ def staff_member_required(view_func): member, displaying the login page if necessary. """ def _checklogin(request, *args, **kwargs): - if not request.user.is_anonymous() and request.user.is_staff: + if request.user.is_authenticated() and request.user.is_staff: # The user is valid. Continue to the admin page. if request.POST.has_key('post_data'): # User must have re-authenticated through a different window @@ -69,10 +70,10 @@ def staff_member_required(view_func): return _display_login_form(request, message) # Check the password. - username = request.POST.get('username', '') - try: - user = User.objects.get(username=username, is_staff=True) - except User.DoesNotExist: + username = request.POST.get('username', None) + password = request.POST.get('password', None) + user = authenticate(username=username, password=password) + if user is None: message = ERROR_MESSAGE if '@' in username: # Mistakenly entered e-mail address instead of username? Look it up. @@ -86,8 +87,9 @@ def staff_member_required(view_func): # The user data is correct; log in the user in and continue. else: - if user.check_password(request.POST.get('password', '')): - request.session[SESSION_KEY] = user.id + if user.is_staff: + login(request, user) + # TODO: set last_login with an event. user.last_login = datetime.datetime.now() user.save() if request.POST.has_key('post_data'): diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py index e48e9800a7..68799fcc17 100644 --- a/django/contrib/admin/views/doc.py +++ b/django/contrib/admin/views/doc.py @@ -14,6 +14,10 @@ import inspect, os, re # Exclude methods starting with these strings from documentation MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_') +class GenericSite(object): + domain = 'example.com' + name = 'my site' + def doc_index(request): if not utils.docutils_is_available: return missing_docutils_page(request) @@ -24,7 +28,7 @@ def bookmarklets(request): # Hack! This couples this view to the URL it lives at. admin_root = request.path[:-len('doc/bookmarklets/')] return render_to_response('admin_doc/bookmarklets.html', { - 'admin_url': "%s://%s%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', get_host(request), admin_root), + 'admin_url': "%s://%s%s" % (request.is_secure() and 'https' or 'http', get_host(request), admin_root), }, context_instance=RequestContext(request)) bookmarklets = staff_member_required(bookmarklets) @@ -102,12 +106,16 @@ def view_index(request): for settings_mod in settings_modules: urlconf = __import__(settings_mod.ROOT_URLCONF, '', '', ['']) view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns) + if Site._meta.installed: + site_obj = Site.objects.get(pk=settings_mod.SITE_ID) + else: + site_obj = GenericSite() for (func, regex) in view_functions: views.append({ 'name': func.__name__, 'module': func.__module__, 'site_id': settings_mod.SITE_ID, - 'site': Site.objects.get(pk=settings_mod.SITE_ID), + 'site': site_obj, 'url': simplify_regex(regex), }) return render_to_response('admin_doc/view_index.html', {'views': views}, context_instance=RequestContext(request)) @@ -180,7 +188,7 @@ def model_detail(request, app_label, model_name): 'name': field.name, 'data_type': data_type, 'verbose': verbose, - 'help': field.help_text, + 'help_text': field.help_text, }) # Gather model methods. @@ -228,6 +236,10 @@ def template_detail(request, template): templates = [] for site_settings_module in settings.ADMIN_FOR: settings_mod = __import__(site_settings_module, '', '', ['']) + if Site._meta.installed: + site_obj = Site.objects.get(pk=settings_mod.SITE_ID) + else: + site_obj = GenericSite() for dir in settings_mod.TEMPLATE_DIRS: template_file = os.path.join(dir, "%s.html" % template) templates.append({ @@ -235,7 +247,7 @@ def template_detail(request, template): 'exists': os.path.exists(template_file), 'contents': lambda: os.path.exists(template_file) and open(template_file).read() or '', 'site_id': settings_mod.SITE_ID, - 'site': Site.objects.get(pk=settings_mod.SITE_ID), + 'site': site_obj, 'order': list(settings_mod.TEMPLATE_DIRS).index(dir), }) return render_to_response('admin_doc/template_detail.html', { diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index a7ee08f1c0..705dfad6c8 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -10,9 +10,6 @@ from django.shortcuts import get_object_or_404, render_to_response from django.db import models from django.db.models.query import handle_legacy_orderlist, QuerySet from django.http import Http404, HttpResponse, HttpResponseRedirect -from django.template import loader -from django.utils import dateformat -from django.utils.dates import MONTHS from django.utils.html import escape from django.utils.text import capfirst, get_text_list import operator diff --git a/django/contrib/admin/views/template.py b/django/contrib/admin/views/template.py index f73b9e4218..1684870842 100644 --- a/django/contrib/admin/views/template.py +++ b/django/contrib/admin/views/template.py @@ -22,7 +22,7 @@ def template_validator(request): new_data = request.POST.copy() errors = manipulator.get_validation_errors(new_data) if not errors: - request.user.add_message('The template is valid.') + request.user.message_set.create(message='The template is valid.') return render_to_response('admin/template_validator.html', { 'title': 'Template validator', 'form': forms.FormWrapper(manipulator, new_data, errors), @@ -32,7 +32,7 @@ template_validator = staff_member_required(template_validator) class TemplateValidator(forms.Manipulator): def __init__(self, settings_modules): self.settings_modules = settings_modules - site_list = Site.objects.get_in_bulk(settings_modules.keys()).values() + site_list = Site.objects.in_bulk(settings_modules.keys()).values() self.fields = ( forms.SelectField('site', is_required=True, choices=[(s.id, s.name) for s in site_list]), forms.LargeTextField('template', is_required=True, rows=25, validator_list=[self.isValidTemplate]), diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index 41adadabcd..a0097a01ed 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -1,29 +1,77 @@ +from django.core.exceptions import ImproperlyConfigured + +SESSION_KEY = '_auth_user_id' +BACKEND_SESSION_KEY = '_auth_user_backend' LOGIN_URL = '/accounts/login/' REDIRECT_FIELD_NAME = 'next' -class NoMatchFound(Exception): pass +def load_backend(path): + i = path.rfind('.') + module, attr = path[:i], path[i+1:] + try: + mod = __import__(module, '', '', [attr]) + except ImportError, e: + raise ImproperlyConfigured, 'Error importing authentication backend %s: "%s"' % (module, e) + try: + cls = getattr(mod, attr) + except AttributeError: + raise ImproperlyConfigured, 'Module "%s" does not define a "%s" authentication backend' % (module, attr) + return cls() + +def get_backends(): + from django.conf import settings + backends = [] + for backend_path in settings.AUTHENTICATION_BACKENDS: + backends.append(load_backend(backend_path)) + return backends -class HasPermission(object): +def authenticate(**credentials): """ - Function that supports multiple implementations via a type registry. The - implemetation called depends on the argument types. + If the given credentials are valid, return a User object. """ - def __init__(self): - self.registry = {} + for backend in get_backends(): + try: + user = backend.authenticate(**credentials) + except TypeError: + # This backend doesn't accept these credentials as arguments. Try the next one. + continue + if user is None: + continue + # Annotate the user object with the path of the backend. + user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__) + return user - def __call__(self, user, permission, obj=None): - # TODO: this isn't very robust. Only matches on exact types. Support - # for matching subclasses and caching registry hits would be helpful, - # but we'll add that later - types = (type(user), type(permission), type(obj)) - func = self.registry.get(types) - if func is not None: - return func(user, permission, obj) - else: - raise NoMatchFound, "%s\n%s" % (self.registry, types) +def login(request, user): + """ + Persist a user id and a backend in the request. This way a user doesn't + have to reauthenticate on every request. + """ + if user is None: + user = request.user + # TODO: It would be nice to support different login methods, like signed cookies. + request.session[SESSION_KEY] = user.id + request.session[BACKEND_SESSION_KEY] = user.backend - def register(self, user_type, permission_type, obj_type, func): - types = (user_type, permission_type, obj_type) - self.registry[types] = func +def logout(request): + """ + Remove the authenticated user's ID from the request. + """ + try: + del request.session[SESSION_KEY] + except KeyError: + pass + try: + del request.session[BACKEND_SESSION_KEY] + except KeyError: + pass -has_permission = HasPermission() +def get_user(request): + from django.contrib.auth.models import AnonymousUser + try: + user_id = request.session[SESSION_KEY] + backend_path = request.session[BACKEND_SESSION_KEY] + backend = load_backend(backend_path) + user = backend.get_user(user_id) or AnonymousUser() + except KeyError: + user = AnonymousUser() + return user diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py new file mode 100644 index 0000000000..4b8efcca46 --- /dev/null +++ b/django/contrib/auth/backends.py @@ -0,0 +1,21 @@ +from django.contrib.auth.models import User + +class ModelBackend: + """ + Authenticate against django.contrib.auth.models.User + """ + # TODO: Model, login attribute name and password attribute name should be + # configurable. + def authenticate(self, username=None, password=None): + try: + user = User.objects.get(username=username) + if user.check_password(password): + return user + except User.DoesNotExist: + return None + + def get_user(self, user_id): + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + return None diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index bc8ec39115..0102496a33 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -13,11 +13,13 @@ def user_passes_test(test_func, login_url=LOGIN_URL): if test_func(request.user): return view_func(request, *args, **kwargs) return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, quote(request.get_full_path()))) + _checklogin.__doc__ = view_func.__doc__ + _checklogin.__dict__ = view_func.__dict__ return _checklogin return _dec -login_required = user_passes_test(lambda u: not u.is_anonymous()) +login_required = user_passes_test(lambda u: u.is_authenticated()) login_required.__doc__ = ( """ Decorator for views that checks that the user is logged in, redirecting diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 800c14375b..8bd9fa44c4 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -1,4 +1,5 @@ from django.contrib.auth.models import User +from django.contrib.auth import authenticate from django.contrib.sites.models import Site from django.template import Context, loader from django.core import validators @@ -20,8 +21,7 @@ class AuthenticationForm(forms.Manipulator): self.fields = [ forms.TextField(field_name="username", length=15, maxlength=30, is_required=True, validator_list=[self.isValidUser, self.hasCookiesEnabled]), - forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True, - validator_list=[self.isValidPasswordForUser]), + forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True), ] self.user_cache = None @@ -30,16 +30,10 @@ class AuthenticationForm(forms.Manipulator): raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.") def isValidUser(self, field_data, all_data): - try: - self.user_cache = User.objects.get(username=field_data) - except User.DoesNotExist: - raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.") - - def isValidPasswordForUser(self, field_data, all_data): + username = field_data + password = all_data.get('password', None) + self.user_cache = authenticate(username=username, password=password) if self.user_cache is None: - return - if not self.user_cache.check_password(field_data): - self.user_cache = None raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.") elif not self.user_cache.is_active: raise validators.ValidationError, _("This account is inactive.") @@ -67,7 +61,7 @@ class PasswordResetForm(forms.Manipulator): except User.DoesNotExist: raise validators.ValidationError, "That e-mail address doesn't have an associated user acount. Are you sure you've registered?" - def save(self, domain_override=None): + def save(self, domain_override=None, email_template_name='registration/password_reset_email.html'): "Calculates a new password randomly and sends it to the user" from django.core.mail import send_mail new_pass = User.objects.make_random_password() @@ -79,7 +73,7 @@ class PasswordResetForm(forms.Manipulator): domain = current_site.domain else: site_name = domain = domain_override - t = loader.get_template('registration/password_reset_email.html') + t = loader.get_template(email_template_name) c = { 'new_password': new_pass, 'email': self.user_cache.email, diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py index a1a0b2e834..a6a60780a7 100644 --- a/django/contrib/auth/middleware.py +++ b/django/contrib/auth/middleware.py @@ -4,12 +4,8 @@ class LazyUser(object): def __get__(self, request, obj_type=None): if self._user is None: - from django.contrib.auth.models import User, AnonymousUser, SESSION_KEY - try: - user_id = request.session[SESSION_KEY] - self._user = User.objects.get(pk=user_id) - except (KeyError, User.DoesNotExist): - self._user = AnonymousUser() + from django.contrib.auth import get_user + self._user = get_user(request) return self._user class AuthenticationMiddleware(object): diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 65392695ae..4077237993 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -1,10 +1,23 @@ from django.core import validators +from django.core.exceptions import ImproperlyConfigured from django.db import backend, connection, models from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext_lazy as _ import datetime -SESSION_KEY = '_auth_user_id' +def check_password(raw_password, enc_password): + """ + Returns a boolean of whether the raw_password was correct. Handles + encryption formats behind the scenes. + """ + algo, salt, hsh = enc_password.split('$') + if algo == 'md5': + import md5 + return hsh == md5.new(salt+raw_password).hexdigest() + elif algo == 'sha1': + import sha + return hsh == sha.new(salt+raw_password).hexdigest() + raise ValueError, "Got unknown password algorithm type in password." class SiteProfileNotAvailable(Exception): pass @@ -113,6 +126,11 @@ class User(models.Model): def is_anonymous(self): "Always returns False. This is a way of comparing User objects to anonymous users." return False + + def is_authenticated(self): + """Always return True. This is a way to tell if the user has been authenticated in templates. + """ + return True def get_full_name(self): "Returns the first_name plus the last_name, with a space in between." @@ -141,14 +159,7 @@ class User(models.Model): self.set_password(raw_password) self.save() return is_correct - algo, salt, hsh = self.password.split('$') - if algo == 'md5': - import md5 - return hsh == md5.new(salt+raw_password).hexdigest() - elif algo == 'sha1': - import sha - return hsh == sha.new(salt+raw_password).hexdigest() - raise ValueError, "Got unknown password algorithm type in password." + return check_password(raw_password, self.password) def get_group_permissions(self): "Returns a list of permission strings that this user has through his/her groups." @@ -234,7 +245,7 @@ class User(models.Model): app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.') model = models.get_model(app_label, model_name) self._profile_cache = model._default_manager.get(user__id__exact=self.id) - except ImportError, ImproperlyConfigured: + except (ImportError, ImproperlyConfigured): raise SiteProfileNotAvailable return self._profile_cache @@ -288,3 +299,6 @@ class AnonymousUser(object): def is_anonymous(self): return True + + def is_authenticated(self): + return False diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 6d908ee025..6882755787 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -3,9 +3,8 @@ from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm from django import forms from django.shortcuts import render_to_response from django.template import RequestContext -from django.contrib.auth.models import SESSION_KEY from django.contrib.sites.models import Site -from django.http import HttpResponse, HttpResponseRedirect +from django.http import HttpResponseRedirect from django.contrib.auth.decorators import login_required from django.contrib.auth import LOGIN_URL, REDIRECT_FIELD_NAME @@ -19,7 +18,8 @@ def login(request, template_name='registration/login.html'): # Light security check -- make sure redirect_to isn't garbage. if not redirect_to or '://' in redirect_to or ' ' in redirect_to: redirect_to = '/accounts/profile/' - request.session[SESSION_KEY] = manipulator.get_user_id() + from django.contrib.auth import login + login(request, manipulator.get_user()) request.session.delete_test_cookie() return HttpResponseRedirect(redirect_to) else: @@ -33,9 +33,9 @@ def login(request, template_name='registration/login.html'): def logout(request, next_page=None, template_name='registration/logged_out.html'): "Logs out the user and displays 'You are logged out' message." - try: - del request.session[SESSION_KEY] - except KeyError: + from django.contrib.auth import logout + logout(request) + if next_page is None: return render_to_response(template_name, {'title': _('Logged out')}, context_instance=RequestContext(request)) else: # Redirect to this page until the session has been cleared. @@ -49,7 +49,8 @@ def redirect_to_login(next, login_url=LOGIN_URL): "Redirects the user to the login page, passing the given 'next' page" return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, next)) -def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html'): +def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html', + email_template_name='registration/password_reset_email.html'): new_data, errors = {}, {} form = PasswordResetForm() if request.POST: @@ -57,9 +58,9 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas errors = form.get_validation_errors(new_data) if not errors: if is_admin_site: - form.save(request.META['HTTP_HOST']) + form.save(domain_override=request.META['HTTP_HOST']) else: - form.save() + form.save(email_template_name=email_template_name) return HttpResponseRedirect('%sdone/' % request.path) return render_to_response(template_name, {'form': forms.FormWrapper(form, new_data, errors)}, context_instance=RequestContext(request)) diff --git a/django/contrib/comments/feeds.py b/django/contrib/comments/feeds.py index cad8c881a1..34cf3d9cef 100644 --- a/django/contrib/comments/feeds.py +++ b/django/contrib/comments/feeds.py @@ -1,7 +1,6 @@ from django.conf import settings from django.contrib.comments.models import Comment, FreeComment from django.contrib.syndication.feeds import Feed -from django.core.exceptions import ObjectDoesNotExist from django.contrib.sites.models import Site class LatestFreeCommentsFeed(Feed): @@ -37,6 +36,6 @@ class LatestCommentsFeed(LatestFreeCommentsFeed): qs = qs.filter(is_removed=False) if settings.COMMENTS_BANNED_USERS_GROUP: where = ['user_id NOT IN (SELECT user_id FROM auth_users_group WHERE group_id = %s)'] - params = [COMMENTS_BANNED_USERS_GROUP] + params = [settings.COMMENTS_BANNED_USERS_GROUP] qs = qs.extra(where=where, params=params) return qs diff --git a/django/contrib/comments/models.py b/django/contrib/comments/models.py index 23b0dfbde6..a8aff1cfb3 100644 --- a/django/contrib/comments/models.py +++ b/django/contrib/comments/models.py @@ -51,7 +51,7 @@ class CommentManager(models.Manager): extra_kwargs.setdefault('select', {}) extra_kwargs['select']['_karma_total_good'] = 'SELECT COUNT(*) FROM comments_karmascore, comments_comment WHERE comments_karmascore.comment_id=comments_comment.id AND score=1' extra_kwargs['select']['_karma_total_bad'] = 'SELECT COUNT(*) FROM comments_karmascore, comments_comment WHERE comments_karmascore.comment_id=comments_comment.id AND score=-1' - return self.filter(**kwargs).extra(**extra_kwargs) + return self.filter(**kwargs).extra(**extra_kwargs) def user_is_moderator(self, user): if user.is_superuser: diff --git a/django/contrib/comments/templates/comments/form.html b/django/contrib/comments/templates/comments/form.html index deb5540c92..c5aa7686a3 100644 --- a/django/contrib/comments/templates/comments/form.html +++ b/django/contrib/comments/templates/comments/form.html @@ -2,10 +2,10 @@ {% if display_form %} <form {% if photos_optional or photos_required %}enctype="multipart/form-data" {% endif %}action="/comments/post/" method="post"> -{% if user.is_anonymous %} -<p>{% trans "Username:" %} <input type="text" name="username" id="id_username" /><br />{% trans "Password:" %} <input type="password" name="password" id="id_password" /> (<a href="/accounts/password_reset/">{% trans "Forgotten your password?" %}</a>)</p> -{% else %} +{% if user.is_authenticated %} <p>{% trans "Username:" %} <strong>{{ user.username }}</strong> (<a href="/accounts/logout/">{% trans "Log out" %}</a>)</p> +{% else %} +<p><label for="id_username">{% trans "Username:" %}</label> <input type="text" name="username" id="id_username" /><br />{% trans "Password:" %} <input type="password" name="password" id="id_password" /> (<a href="/accounts/password_reset/">{% trans "Forgotten your password?" %}</a>)</p> {% endif %} {% if ratings_optional or ratings_required %} @@ -20,15 +20,19 @@ {% endif %} {% if photos_optional or photos_required %} -<p>{% trans "Post a photo" %} ({% if photos_required %}{% trans "Required" %}{% else %}{% trans "Optional" %}{% endif %}): <input type="file" name="photo" /></p> +<p><label for="id_photo">{% trans "Post a photo" %}</label> ({% if photos_required %}{% trans "Required" %}{% else %}{% trans "Optional" %}{% endif %}): +<input type="file" name="photo" id="id_photo" /></p> <input type="hidden" name="photo_options" value="{{ photo_options }}" /> {% endif %} -<p>{% trans "Comment:" %}<br /><textarea name="comment" id="id_comment" rows="10" cols="60"></textarea></p> +<p><label for="id_comment">{% trans "Comment:" %}</label><br /> +<textarea name="comment" id="id_comment" rows="10" cols="60"></textarea></p> +<p> <input type="hidden" name="options" value="{{ options }}" /> <input type="hidden" name="target" value="{{ target }}" /> <input type="hidden" name="gonzo" value="{{ hash }}" /> -<p><input type="submit" name="preview" value="{% trans "Preview comment" %}" /></p> +<input type="submit" name="preview" value="{% trans "Preview comment" %}" /> +</p> </form> {% endif %} diff --git a/django/contrib/comments/templates/comments/freeform.html b/django/contrib/comments/templates/comments/freeform.html index 0c52bfab29..f0d00b91c7 100644 --- a/django/contrib/comments/templates/comments/freeform.html +++ b/django/contrib/comments/templates/comments/freeform.html @@ -1,11 +1,13 @@ {% load i18n %} {% if display_form %} <form action="/comments/postfree/" method="post"> -<p>{% trans "Your name:" %} <input type="text" id="id_person_name" name="person_name" /></p> -<p>{% trans "Comment:" %}<br /><textarea name="comment" id="id_comment" rows="10" cols="60"></textarea></p> +<p><label for="id_person_name">{% trans "Your name:" %}</label> <input type="text" id="id_person_name" name="person_name" /></p> +<p><label for="id_comment">{% trans "Comment:" %}</label><br /><textarea name="comment" id="id_comment" rows="10" cols="60"></textarea></p> +<p> <input type="hidden" name="options" value="{{ options }}" /> <input type="hidden" name="target" value="{{ target }}" /> <input type="hidden" name="gonzo" value="{{ hash }}" /> -<p><input type="submit" name="preview" value="{% trans "Preview comment" %}" /></p> +<input type="submit" name="preview" value="{% trans "Preview comment" %}" /> +</p> </form> {% endif %} diff --git a/django/contrib/comments/templatetags/comments.py b/django/contrib/comments/templatetags/comments.py index a3893fdf61..c3a2fd40d8 100644 --- a/django/contrib/comments/templatetags/comments.py +++ b/django/contrib/comments/templatetags/comments.py @@ -114,7 +114,7 @@ class CommentListNode(template.Node): comment_list = get_list_function(**kwargs).order_by(self.ordering + 'submit_date').select_related() if not self.free: - if context.has_key('user') and not context['user'].is_anonymous(): + if context.has_key('user') and context['user'].is_authenticated(): user_id = context['user'].id context['user_can_moderate_comments'] = Comment.objects.user_is_moderator(context['user']) else: diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py index 316f3e719b..c19c62fc88 100644 --- a/django/contrib/comments/views/comments.py +++ b/django/contrib/comments/views/comments.py @@ -5,8 +5,7 @@ from django.http import Http404 from django.core.exceptions import ObjectDoesNotExist from django.shortcuts import render_to_response from django.template import RequestContext -from django.contrib.auth.models import SESSION_KEY -from django.contrib.comments.models import Comment, FreeComment, PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC +from django.contrib.comments.models import Comment, FreeComment, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC from django.contrib.contenttypes.models import ContentType from django.contrib.auth.forms import AuthenticationForm from django.http import HttpResponseRedirect @@ -64,7 +63,7 @@ class PublicCommentManipulator(AuthenticationForm): validator_list=get_validator_list(8), ), ]) - if not user.is_anonymous(): + if user.is_authenticated(): self["username"].is_required = False self["username"].validator_list = [] self["password"].is_required = False @@ -219,7 +218,8 @@ def post_comment(request): # If user gave correct username/password and wasn't already logged in, log them in # so they don't have to enter a username/password again. if manipulator.get_user() and new_data.has_key('password') and manipulator.get_user().check_password(new_data['password']): - request.session[SESSION_KEY] = manipulator.get_user_id() + from django.contrib.auth import login + login(request, manipulator.get_user()) if errors or request.POST.has_key('preview'): class CommentFormWrapper(forms.FormWrapper): def __init__(self, manipulator, new_data, errors, rating_choices): diff --git a/django/contrib/comments/views/karma.py b/django/contrib/comments/views/karma.py index becb02e128..8c18523feb 100644 --- a/django/contrib/comments/views/karma.py +++ b/django/contrib/comments/views/karma.py @@ -15,7 +15,7 @@ def vote(request, comment_id, vote): rating = {'up': 1, 'down': -1}.get(vote, False) if not rating: raise Http404, "Invalid vote" - if request.user.is_anonymous(): + if not request.user.is_authenticated(): raise Http404, _("Anonymous users cannot vote") try: comment = Comment.objects.get(pk=comment_id) diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py index 733706257c..bc2a392121 100644 --- a/django/contrib/flatpages/models.py +++ b/django/contrib/flatpages/models.py @@ -10,7 +10,7 @@ class FlatPage(models.Model): content = models.TextField(_('content')) enable_comments = models.BooleanField(_('enable comments')) template_name = models.CharField(_('template name'), maxlength=70, blank=True, - help_text=_("Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'.")) + help_text=_("Example: 'flatpages/contact_page.html'. If this isn't provided, the system will use 'flatpages/default.html'.")) registration_required = models.BooleanField(_('registration required'), help_text=_("If this is checked, only logged-in users will be able to view the page.")) sites = models.ManyToManyField(Site) class Meta: diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py index 1441f6f3a3..4ad24795f7 100644 --- a/django/contrib/flatpages/views.py +++ b/django/contrib/flatpages/views.py @@ -22,7 +22,7 @@ def flatpage(request, url): f = get_object_or_404(FlatPage, url__exact=url, sites__id__exact=settings.SITE_ID) # If registration is required for accessing this page, and the user isn't # logged in, redirect to the login page. - if f.registration_required and request.user.is_anonymous(): + if f.registration_required and not request.user.is_authenticated(): from django.contrib.auth.views import redirect_to_login return redirect_to_login(request.path) if f.template_name: diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index dc8a9da031..4bb135cc32 100644 --- a/django/contrib/markup/templatetags/markup.py +++ b/django/contrib/markup/templatetags/markup.py @@ -27,7 +27,7 @@ def textile(value): raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed." return value else: - return textile.textile(value) + return textile.textile(value, encoding=settings.DEFAULT_CHARSET, output=settings.DEFAULT_CHARSET) def markdown(value): try: @@ -47,7 +47,8 @@ def restructuredtext(value): raise template.TemplateSyntaxError, "Error in {% restructuredtext %} filter: The Python docutils library isn't installed." return value else: - parts = publish_parts(source=value, writer_name="html4css1") + docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {}) + parts = publish_parts(source=value, writer_name="html4css1", settings_overrides=docutils_settings) return parts["fragment"] register.filter(textile) diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py index 8e2d011e3f..f684cd381e 100644 --- a/django/contrib/sessions/models.py +++ b/django/contrib/sessions/models.py @@ -32,11 +32,21 @@ class SessionManager(models.Manager): return s class Session(models.Model): - """Django provides full support for anonymous sessions. The session framework lets you store and retrieve arbitrary data on a per-site-visitor basis. It stores data on the server side and abstracts the sending and receiving of cookies. Cookies contain a session ID -- not the data itself. + """ + Django provides full support for anonymous sessions. The session + framework lets you store and retrieve arbitrary data on a + per-site-visitor basis. It stores data on the server side and + abstracts the sending and receiving of cookies. Cookies contain a + session ID -- not the data itself. - The Django sessions framework is entirely cookie-based. It does not fall back to putting session IDs in URLs. This is an intentional design decision. Not only does that behavior make URLs ugly, it makes your site vulnerable to session-ID theft via the "Referer" header. + The Django sessions framework is entirely cookie-based. It does + not fall back to putting session IDs in URLs. This is an intentional + design decision. Not only does that behavior make URLs ugly, it makes + your site vulnerable to session-ID theft via the "Referer" header. - For complete documentation on using Sessions in your code, consult the sessions documentation that is shipped with Django (also available on the Django website). + For complete documentation on using Sessions in your code, consult + the sessions documentation that is shipped with Django (also available + on the Django website). """ session_key = models.CharField(_('session key'), maxlength=40, primary_key=True) session_data = models.TextField(_('session data')) diff --git a/django/contrib/syndication/feeds.py b/django/contrib/syndication/feeds.py index 3deefc5866..119615a0b9 100644 --- a/django/contrib/syndication/feeds.py +++ b/django/contrib/syndication/feeds.py @@ -16,10 +16,14 @@ class Feed(object): item_pubdate = None item_enclosure_url = None feed_type = feedgenerator.DefaultFeed + title_template = None + description_template = None def __init__(self, slug, feed_url): self.slug = slug self.feed_url = feed_url + self.title_template_name = self.title_template or ('feeds/%s_title.html' % slug) + self.description_template_name = self.description_template or ('feeds/%s_description.html' % slug) def item_link(self, item): try: @@ -69,7 +73,7 @@ class Feed(object): link = link, description = self.__get_dynamic_attr('description', obj), language = settings.LANGUAGE_CODE.decode(), - feed_url = add_domain(current_site, self.feed_url), + feed_url = add_domain(current_site, self.__get_dynamic_attr('feed_url', obj)), author_name = self.__get_dynamic_attr('author_name', obj), author_link = self.__get_dynamic_attr('author_link', obj), author_email = self.__get_dynamic_attr('author_email', obj), @@ -77,13 +81,13 @@ class Feed(object): ) try: - title_template = loader.get_template('feeds/%s_title.html' % self.slug) + title_tmp = loader.get_template(self.title_template_name) except TemplateDoesNotExist: - title_template = Template('{{ obj }}') + title_tmp = Template('{{ obj }}') try: - description_template = loader.get_template('feeds/%s_description.html' % self.slug) + description_tmp = loader.get_template(self.description_template_name) except TemplateDoesNotExist: - description_template = Template('{{ obj }}') + description_tmp = Template('{{ obj }}') for item in self.__get_dynamic_attr('items', obj): link = add_domain(current_site.domain, self.__get_dynamic_attr('item_link', item)) @@ -102,9 +106,9 @@ class Feed(object): else: author_email = author_link = None feed.add_item( - title = title_template.render(Context({'obj': item, 'site': current_site})).decode('utf-8'), + title = title_tmp.render(Context({'obj': item, 'site': current_site})).decode('utf-8'), link = link, - description = description_template.render(Context({'obj': item, 'site': current_site})).decode('utf-8'), + description = description_tmp.render(Context({'obj': item, 'site': current_site})).decode('utf-8'), unique_id = link, enclosure = enc, pubdate = self.__get_dynamic_attr('item_pubdate', item), diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py index 1b54addded..4a0d44a44e 100644 --- a/django/core/cache/backends/db.py +++ b/django/core/cache/backends/db.py @@ -1,7 +1,7 @@ "Database cache backend." from django.core.cache.backends.base import BaseCache -from django.db import connection, transaction +from django.db import connection, transaction, DatabaseError import base64, time from datetime import datetime try: diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py index 5bd6da5857..0e21b80ed8 100644 --- a/django/core/cache/backends/locmem.py +++ b/django/core/cache/backends/locmem.py @@ -3,10 +3,6 @@ from django.core.cache.backends.simple import CacheClass as SimpleCacheClass from django.utils.synch import RWLock import copy, time -try: - import cPickle as pickle -except ImportError: - import pickle class CacheClass(SimpleCacheClass): def __init__(self, host, params): diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index c25ff2b14e..62217acdce 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -119,7 +119,6 @@ class BaseHandler(object): Returns an HttpResponse that displays a PUBLIC error message for a fundamental error. """ - from django.core import urlresolvers callback, param_dict = resolver.resolve500() return callback(request, **param_dict) diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py index 5a20ce9f67..07c98e3b59 100644 --- a/django/core/handlers/modpython.py +++ b/django/core/handlers/modpython.py @@ -23,6 +23,9 @@ class ModPythonRequest(http.HttpRequest): def get_full_path(self): return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '') + def is_secure(self): + return self._req.subprocess_env.has_key('HTTPS') and self._req.subprocess_env['HTTPS'] == 'on' + def _load_post_and_files(self): "Populates self._post and self._files" if self._req.headers_in.has_key('content-type') and self._req.headers_in['content-type'].startswith('multipart'): @@ -145,7 +148,6 @@ class ModPythonHandler(BaseHandler): def populate_apache_request(http_response, mod_python_req): "Populates the mod_python request object with an HttpResponse" - from django.conf import settings mod_python_req.content_type = http_response['Content-Type'] for key, value in http_response.headers.items(): if key != 'Content-Type': diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 00a9a2ca53..5c48c9dace 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -54,18 +54,20 @@ class WSGIRequest(http.HttpRequest): def __init__(self, environ): self.environ = environ self.path = environ['PATH_INFO'] - self.META = environ + self.META = environ self.method = environ['REQUEST_METHOD'].upper() def __repr__(self): - from pprint import pformat - return '<DjangoRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \ + return '<WSGIRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \ (pformat(self.GET), pformat(self.POST), pformat(self.COOKIES), pformat(self.META)) def get_full_path(self): return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '') + def is_secure(self): + return self.environ.has_key('HTTPS') and self.environ['HTTPS'] == 'on' + def _load_post_and_files(self): # Populates self._post and self._files if self.method == 'POST': diff --git a/django/core/management.py b/django/core/management.py index ea572729d9..7de6c86a04 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -45,8 +45,8 @@ def disable_termcolors(): global style style = dummy() -# Disable terminal coloring on Windows or if somebody's piping the output. -if sys.platform == 'win32' or not sys.stdout.isatty(): +# Disable terminal coloring on Windows, Pocket PC, or if somebody's piping the output. +if sys.platform == 'win32' or sys.platform == 'Pocket PC' or not sys.stdout.isatty(): disable_termcolors() def _is_valid_dir_name(s): @@ -78,7 +78,7 @@ def get_version(): from django import VERSION v = '.'.join([str(i) for i in VERSION[:-1]]) if VERSION[-1]: - v += ' (%s)' % VERSION[-1] + v += '-' + VERSION[-1] return v def get_sql_create(app): @@ -95,44 +95,39 @@ def get_sql_create(app): sys.exit(1) # Get installed models, so we generate REFERENCES right - installed_models = _get_installed_models(_get_table_list()) - final_output = [] - models_output = set(installed_models) + known_models = set(_get_installed_models(_get_table_list())) pending_references = {} app_models = models.get_models(app) - for klass in app_models: - output, references = _get_sql_model_create(klass, models_output) + for model in app_models: + output, references = _get_sql_model_create(model, known_models) final_output.extend(output) for refto, refs in references.items(): - try: - pending_references[refto].extend(refs) - except KeyError: - pending_references[refto] = refs - final_output.extend(_get_sql_for_pending_references(klass, pending_references)) + pending_references.setdefault(refto,[]).extend(refs) + final_output.extend(_get_sql_for_pending_references(model, pending_references)) # Keep track of the fact that we've created the table for this model. - models_output.add(klass) + known_models.add(model) # Create the many-to-many join tables. - for klass in app_models: - final_output.extend(_get_many_to_many_sql_for_model(klass)) + for model in app_models: + final_output.extend(_get_many_to_many_sql_for_model(model)) # Handle references to tables that are from other apps # but don't exist physically not_installed_models = set(pending_references.keys()) if not_installed_models: final_output.append('-- The following references should be added but depend on non-existant tables:') - for klass in not_installed_models: + for model in not_installed_models: final_output.extend(['-- ' + sql for sql in - _get_sql_for_pending_references(klass, pending_references)]) + _get_sql_for_pending_references(model, pending_references)]) return final_output get_sql_create.help_doc = "Prints the CREATE TABLE SQL statements for the given app name(s)." get_sql_create.args = APP_ARGS -def _get_sql_model_create(klass, models_already_seen=set()): +def _get_sql_model_create(model, known_models=set()): """ Get the SQL required to create a single model. @@ -141,7 +136,7 @@ def _get_sql_model_create(klass, models_already_seen=set()): from django.db import backend, get_creation_module, models data_types = get_creation_module().DATA_TYPES - opts = klass._meta + opts = model._meta final_output = [] table_output = [] pending_references = {} @@ -163,7 +158,7 @@ def _get_sql_model_create(klass, models_already_seen=set()): if f.primary_key: field_output.append(style.SQL_KEYWORD('PRIMARY KEY')) if f.rel: - if f.rel.to in models_already_seen: + if f.rel.to in known_models: field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \ style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)) + ' (' + \ style.SQL_FIELD(backend.quote_name(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' @@ -171,7 +166,7 @@ def _get_sql_model_create(klass, models_already_seen=set()): else: # We haven't yet created the table to which this field # is related, so save it for later. - pr = pending_references.setdefault(f.rel.to, []).append((klass, f)) + pr = pending_references.setdefault(f.rel.to, []).append((model, f)) table_output.append(' '.join(field_output)) if opts.order_with_respect_to: table_output.append(style.SQL_FIELD(backend.quote_name('_order')) + ' ' + \ @@ -189,7 +184,7 @@ def _get_sql_model_create(klass, models_already_seen=set()): return final_output, pending_references -def _get_sql_for_pending_references(klass, pending_references): +def _get_sql_for_pending_references(model, pending_references): """ Get any ALTER TABLE statements to add constraints after the fact. """ @@ -198,28 +193,30 @@ def _get_sql_for_pending_references(klass, pending_references): final_output = [] if backend.supports_constraints: - opts = klass._meta - if klass in pending_references: - for rel_class, f in pending_references[klass]: + opts = model._meta + if model in pending_references: + for rel_class, f in pending_references[model]: rel_opts = rel_class._meta r_table = rel_opts.db_table r_col = f.column table = opts.db_table col = opts.get_field(f.rel.field_name).column + # For MySQL, r_name must be unique in the first 64 characters. + # So we are careful with character usage here. + r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s);' % \ - (backend.quote_name(r_table), - backend.quote_name('%s_referencing_%s_%s' % (r_col, table, col)), + (backend.quote_name(r_table), r_name, backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col))) - del pending_references[klass] + del pending_references[model] return final_output -def _get_many_to_many_sql_for_model(klass): +def _get_many_to_many_sql_for_model(model): from django.db import backend, get_creation_module from django.db.models import GenericRel - + data_types = get_creation_module().DATA_TYPES - opts = klass._meta + opts = model._meta final_output = [] for f in opts.many_to_many: if not isinstance(f.rel, GenericRel): @@ -273,37 +270,37 @@ def get_sql_delete(app): references_to_delete = {} app_models = models.get_models(app) - for klass in app_models: - if cursor and klass._meta.db_table in table_names: + for model in app_models: + if cursor and model._meta.db_table in table_names: # The table exists, so it needs to be dropped - opts = klass._meta + opts = model._meta for f in opts.fields: if f.rel and f.rel.to not in to_delete: - references_to_delete.setdefault(f.rel.to, []).append( (klass, f) ) + references_to_delete.setdefault(f.rel.to, []).append( (model, f) ) - to_delete.add(klass) + to_delete.add(model) - for klass in app_models: - if cursor and klass._meta.db_table in table_names: + for model in app_models: + if cursor and model._meta.db_table in table_names: # Drop the table now output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), - style.SQL_TABLE(backend.quote_name(klass._meta.db_table)))) - if backend.supports_constraints and references_to_delete.has_key(klass): - for rel_class, f in references_to_delete[klass]: + style.SQL_TABLE(backend.quote_name(model._meta.db_table)))) + if backend.supports_constraints and references_to_delete.has_key(model): + for rel_class, f in references_to_delete[model]: table = rel_class._meta.db_table col = f.column - r_table = klass._meta.db_table - r_col = klass._meta.get_field(f.rel.field_name).column + r_table = model._meta.db_table + r_col = model._meta.get_field(f.rel.field_name).column output.append('%s %s %s %s;' % \ (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(backend.quote_name(table)), style.SQL_KEYWORD(backend.get_drop_foreignkey_sql()), style.SQL_FIELD(backend.quote_name("%s_referencing_%s_%s" % (col, r_table, r_col))))) - del references_to_delete[klass] + del references_to_delete[model] # Output DROP TABLE statements for many-to-many tables. - for klass in app_models: - opts = klass._meta + for model in app_models: + opts = model._meta for f in opts.many_to_many: if cursor and f.m2m_db_table() in table_names: output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'), @@ -360,8 +357,8 @@ def get_sql_initial_data(app): app_models = get_models(app) app_dir = os.path.normpath(os.path.join(os.path.dirname(app.__file__), 'sql')) - for klass in app_models: - output.extend(get_sql_initial_data_for_model(klass)) + for model in app_models: + output.extend(get_sql_initial_data_for_model(model)) return output get_sql_initial_data.help_doc = "Prints the initial INSERT SQL statements for the given app name(s)." @@ -371,18 +368,18 @@ def get_sql_sequence_reset(app): "Returns a list of the SQL statements to reset PostgreSQL sequences for the given app." from django.db import backend, models output = [] - for klass in models.get_models(app): - for f in klass._meta.fields: + for model in models.get_models(app): + for f in model._meta.fields: if isinstance(f, models.AutoField): output.append("%s setval('%s', (%s max(%s) %s %s));" % \ (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD('%s_%s_seq' % (klass._meta.db_table, f.column)), + style.SQL_FIELD('%s_%s_seq' % (model._meta.db_table, f.column)), style.SQL_KEYWORD('SELECT'), style.SQL_FIELD(backend.quote_name(f.column)), style.SQL_KEYWORD('FROM'), - style.SQL_TABLE(backend.quote_name(klass._meta.db_table)))) + style.SQL_TABLE(backend.quote_name(model._meta.db_table)))) break # Only one AutoField is allowed per model, so don't bother continuing. - for f in klass._meta.many_to_many: + for f in model._meta.many_to_many: output.append("%s setval('%s', (%s max(%s) %s %s));" % \ (style.SQL_KEYWORD('SELECT'), style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()), @@ -399,15 +396,15 @@ def get_sql_indexes(app): from django.db import backend, models output = [] - for klass in models.get_models(app): - for f in klass._meta.fields: + for model in models.get_models(app): + for f in model._meta.fields: if f.db_index: unique = f.unique and 'UNIQUE ' or '' output.append( style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \ - style.SQL_TABLE('%s_%s' % (klass._meta.db_table, f.column)) + ' ' + \ + style.SQL_TABLE('%s_%s' % (model._meta.db_table, f.column)) + ' ' + \ style.SQL_KEYWORD('ON') + ' ' + \ - style.SQL_TABLE(backend.quote_name(klass._meta.db_table)) + ' ' + \ + style.SQL_TABLE(backend.quote_name(model._meta.db_table)) + ' ' + \ "(%s);" % style.SQL_FIELD(backend.quote_name(f.column)) ) return output @@ -517,14 +514,14 @@ def get_admin_index(app): app_label = app_models[0]._meta.app_label output.append('{%% if perms.%s %%}' % app_label) output.append('<div class="module"><h2>%s</h2><table>' % app_label.title()) - for klass in app_models: - if klass._meta.admin: + for model in app_models: + if model._meta.admin: output.append(MODULE_TEMPLATE % { 'app': app_label, - 'mod': klass._meta.module_name, - 'name': capfirst(klass._meta.verbose_name_plural), - 'addperm': klass._meta.get_add_permission(), - 'changeperm': klass._meta.get_change_permission(), + 'mod': model._meta.module_name, + 'name': capfirst(model._meta.verbose_name_plural), + 'addperm': model._meta.get_add_permission(), + 'changeperm': model._meta.get_change_permission(), }) output.append('</table></div>') output.append('{% endif %}') @@ -592,7 +589,6 @@ install.args = APP_ARGS def reset(app): "Executes the equivalent of 'get_sql_reset' in the current database." from django.db import connection, transaction - from cStringIO import StringIO app_name = app.__name__.split('.')[-2] disable_termcolors() @@ -692,7 +688,6 @@ startapp.args = "[appname]" def inspectdb(): "Generator that introspects the tables in the given database name and returns a Django model, one line at a time." from django.db import connection, get_introspection_module - from django.conf import settings import keyword introspection_module = get_introspection_module() @@ -803,9 +798,9 @@ class ModelErrorCollection: self.errors = [] self.outfile = outfile - def add(self, opts, error): - self.errors.append((opts, error)) - self.outfile.write(style.ERROR("%s.%s: %s\n" % (opts.app_label, opts.module_name, error))) + def add(self, context, error): + self.errors.append((context, error)) + self.outfile.write(style.ERROR("%s: %s\n" % (context, error))) def get_validation_errors(outfile, app=None): """ @@ -814,9 +809,14 @@ def get_validation_errors(outfile, app=None): Returns number of errors. """ from django.db import models + from django.db.models.loading import get_app_errors from django.db.models.fields.related import RelatedObject e = ModelErrorCollection(outfile) + + for (app_name, error) in get_app_errors().items(): + e.add(app_name, error) + for cls in models.get_models(app): opts = cls._meta @@ -858,18 +858,29 @@ def get_validation_errors(outfile, app=None): e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, rel_opts.object_name)) rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() + rel_query_name = f.related_query_name() for r in rel_opts.fields: if r.name == rel_name: - e.add(opts, "'%s' accessor name '%s.%s' clashes with another field. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) + e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) + if r.name == rel_query_name: + e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) for r in rel_opts.many_to_many: if r.name == rel_name: - e.add(opts, "'%s' accessor name '%s.%s' clashes with a m2m field. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) + e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) + if r.name == rel_query_name: + e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) for r in rel_opts.get_all_related_many_to_many_objects(): if r.get_accessor_name() == rel_name: - e.add(opts, "'%s' accessor name '%s.%s' clashes with a related m2m field. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) + e.add(opts, "Accessor for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) + if r.get_accessor_name() == rel_query_name: + e.add(opts, "Reverse query name for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) for r in rel_opts.get_all_related_objects(): - if r.get_accessor_name() == rel_name and r.field is not f: - e.add(opts, "'%s' accessor name '%s.%s' clashes with another related field. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) + if r.field is not f: + if r.get_accessor_name() == rel_name: + e.add(opts, "Accessor for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) + if r.get_accessor_name() == rel_query_name: + e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) + for i, f in enumerate(opts.many_to_many): # Check to see if the related m2m field will clash with any @@ -879,18 +890,28 @@ def get_validation_errors(outfile, app=None): e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, rel_opts.object_name)) rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() + rel_query_name = f.related_query_name() for r in rel_opts.fields: if r.name == rel_name: - e.add(opts, "'%s' m2m accessor name '%s.%s' clashes with another field. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) + e.add(opts, "Accessor for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) + if r.name == rel_query_name: + e.add(opts, "Reverse query name for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) for r in rel_opts.many_to_many: if r.name == rel_name: - e.add(opts, "'%s' m2m accessor name '%s.%s' clashes with a m2m field. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) + e.add(opts, "Accessor for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) + if r.name == rel_query_name: + e.add(opts, "Reverse query name for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) for r in rel_opts.get_all_related_many_to_many_objects(): - if r.get_accessor_name() == rel_name and r.field is not f: - e.add(opts, "'%s' m2m accessor name '%s.%s' clashes with a related m2m field. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) + if r.field is not f: + if r.get_accessor_name() == rel_name: + e.add(opts, "Accessor for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) + if r.get_accessor_name() == rel_query_name: + e.add(opts, "Reverse query name for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) for r in rel_opts.get_all_related_objects(): if r.get_accessor_name() == rel_name: - e.add(opts, "'%s' m2m accessor name '%s.%s' clashes with another related field. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) + e.add(opts, "Accessor for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) + if r.get_accessor_name() == rel_query_name: + e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) # Check admin attribute. if opts.admin is not None: @@ -910,6 +931,19 @@ def get_validation_errors(outfile, app=None): else: if isinstance(f, models.ManyToManyField): e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn) + # list_display_links + if opts.admin.list_display_links and not opts.admin.list_display: + e.add(opts, '"admin.list_display" must be defined for "admin.list_display_links" to be used.') + if not isinstance(opts.admin.list_display_links, (list, tuple)): + e.add(opts, '"admin.list_display_links", if given, must be set to a list or tuple.') + else: + for fn in opts.admin.list_display_links: + try: + f = opts.get_field(fn) + except models.FieldDoesNotExist: + e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn) + if fn not in opts.admin.list_display: + e.add(opts, '"admin.list_display_links" refers to %r, which is not defined in "admin.list_display".' % fn) # list_filter if not isinstance(opts.admin.list_filter, (list, tuple)): e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.') @@ -919,6 +953,12 @@ def get_validation_errors(outfile, app=None): f = opts.get_field(fn) except models.FieldDoesNotExist: e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn) + # date_hierarchy + if opts.admin.date_hierarchy: + try: + f = opts.get_field(opts.admin.date_hierarchy) + except models.FieldDoesNotExist: + e.add(opts, '"admin.date_hierarchy" refers to %r, which isn\'t a field.' % opts.admin.date_hierarchy) # Check ordering attribute. if opts.ordering: @@ -936,6 +976,8 @@ def get_validation_errors(outfile, app=None): # Check core=True, if needed. for related in opts.get_followed_related_objects(): + if not related.edit_inline: + continue try: for f in related.opts.fields: if f.core: @@ -975,12 +1017,15 @@ def _check_for_validation_errors(app=None): s = StringIO() num_errors = get_validation_errors(s, app) if num_errors: - sys.stderr.write(style.ERROR("Error: %s couldn't be installed, because there were errors in your model:\n" % app)) + if app: + sys.stderr.write(style.ERROR("Error: %s couldn't be installed, because there were errors in your model:\n" % app)) + else: + sys.stderr.write(style.ERROR("Error: Couldn't install apps, because there were errors in one or more models:\n")) s.seek(0) sys.stderr.write(s.read()) sys.exit(1) -def runserver(addr, port): +def runserver(addr, port, use_reloader=True): "Starts a lightweight Web server for development." from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException from django.core.handlers.wsgi import WSGIHandler @@ -1014,9 +1059,12 @@ def runserver(addr, port): sys.exit(1) except KeyboardInterrupt: sys.exit(0) - from django.utils import autoreload - autoreload.main(inner_run) -runserver.args = '[optional port number, or ipaddr:port]' + if use_reloader: + from django.utils import autoreload + autoreload.main(inner_run) + else: + inner_run() +runserver.args = '[--noreload] [optional port number, or ipaddr:port]' def createcachetable(tablename): "Creates the table needed to use the SQL cache backend" @@ -1165,6 +1213,8 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None): help='Lets you manually add a directory the Python path, e.g. "/home/djangoprojects/myproject".') parser.add_option('--plain', action='store_true', dest='plain', help='Tells Django to use plain Python, not IPython, for "shell" command.') + parser.add_option('--noreload', action='store_false', dest='use_reloader', default=True, + help='Tells Django to NOT use the auto-reloader when running the development server.') options, args = parser.parse_args(argv[1:]) # Take care of options. @@ -1220,7 +1270,7 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None): addr, port = args[1].split(':') except ValueError: addr, port = '', args[1] - action_mapping[action](addr, port) + action_mapping[action](addr, port, options.use_reloader) elif action == 'runfcgi': action_mapping[action](args[1:]) else: diff --git a/django/core/paginator.py b/django/core/paginator.py index 195ad1009e..026fe0a675 100644 --- a/django/core/paginator.py +++ b/django/core/paginator.py @@ -1,4 +1,3 @@ -from copy import copy from math import ceil class InvalidPage(Exception): diff --git a/django/core/serializers/__init__.py b/django/core/serializers/__init__.py new file mode 100644 index 0000000000..75e087ee1b --- /dev/null +++ b/django/core/serializers/__init__.py @@ -0,0 +1,78 @@ +""" +Interfaces for serializing Django objects. + +Usage:: + + >>> from django.core import serializers + >>> json = serializers.serialize("json", some_query_set) + >>> objects = list(serializers.deserialize("json", json)) + +To add your own serializers, use the SERIALIZATION_MODULES setting:: + + SERIALIZATION_MODULES = { + "csv" : "path.to.csv.serializer", + "txt" : "path.to.txt.serializer", + } + +""" + +from django.conf import settings + +# Built-in serializers +BUILTIN_SERIALIZERS = { + "xml" : "django.core.serializers.xml_serializer", + "python" : "django.core.serializers.python", + "json" : "django.core.serializers.json", +} + +_serializers = {} + +def register_serializer(format, serializer_module): + """Register a new serializer by passing in a module name.""" + module = __import__(serializer_module, '', '', ['']) + _serializers[format] = module + +def unregister_serializer(format): + """Unregister a given serializer""" + del _serializers[format] + +def get_serializer(format): + if not _serializers: + _load_serializers() + return _serializers[format].Serializer + +def get_deserializer(format): + if not _serializers: + _load_serializers() + return _serializers[format].Deserializer + +def serialize(format, queryset, **options): + """ + Serialize a queryset (or any iterator that returns database objects) using + a certain serializer. + """ + s = get_serializer(format)() + s.serialize(queryset, **options) + return s.getvalue() + +def deserialize(format, stream_or_string): + """ + Deserialize a stream or a string. Returns an iterator that yields ``(obj, + m2m_relation_dict)``, where ``obj`` is a instantiated -- but *unsaved* -- + object, and ``m2m_relation_dict`` is a dictionary of ``{m2m_field_name : + list_of_related_objects}``. + """ + d = get_deserializer(format) + return d(stream_or_string) + +def _load_serializers(): + """ + Register built-in and settings-defined serializers. This is done lazily so + that user code has a chance to (e.g.) set up custom settings without + needing to be careful of import order. + """ + for format in BUILTIN_SERIALIZERS: + register_serializer(format, BUILTIN_SERIALIZERS[format]) + if hasattr(settings, "SERIALIZATION_MODULES"): + for format in settings.SERIALIZATION_MODULES: + register_serializer(format, settings.SERIALIZATION_MODULES[format])
\ No newline at end of file diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py new file mode 100644 index 0000000000..e939c0c6e7 --- /dev/null +++ b/django/core/serializers/base.py @@ -0,0 +1,161 @@ +""" +Module for abstract serializer/unserializer base classes. +""" + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO +from django.db import models + +class SerializationError(Exception): + """Something bad happened during serialization.""" + pass + +class DeserializationError(Exception): + """Something bad happened during deserialization.""" + pass + +class Serializer(object): + """ + Abstract serializer base class. + """ + + def serialize(self, queryset, **options): + """ + Serialize a queryset. + """ + self.options = options + + self.stream = options.get("stream", StringIO()) + + self.start_serialization() + for obj in queryset: + self.start_object(obj) + for field in obj._meta.fields: + if field is obj._meta.pk: + continue + elif field.rel is None: + self.handle_field(obj, field) + else: + self.handle_fk_field(obj, field) + for field in obj._meta.many_to_many: + self.handle_m2m_field(obj, field) + self.end_object(obj) + self.end_serialization() + return self.getvalue() + + def get_string_value(self, obj, field): + """ + Convert a field's value to a string. + """ + if isinstance(field, models.DateTimeField): + value = getattr(obj, field.name).strftime("%Y-%m-%d %H:%M:%S") + elif isinstance(field, models.FileField): + value = getattr(obj, "get_%s_url" % field.name, lambda: None)() + else: + value = field.flatten_data(follow=None, obj=obj).get(field.name, "") + return str(value) + + def start_serialization(self): + """ + Called when serializing of the queryset starts. + """ + raise NotImplementedError + + def end_serialization(self): + """ + Called when serializing of the queryset ends. + """ + pass + + def start_object(self, obj): + """ + Called when serializing of an object starts. + """ + raise NotImplementedError + + def end_object(self, obj): + """ + Called when serializing of an object ends. + """ + pass + + def handle_field(self, obj, field): + """ + Called to handle each individual (non-relational) field on an object. + """ + raise NotImplementedError + + def handle_fk_field(self, obj, field): + """ + Called to handle a ForeignKey field. + """ + raise NotImplementedError + + def handle_m2m_field(self, obj, field): + """ + Called to handle a ManyToManyField. + """ + raise NotImplementedError + + def getvalue(self): + """ + Return the fully serialized queryset. + """ + return self.stream.getvalue() + +class Deserializer(object): + """ + Abstract base deserializer class. + """ + + def __init__(self, stream_or_string, **options): + """ + Init this serializer given a stream or a string + """ + self.options = options + if isinstance(stream_or_string, basestring): + self.stream = StringIO(stream_or_string) + else: + self.stream = stream_or_string + # hack to make sure that the models have all been loaded before + # deserialization starts (otherwise subclass calls to get_model() + # and friends might fail...) + models.get_apps() + + def __iter__(self): + return self + + def next(self): + """Iteration iterface -- return the next item in the stream""" + raise NotImplementedError + +class DeserializedObject(object): + """ + A deserialzed model. + + Basically a container for holding the pre-saved deserialized data along + with the many-to-many data saved with the object. + + Call ``save()`` to save the object (with the many-to-many data) to the + database; call ``save(save_m2m=False)`` to save just the object fields + (and not touch the many-to-many stuff.) + """ + + def __init__(self, obj, m2m_data=None): + self.object = obj + self.m2m_data = m2m_data + + def __repr__(self): + return "<DeserializedObject: %s>" % str(self.object) + + def save(self, save_m2m=True): + self.object.save() + if self.m2m_data and save_m2m: + for accessor_name, object_list in self.m2m_data.items(): + setattr(self.object, accessor_name, object_list) + + # prevent a second (possibly accidental) call to save() from saving + # the m2m data twice. + self.m2m_data = None diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py new file mode 100644 index 0000000000..a8b4259099 --- /dev/null +++ b/django/core/serializers/json.py @@ -0,0 +1,51 @@ +""" +Serialize data to/from JSON +""" + +import datetime +from django.utils import simplejson +from django.core.serializers.python import Serializer as PythonSerializer +from django.core.serializers.python import Deserializer as PythonDeserializer +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +class Serializer(PythonSerializer): + """ + Convert a queryset to JSON. + """ + def end_serialization(self): + simplejson.dump(self.objects, self.stream, cls=DateTimeAwareJSONEncoder) + + def getvalue(self): + return self.stream.getvalue() + +def Deserializer(stream_or_string, **options): + """ + Deserialize a stream or string of JSON data. + """ + if isinstance(stream_or_string, basestring): + stream = StringIO(stream_or_string) + else: + stream = stream_or_string + for obj in PythonDeserializer(simplejson.load(stream)): + yield obj + +class DateTimeAwareJSONEncoder(simplejson.JSONEncoder): + """ + JSONEncoder subclass that knows how to encode date/time types + """ + + DATE_FORMAT = "%Y-%m-%d" + TIME_FORMAT = "%H:%M:%S" + + def default(self, o): + if isinstance(o, datetime.datetime): + return o.strftime("%s %s" % (self.DATE_FORMAT, self.TIME_FORMAT)) + elif isinstance(o, datetime.date): + return o.strftime(self.DATE_FORMAT) + elif isinstance(o, datetime.time): + return o.strftime(self.TIME_FORMAT) + else: + return super(self, DateTimeAwareJSONEncoder).default(o)
\ No newline at end of file diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py new file mode 100644 index 0000000000..4181bc7f2b --- /dev/null +++ b/django/core/serializers/python.py @@ -0,0 +1,101 @@ +""" +A Python "serializer". Doesn't do much serializing per se -- just converts to +and from basic Python data types (lists, dicts, strings, etc.). Useful as a basis for +other serializers. +""" + +from django.conf import settings +from django.core.serializers import base +from django.db import models + +class Serializer(base.Serializer): + """ + Serializes a QuerySet to basic Python objects. + """ + + def start_serialization(self): + self._current = None + self.objects = [] + + def end_serialization(self): + pass + + def start_object(self, obj): + self._current = {} + + def end_object(self, obj): + self.objects.append({ + "model" : str(obj._meta), + "pk" : str(obj._get_pk_val()), + "fields" : self._current + }) + self._current = None + + def handle_field(self, obj, field): + self._current[field.name] = getattr(obj, field.name) + + def handle_fk_field(self, obj, field): + related = getattr(obj, field.name) + if related is not None: + related = related._get_pk_val() + self._current[field.name] = related + + def handle_m2m_field(self, obj, field): + self._current[field.name] = [related._get_pk_val() for related in getattr(obj, field.name).iterator()] + + def getvalue(self): + return self.objects + +def Deserializer(object_list, **options): + """ + Deserialize simple Python objects back into Django ORM instances. + + It's expected that you pass the Python objects themselves (instead of a + stream or a string) to the constructor + """ + models.get_apps() + for d in object_list: + # Look up the model and starting build a dict of data for it. + Model = _get_model(d["model"]) + data = {Model._meta.pk.name : d["pk"]} + m2m_data = {} + + # Handle each field + for (field_name, field_value) in d["fields"].iteritems(): + if isinstance(field_value, unicode): + field_value = field_value.encode(options.get("encoding", settings.DEFAULT_CHARSET)) + + field = Model._meta.get_field(field_name) + + # Handle M2M relations (with in_bulk() for performance) + if field.rel and isinstance(field.rel, models.ManyToManyRel): + pks = [] + for pk in field_value: + if isinstance(pk, unicode): + pk = pk.encode(options.get("encoding", settings.DEFAULT_CHARSET)) + m2m_data[field.name] = field.rel.to._default_manager.in_bulk(field_value).values() + + # Handle FK fields + elif field.rel and isinstance(field.rel, models.ManyToOneRel): + try: + data[field.name] = field.rel.to._default_manager.get(pk=field_value) + except field.rel.to.DoesNotExist: + data[field.name] = None + + # Handle all other fields + else: + data[field.name] = field.to_python(field_value) + + yield base.DeserializedObject(Model(**data), m2m_data) + +def _get_model(model_identifier): + """ + Helper to look up a model from an "app_label.module_name" string. + """ + try: + Model = models.get_model(*model_identifier.split(".")) + except TypeError: + Model = None + if Model is None: + raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier) + return Model diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py new file mode 100644 index 0000000000..09fff408cf --- /dev/null +++ b/django/core/serializers/xml_serializer.py @@ -0,0 +1,217 @@ +""" +XML serializer. +""" + +from django.conf import settings +from django.core.serializers import base +from django.db import models +from django.utils.xmlutils import SimplerXMLGenerator +from xml.dom import pulldom + +class Serializer(base.Serializer): + """ + Serializes a QuerySet to XML. + """ + + def start_serialization(self): + """ + Start serialization -- open the XML document and the root element. + """ + self.xml = SimplerXMLGenerator(self.stream, self.options.get("encoding", settings.DEFAULT_CHARSET)) + self.xml.startDocument() + self.xml.startElement("django-objects", {"version" : "1.0"}) + + def end_serialization(self): + """ + End serialization -- end the document. + """ + self.xml.endElement("django-objects") + self.xml.endDocument() + + def start_object(self, obj): + """ + Called as each object is handled. + """ + if not hasattr(obj, "_meta"): + raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj)) + + self.xml.startElement("object", { + "pk" : str(obj._get_pk_val()), + "model" : str(obj._meta), + }) + + def end_object(self, obj): + """ + Called after handling all fields for an object. + """ + self.xml.endElement("object") + + def handle_field(self, obj, field): + """ + Called to handle each field on an object (except for ForeignKeys and + ManyToManyFields) + """ + self.xml.startElement("field", { + "name" : field.name, + "type" : field.get_internal_type() + }) + + # Get a "string version" of the object's data (this is handled by the + # serializer base class). None is handled specially. + value = self.get_string_value(obj, field) + if value is not None: + self.xml.characters(str(value)) + + self.xml.endElement("field") + + def handle_fk_field(self, obj, field): + """ + Called to handle a ForeignKey (we need to treat them slightly + differently from regular fields). + """ + self._start_relational_field(field) + related = getattr(obj, field.name) + if related is not None: + self.xml.characters(str(related._get_pk_val())) + else: + self.xml.addQuickElement("None") + self.xml.endElement("field") + + def handle_m2m_field(self, obj, field): + """ + Called to handle a ManyToManyField. Related objects are only + serialized as references to the object's PK (i.e. the related *data* + is not dumped, just the relation). + """ + self._start_relational_field(field) + for relobj in getattr(obj, field.name).iterator(): + self.xml.addQuickElement("object", attrs={"pk" : str(relobj._get_pk_val())}) + self.xml.endElement("field") + + def _start_relational_field(self, field): + """ + Helper to output the <field> element for relational fields + """ + self.xml.startElement("field", { + "name" : field.name, + "rel" : field.rel.__class__.__name__, + "to" : str(field.rel.to._meta), + }) + +class Deserializer(base.Deserializer): + """ + Deserialize XML. + """ + + def __init__(self, stream_or_string, **options): + super(Deserializer, self).__init__(stream_or_string, **options) + self.encoding = self.options.get("encoding", settings.DEFAULT_CHARSET) + self.event_stream = pulldom.parse(self.stream) + + def next(self): + for event, node in self.event_stream: + if event == "START_ELEMENT" and node.nodeName == "object": + self.event_stream.expandNode(node) + return self._handle_object(node) + raise StopIteration + + def _handle_object(self, node): + """ + Convert an <object> node to a DeserializedObject. + """ + # Look up the model using the model loading mechanism. If this fails, bail. + Model = self._get_model_from_node(node, "model") + + # Start building a data dictionary from the object. If the node is + # missing the pk attribute, bail. + pk = node.getAttribute("pk") + if not pk: + raise base.DeserializationError("<object> node is missing the 'pk' attribute") + data = {Model._meta.pk.name : pk} + + # Also start building a dict of m2m data (this is saved as + # {m2m_accessor_attribute : [list_of_related_objects]}) + m2m_data = {} + + # Deseralize each field. + for field_node in node.getElementsByTagName("field"): + # If the field is missing the name attribute, bail (are you + # sensing a pattern here?) + field_name = field_node.getAttribute("name") + if not field_name: + raise base.DeserializationError("<field> node is missing the 'name' attribute") + + # Get the field from the Model. This will raise a + # FieldDoesNotExist if, well, the field doesn't exist, which will + # be propagated correctly. + field = Model._meta.get_field(field_name) + + # As is usually the case, relation fields get the special treatment. + if field.rel and isinstance(field.rel, models.ManyToManyRel): + m2m_data[field.name] = self._handle_m2m_field_node(field_node) + elif field.rel and isinstance(field.rel, models.ManyToOneRel): + data[field.name] = self._handle_fk_field_node(field_node) + else: + value = field.to_python(getInnerText(field_node).strip().encode(self.encoding)) + data[field.name] = value + + # Return a DeserializedObject so that the m2m data has a place to live. + return base.DeserializedObject(Model(**data), m2m_data) + + def _handle_fk_field_node(self, node): + """ + Handle a <field> node for a ForeignKey + """ + # Try to set the foreign key by looking up the foreign related object. + # If it doesn't exist, set the field to None (which might trigger + # validation error, but that's expected). + RelatedModel = self._get_model_from_node(node, "to") + return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding)) + + def _handle_m2m_field_node(self, node): + """ + Handle a <field> node for a ManyToManyField + """ + # Load the related model + RelatedModel = self._get_model_from_node(node, "to") + + # Look up all the related objects. Using the in_bulk() lookup ensures + # that missing related objects don't cause an exception + related_ids = [c.getAttribute("pk").encode(self.encoding) for c in node.getElementsByTagName("object")] + return RelatedModel._default_manager.in_bulk(related_ids).values() + + def _get_model_from_node(self, node, attr): + """ + Helper to look up a model from a <object model=...> or a <field + rel=... to=...> node. + """ + model_identifier = node.getAttribute(attr) + if not model_identifier: + raise base.DeserializationError( + "<%s> node is missing the required '%s' attribute" \ + % (node.nodeName, attr)) + try: + Model = models.get_model(*model_identifier.split(".")) + except TypeError: + Model = None + if Model is None: + raise base.DeserializationError( + "<%s> node has invalid model identifier: '%s'" % \ + (node.nodeName, model_identifier)) + return Model + + +def getInnerText(node): + """ + Get all the inner text of a DOM node (recursively). + """ + # inspired by http://mail.python.org/pipermail/xml-sig/2005-March/011022.html + inner_text = [] + for child in node.childNodes: + if child.nodeType == child.TEXT_NODE or child.nodeType == child.CDATA_SECTION_NODE: + inner_text.append(child.data) + elif child.nodeType == child.ELEMENT_NODE: + inner_text.extend(getInnerText(child)) + else: + pass + return "".join(inner_text)
\ No newline at end of file diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index 259a931594..7ce5706c23 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -8,7 +8,7 @@ been reviewed for security issues. Don't use it for production use. """ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -from types import ListType, StringType, TupleType +from types import ListType, StringType import os, re, sys, time, urllib __version__ = "0.1" diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index a1661a2ecd..1da25c9f83 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -130,12 +130,13 @@ class RegexURLPattern(object): return reverse_helper(self.regex, *args, **kwargs) class RegexURLResolver(object): - def __init__(self, regex, urlconf_name): + def __init__(self, regex, urlconf_name, default_kwargs=None): # regex is a string representing a regular expression. # urlconf_name is a string representing the module containing urlconfs. self.regex = re.compile(regex) self.urlconf_name = urlconf_name self.callback = None + self.default_kwargs = default_kwargs or {} def resolve(self, path): tried = [] @@ -149,7 +150,8 @@ class RegexURLResolver(object): tried.extend([(pattern.regex.pattern + ' ' + t) for t in e.args[0]['tried']]) else: if sub_match: - return sub_match[0], sub_match[1], dict(match.groupdict(), **sub_match[2]) + sub_match_dict = dict(self.default_kwargs, **sub_match[2]) + return sub_match[0], sub_match[1], dict(match.groupdict(), **sub_match_dict) tried.append(pattern.regex.pattern) raise Resolver404, {'tried': tried, 'path': new_path} @@ -201,3 +203,19 @@ class RegexURLResolver(object): sub_match = self.reverse(viewname, *args, **kwargs) result = reverse_helper(self.regex, *args, **kwargs) return result + sub_match + +def resolve(path, urlconf=None): + if urlconf is None: + from django.conf import settings + urlconf = settings.ROOT_URLCONF + resolver = RegexURLResolver(r'^/', urlconf) + return resolver.resolve(path) + +def reverse(viewname, urlconf=None, args=None, kwargs=None): + args = args or [] + kwargs = kwargs or {} + if urlconf is None: + from django.conf import settings + urlconf = settings.ROOT_URLCONF + resolver = RegexURLResolver(r'^/', urlconf) + return '/' + resolver.reverse(viewname, *args, **kwargs) diff --git a/django/core/validators.py b/django/core/validators.py index f98589578e..81bea23e36 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -23,7 +23,7 @@ ansi_datetime_re = re.compile('^%s %s$' % (_datere, _timere)) email_re = re.compile( r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string - r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}$', re.IGNORECASE) # domain + r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain integer_re = re.compile(r'^-?\d+$') ip4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE) diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index a522f24f2f..23ea76316f 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -40,7 +40,7 @@ class MysqlDebugWrapper: def executemany(self, sql, param_list): try: return self.cursor.executemany(sql, param_list) - except Database.Warning: + except Database.Warning, w: self.cursor.execute("SHOW WARNINGS") raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall()) diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index e1fbfff828..2c77f76ee3 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -1,4 +1,3 @@ -from django.db import transaction from django.db.backends.mysql.base import quote_name from MySQLdb import ProgrammingError, OperationalError from MySQLdb.constants import FIELD_TYPE diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 9943ac9610..dfe2df11dc 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -10,7 +10,6 @@ try: except ImportError, e: from django.core.exceptions import ImproperlyConfigured raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e -import types DatabaseError = Database.Error @@ -122,7 +121,6 @@ OPERATOR_MAPPING = { 'iexact': 'LIKE %s', 'contains': 'LIKE %s', 'icontains': 'LIKE %s', - 'ne': '!= %s', 'gt': '> %s', 'gte': '>= %s', 'lt': '< %s', diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py index 656741e440..ecc8f372a8 100644 --- a/django/db/backends/oracle/introspection.py +++ b/django/db/backends/oracle/introspection.py @@ -1,5 +1,3 @@ -from django.db import transaction -from django.db.backends.oracle.base import quote_name import re foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)") diff --git a/django/db/backends/postgresql/client.py b/django/db/backends/postgresql/client.py index 3d0d7a0d2a..8123ec7848 100644 --- a/django/db/backends/postgresql/client.py +++ b/django/db/backends/postgresql/client.py @@ -2,13 +2,14 @@ from django.conf import settings import os def runshell(): - args = [''] - args += ["-U%s" % settings.DATABASE_USER] + args = ['psql'] + if settings.DATABASE_USER: + args += ["-U", settings.DATABASE_USER] if settings.DATABASE_PASSWORD: args += ["-W"] if settings.DATABASE_HOST: - args += ["-h %s" % settings.DATABASE_HOST] + args.extend(["-h", settings.DATABASE_HOST]) if settings.DATABASE_PORT: - args += ["-p %s" % settings.DATABASE_PORT] + args.extend(["-p", str(settings.DATABASE_PORT)]) args += [settings.DATABASE_NAME] os.execvp('psql', args) diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py index c3a16d61c3..6e1d60c4ff 100644 --- a/django/db/backends/postgresql/introspection.py +++ b/django/db/backends/postgresql/introspection.py @@ -1,4 +1,3 @@ -from django.db import transaction from django.db.backends.postgresql.base import quote_name def get_table_list(cursor): diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py index b991493d39..a546da8c45 100644 --- a/django/db/backends/postgresql_psycopg2/introspection.py +++ b/django/db/backends/postgresql_psycopg2/introspection.py @@ -1,4 +1,3 @@ -from django.db import transaction from django.db.backends.postgresql_psycopg2.base import quote_name def get_table_list(cursor): diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py index c5fa738249..4e22c5ea42 100644 --- a/django/db/backends/sqlite3/introspection.py +++ b/django/db/backends/sqlite3/introspection.py @@ -1,19 +1,56 @@ -from django.db import transaction from django.db.backends.sqlite3.base import quote_name def get_table_list(cursor): - cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name") + "Returns a list of table names in the current database." + # Skip the sqlite_sequence system table used for autoincrement key + # generation. + cursor.execute(""" + SELECT name FROM sqlite_master + WHERE type='table' AND NOT name='sqlite_sequence' + ORDER BY name""") return [row[0] for row in cursor.fetchall()] def get_table_description(cursor, table_name): - cursor.execute("PRAGMA table_info(%s)" % quote_name(table_name)) - return [(row[1], row[2], None, None) for row in cursor.fetchall()] + "Returns a description of the table, with the DB-API cursor.description interface." + return [(info['name'], info['type'], None, None, None, None, + info['null_ok']) for info in _table_info(cursor, table_name)] def get_relations(cursor, table_name): raise NotImplementedError def get_indexes(cursor, table_name): - raise NotImplementedError + """ + Returns a dictionary of fieldname -> infodict for the given table, + where each infodict is in the format: + {'primary_key': boolean representing whether it's the primary key, + 'unique': boolean representing whether it's a unique index} + """ + indexes = {} + for info in _table_info(cursor, table_name): + indexes[info['name']] = {'primary_key': info['pk'] != 0, + 'unique': False} + cursor.execute('PRAGMA index_list(%s)' % quote_name(table_name)) + # seq, name, unique + for index, unique in [(field[1], field[2]) for field in cursor.fetchall()]: + if not unique: + continue + cursor.execute('PRAGMA index_info(%s)' % quote_name(index)) + info = cursor.fetchall() + # Skip indexes across multiple fields + if len(info) != 1: + continue + name = info[0][2] # seqno, cid, name + indexes[name]['unique'] = True + return indexes + +def _table_info(cursor, name): + cursor.execute('PRAGMA table_info(%s)' % quote_name(name)) + # cid, name, type, notnull, dflt_value, pk + return [{'name': field[1], + 'type': field[2], + 'null_ok': not field[3], + 'pk': field[5] # undocumented + } for field in cursor.fetchall()] # Maps SQL types to Django Field types. Some of the SQL types have multiple # entries here because SQLite allows for anything and doesn't normalize the diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 82b1238723..54df38cc7b 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -16,6 +16,18 @@ from django.utils.text import capfirst # Admin stages. ADD, CHANGE, BOTH = 1, 2, 3 +# Decorator. Takes a function that returns a tuple in this format: +# (viewname, viewargs, viewkwargs) +# Returns a function that calls urlresolvers.reverse() on that data, to return +# the URL for those parameters. +def permalink(func): + from django.core.urlresolvers import reverse + def inner(*args, **kwargs): + bits = func(*args, **kwargs) + viewname = bits[0] + return reverse(bits[0], None, *bits[1:2]) + return inner + class LazyDate(object): """ Use in limit_choices_to to compare the field to dates calculated at run time diff --git a/django/db/models/base.py b/django/db/models/base.py index de42a5790b..657236571d 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -4,12 +4,11 @@ from django.core import validators from django.core.exceptions import ObjectDoesNotExist from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist from django.db.models.fields.related import OneToOneRel, ManyToOneRel -from django.db.models.related import RelatedObject -from django.db.models.query import orderlist2sql, delete_objects +from django.db.models.query import delete_objects from django.db.models.options import Options, AdminOptions from django.db import connection, backend, transaction from django.db.models import signals -from django.db.models.loading import register_models +from django.db.models.loading import register_models, get_model from django.dispatch import dispatcher from django.utils.datastructures import SortedDict from django.utils.functional import curry @@ -44,6 +43,11 @@ class ModelBase(type): # For 'django.contrib.sites.models', this would be 'sites'. 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) + if m is not None: + return m + # Add all attributes to the class. for obj_name, obj in attrs.items(): new_class.add_to_class(obj_name, obj) @@ -60,7 +64,11 @@ class ModelBase(type): new_class._prepare() register_models(new_class._meta.app_label, new_class) - return new_class + # Because of the way imports happen (recursively), we may or may not be + # the first class for this model to register with the framework. There + # should only be one class for each model, so we must always return the + # registered version. + return get_model(new_class._meta.app_label, name, False) class Model(object): __metaclass__ = ModelBase @@ -395,10 +403,10 @@ def method_set_order(ordered_obj, self, id_list): cursor = connection.cursor() # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s" sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \ - (backend.quote_name(ordered_obj.db_table), backend.quote_name('_order'), - backend.quote_name(ordered_obj.order_with_respect_to.column), - backend.quote_name(ordered_obj.pk.column)) - rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name) + (backend.quote_name(ordered_obj._meta.db_table), backend.quote_name('_order'), + backend.quote_name(ordered_obj._meta.order_with_respect_to.column), + backend.quote_name(ordered_obj._meta.pk.column)) + rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name) cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)]) transaction.commit_unless_managed() diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index bc6042ae59..690695c75d 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -4,9 +4,9 @@ from django.conf import settings from django.core import validators from django import forms from django.core.exceptions import ObjectDoesNotExist -from django.utils.functional import curry, lazy +from django.utils.functional import curry from django.utils.text import capfirst -from django.utils.translation import gettext, gettext_lazy, ngettext +from django.utils.translation import gettext, gettext_lazy import datetime, os, time class NOT_PROVIDED: @@ -162,7 +162,7 @@ class Field(object): def get_db_prep_lookup(self, lookup_type, value): "Returns field's value prepared for database lookup." - if lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'ne', 'year', 'month', 'day', 'search'): + if lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'year', 'month', 'day', 'search'): return [value] elif lookup_type in ('range', 'in'): return value @@ -247,9 +247,9 @@ class Field(object): params['is_required'] = not self.blank and not self.primary_key and not rel # BooleanFields (CheckboxFields) are a special case. They don't take - # is_required or validator_list. + # is_required. if isinstance(self, BooleanField): - del params['validator_list'], params['is_required'] + del params['is_required'] # If this field is in a related context, check whether any other fields # in the related object have core=True. If so, add a validator -- @@ -289,8 +289,11 @@ class Field(object): if self.choices: return first_choice + list(self.choices) rel_model = self.rel.to - return first_choice + [(x._get_pk_val(), str(x)) - for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)] + if hasattr(self.rel, 'get_related_field'): + lst = [(getattr(x, self.rel.get_related_field().attname), str(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)] + else: + lst = [(x._get_pk_val(), str(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)] + return first_choice + lst def get_choices_default(self): if self.radio_admin: @@ -364,8 +367,8 @@ class BooleanField(Field): def to_python(self, value): if value in (True, False): return value - if value is 't': return True - if value is 'f': return False + if value in ('t', 'True'): return True + if value in ('f', 'False'): return False raise validators.ValidationError, gettext("This value must be either True or False.") def get_manipulator_field_objs(self): @@ -406,12 +409,15 @@ class DateField(Field): if isinstance(value, datetime.date): return value validators.isValidANSIDate(value, None) - return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3]) + try: + return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3]) + except ValueError: + raise validators.ValidationError, gettext('Enter a valid date in YYYY-MM-DD format.') def get_db_prep_lookup(self, lookup_type, value): if lookup_type == 'range': value = [str(v) for v in value] - elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'ne'): + elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte') and hasattr(value, 'strftime'): value = value.strftime('%Y-%m-%d') else: value = str(value) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 5ee1aec022..aa232c07f3 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1,4 +1,4 @@ -from django.db import backend, connection, transaction +from django.db import backend, transaction from django.db.models import signals, get_model from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class from django.db.models.related import RelatedObject @@ -23,9 +23,9 @@ def add_lookup(rel_cls, field): name = field.rel.to module = rel_cls.__module__ key = (module, name) - # Has the model already been loaded? + # Has the model already been loaded? # If so, resolve the string reference right away - model = get_model(rel_cls._meta.app_label,field.rel.to) + model = get_model(rel_cls._meta.app_label, field.rel.to, False) if model: field.rel.to = model field.do_related_class(model, rel_cls) @@ -46,7 +46,7 @@ def manipulator_valid_rel_key(f, self, field_data, all_data): "Validates that the value is a valid foreign key" klass = f.rel.to try: - klass._default_manager.get(pk=field_data) + klass._default_manager.get(**{f.rel.field_name: field_data}) except klass.DoesNotExist: raise validators.ValidationError, _("Please enter a valid %s.") % f.verbose_name @@ -78,6 +78,32 @@ class RelatedField(object): related = RelatedObject(other, cls, self) self.contribute_to_related_class(other, related) + def get_db_prep_lookup(self, lookup_type, value): + # If we are doing a lookup on a Related Field, we must be + # comparing object instances. The value should be the PK of value, + # not value itself. + def pk_trace(value): + # Value may be a primary key, or an object held in a relation. + # If it is an object, then we need to get the primary key value for + # that object. In certain conditions (especially one-to-one relations), + # the primary key may itself be an object - so we need to keep drilling + # down until we hit a value that can be used for a comparison. + v = value + try: + while True: + v = getattr(v, v._meta.pk.name) + except AttributeError: + pass + return v + + if lookup_type == 'exact': + return [pk_trace(value)] + if lookup_type == 'in': + return [pk_trace(v) for v in value] + elif lookup_type == 'isnull': + return [] + raise TypeError, "Related Field has invalid lookup: %s" % lookup_type + def _get_related_query_name(self, opts): # This method defines the name that can be used to identify this related object # in a table-spanning query. It uses the lower-cased object_name by default, diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 4eafc22fb4..ece41ed6e4 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -2,6 +2,8 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured +import sys +import os __all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models') @@ -10,7 +12,10 @@ _app_list = [] # Cache of installed apps. _app_models = {} # Dictionary of models against app label # Each value is a dictionary of model name: model class # Applabel and Model entry exists in cache when individual model is loaded. -_loaded = False # Has the contents of settings.INSTALLED_APPS been loaded? +_app_errors = {} # Dictionary of errors that were experienced when loading the INSTALLED_APPS + # Key is the app_name of the model, value is the exception that was raised + # during model loading. +_loaded = False # Has the contents of settings.INSTALLED_APPS been loaded? # i.e., has get_apps() been called? def get_apps(): @@ -22,28 +27,40 @@ def get_apps(): for app_name in settings.INSTALLED_APPS: try: load_app(app_name) - except ImportError: - pass # Assume this app doesn't have a models.py in it. - # GOTCHA: It may have a models.py that raises ImportError. - except AttributeError: - pass # This app doesn't have a models.py in it. + except Exception, e: + # Problem importing the app + _app_errors[app_name] = e return _app_list -def get_app(app_label): - "Returns the module containing the models for the given app_label." +def get_app(app_label, emptyOK = False): + "Returns the module containing the models for the given app_label. If the app has no models in it and 'emptyOK' is True, returns None." get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish. for app_name in settings.INSTALLED_APPS: if app_label == app_name.split('.')[-1]: - return load_app(app_name) + mod = load_app(app_name) + if mod is None: + if emptyOK: + return None + else: + return mod raise ImproperlyConfigured, "App with label %s could not be found" % app_label def load_app(app_name): "Loads the app with the provided fully qualified name, and returns the model module." + global _app_list mod = __import__(app_name, '', '', ['models']) + if not hasattr(mod, 'models'): + return None if mod.models not in _app_list: _app_list.append(mod.models) return mod.models - + +def get_app_errors(): + "Returns the map of known problems with the INSTALLED_APPS" + global _app_errors + get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish. + return _app_errors + def get_models(app_mod=None): """ Given a module containing models, returns a list of the models. Otherwise @@ -58,12 +75,15 @@ def get_models(app_mod=None): model_list.extend(get_models(app_mod)) return model_list -def get_model(app_label, model_name): +def get_model(app_label, model_name, seed_cache = True): """ - Returns the model matching the given app_label and case-insensitive model_name. + Returns the model matching the given app_label and case-insensitive + model_name. + Returns None if no model is found. """ - get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish. + if seed_cache: + get_apps() try: model_dict = _app_models[app_label] except KeyError: @@ -83,4 +103,14 @@ def register_models(app_label, *models): # in the _app_models dictionary model_name = model._meta.object_name.lower() model_dict = _app_models.setdefault(app_label, {}) + if model_dict.has_key(model_name): + # The same model may be imported via different paths (e.g. + # appname.models and project.appname.models). We use the source + # filename as a means to detect identity. + fname1 = os.path.abspath(sys.modules[model.__module__].__file__) + fname2 = os.path.abspath(sys.modules[model_dict[model_name].__module__].__file__) + # Since the filename extension could be .py the first time and .pyc + # or .pyo the second time, ignore the extension when comparing. + if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]: + continue model_dict[model_name] = model diff --git a/django/db/models/manager.py b/django/db/models/manager.py index f679c5492c..6005874516 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -1,10 +1,7 @@ -from django.utils.functional import curry -from django.db import backend, connection from django.db.models.query import QuerySet from django.dispatch import dispatcher from django.db.models import signals from django.db.models.fields import FieldDoesNotExist -from django.utils.datastructures import SortedDict # Size of each "chunk" for get_iterator calls. # Larger values are slightly faster at the expense of more storage space. @@ -69,8 +66,11 @@ class Manager(object): def get(self, *args, **kwargs): return self.get_query_set().get(*args, **kwargs) - def get_or_create(self, *args, **kwargs): - return self.get_query_set().get_or_create(*args, **kwargs) + def get_or_create(self, **kwargs): + return self.get_query_set().get_or_create(**kwargs) + + def create(self, **kwargs): + return self.get_query_set().create(**kwargs) def filter(self, *args, **kwargs): return self.get_query_set().filter(*args, **kwargs) diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py index 454c318e5d..69e13dd160 100644 --- a/django/db/models/manipulators.py +++ b/django/db/models/manipulators.py @@ -5,7 +5,7 @@ from django.db.models.fields import FileField, AutoField from django.dispatch import dispatcher from django.db.models import signals from django.utils.functional import curry -from django.utils.datastructures import DotExpandedDict, MultiValueDict +from django.utils.datastructures import DotExpandedDict from django.utils.text import capfirst import types @@ -76,7 +76,7 @@ class AutomaticManipulator(forms.Manipulator): # Add field for ordering. if self.change and self.opts.get_ordered_objects(): - self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_")) + self.fields.append(forms.CommaSeparatedIntegerField(field_name="order_")) def save(self, new_data): # TODO: big cleanup when core fields go -> use recursive manipulators. diff --git a/django/db/models/options.py b/django/db/models/options.py index f8149bdf5c..ff0d112d16 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -87,7 +87,10 @@ class Options(object): def __repr__(self): return '<Options for %s>' % self.object_name - + + def __str__(self): + return "%s.%s" % (self.app_label, self.module_name) + def get_field(self, name, many_to_many=True): "Returns the requested field by name. Raises FieldDoesNotExist on error." to_search = many_to_many and (self.fields + self.many_to_many) or self.fields @@ -196,12 +199,13 @@ class Options(object): return self._field_types[field_type] class AdminOptions(object): - def __init__(self, fields=None, js=None, list_display=None, list_filter=None, + def __init__(self, fields=None, js=None, list_display=None, list_display_links=None, list_filter=None, date_hierarchy=None, save_as=False, ordering=None, search_fields=None, save_on_top=False, list_select_related=False, manager=None, list_per_page=100): self.fields = fields self.js = js or [] self.list_display = list_display or ['__str__'] + self.list_display_links = list_display_links or [] self.list_filter = list_filter or [] self.date_hierarchy = date_hierarchy self.save_as, self.ordering = save_as, ordering diff --git a/django/db/models/query.py b/django/db/models/query.py index e826efa779..0b85c3f515 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -10,8 +10,17 @@ import re if not hasattr(__builtins__, 'set'): from sets import Set as set +# The string constant used to separate query parts LOOKUP_SEPARATOR = '__' +# The list of valid query types +QUERY_TERMS = ( + 'exact', 'iexact', 'contains', 'icontains', + 'gt', 'gte', 'lt', 'lte', 'in', + 'startswith', 'istartswith', 'endswith', 'iendswith', + 'range', 'year', 'month', 'day', 'isnull', 'search', +) + # Size of each "chunk" for get_iterator calls. # Larger values are slightly faster at the expense of more storage space. GET_ITERATOR_CHUNK_SIZE = 100 @@ -76,8 +85,8 @@ class QuerySet(object): self._where = [] # List of extra WHERE clauses to use. self._params = [] # List of params to use for extra WHERE clauses. self._tables = [] # List of extra tables to use. - self._offset = None # OFFSET clause - self._limit = None # LIMIT clause + self._offset = None # OFFSET clause. + self._limit = None # LIMIT clause. self._result_cache = None ######################## @@ -205,6 +214,15 @@ class QuerySet(object): assert len(obj_list) == 1, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.model._meta.object_name, len(obj_list), kwargs) return obj_list[0] + def create(self, **kwargs): + """ + Create a new object with the given kwargs, saving it to the database + and returning the created object. + """ + obj = self.model(**kwargs) + obj.save() + return obj + def get_or_create(self, **kwargs): """ Looks up an object with the given kwargs, creating one if necessary. @@ -423,8 +441,7 @@ class QuerySet(object): params = self._params[:] # Convert self._filters into SQL. - tables2, joins2, where2, params2 = self._filters.get_sql(opts) - tables.extend(tables2) + joins2, where2, params2 = self._filters.get_sql(opts) joins.update(joins2) where.extend(where2) params.extend(params2) @@ -552,16 +569,15 @@ class QOperator(object): self.args = args def get_sql(self, opts): - tables, joins, where, params = [], SortedDict(), [], [] + joins, where, params = SortedDict(), [], [] for val in self.args: - tables2, joins2, where2, params2 = val.get_sql(opts) - tables.extend(tables2) + joins2, where2, params2 = val.get_sql(opts) joins.update(joins2) where.extend(where2) params.extend(params2) if where: - return tables, joins, ['(%s)' % self.operator.join(where)], params - return tables, joins, [], params + return joins, ['(%s)' % self.operator.join(where)], params + return joins, [], params class QAnd(QOperator): "Encapsulates a combined query that uses 'AND'." @@ -612,9 +628,9 @@ class QNot(Q): self.q = q def get_sql(self, opts): - tables, joins, where, params = self.q.get_sql(opts) + joins, where, params = self.q.get_sql(opts) where2 = ['(NOT (%s))' % " AND ".join(where)] - return tables, joins, where2, params + return joins, where2, params def get_where_clause(lookup_type, table_prefix, field_name, value): if table_prefix.endswith('.'): @@ -649,27 +665,28 @@ def get_cached_row(klass, row, index_start): def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen): """ Helper function that recursively populates the select, tables and where (in - place) for fill-cache queries. + place) for select_related queries. """ + qn = backend.quote_name for f in opts.fields: if f.rel and not f.null: db_table = f.rel.to._meta.db_table if db_table not in cache_tables_seen: - tables.append(backend.quote_name(db_table)) + tables.append(qn(db_table)) else: # The table was already seen, so give it a table alias. new_prefix = '%s%s' % (db_table, len(cache_tables_seen)) - tables.append('%s %s' % (backend.quote_name(db_table), backend.quote_name(new_prefix))) + tables.append('%s %s' % (qn(db_table), qn(new_prefix))) db_table = new_prefix cache_tables_seen.append(db_table) where.append('%s.%s = %s.%s' % \ - (backend.quote_name(old_prefix), backend.quote_name(f.column), - backend.quote_name(db_table), backend.quote_name(f.rel.get_related_field().column))) - select.extend(['%s.%s' % (backend.quote_name(db_table), backend.quote_name(f2.column)) for f2 in f.rel.to._meta.fields]) + (qn(old_prefix), qn(f.column), qn(db_table), qn(f.rel.get_related_field().column))) + select.extend(['%s.%s' % (qn(db_table), qn(f2.column)) for f2 in f.rel.to._meta.fields]) fill_table_cache(f.rel.to._meta, select, tables, where, db_table, cache_tables_seen) def parse_lookup(kwarg_items, opts): # Helper function that handles converting API kwargs # (e.g. "name__exact": "tom") to SQL. + # Returns a tuple of (tables, joins, where, params). # 'joins' is a sorted dictionary describing the tables that must be joined # to complete the query. The dictionary is sorted because creation order @@ -687,38 +704,38 @@ def parse_lookup(kwarg_items, opts): # At present, this method only every returns INNER JOINs; the option is # there for others to implement custom Q()s, etc that return other join # types. - tables, joins, where, params = [], SortedDict(), [], [] + joins, where, params = SortedDict(), [], [] for kwarg, value in kwarg_items: if value is not None: path = kwarg.split(LOOKUP_SEPARATOR) # Extract the last elements of the kwarg. - # The very-last is the clause (equals, like, etc). - # The second-last is the table column on which the clause is + # The very-last is the lookup_type (equals, like, etc). + # The second-last is the table column on which the lookup_type is # to be performed. # The exceptions to this are: # 1) "pk", which is an implicit id__exact; - # if we find "pk", make the clause "exact', and insert + # if we find "pk", make the lookup_type "exact', and insert # a dummy name of None, which we will replace when # we know which table column to grab as the primary key. - # 2) If there is only one part, assume it to be an __exact - clause = path.pop() - if clause == 'pk': - clause = 'exact' + # 2) If there is only one part, or the last part is not a query + # term, assume that the query is an __exact + lookup_type = path.pop() + if lookup_type == 'pk': + lookup_type = 'exact' path.append(None) - elif len(path) == 0: - path.append(clause) - clause = 'exact' + elif len(path) == 0 or lookup_type not in QUERY_TERMS: + path.append(lookup_type) + lookup_type = 'exact' if len(path) < 1: raise TypeError, "Cannot parse keyword query %r" % kwarg - tables2, joins2, where2, params2 = lookup_inner(path, clause, value, opts, opts.db_table, None) - tables.extend(tables2) + joins2, where2, params2 = lookup_inner(path, lookup_type, value, opts, opts.db_table, None) joins.update(joins2) where.extend(where2) params.extend(params2) - return tables, joins, where, params + return joins, where, params class FieldFound(Exception): "Exception used to short circuit field-finding operations." @@ -737,8 +754,9 @@ def find_field(name, field_list, related_query): return None return matches[0] -def lookup_inner(path, clause, value, opts, table, column): - tables, joins, where, params = [], SortedDict(), [], [] +def lookup_inner(path, lookup_type, value, opts, table, column): + qn = backend.quote_name + joins, where, params = SortedDict(), [], [] current_opts = opts current_table = table current_column = column @@ -756,7 +774,7 @@ def lookup_inner(path, clause, value, opts, table, column): # Does the name belong to a defined many-to-many field? field = find_field(name, current_opts.many_to_many, False) if field: - new_table = current_table + LOOKUP_SEPARATOR + name + new_table = current_table + '__' + name new_opts = field.rel.to._meta new_column = new_opts.pk.column @@ -773,7 +791,7 @@ def lookup_inner(path, clause, value, opts, table, column): # Does the name belong to a reverse defined many-to-many field? field = find_field(name, current_opts.get_all_related_many_to_many_objects(), True) if field: - new_table = current_table + LOOKUP_SEPARATOR + name + new_table = current_table + '__' + name new_opts = field.opts new_column = new_opts.pk.column @@ -790,7 +808,7 @@ def lookup_inner(path, clause, value, opts, table, column): # Does the name belong to a one-to-many field? field = find_field(name, current_opts.get_all_related_objects(), True) if field: - new_table = table + LOOKUP_SEPARATOR + name + new_table = table + '__' + name new_opts = field.opts new_column = field.field.column join_column = opts.pk.column @@ -804,7 +822,7 @@ def lookup_inner(path, clause, value, opts, table, column): field = find_field(name, current_opts.fields, False) if field: if field.rel: # One-to-One/Many-to-one field - new_table = current_table + LOOKUP_SEPARATOR + name + new_table = current_table + '__' + name new_opts = field.rel.to._meta new_column = new_opts.pk.column join_column = field.column @@ -813,72 +831,85 @@ def lookup_inner(path, clause, value, opts, table, column): except FieldFound: # Match found, loop has been shortcut. pass - except: # Any other exception; rethrow - raise else: # No match found. raise TypeError, "Cannot resolve keyword '%s' into field" % name - # Check to see if an intermediate join is required between current_table + # Check whether an intermediate join is required between current_table # and new_table. if intermediate_table: - joins[backend.quote_name(current_table)] = ( - backend.quote_name(intermediate_table), - "LEFT OUTER JOIN", - "%s.%s = %s.%s" % \ - (backend.quote_name(table), - backend.quote_name(current_opts.pk.column), - backend.quote_name(current_table), - backend.quote_name(intermediate_column)) + joins[qn(current_table)] = ( + qn(intermediate_table), "LEFT OUTER JOIN", + "%s.%s = %s.%s" % (qn(table), qn(current_opts.pk.column), qn(current_table), qn(intermediate_column)) ) if path: + # There are elements left in the path. More joins are required. if len(path) == 1 and path[0] in (new_opts.pk.name, None) \ - and clause in ('exact', 'isnull') and not join_required: - # If the last name query is for a key, and the search is for - # isnull/exact, then the current (for N-1) or intermediate - # (for N-N) table can be used for the search - no need to join an - # extra table just to check the primary key. + and lookup_type in ('exact', 'isnull') and not join_required: + # If the next and final name query is for a primary key, + # and the search is for isnull/exact, then the current + # (for N-1) or intermediate (for N-N) table can be used + # for the search. No need to join an extra table just + # to check the primary key. new_table = current_table else: # There are 1 or more name queries pending, and we have ruled out # any shortcuts; therefore, a join is required. - joins[backend.quote_name(new_table)] = ( - backend.quote_name(new_opts.db_table), - "INNER JOIN", - "%s.%s = %s.%s" % - (backend.quote_name(current_table), - backend.quote_name(join_column), - backend.quote_name(new_table), - backend.quote_name(new_column)) + joins[qn(new_table)] = ( + qn(new_opts.db_table), "INNER JOIN", + "%s.%s = %s.%s" % (qn(current_table), qn(join_column), qn(new_table), qn(new_column)) ) # If we have made the join, we don't need to tell subsequent # recursive calls about the column name we joined on. join_column = None # There are name queries remaining. Recurse deeper. - tables2, joins2, where2, params2 = lookup_inner(path, clause, value, new_opts, new_table, join_column) + joins2, where2, params2 = lookup_inner(path, lookup_type, value, new_opts, new_table, join_column) - tables.extend(tables2) joins.update(joins2) where.extend(where2) params.extend(params2) else: - # Evaluate clause on current table. - if name in (current_opts.pk.name, None) and clause in ('exact', 'isnull') and current_column: - # If this is an exact/isnull key search, and the last pass - # found/introduced a current/intermediate table that we can use to - # optimize the query, then use that column name. + # No elements left in path. Current element is the element on which + # the search is being performed. + + if join_required: + # Last query term is a RelatedObject + if field.field.rel.multiple: + # RelatedObject is from a 1-N relation. + # Join is required; query operates on joined table. + column = new_opts.pk.name + joins[qn(new_table)] = ( + qn(new_opts.db_table), "INNER JOIN", + "%s.%s = %s.%s" % (qn(current_table), qn(join_column), qn(new_table), qn(new_column)) + ) + current_table = new_table + else: + # RelatedObject is from a 1-1 relation, + # No need to join; get the pk value from the related object, + # and compare using that. + column = current_opts.pk.name + elif intermediate_table: + # Last query term is a related object from an N-N relation. + # Join from intermediate table is sufficient. + column = join_column + elif name == current_opts.pk.name and lookup_type in ('exact', 'isnull') and current_column: + # Last query term is for a primary key. If previous iterations + # introduced a current/intermediate table that can be used to + # optimize the query, then use that table and column name. column = current_column else: + # Last query term was a normal field. column = field.column - where.append(get_where_clause(clause, current_table + '.', column, value)) - params.extend(field.get_db_prep_lookup(clause, value)) + where.append(get_where_clause(lookup_type, current_table + '.', column, value)) + params.extend(field.get_db_prep_lookup(lookup_type, value)) - return tables, joins, where, params + return joins, where, params def delete_objects(seen_objs): "Iterate through a list of seen classes, and remove any instances that are referred to" + qn = backend.quote_name ordered_classes = seen_objs.keys() ordered_classes.reverse() @@ -896,24 +927,21 @@ def delete_objects(seen_objs): for related in cls._meta.get_all_related_many_to_many_objects(): for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ - (backend.quote_name(related.field.m2m_db_table()), - backend.quote_name(related.field.m2m_reverse_name()), + (qn(related.field.m2m_db_table()), + qn(related.field.m2m_reverse_name()), ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) for f in cls._meta.many_to_many: for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ - (backend.quote_name(f.m2m_db_table()), - backend.quote_name(f.m2m_column_name()), - ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), + (qn(f.m2m_db_table()), qn(f.m2m_column_name()), + ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) for field in cls._meta.fields: if field.rel and field.null and field.rel.to in seen_objs: for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): cursor.execute("UPDATE %s SET %s=NULL WHERE %s IN (%s)" % \ - (backend.quote_name(cls._meta.db_table), - backend.quote_name(field.column), - backend.quote_name(cls._meta.pk.column), + (qn(cls._meta.db_table), qn(field.column), qn(cls._meta.pk.column), ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) @@ -923,9 +951,8 @@ def delete_objects(seen_objs): pk_list = [pk for pk,instance in seen_objs[cls]] for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ - (backend.quote_name(cls._meta.db_table), - backend.quote_name(cls._meta.pk.column), - ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), + (qn(cls._meta.db_table), qn(cls._meta.pk.column), + ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) # Last cleanup; set NULLs where there once was a reference to the object, diff --git a/django/db/models/related.py b/django/db/models/related.py index 4ab8cde5e7..ee3b916cf4 100644 --- a/django/db/models/related.py +++ b/django/db/models/related.py @@ -70,6 +70,10 @@ class RelatedObject(object): else: return [None] * self.field.rel.num_in_admin + def get_db_prep_lookup(self, lookup_type, value): + # Defer to the actual field definition for db prep + return self.field.get_db_prep_lookup(lookup_type, value) + def editable_fields(self): "Get the fields in this class that should be edited inline." return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field] diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index d93f696685..1a617b5946 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -6,24 +6,24 @@ system. Module attributes of note: - Any -- Singleton used to signal either "Any Sender" or - "Any Signal". See documentation of the _Any class. - Anonymous -- Singleton used to signal "Anonymous Sender" - See documentation of the _Anonymous class. + Any -- Singleton used to signal either "Any Sender" or + "Any Signal". See documentation of the _Any class. + Anonymous -- Singleton used to signal "Anonymous Sender" + See documentation of the _Anonymous class. Internal attributes: - WEAKREF_TYPES -- tuple of types/classes which represent - weak references to receivers, and thus must be de- - referenced on retrieval to retrieve the callable - object - connections -- { senderkey (id) : { signal : [receivers...]}} - senders -- { senderkey (id) : weakref(sender) } - used for cleaning up sender references on sender - deletion - sendersBack -- { receiverkey (id) : [senderkey (id)...] } - used for cleaning up receiver references on receiver - deletion, (considerably speeds up the cleanup process - vs. the original code.) + WEAKREF_TYPES -- tuple of types/classes which represent + weak references to receivers, and thus must be de- + referenced on retrieval to retrieve the callable + object + connections -- { senderkey (id) : { signal : [receivers...]}} + senders -- { senderkey (id) : weakref(sender) } + used for cleaning up sender references on sender + deletion + sendersBack -- { receiverkey (id) : [senderkey (id)...] } + used for cleaning up receiver references on receiver + deletion, (considerably speeds up the cleanup process + vs. the original code.) """ from __future__ import generators import types, weakref @@ -34,44 +34,44 @@ __cvsid__ = "$Id: dispatcher.py,v 1.9 2005/09/17 04:55:57 mcfletch Exp $" __version__ = "$Revision: 1.9 $"[11:-2] try: - True + True except NameError: - True = 1==1 - False = 1==0 + True = 1==1 + False = 1==0 class _Parameter: - """Used to represent default parameter values.""" - def __repr__(self): - return self.__class__.__name__ + """Used to represent default parameter values.""" + def __repr__(self): + return self.__class__.__name__ class _Any(_Parameter): - """Singleton used to signal either "Any Sender" or "Any Signal" + """Singleton used to signal either "Any Sender" or "Any Signal" - The Any object can be used with connect, disconnect, - send, or sendExact to signal that the parameter given - Any should react to all senders/signals, not just - a particular sender/signal. - """ + The Any object can be used with connect, disconnect, + send, or sendExact to signal that the parameter given + Any should react to all senders/signals, not just + a particular sender/signal. + """ Any = _Any() class _Anonymous(_Parameter): - """Singleton used to signal "Anonymous Sender" + """Singleton used to signal "Anonymous Sender" - The Anonymous object is used to signal that the sender - of a message is not specified (as distinct from being - "any sender"). Registering callbacks for Anonymous - will only receive messages sent without senders. Sending - with anonymous will only send messages to those receivers - registered for Any or Anonymous. + The Anonymous object is used to signal that the sender + of a message is not specified (as distinct from being + "any sender"). Registering callbacks for Anonymous + will only receive messages sent without senders. Sending + with anonymous will only send messages to those receivers + registered for Any or Anonymous. - Note: - The default sender for connect is Any, while the - default sender for send is Anonymous. This has - the effect that if you do not specify any senders - in either function then all messages are routed - as though there was a single sender (Anonymous) - being used everywhere. - """ + Note: + The default sender for connect is Any, while the + default sender for send is Anonymous. This has + the effect that if you do not specify any senders + in either function then all messages are routed + as though there was a single sender (Anonymous) + being used everywhere. + """ Anonymous = _Anonymous() WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) @@ -82,416 +82,416 @@ sendersBack = {} def connect(receiver, signal=Any, sender=Any, weak=True): - """Connect receiver to sender for signal + """Connect receiver to sender for signal - receiver -- a callable Python object which is to receive - messages/signals/events. Receivers must be hashable - objects. + receiver -- a callable Python object which is to receive + messages/signals/events. Receivers must be hashable + objects. - if weak is True, then receiver must be weak-referencable - (more precisely saferef.safeRef() must be able to create - a reference to the receiver). - - Receivers are fairly flexible in their specification, - as the machinery in the robustApply module takes care - of most of the details regarding figuring out appropriate - subsets of the sent arguments to apply to a given - receiver. + if weak is True, then receiver must be weak-referencable + (more precisely saferef.safeRef() must be able to create + a reference to the receiver). + + Receivers are fairly flexible in their specification, + as the machinery in the robustApply module takes care + of most of the details regarding figuring out appropriate + subsets of the sent arguments to apply to a given + receiver. - Note: - if receiver is itself a weak reference (a callable), - it will be de-referenced by the system's machinery, - so *generally* weak references are not suitable as - receivers, though some use might be found for the - facility whereby a higher-level library passes in - pre-weakrefed receiver references. + Note: + if receiver is itself a weak reference (a callable), + it will be de-referenced by the system's machinery, + so *generally* weak references are not suitable as + receivers, though some use might be found for the + facility whereby a higher-level library passes in + pre-weakrefed receiver references. - signal -- the signal to which the receiver should respond - - if Any, receiver will receive any signal from the - indicated sender (which might also be Any, but is not - necessarily Any). - - Otherwise must be a hashable Python object other than - None (DispatcherError raised on None). - - sender -- the sender to which the receiver should respond - - if Any, receiver will receive the indicated signals - from any sender. - - if Anonymous, receiver will only receive indicated - signals from send/sendExact which do not specify a - sender, or specify Anonymous explicitly as the sender. + signal -- the signal to which the receiver should respond + + if Any, receiver will receive any signal from the + indicated sender (which might also be Any, but is not + necessarily Any). + + Otherwise must be a hashable Python object other than + None (DispatcherError raised on None). + + sender -- the sender to which the receiver should respond + + if Any, receiver will receive the indicated signals + from any sender. + + if Anonymous, receiver will only receive indicated + signals from send/sendExact which do not specify a + sender, or specify Anonymous explicitly as the sender. - Otherwise can be any python object. - - weak -- whether to use weak references to the receiver - By default, the module will attempt to use weak - references to the receiver objects. If this parameter - is false, then strong references will be used. + Otherwise can be any python object. + + weak -- whether to use weak references to the receiver + By default, the module will attempt to use weak + references to the receiver objects. If this parameter + is false, then strong references will be used. - returns None, may raise DispatcherTypeError - """ - if signal is None: - raise errors.DispatcherTypeError( - 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender) - ) - if weak: - receiver = saferef.safeRef(receiver, onDelete=_removeReceiver) - senderkey = id(sender) - if connections.has_key(senderkey): - signals = connections[senderkey] - else: - connections[senderkey] = signals = {} - # Keep track of senders for cleanup. - # Is Anonymous something we want to clean up? - if sender not in (None, Anonymous, Any): - def remove(object, senderkey=senderkey): - _removeSender(senderkey=senderkey) - # Skip objects that can not be weakly referenced, which means - # they won't be automatically cleaned up, but that's too bad. - try: - weakSender = weakref.ref(sender, remove) - senders[senderkey] = weakSender - except: - pass - - receiverID = id(receiver) - # get current set, remove any current references to - # this receiver in the set, including back-references - if signals.has_key(signal): - receivers = signals[signal] - _removeOldBackRefs(senderkey, signal, receiver, receivers) - else: - receivers = signals[signal] = [] - try: - current = sendersBack.get( receiverID ) - if current is None: - sendersBack[ receiverID ] = current = [] - if senderkey not in current: - current.append(senderkey) - except: - pass + returns None, may raise DispatcherTypeError + """ + if signal is None: + raise errors.DispatcherTypeError( + 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender) + ) + if weak: + receiver = saferef.safeRef(receiver, onDelete=_removeReceiver) + senderkey = id(sender) + if connections.has_key(senderkey): + signals = connections[senderkey] + else: + connections[senderkey] = signals = {} + # Keep track of senders for cleanup. + # Is Anonymous something we want to clean up? + if sender not in (None, Anonymous, Any): + def remove(object, senderkey=senderkey): + _removeSender(senderkey=senderkey) + # Skip objects that can not be weakly referenced, which means + # they won't be automatically cleaned up, but that's too bad. + try: + weakSender = weakref.ref(sender, remove) + senders[senderkey] = weakSender + except: + pass + + receiverID = id(receiver) + # get current set, remove any current references to + # this receiver in the set, including back-references + if signals.has_key(signal): + receivers = signals[signal] + _removeOldBackRefs(senderkey, signal, receiver, receivers) + else: + receivers = signals[signal] = [] + try: + current = sendersBack.get( receiverID ) + if current is None: + sendersBack[ receiverID ] = current = [] + if senderkey not in current: + current.append(senderkey) + except: + pass - receivers.append(receiver) + receivers.append(receiver) def disconnect(receiver, signal=Any, sender=Any, weak=True): - """Disconnect receiver from sender for signal + """Disconnect receiver from sender for signal - receiver -- the registered receiver to disconnect - signal -- the registered signal to disconnect - sender -- the registered sender to disconnect - weak -- the weakref state to disconnect + receiver -- the registered receiver to disconnect + signal -- the registered signal to disconnect + sender -- the registered sender to disconnect + weak -- the weakref state to disconnect - disconnect reverses the process of connect, - the semantics for the individual elements are - logically equivalent to a tuple of - (receiver, signal, sender, weak) used as a key - to be deleted from the internal routing tables. - (The actual process is slightly more complex - but the semantics are basically the same). + disconnect reverses the process of connect, + the semantics for the individual elements are + logically equivalent to a tuple of + (receiver, signal, sender, weak) used as a key + to be deleted from the internal routing tables. + (The actual process is slightly more complex + but the semantics are basically the same). - Note: - Using disconnect is not required to cleanup - routing when an object is deleted, the framework - will remove routes for deleted objects - automatically. It's only necessary to disconnect - if you want to stop routing to a live object. - - returns None, may raise DispatcherTypeError or - DispatcherKeyError - """ - if signal is None: - raise errors.DispatcherTypeError( - 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender) - ) - if weak: receiver = saferef.safeRef(receiver) - senderkey = id(sender) - try: - signals = connections[senderkey] - receivers = signals[signal] - except KeyError: - raise errors.DispatcherKeyError( - """No receivers found for signal %r from sender %r""" %( - signal, - sender - ) - ) - try: - # also removes from receivers - _removeOldBackRefs(senderkey, signal, receiver, receivers) - except ValueError: - raise errors.DispatcherKeyError( - """No connection to receiver %s for signal %s from sender %s""" %( - receiver, - signal, - sender - ) - ) - _cleanupConnections(senderkey, signal) + Note: + Using disconnect is not required to cleanup + routing when an object is deleted, the framework + will remove routes for deleted objects + automatically. It's only necessary to disconnect + if you want to stop routing to a live object. + + returns None, may raise DispatcherTypeError or + DispatcherKeyError + """ + if signal is None: + raise errors.DispatcherTypeError( + 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender) + ) + if weak: receiver = saferef.safeRef(receiver) + senderkey = id(sender) + try: + signals = connections[senderkey] + receivers = signals[signal] + except KeyError: + raise errors.DispatcherKeyError( + """No receivers found for signal %r from sender %r""" %( + signal, + sender + ) + ) + try: + # also removes from receivers + _removeOldBackRefs(senderkey, signal, receiver, receivers) + except ValueError: + raise errors.DispatcherKeyError( + """No connection to receiver %s for signal %s from sender %s""" %( + receiver, + signal, + sender + ) + ) + _cleanupConnections(senderkey, signal) def getReceivers( sender = Any, signal = Any ): - """Get list of receivers from global tables + """Get list of receivers from global tables - This utility function allows you to retrieve the - raw list of receivers from the connections table - for the given sender and signal pair. + This utility function allows you to retrieve the + raw list of receivers from the connections table + for the given sender and signal pair. - Note: - there is no guarantee that this is the actual list - stored in the connections table, so the value - should be treated as a simple iterable/truth value - rather than, for instance a list to which you - might append new records. + Note: + there is no guarantee that this is the actual list + stored in the connections table, so the value + should be treated as a simple iterable/truth value + rather than, for instance a list to which you + might append new records. - Normally you would use liveReceivers( getReceivers( ...)) - to retrieve the actual receiver objects as an iterable - object. - """ - try: - return connections[id(sender)][signal] - except KeyError: - return [] + Normally you would use liveReceivers( getReceivers( ...)) + to retrieve the actual receiver objects as an iterable + object. + """ + try: + return connections[id(sender)][signal] + except KeyError: + return [] def liveReceivers(receivers): - """Filter sequence of receivers to get resolved, live receivers + """Filter sequence of receivers to get resolved, live receivers - This is a generator which will iterate over - the passed sequence, checking for weak references - and resolving them, then returning all live - receivers. - """ - for receiver in receivers: - if isinstance( receiver, WEAKREF_TYPES): - # Dereference the weak reference. - receiver = receiver() - if receiver is not None: - yield receiver - else: - yield receiver + This is a generator which will iterate over + the passed sequence, checking for weak references + and resolving them, then returning all live + receivers. + """ + for receiver in receivers: + if isinstance( receiver, WEAKREF_TYPES): + # Dereference the weak reference. + receiver = receiver() + if receiver is not None: + yield receiver + else: + yield receiver def getAllReceivers( sender = Any, signal = Any ): - """Get list of all receivers from global tables + """Get list of all receivers from global tables - This gets all receivers which should receive - the given signal from sender, each receiver should - be produced only once by the resulting generator - """ - receivers = {} - for set in ( - # Get receivers that receive *this* signal from *this* sender. - getReceivers( sender, signal ), - # Add receivers that receive *any* signal from *this* sender. - getReceivers( sender, Any ), - # Add receivers that receive *this* signal from *any* sender. - getReceivers( Any, signal ), - # Add receivers that receive *any* signal from *any* sender. - getReceivers( Any, Any ), - ): - for receiver in set: - if receiver: # filter out dead instance-method weakrefs - try: - if not receivers.has_key( receiver ): - receivers[receiver] = 1 - yield receiver - except TypeError: - # dead weakrefs raise TypeError on hash... - pass + This gets all receivers which should receive + the given signal from sender, each receiver should + be produced only once by the resulting generator + """ + receivers = {} + for set in ( + # Get receivers that receive *this* signal from *this* sender. + getReceivers( sender, signal ), + # Add receivers that receive *any* signal from *this* sender. + getReceivers( sender, Any ), + # Add receivers that receive *this* signal from *any* sender. + getReceivers( Any, signal ), + # Add receivers that receive *any* signal from *any* sender. + getReceivers( Any, Any ), + ): + for receiver in set: + if receiver: # filter out dead instance-method weakrefs + try: + if not receivers.has_key( receiver ): + receivers[receiver] = 1 + yield receiver + except TypeError: + # dead weakrefs raise TypeError on hash... + pass def send(signal=Any, sender=Anonymous, *arguments, **named): - """Send signal from sender to all connected receivers. - - signal -- (hashable) signal value, see connect for details + """Send signal from sender to all connected receivers. + + signal -- (hashable) signal value, see connect for details - sender -- the sender of the signal - - if Any, only receivers registered for Any will receive - the message. + sender -- the sender of the signal + + if Any, only receivers registered for Any will receive + the message. - if Anonymous, only receivers registered to receive - messages from Anonymous or Any will receive the message + if Anonymous, only receivers registered to receive + messages from Anonymous or Any will receive the message - Otherwise can be any python object (normally one - registered with a connect if you actually want - something to occur). + Otherwise can be any python object (normally one + registered with a connect if you actually want + something to occur). - arguments -- positional arguments which will be passed to - *all* receivers. Note that this may raise TypeErrors - if the receivers do not allow the particular arguments. - Note also that arguments are applied before named - arguments, so they should be used with care. + arguments -- positional arguments which will be passed to + *all* receivers. Note that this may raise TypeErrors + if the receivers do not allow the particular arguments. + Note also that arguments are applied before named + arguments, so they should be used with care. - named -- named arguments which will be filtered according - to the parameters of the receivers to only provide those - acceptable to the receiver. + named -- named arguments which will be filtered according + to the parameters of the receivers to only provide those + acceptable to the receiver. - Return a list of tuple pairs [(receiver, response), ... ] + Return a list of tuple pairs [(receiver, response), ... ] - if any receiver raises an error, the error propagates back - through send, terminating the dispatch loop, so it is quite - possible to not have all receivers called if a raises an - error. - """ - # Call each receiver with whatever arguments it can accept. - # Return a list of tuple pairs [(receiver, response), ... ]. - responses = [] - for receiver in liveReceivers(getAllReceivers(sender, signal)): - response = robustapply.robustApply( - receiver, - signal=signal, - sender=sender, - *arguments, - **named - ) - responses.append((receiver, response)) - return responses + if any receiver raises an error, the error propagates back + through send, terminating the dispatch loop, so it is quite + possible to not have all receivers called if a raises an + error. + """ + # Call each receiver with whatever arguments it can accept. + # Return a list of tuple pairs [(receiver, response), ... ]. + responses = [] + for receiver in liveReceivers(getAllReceivers(sender, signal)): + response = robustapply.robustApply( + receiver, + signal=signal, + sender=sender, + *arguments, + **named + ) + responses.append((receiver, response)) + return responses def sendExact( signal=Any, sender=Anonymous, *arguments, **named ): - """Send signal only to those receivers registered for exact message + """Send signal only to those receivers registered for exact message - sendExact allows for avoiding Any/Anonymous registered - handlers, sending only to those receivers explicitly - registered for a particular signal on a particular - sender. - """ - responses = [] - for receiver in liveReceivers(getReceivers(sender, signal)): - response = robustapply.robustApply( - receiver, - signal=signal, - sender=sender, - *arguments, - **named - ) - responses.append((receiver, response)) - return responses - + sendExact allows for avoiding Any/Anonymous registered + handlers, sending only to those receivers explicitly + registered for a particular signal on a particular + sender. + """ + responses = [] + for receiver in liveReceivers(getReceivers(sender, signal)): + response = robustapply.robustApply( + receiver, + signal=signal, + sender=sender, + *arguments, + **named + ) + responses.append((receiver, response)) + return responses + def _removeReceiver(receiver): - """Remove receiver from connections.""" - if not sendersBack: - # During module cleanup the mapping will be replaced with None - return False - backKey = id(receiver) - for senderkey in sendersBack.get(backKey,()): - try: - signals = connections[senderkey].keys() - except KeyError,err: - pass - else: - for signal in signals: - try: - receivers = connections[senderkey][signal] - except KeyError: - pass - else: - try: - receivers.remove( receiver ) - except Exception, err: - pass - _cleanupConnections(senderkey, signal) - try: - del sendersBack[ backKey ] - except KeyError: - pass - + """Remove receiver from connections.""" + if not sendersBack: + # During module cleanup the mapping will be replaced with None + return False + backKey = id(receiver) + for senderkey in sendersBack.get(backKey,()): + try: + signals = connections[senderkey].keys() + except KeyError,err: + pass + else: + for signal in signals: + try: + receivers = connections[senderkey][signal] + except KeyError: + pass + else: + try: + receivers.remove( receiver ) + except Exception, err: + pass + _cleanupConnections(senderkey, signal) + try: + del sendersBack[ backKey ] + except KeyError: + pass + def _cleanupConnections(senderkey, signal): - """Delete any empty signals for senderkey. Delete senderkey if empty.""" - try: - receivers = connections[senderkey][signal] - except: - pass - else: - if not receivers: - # No more connected receivers. Therefore, remove the signal. - try: - signals = connections[senderkey] - except KeyError: - pass - else: - del signals[signal] - if not signals: - # No more signal connections. Therefore, remove the sender. - _removeSender(senderkey) + """Delete any empty signals for senderkey. Delete senderkey if empty.""" + try: + receivers = connections[senderkey][signal] + except: + pass + else: + if not receivers: + # No more connected receivers. Therefore, remove the signal. + try: + signals = connections[senderkey] + except KeyError: + pass + else: + del signals[signal] + if not signals: + # No more signal connections. Therefore, remove the sender. + _removeSender(senderkey) def _removeSender(senderkey): - """Remove senderkey from connections.""" - _removeBackrefs(senderkey) - try: - del connections[senderkey] - except KeyError: - pass - # Senderkey will only be in senders dictionary if sender - # could be weakly referenced. - try: - del senders[senderkey] - except: - pass + """Remove senderkey from connections.""" + _removeBackrefs(senderkey) + try: + del connections[senderkey] + except KeyError: + pass + # Senderkey will only be in senders dictionary if sender + # could be weakly referenced. + try: + del senders[senderkey] + except: + pass def _removeBackrefs( senderkey): - """Remove all back-references to this senderkey""" - try: - signals = connections[senderkey] - except KeyError: - signals = None - else: - items = signals.items() - def allReceivers( ): - for signal,set in items: - for item in set: - yield item - for receiver in allReceivers(): - _killBackref( receiver, senderkey ) + """Remove all back-references to this senderkey""" + try: + signals = connections[senderkey] + except KeyError: + signals = None + else: + items = signals.items() + def allReceivers( ): + for signal,set in items: + for item in set: + yield item + for receiver in allReceivers(): + _killBackref( receiver, senderkey ) def _removeOldBackRefs(senderkey, signal, receiver, receivers): - """Kill old sendersBack references from receiver + """Kill old sendersBack references from receiver - This guards against multiple registration of the same - receiver for a given signal and sender leaking memory - as old back reference records build up. + This guards against multiple registration of the same + receiver for a given signal and sender leaking memory + as old back reference records build up. - Also removes old receiver instance from receivers - """ - try: - index = receivers.index(receiver) - # need to scan back references here and remove senderkey - except ValueError: - return False - else: - oldReceiver = receivers[index] - del receivers[index] - found = 0 - signals = connections.get(signal) - if signals is not None: - for sig,recs in connections.get(signal,{}).iteritems(): - if sig != signal: - for rec in recs: - if rec is oldReceiver: - found = 1 - break - if not found: - _killBackref( oldReceiver, senderkey ) - return True - return False - - + Also removes old receiver instance from receivers + """ + try: + index = receivers.index(receiver) + # need to scan back references here and remove senderkey + except ValueError: + return False + else: + oldReceiver = receivers[index] + del receivers[index] + found = 0 + signals = connections.get(signal) + if signals is not None: + for sig,recs in connections.get(signal,{}).iteritems(): + if sig != signal: + for rec in recs: + if rec is oldReceiver: + found = 1 + break + if not found: + _killBackref( oldReceiver, senderkey ) + return True + return False + + def _killBackref( receiver, senderkey ): - """Do the actual removal of back reference from receiver to senderkey""" - receiverkey = id(receiver) - set = sendersBack.get( receiverkey, () ) - while senderkey in set: - try: - set.remove( senderkey ) - except: - break - if not set: - try: - del sendersBack[ receiverkey ] - except KeyError: - pass - return True + """Do the actual removal of back reference from receiver to senderkey""" + receiverkey = id(receiver) + set = sendersBack.get( receiverkey, () ) + while senderkey in set: + try: + set.remove( senderkey ) + except: + break + if not set: + try: + del sendersBack[ receiverkey ] + except KeyError: + pass + return True diff --git a/django/dispatch/errors.py b/django/dispatch/errors.py index a2eb32ed75..a4c924dbe9 100644 --- a/django/dispatch/errors.py +++ b/django/dispatch/errors.py @@ -2,9 +2,9 @@ """ class DispatcherError(Exception): - """Base class for all Dispatcher errors""" + """Base class for all Dispatcher errors""" class DispatcherKeyError(KeyError, DispatcherError): - """Error raised when unknown (sender,signal) set specified""" + """Error raised when unknown (sender,signal) set specified""" class DispatcherTypeError(TypeError, DispatcherError): - """Error raised when inappropriate signal-type specified (None)""" + """Error raised when inappropriate signal-type specified (None)""" diff --git a/django/dispatch/license.txt b/django/dispatch/license.txt index 2f0b6b5ef2..8ff5d28613 100644 --- a/django/dispatch/license.txt +++ b/django/dispatch/license.txt @@ -1,34 +1,34 @@ PyDispatcher License - Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - - The name of Patrick K. O'Brien, or the name of any Contributor, - may not be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - OF THE POSSIBILITY OF SUCH DAMAGE. + Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + The name of Patrick K. O'Brien, or the name of any Contributor, + may not be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/django/dispatch/robust.py b/django/dispatch/robust.py index ba19934d43..8b1590de35 100644 --- a/django/dispatch/robust.py +++ b/django/dispatch/robust.py @@ -3,55 +3,55 @@ from django.dispatch.dispatcher import Any, Anonymous, liveReceivers, getAllRece from django.dispatch.robustapply import robustApply def sendRobust( - signal=Any, - sender=Anonymous, - *arguments, **named + signal=Any, + sender=Anonymous, + *arguments, **named ): - """Send signal from sender to all connected receivers catching errors - - signal -- (hashable) signal value, see connect for details + """Send signal from sender to all connected receivers catching errors + + signal -- (hashable) signal value, see connect for details - sender -- the sender of the signal - - if Any, only receivers registered for Any will receive - the message. + sender -- the sender of the signal + + if Any, only receivers registered for Any will receive + the message. - if Anonymous, only receivers registered to receive - messages from Anonymous or Any will receive the message + if Anonymous, only receivers registered to receive + messages from Anonymous or Any will receive the message - Otherwise can be any python object (normally one - registered with a connect if you actually want - something to occur). + Otherwise can be any python object (normally one + registered with a connect if you actually want + something to occur). - arguments -- positional arguments which will be passed to - *all* receivers. Note that this may raise TypeErrors - if the receivers do not allow the particular arguments. - Note also that arguments are applied before named - arguments, so they should be used with care. + arguments -- positional arguments which will be passed to + *all* receivers. Note that this may raise TypeErrors + if the receivers do not allow the particular arguments. + Note also that arguments are applied before named + arguments, so they should be used with care. - named -- named arguments which will be filtered according - to the parameters of the receivers to only provide those - acceptable to the receiver. + named -- named arguments which will be filtered according + to the parameters of the receivers to only provide those + acceptable to the receiver. - Return a list of tuple pairs [(receiver, response), ... ] + Return a list of tuple pairs [(receiver, response), ... ] - if any receiver raises an error (specifically any subclass of Exception), - the error instance is returned as the result for that receiver. - """ - # Call each receiver with whatever arguments it can accept. - # Return a list of tuple pairs [(receiver, response), ... ]. - responses = [] - for receiver in liveReceivers(getAllReceivers(sender, signal)): - try: - response = robustApply( - receiver, - signal=signal, - sender=sender, - *arguments, - **named - ) - except Exception, err: - responses.append((receiver, err)) - else: - responses.append((receiver, response)) - return responses + if any receiver raises an error (specifically any subclass of Exception), + the error instance is returned as the result for that receiver. + """ + # Call each receiver with whatever arguments it can accept. + # Return a list of tuple pairs [(receiver, response), ... ]. + responses = [] + for receiver in liveReceivers(getAllReceivers(sender, signal)): + try: + response = robustApply( + receiver, + signal=signal, + sender=sender, + *arguments, + **named + ) + except Exception, err: + responses.append((receiver, err)) + else: + responses.append((receiver, response)) + return responses diff --git a/django/dispatch/robustapply.py b/django/dispatch/robustapply.py index 0350e60cfc..14ba2b51ac 100644 --- a/django/dispatch/robustapply.py +++ b/django/dispatch/robustapply.py @@ -7,43 +7,41 @@ those which are acceptable. """ def function( receiver ): - """Get function-like callable object for given receiver + """Get function-like callable object for given receiver - returns (function_or_method, codeObject, fromMethod) + returns (function_or_method, codeObject, fromMethod) - If fromMethod is true, then the callable already - has its first argument bound - """ - if hasattr(receiver, '__call__'): - # receiver is a class instance; assume it is callable. - # Reassign receiver to the actual method that will be called. - if hasattr( receiver.__call__, 'im_func') or hasattr( receiver.__call__, 'im_code'): - receiver = receiver.__call__ - if hasattr( receiver, 'im_func' ): - # an instance-method... - return receiver, receiver.im_func.func_code, 1 - elif not hasattr( receiver, 'func_code'): - raise ValueError('unknown reciever type %s %s'%(receiver, type(receiver))) - return receiver, receiver.func_code, 0 + If fromMethod is true, then the callable already + has its first argument bound + """ + if hasattr(receiver, '__call__'): + # receiver is a class instance; assume it is callable. + # Reassign receiver to the actual method that will be called. + if hasattr( receiver.__call__, 'im_func') or hasattr( receiver.__call__, 'im_code'): + receiver = receiver.__call__ + if hasattr( receiver, 'im_func' ): + # an instance-method... + return receiver, receiver.im_func.func_code, 1 + elif not hasattr( receiver, 'func_code'): + raise ValueError('unknown reciever type %s %s'%(receiver, type(receiver))) + return receiver, receiver.func_code, 0 def robustApply(receiver, *arguments, **named): - """Call receiver with arguments and an appropriate subset of named - """ - receiver, codeObject, startIndex = function( receiver ) - acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount] - for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]: - if named.has_key( name ): - raise TypeError( - """Argument %r specified both positionally and as a keyword for calling %r"""% ( - name, receiver, - ) - ) - if not (codeObject.co_flags & 8): - # fc does not have a **kwds type parameter, therefore - # remove unacceptable arguments. - for arg in named.keys(): - if arg not in acceptable: - del named[arg] - return receiver(*arguments, **named) - -
\ No newline at end of file + """Call receiver with arguments and an appropriate subset of named + """ + receiver, codeObject, startIndex = function( receiver ) + acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount] + for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]: + if named.has_key( name ): + raise TypeError( + """Argument %r specified both positionally and as a keyword for calling %r"""% ( + name, receiver, + ) + ) + if not (codeObject.co_flags & 8): + # fc does not have a **kwds type parameter, therefore + # remove unacceptable arguments. + for arg in named.keys(): + if arg not in acceptable: + del named[arg] + return receiver(*arguments, **named) diff --git a/django/dispatch/saferef.py b/django/dispatch/saferef.py index 6b3eda1d38..74b7a41942 100644 --- a/django/dispatch/saferef.py +++ b/django/dispatch/saferef.py @@ -2,164 +2,164 @@ import weakref, traceback def safeRef(target, onDelete = None): - """Return a *safe* weak reference to a callable target + """Return a *safe* weak reference to a callable target - target -- the object to be weakly referenced, if it's a - bound method reference, will create a BoundMethodWeakref, - otherwise creates a simple weakref. - onDelete -- if provided, will have a hard reference stored - to the callable to be called after the safe reference - goes out of scope with the reference object, (either a - weakref or a BoundMethodWeakref) as argument. - """ - if hasattr(target, 'im_self'): - if target.im_self is not None: - # Turn a bound method into a BoundMethodWeakref instance. - # Keep track of these instances for lookup by disconnect(). - assert hasattr(target, 'im_func'), """safeRef target %r has im_self, but no im_func, don't know how to create reference"""%( target,) - reference = BoundMethodWeakref( - target=target, - onDelete=onDelete - ) - return reference - if callable(onDelete): - return weakref.ref(target, onDelete) - else: - return weakref.ref( target ) + target -- the object to be weakly referenced, if it's a + bound method reference, will create a BoundMethodWeakref, + otherwise creates a simple weakref. + onDelete -- if provided, will have a hard reference stored + to the callable to be called after the safe reference + goes out of scope with the reference object, (either a + weakref or a BoundMethodWeakref) as argument. + """ + if hasattr(target, 'im_self'): + if target.im_self is not None: + # Turn a bound method into a BoundMethodWeakref instance. + # Keep track of these instances for lookup by disconnect(). + assert hasattr(target, 'im_func'), """safeRef target %r has im_self, but no im_func, don't know how to create reference"""%( target,) + reference = BoundMethodWeakref( + target=target, + onDelete=onDelete + ) + return reference + if callable(onDelete): + return weakref.ref(target, onDelete) + else: + return weakref.ref( target ) class BoundMethodWeakref(object): - """'Safe' and reusable weak references to instance methods + """'Safe' and reusable weak references to instance methods - BoundMethodWeakref objects provide a mechanism for - referencing a bound method without requiring that the - method object itself (which is normally a transient - object) is kept alive. Instead, the BoundMethodWeakref - object keeps weak references to both the object and the - function which together define the instance method. + BoundMethodWeakref objects provide a mechanism for + referencing a bound method without requiring that the + method object itself (which is normally a transient + object) is kept alive. Instead, the BoundMethodWeakref + object keeps weak references to both the object and the + function which together define the instance method. - Attributes: - key -- the identity key for the reference, calculated - by the class's calculateKey method applied to the - target instance method - deletionMethods -- sequence of callable objects taking - single argument, a reference to this object which - will be called when *either* the target object or - target function is garbage collected (i.e. when - this object becomes invalid). These are specified - as the onDelete parameters of safeRef calls. - weakSelf -- weak reference to the target object - weakFunc -- weak reference to the target function + Attributes: + key -- the identity key for the reference, calculated + by the class's calculateKey method applied to the + target instance method + deletionMethods -- sequence of callable objects taking + single argument, a reference to this object which + will be called when *either* the target object or + target function is garbage collected (i.e. when + this object becomes invalid). These are specified + as the onDelete parameters of safeRef calls. + weakSelf -- weak reference to the target object + weakFunc -- weak reference to the target function - Class Attributes: - _allInstances -- class attribute pointing to all live - BoundMethodWeakref objects indexed by the class's - calculateKey(target) method applied to the target - objects. This weak value dictionary is used to - short-circuit creation so that multiple references - to the same (object, function) pair produce the - same BoundMethodWeakref instance. + Class Attributes: + _allInstances -- class attribute pointing to all live + BoundMethodWeakref objects indexed by the class's + calculateKey(target) method applied to the target + objects. This weak value dictionary is used to + short-circuit creation so that multiple references + to the same (object, function) pair produce the + same BoundMethodWeakref instance. - """ - _allInstances = weakref.WeakValueDictionary() - def __new__( cls, target, onDelete=None, *arguments,**named ): - """Create new instance or return current instance + """ + _allInstances = weakref.WeakValueDictionary() + def __new__( cls, target, onDelete=None, *arguments,**named ): + """Create new instance or return current instance - Basically this method of construction allows us to - short-circuit creation of references to already- - referenced instance methods. The key corresponding - to the target is calculated, and if there is already - an existing reference, that is returned, with its - deletionMethods attribute updated. Otherwise the - new instance is created and registered in the table - of already-referenced methods. - """ - key = cls.calculateKey(target) - current =cls._allInstances.get(key) - if current is not None: - current.deletionMethods.append( onDelete) - return current - else: - base = super( BoundMethodWeakref, cls).__new__( cls ) - cls._allInstances[key] = base - base.__init__( target, onDelete, *arguments,**named) - return base - def __init__(self, target, onDelete=None): - """Return a weak-reference-like instance for a bound method + Basically this method of construction allows us to + short-circuit creation of references to already- + referenced instance methods. The key corresponding + to the target is calculated, and if there is already + an existing reference, that is returned, with its + deletionMethods attribute updated. Otherwise the + new instance is created and registered in the table + of already-referenced methods. + """ + key = cls.calculateKey(target) + current =cls._allInstances.get(key) + if current is not None: + current.deletionMethods.append( onDelete) + return current + else: + base = super( BoundMethodWeakref, cls).__new__( cls ) + cls._allInstances[key] = base + base.__init__( target, onDelete, *arguments,**named) + return base + def __init__(self, target, onDelete=None): + """Return a weak-reference-like instance for a bound method - target -- the instance-method target for the weak - reference, must have im_self and im_func attributes - and be reconstructable via: - target.im_func.__get__( target.im_self ) - which is true of built-in instance methods. - onDelete -- optional callback which will be called - when this weak reference ceases to be valid - (i.e. either the object or the function is garbage - collected). Should take a single argument, - which will be passed a pointer to this object. - """ - def remove(weak, self=self): - """Set self.isDead to true when method or instance is destroyed""" - methods = self.deletionMethods[:] - del self.deletionMethods[:] - try: - del self.__class__._allInstances[ self.key ] - except KeyError: - pass - for function in methods: - try: - if callable( function ): - function( self ) - except Exception, e: - try: - traceback.print_exc() - except AttributeError, err: - print '''Exception during saferef %s cleanup function %s: %s'''%( - self, function, e - ) - self.deletionMethods = [onDelete] - self.key = self.calculateKey( target ) - self.weakSelf = weakref.ref(target.im_self, remove) - self.weakFunc = weakref.ref(target.im_func, remove) - self.selfName = str(target.im_self) - self.funcName = str(target.im_func.__name__) - def calculateKey( cls, target ): - """Calculate the reference key for this reference + target -- the instance-method target for the weak + reference, must have im_self and im_func attributes + and be reconstructable via: + target.im_func.__get__( target.im_self ) + which is true of built-in instance methods. + onDelete -- optional callback which will be called + when this weak reference ceases to be valid + (i.e. either the object or the function is garbage + collected). Should take a single argument, + which will be passed a pointer to this object. + """ + def remove(weak, self=self): + """Set self.isDead to true when method or instance is destroyed""" + methods = self.deletionMethods[:] + del self.deletionMethods[:] + try: + del self.__class__._allInstances[ self.key ] + except KeyError: + pass + for function in methods: + try: + if callable( function ): + function( self ) + except Exception, e: + try: + traceback.print_exc() + except AttributeError, err: + print '''Exception during saferef %s cleanup function %s: %s'''%( + self, function, e + ) + self.deletionMethods = [onDelete] + self.key = self.calculateKey( target ) + self.weakSelf = weakref.ref(target.im_self, remove) + self.weakFunc = weakref.ref(target.im_func, remove) + self.selfName = str(target.im_self) + self.funcName = str(target.im_func.__name__) + def calculateKey( cls, target ): + """Calculate the reference key for this reference - Currently this is a two-tuple of the id()'s of the - target object and the target function respectively. - """ - return (id(target.im_self),id(target.im_func)) - calculateKey = classmethod( calculateKey ) - def __str__(self): - """Give a friendly representation of the object""" - return """%s( %s.%s )"""%( - self.__class__.__name__, - self.selfName, - self.funcName, - ) - __repr__ = __str__ - def __nonzero__( self ): - """Whether we are still a valid reference""" - return self() is not None - def __cmp__( self, other ): - """Compare with another reference""" - if not isinstance (other,self.__class__): - return cmp( self.__class__, type(other) ) - return cmp( self.key, other.key) - def __call__(self): - """Return a strong reference to the bound method + Currently this is a two-tuple of the id()'s of the + target object and the target function respectively. + """ + return (id(target.im_self),id(target.im_func)) + calculateKey = classmethod( calculateKey ) + def __str__(self): + """Give a friendly representation of the object""" + return """%s( %s.%s )"""%( + self.__class__.__name__, + self.selfName, + self.funcName, + ) + __repr__ = __str__ + def __nonzero__( self ): + """Whether we are still a valid reference""" + return self() is not None + def __cmp__( self, other ): + """Compare with another reference""" + if not isinstance (other,self.__class__): + return cmp( self.__class__, type(other) ) + return cmp( self.key, other.key) + def __call__(self): + """Return a strong reference to the bound method - If the target cannot be retrieved, then will - return None, otherwise returns a bound instance - method for our object and function. + If the target cannot be retrieved, then will + return None, otherwise returns a bound instance + method for our object and function. - Note: - You may call this method any number of times, - as it does not invalidate the reference. - """ - target = self.weakSelf() - if target is not None: - function = self.weakFunc() - if function is not None: - return function.__get__(target) - return None + Note: + You may call this method any number of times, + as it does not invalidate the reference. + """ + target = self.weakSelf() + if target is not None: + function = self.weakFunc() + if function is not None: + return function.__get__(target) + return None diff --git a/django/forms/__init__.py b/django/forms/__init__.py index 1e9cb2c596..730f7a54da 100644 --- a/django/forms/__init__.py +++ b/django/forms/__init__.py @@ -434,10 +434,12 @@ class HiddenField(FormField): (self.get_id(), self.field_name, escape(data)) class CheckboxField(FormField): - def __init__(self, field_name, checked_by_default=False): + def __init__(self, field_name, checked_by_default=False, validator_list=None): + if validator_list is None: validator_list = [] self.field_name = field_name self.checked_by_default = checked_by_default - self.is_required, self.validator_list = False, [] # because the validator looks for these + self.is_required = False # because the validator looks for these + self.validator_list = validator_list[:] def render(self, data): checked_html = '' @@ -773,15 +775,18 @@ class DatetimeField(TextField): def html2python(data): "Converts the field into a datetime.datetime object" import datetime - date, time = data.split() - y, m, d = date.split('-') - timebits = time.split(':') - h, mn = timebits[:2] - if len(timebits) > 2: - s = int(timebits[2]) - else: - s = 0 - return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s) + try: + date, time = data.split() + y, m, d = date.split('-') + timebits = time.split(':') + h, mn = timebits[:2] + if len(timebits) > 2: + s = int(timebits[2]) + else: + s = 0 + return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s) + except ValueError: + return None html2python = staticmethod(html2python) class DateField(TextField): @@ -806,7 +811,7 @@ class DateField(TextField): time_tuple = time.strptime(data, '%Y-%m-%d') return datetime.date(*time_tuple[0:3]) except (ValueError, TypeError): - return data + return None html2python = staticmethod(html2python) class TimeField(TextField): diff --git a/django/http/__init__.py b/django/http/__init__.py index 8fb4b293fc..06f6a106ee 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -1,3 +1,4 @@ +import os from Cookie import SimpleCookie from pprint import pformat from urllib import urlencode, quote @@ -37,6 +38,9 @@ class HttpRequest(object): def get_full_path(self): return '' + + def is_secure(self): + return os.environ.get("HTTPS") == "on" def parse_file_upload(header_dict, post_data): "Returns a tuple of (POST MultiValueDict, FILES MultiValueDict)" diff --git a/django/middleware/cache.py b/django/middleware/cache.py index 80c8626db8..08e77d1375 100644 --- a/django/middleware/cache.py +++ b/django/middleware/cache.py @@ -1,7 +1,6 @@ from django.conf import settings from django.core.cache import cache from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers -from django.http import HttpResponseNotModified class CacheMiddleware(object): """ @@ -10,6 +9,11 @@ class CacheMiddleware(object): Only parameter-less GET or HEAD-requests with status code 200 are cached. + If CACHE_MIDDLEWARE_ANONYMOUS_ONLY is set to True, only anonymous requests + (i.e., those node made by a logged-in user) will be cached. This is a + simple and effective way of avoiding the caching of the Django admin (and + any other user-specific content). + This middleware expects that a HEAD request is answered with a response exactly like the corresponding GET request. @@ -23,13 +27,17 @@ class CacheMiddleware(object): This middleware also sets ETag, Last-Modified, Expires and Cache-Control headers on the response object. """ - def __init__(self, cache_timeout=None, key_prefix=None): + def __init__(self, cache_timeout=None, key_prefix=None, cache_anonymous_only=None): self.cache_timeout = cache_timeout if cache_timeout is None: self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS self.key_prefix = key_prefix if key_prefix is None: self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX + if cache_anonymous_only is None: + self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False) + else: + self.cache_anonymous_only = cache_anonymous_only def process_request(self, request): "Checks whether the page is already cached and returns the cached version if available." @@ -37,6 +45,10 @@ class CacheMiddleware(object): request._cache_update_cache = False return None # Don't bother checking the cache. + if self.cache_anonymous_only and request.user.is_authenticated(): + request._cache_update_cache = False + return None # Don't cache requests from authenticated users. + cache_key = get_cache_key(request, self.key_prefix) if cache_key is None: request._cache_update_cache = True diff --git a/django/middleware/common.py b/django/middleware/common.py index a42c54751d..d63b71fed7 100644 --- a/django/middleware/common.py +++ b/django/middleware/common.py @@ -1,7 +1,7 @@ from django.conf import settings from django import http from django.core.mail import mail_managers -import md5, os +import md5 class CommonMiddleware(object): """ @@ -44,7 +44,7 @@ class CommonMiddleware(object): if new_url != old_url: # Redirect if new_url[0]: - newurl = "%s://%s%s" % (os.environ.get('HTTPS') == 'on' and 'https' or 'http', new_url[0], new_url[1]) + newurl = "%s://%s%s" % (request.is_secure() and 'https' or 'http', new_url[0], new_url[1]) else: newurl = new_url[1] if request.GET: diff --git a/django/middleware/gzip.py b/django/middleware/gzip.py index cc8a68406c..7d860abdb1 100644 --- a/django/middleware/gzip.py +++ b/django/middleware/gzip.py @@ -12,7 +12,11 @@ class GZipMiddleware(object): """ def process_response(self, request, response): patch_vary_headers(response, ('Accept-Encoding',)) - if response.has_header('Content-Encoding'): + + # Avoid gzipping if we've already got a content-encoding or if the + # content-type is Javascript (silly IE...) + is_js = "javascript" in response.headers.get('Content-Type', '').lower() + if response.has_header('Content-Encoding') or is_js: return response ae = request.META.get('HTTP_ACCEPT_ENCODING', '') diff --git a/django/middleware/transaction.py b/django/middleware/transaction.py index 4128e012f2..96b1538d9d 100644 --- a/django/middleware/transaction.py +++ b/django/middleware/transaction.py @@ -1,4 +1,3 @@ -from django.conf import settings from django.db import transaction class TransactionMiddleware(object): diff --git a/django/template/__init__.py b/django/template/__init__.py index 08f433fec9..ba7ca4c02b 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -171,7 +171,7 @@ class Token(object): self.contents[:20].replace('\n', '')) def split_contents(self): - return smart_split(self.contents) + return list(smart_split(self.contents)) class Lexer(object): def __init__(self, template_string, origin): @@ -318,7 +318,7 @@ class Parser(object): self.tags.update(lib.tags) self.filters.update(lib.filters) - def compile_filter(self,token): + def compile_filter(self, token): "Convenient wrapper for FilterExpression" return FilterExpression(token, self) @@ -358,7 +358,7 @@ class DebugParser(Parser): super(DebugParser, self).extend_nodelist(nodelist, node, token) def unclosed_block_tag(self, parse_until): - (command, source) = self.command_stack.pop() + command, source = self.command_stack.pop() msg = "Unclosed tag '%s'. Looking for one of: %s " % (command, ', '.join(parse_until)) raise self.source_error( source, msg) @@ -543,11 +543,14 @@ class FilterExpression(object): raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:] self.var, self.filters = var, filters - def resolve(self, context): + def resolve(self, context, ignore_failures=False): try: obj = resolve_variable(self.var, context) except VariableDoesNotExist: - obj = settings.TEMPLATE_STRING_IF_INVALID + if ignore_failures: + return None + else: + return settings.TEMPLATE_STRING_IF_INVALID for func, args in self.filters: arg_vals = [] for lookup, arg in args: @@ -611,7 +614,11 @@ def resolve_variable(path, context): (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') """ - if path[0].isdigit(): + if path == 'False': + current = False + elif path == 'True': + current = True + elif path[0].isdigit(): number_type = '.' in path and float or int try: current = number_type(path) @@ -701,9 +708,9 @@ class DebugNodeList(NodeList): if not hasattr(e, 'source'): e.source = node.source raise - except Exception: + except Exception, e: from sys import exc_info - wrapped = TemplateSyntaxError('Caught an exception while rendering.') + wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e) wrapped.source = node.source wrapped.exc_info = exc_info() raise wrapped @@ -751,7 +758,7 @@ class DebugVariableNode(VariableNode): def generic_tag_compiler(params, defaults, name, node_class, parser, token): "Returns a template.Node subclass." - bits = token.contents.split()[1:] + bits = token.split_contents()[1:] bmax = len(params) def_len = defaults and len(defaults) or 0 bmin = bmax - def_len @@ -810,7 +817,7 @@ class Library(object): self.filters[name] = filter_func return filter_func else: - raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r, %r)", (name, compile_function, has_arg) + raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func) def filter_function(self, func): self.filters[func.__name__] = func diff --git a/django/template/context.py b/django/template/context.py index 44a97f95a8..6d9efdc7ec 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -37,7 +37,7 @@ class Context(object): for d in self.dicts: if d.has_key(key): return d[key] - return settings.TEMPLATE_STRING_IF_INVALID + raise KeyError(key) def __delitem__(self, key): "Delete a variable from the current context" @@ -49,7 +49,7 @@ class Context(object): return True return False - def get(self, key, otherwise): + def get(self, key, otherwise=None): for d in self.dicts: if d.has_key(key): return d[key] diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 9bd6e7cb3c..c75f371ec8 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -133,7 +133,7 @@ def wordwrap(value, arg): """ Wraps words at specified line length - Argument: number of words to wrap the text at. + Argument: number of characters to wrap the text at. """ from django.utils.text import wrap return wrap(str(value), int(arg)) @@ -430,20 +430,32 @@ def filesizeformat(bytes): return "%.1f MB" % (bytes / (1024 * 1024)) return "%.1f GB" % (bytes / (1024 * 1024 * 1024)) -def pluralize(value): - "Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'" +def pluralize(value, arg='s'): + """ + Returns a plural suffix if the value is not 1, for '1 vote' vs. '2 votes' + By default, 's' is used as a suffix; if an argument is provided, that string + is used instead. If the provided argument contains a comma, the text before + the comma is used for the singular case. + """ + if not ',' in arg: + arg = ',' + arg + bits = arg.split(',') + if len(bits) > 2: + return '' + singular_suffix, plural_suffix = bits[:2] + try: if int(value) != 1: - return 's' + return plural_suffix except ValueError: # invalid string that's not a number pass except TypeError: # value isn't a string or a number; maybe it's a list? try: if len(value) != 1: - return 's' + return plural_suffix except TypeError: # len() of unsized object pass - return '' + return singular_suffix def phone2numeric(value): "Takes a phone number and converts it in to its numerical equivalent" diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 88cb5f68be..0a4fe33d82 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -45,7 +45,10 @@ class FirstOfNode(Node): def render(self, context): for var in self.vars: - value = resolve_variable(var, context) + try: + value = resolve_variable(var, context) + except VariableDoesNotExist: + continue if value: return str(value) return '' @@ -144,8 +147,14 @@ class IfEqualNode(Node): return "<IfEqualNode>" def render(self, context): - val1 = resolve_variable(self.var1, context) - val2 = resolve_variable(self.var2, context) + try: + val1 = resolve_variable(self.var1, context) + except VariableDoesNotExist: + val1 = None + try: + val2 = resolve_variable(self.var2, context) + except VariableDoesNotExist: + val2 = None if (self.negate and val1 != val2) or (not self.negate and val1 == val2): return self.nodelist_true.render(context) return self.nodelist_false.render(context) @@ -177,7 +186,7 @@ class IfNode(Node): if self.link_type == IfNode.LinkTypes.or_: for ifnot, bool_expr in self.bool_exprs: try: - value = bool_expr.resolve(context) + value = bool_expr.resolve(context, True) except VariableDoesNotExist: value = None if (value and not ifnot) or (ifnot and not value): @@ -186,7 +195,7 @@ class IfNode(Node): else: for ifnot, bool_expr in self.bool_exprs: try: - value = bool_expr.resolve(context) + value = bool_expr.resolve(context, True) except VariableDoesNotExist: value = None if not ((value and not ifnot) or (ifnot and not value)): diff --git a/django/template/loader.py b/django/template/loader.py index ebca582ef9..60f24554f1 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -21,7 +21,7 @@ # installed, because pkg_resources is necessary to read eggs. from django.core.exceptions import ImproperlyConfigured -from django.template import Origin, StringOrigin, Template, Context, TemplateDoesNotExist, add_to_builtins +from django.template import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins from django.conf import settings template_source_loaders = None diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py index e0d9a70a98..7f22f207b6 100644 --- a/django/template/loader_tags.py +++ b/django/template/loader_tags.py @@ -1,5 +1,5 @@ from django.template import TemplateSyntaxError, TemplateDoesNotExist, resolve_variable -from django.template import Library, Context, Node +from django.template import Library, Node from django.template.loader import get_template, get_template_from_string, find_template_source from django.conf import settings @@ -50,6 +50,8 @@ class ExtendsNode(Node): if self.parent_name_expr: error_msg += " Got this from the %r variable." % self.parent_name_expr #TODO nice repr. raise TemplateSyntaxError, error_msg + if hasattr(parent, 'render'): + return parent try: source, origin = find_template_source(parent, self.template_dirs) except TemplateDoesNotExist: @@ -125,7 +127,7 @@ def do_block(parser, token): if block_name in parser.__loaded_blocks: raise TemplateSyntaxError, "'%s' tag with name '%s' appears more than once" % (bits[0], block_name) parser.__loaded_blocks.append(block_name) - except AttributeError: # parser._loaded_blocks isn't a list yet + except AttributeError: # parser.__loaded_blocks isn't a list yet parser.__loaded_blocks = [block_name] nodelist = parser.parse(('endblock',)) parser.delete_first_token() @@ -137,8 +139,9 @@ def do_extends(parser, token): This tag may be used in two ways: ``{% extends "base" %}`` (with quotes) uses the literal value "base" as the name of the parent template to extend, - or ``{% extends variable %}`` uses the value of ``variable`` as the name - of the parent template to extend. + or ``{% extends variable %}`` uses the value of ``variable`` as either the + name of the parent template to extend (if it evaluates to a string,) or as + the parent tempate itelf (if it evaluates to a Template object). """ bits = token.contents.split() if len(bits) != 2: diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index 37b8d93908..bf6497f9aa 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -1,8 +1,7 @@ -from django.template import Node, NodeList, Template, Context, resolve_variable +from django.template import Node, resolve_variable from django.template import TemplateSyntaxError, TokenParser, Library -from django.template import TOKEN_BLOCK, TOKEN_TEXT, TOKEN_VAR +from django.template import TOKEN_TEXT, TOKEN_VAR from django.utils import translation -import re, sys register = Library() @@ -12,7 +11,7 @@ class GetAvailableLanguagesNode(Node): def render(self, context): from django.conf import settings - context[self.variable] = settings.LANGUAGES + context[self.variable] = [(k, translation.gettext(v)) for k, v in settings.LANGUAGES] return '' class GetCurrentLanguageNode(Node): @@ -30,7 +29,7 @@ class GetCurrentLanguageBidiNode(Node): def render(self, context): context[self.variable] = translation.get_language_bidi() return '' - + class TranslateNode(Node): def __init__(self, value, noop): self.value = value @@ -171,7 +170,7 @@ def do_translate(parser, token): else: noop = False return (value, noop) - (value, noop) = TranslateParser(token.contents).top() + value, noop = TranslateParser(token.contents).top() return TranslateNode(value, noop) def do_block_translate(parser, token): @@ -216,7 +215,7 @@ def do_block_translate(parser, token): raise TemplateSyntaxError, "unknown subtag %s for 'blocktrans' found" % tag return (countervar, counter, extra_context) - (countervar, counter, extra_context) = BlockTranslateParser(token.contents).top() + countervar, counter, extra_context = BlockTranslateParser(token.contents).top() singular = [] plural = [] @@ -228,7 +227,7 @@ def do_block_translate(parser, token): break if countervar and counter: if token.contents.strip() != 'plural': - raise TemplateSyntaxError, "'blocktrans' doesn't allow other block tags inside it" % tag + raise TemplateSyntaxError, "'blocktrans' doesn't allow other block tags inside it" while parser.tokens: token = parser.next_token() if token.token_type in (TOKEN_VAR, TOKEN_TEXT): diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index 6363af7835..e05b7fafe1 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -35,6 +35,14 @@ try: except ImportError: import dummy_thread as thread +# This import does nothing, but it's necessary to avoid some race conditions +# in the threading module. See http://code.djangoproject.com/ticket/2330 . +try: + import threading +except ImportError: + pass + + RUN_RELOADER = True def reloader_thread(): diff --git a/django/utils/simplejson/LICENSE.txt b/django/utils/simplejson/LICENSE.txt new file mode 100644 index 0000000000..90251a9f62 --- /dev/null +++ b/django/utils/simplejson/LICENSE.txt @@ -0,0 +1,20 @@ +simplejson 1.3 +Copyright (c) 2006 Bob Ippolito + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/django/utils/simplejson/__init__.py b/django/utils/simplejson/__init__.py new file mode 100644 index 0000000000..f88329b950 --- /dev/null +++ b/django/utils/simplejson/__init__.py @@ -0,0 +1,221 @@ +r""" +A simple, fast, extensible JSON encoder and decoder + +JSON (JavaScript Object Notation) <http://json.org> is a subset of +JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data +interchange format. + +simplejson exposes an API familiar to uses of the standard library +marshal and pickle modules. + +Encoding basic Python object hierarchies:: + + >>> import simplejson + >>> simplejson.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) + '["foo", {"bar": ["baz", null, 1.0, 2]}]' + >>> print simplejson.dumps("\"foo\bar") + "\"foo\bar" + >>> print simplejson.dumps(u'\u1234') + "\u1234" + >>> print simplejson.dumps('\\') + "\\" + >>> print simplejson.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True) + {"a": 0, "b": 0, "c": 0} + >>> from StringIO import StringIO + >>> io = StringIO() + >>> simplejson.dump(['streaming API'], io) + >>> io.getvalue() + '["streaming API"]' + +Decoding JSON:: + + >>> import simplejson + >>> simplejson.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') + [u'foo', {u'bar': [u'baz', None, 1.0, 2]}] + >>> simplejson.loads('"\\"foo\\bar"') + u'"foo\x08ar' + >>> from StringIO import StringIO + >>> io = StringIO('["streaming API"]') + >>> simplejson.load(io) + [u'streaming API'] + +Specializing JSON object decoding:: + + >>> import simplejson + >>> def as_complex(dct): + ... if '__complex__' in dct: + ... return complex(dct['real'], dct['imag']) + ... return dct + ... + >>> simplejson.loads('{"__complex__": true, "real": 1, "imag": 2}', + ... object_hook=as_complex) + (1+2j) + +Extending JSONEncoder:: + + >>> import simplejson + >>> class ComplexEncoder(simplejson.JSONEncoder): + ... def default(self, obj): + ... if isinstance(obj, complex): + ... return [obj.real, obj.imag] + ... return simplejson.JSONEncoder.default(self, obj) + ... + >>> dumps(2 + 1j, cls=ComplexEncoder) + '[2.0, 1.0]' + >>> ComplexEncoder().encode(2 + 1j) + '[2.0, 1.0]' + >>> list(ComplexEncoder().iterencode(2 + 1j)) + ['[', '2.0', ', ', '1.0', ']'] + + +Note that the JSON produced by this module is a subset of YAML, +so it may be used as a serializer for that as well. +""" +__version__ = '1.3' +__all__ = [ + 'dump', 'dumps', 'load', 'loads', + 'JSONDecoder', 'JSONEncoder', +] + +from django.utils.simplejson.decoder import JSONDecoder +from django.utils.simplejson.encoder import JSONEncoder + +def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, **kw): + """ + Serialize ``obj`` as a JSON formatted stream to ``fp`` (a + ``.write()``-supporting file-like object). + + If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is ``False``, then the some chunks written to ``fp`` + may be ``unicode`` instances, subject to normal Python ``str`` to + ``unicode`` coercion rules. Unless ``fp.write()`` explicitly + understands ``unicode`` (as in ``codecs.getwriter()``) this is likely + to cause an error. + + If ``check_circular`` is ``False``, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) + in strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + """ + if cls is None: + cls = JSONEncoder + iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, + **kw).iterencode(obj) + # could accelerate with writelines in some versions of Python, at + # a debuggability cost + for chunk in iterable: + fp.write(chunk) + +def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, **kw): + """ + Serialize ``obj`` to a JSON formatted ``str``. + + If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is ``False``, then the return value will be a + ``unicode`` instance subject to normal Python ``str`` to ``unicode`` + coercion rules instead of being escaped to an ASCII ``str``. + + If ``check_circular`` is ``False``, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in + strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + """ + if cls is None: + cls = JSONEncoder + return cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, **kw).encode(obj) + +def load(fp, encoding=None, cls=None, object_hook=None, **kw): + """ + Deserialize ``fp`` (a ``.read()``-supporting file-like object containing + a JSON document) to a Python object. + + If the contents of ``fp`` is encoded with an ASCII based encoding other + than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must + be specified. Encodings that are not ASCII based (such as UCS-2) are + not allowed, and should be wrapped with + ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode`` + object and passed to ``loads()`` + + ``object_hook`` is an optional function that will be called with the + result of any object literal decode (a ``dict``). The return value of + ``object_hook`` will be used instead of the ``dict``. This feature + can be used to implement custom decoders (e.g. JSON-RPC class hinting). + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + """ + if cls is None: + cls = JSONDecoder + if object_hook is not None: + kw['object_hook'] = object_hook + return cls(encoding=encoding, **kw).decode(fp.read()) + +def loads(s, encoding=None, cls=None, object_hook=None, **kw): + """ + Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON + document) to a Python object. + + If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding + other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name + must be specified. Encodings that are not ASCII based (such as UCS-2) + are not allowed and should be decoded to ``unicode`` first. + + ``object_hook`` is an optional function that will be called with the + result of any object literal decode (a ``dict``). The return value of + ``object_hook`` will be used instead of the ``dict``. This feature + can be used to implement custom decoders (e.g. JSON-RPC class hinting). + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + """ + if cls is None: + cls = JSONDecoder + if object_hook is not None: + kw['object_hook'] = object_hook + return cls(encoding=encoding, **kw).decode(s) + +def read(s): + """ + json-py API compatibility hook. Use loads(s) instead. + """ + import warnings + warnings.warn("simplejson.loads(s) should be used instead of read(s)", + DeprecationWarning) + return loads(s) + +def write(obj): + """ + json-py API compatibility hook. Use dumps(s) instead. + """ + import warnings + warnings.warn("simplejson.dumps(s) should be used instead of write(s)", + DeprecationWarning) + return dumps(obj) + + diff --git a/django/utils/simplejson/decoder.py b/django/utils/simplejson/decoder.py new file mode 100644 index 0000000000..684af8c9ad --- /dev/null +++ b/django/utils/simplejson/decoder.py @@ -0,0 +1,271 @@ +""" +Implementation of JSONDecoder +""" +import re + +from django.utils.simplejson.scanner import Scanner, pattern + +FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL + +def _floatconstants(): + import struct + import sys + _BYTES = '7FF80000000000007FF0000000000000'.decode('hex') + if sys.byteorder != 'big': + _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1] + nan, inf = struct.unpack('dd', _BYTES) + return nan, inf, -inf + +NaN, PosInf, NegInf = _floatconstants() + +def linecol(doc, pos): + lineno = doc.count('\n', 0, pos) + 1 + if lineno == 1: + colno = pos + else: + colno = pos - doc.rindex('\n', 0, pos) + return lineno, colno + +def errmsg(msg, doc, pos, end=None): + lineno, colno = linecol(doc, pos) + if end is None: + return '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos) + endlineno, endcolno = linecol(doc, end) + return '%s: line %d column %d - line %d column %d (char %d - %d)' % ( + msg, lineno, colno, endlineno, endcolno, pos, end) + +_CONSTANTS = { + '-Infinity': NegInf, + 'Infinity': PosInf, + 'NaN': NaN, + 'true': True, + 'false': False, + 'null': None, +} + +def JSONConstant(match, context, c=_CONSTANTS): + return c[match.group(0)], None +pattern('(-?Infinity|NaN|true|false|null)')(JSONConstant) + +def JSONNumber(match, context): + match = JSONNumber.regex.match(match.string, *match.span()) + integer, frac, exp = match.groups() + if frac or exp: + res = float(integer + (frac or '') + (exp or '')) + else: + res = int(integer) + return res, None +pattern(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?')(JSONNumber) + +STRINGCHUNK = re.compile(r'(.*?)(["\\])', FLAGS) +BACKSLASH = { + '"': u'"', '\\': u'\\', '/': u'/', + 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t', +} + +DEFAULT_ENCODING = "utf-8" + +def scanstring(s, end, encoding=None, _b=BACKSLASH, _m=STRINGCHUNK.match): + if encoding is None: + encoding = DEFAULT_ENCODING + chunks = [] + _append = chunks.append + begin = end - 1 + while 1: + chunk = _m(s, end) + if chunk is None: + raise ValueError( + errmsg("Unterminated string starting at", s, begin)) + end = chunk.end() + content, terminator = chunk.groups() + if content: + if not isinstance(content, unicode): + content = unicode(content, encoding) + _append(content) + if terminator == '"': + break + try: + esc = s[end] + except IndexError: + raise ValueError( + errmsg("Unterminated string starting at", s, begin)) + if esc != 'u': + try: + m = _b[esc] + except KeyError: + raise ValueError( + errmsg("Invalid \\escape: %r" % (esc,), s, end)) + end += 1 + else: + esc = s[end + 1:end + 5] + try: + m = unichr(int(esc, 16)) + if len(esc) != 4 or not esc.isalnum(): + raise ValueError + except ValueError: + raise ValueError(errmsg("Invalid \\uXXXX escape", s, end)) + end += 5 + _append(m) + return u''.join(chunks), end + +def JSONString(match, context): + encoding = getattr(context, 'encoding', None) + return scanstring(match.string, match.end(), encoding) +pattern(r'"')(JSONString) + +WHITESPACE = re.compile(r'\s*', FLAGS) + +def JSONObject(match, context, _w=WHITESPACE.match): + pairs = {} + s = match.string + end = _w(s, match.end()).end() + nextchar = s[end:end + 1] + # trivial empty object + if nextchar == '}': + return pairs, end + 1 + if nextchar != '"': + raise ValueError(errmsg("Expecting property name", s, end)) + end += 1 + encoding = getattr(context, 'encoding', None) + while True: + key, end = scanstring(s, end, encoding) + end = _w(s, end).end() + if s[end:end + 1] != ':': + raise ValueError(errmsg("Expecting : delimiter", s, end)) + end = _w(s, end + 1).end() + try: + value, end = JSONScanner.iterscan(s, idx=end).next() + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + pairs[key] = value + end = _w(s, end).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar == '}': + break + if nextchar != ',': + raise ValueError(errmsg("Expecting , delimiter", s, end - 1)) + end = _w(s, end).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar != '"': + raise ValueError(errmsg("Expecting property name", s, end - 1)) + object_hook = getattr(context, 'object_hook', None) + if object_hook is not None: + pairs = object_hook(pairs) + return pairs, end +pattern(r'{')(JSONObject) + +def JSONArray(match, context, _w=WHITESPACE.match): + values = [] + s = match.string + end = _w(s, match.end()).end() + # look-ahead for trivial empty array + nextchar = s[end:end + 1] + if nextchar == ']': + return values, end + 1 + while True: + try: + value, end = JSONScanner.iterscan(s, idx=end).next() + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + values.append(value) + end = _w(s, end).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar == ']': + break + if nextchar != ',': + raise ValueError(errmsg("Expecting , delimiter", s, end)) + end = _w(s, end).end() + return values, end +pattern(r'\[')(JSONArray) + +ANYTHING = [ + JSONObject, + JSONArray, + JSONString, + JSONConstant, + JSONNumber, +] + +JSONScanner = Scanner(ANYTHING) + +class JSONDecoder(object): + """ + Simple JSON <http://json.org> decoder + + Performs the following translations in decoding: + + +---------------+-------------------+ + | JSON | Python | + +===============+===================+ + | object | dict | + +---------------+-------------------+ + | array | list | + +---------------+-------------------+ + | string | unicode | + +---------------+-------------------+ + | number (int) | int, long | + +---------------+-------------------+ + | number (real) | float | + +---------------+-------------------+ + | true | True | + +---------------+-------------------+ + | false | False | + +---------------+-------------------+ + | null | None | + +---------------+-------------------+ + + It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as + their corresponding ``float`` values, which is outside the JSON spec. + """ + + _scanner = Scanner(ANYTHING) + __all__ = ['__init__', 'decode', 'raw_decode'] + + def __init__(self, encoding=None, object_hook=None): + """ + ``encoding`` determines the encoding used to interpret any ``str`` + objects decoded by this instance (utf-8 by default). It has no + effect when decoding ``unicode`` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as ``unicode``. + + ``object_hook``, if specified, will be called with the result + of every JSON object decoded and its return value will be used in + place of the given ``dict``. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + """ + self.encoding = encoding + self.object_hook = object_hook + + def decode(self, s, _w=WHITESPACE.match): + """ + Return the Python representation of ``s`` (a ``str`` or ``unicode`` + instance containing a JSON document) + """ + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + end = _w(s, end).end() + if end != len(s): + raise ValueError(errmsg("Extra data", s, end, len(s))) + return obj + + def raw_decode(self, s, **kw): + """ + Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning + with a JSON document) and return a 2-tuple of the Python + representation and the index in ``s`` where the document ended. + + This can be used to decode a JSON document from a string that may + have extraneous data at the end. + """ + kw.setdefault('context', self) + try: + obj, end = self._scanner.iterscan(s, **kw).next() + except StopIteration: + raise ValueError("No JSON object could be decoded") + return obj, end + +__all__ = ['JSONDecoder'] diff --git a/django/utils/simplejson/encoder.py b/django/utils/simplejson/encoder.py new file mode 100644 index 0000000000..bb1aba09f0 --- /dev/null +++ b/django/utils/simplejson/encoder.py @@ -0,0 +1,289 @@ +""" +Implementation of JSONEncoder +""" +import re + +# this should match any kind of infinity +INFCHARS = re.compile(r'[infINF]') +ESCAPE = re.compile(r'[\x00-\x19\\"\b\f\n\r\t]') +ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])') +ESCAPE_DCT = { + '\\': '\\\\', + '"': '\\"', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', +} +for i in range(20): + ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) + +def floatstr(o, allow_nan=True): + s = str(o) + # If the first non-sign is a digit then it's not a special value + if (o < 0.0 and s[1].isdigit()) or s[0].isdigit(): + return s + elif not allow_nan: + raise ValueError("Out of range float values are not JSON compliant: %r" + % (o,)) + # These are the string representations on the platforms I've tried + if s == 'nan': + return 'NaN' + if s == 'inf': + return 'Infinity' + if s == '-inf': + return '-Infinity' + # NaN should either be inequal to itself, or equal to everything + if o != o or o == 0.0: + return 'NaN' + # Last ditch effort, assume inf + if o < 0: + return '-Infinity' + return 'Infinity' + +def encode_basestring(s): + """ + Return a JSON representation of a Python string + """ + def replace(match): + return ESCAPE_DCT[match.group(0)] + return '"' + ESCAPE.sub(replace, s) + '"' + +def encode_basestring_ascii(s): + def replace(match): + s = match.group(0) + try: + return ESCAPE_DCT[s] + except KeyError: + return '\\u%04x' % (ord(s),) + return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' + + +class JSONEncoder(object): + """ + Extensible JSON <http://json.org> encoder for Python data structures. + + Supports the following objects and types by default: + + +-------------------+---------------+ + | Python | JSON | + +===================+===============+ + | dict | object | + +-------------------+---------------+ + | list, tuple | array | + +-------------------+---------------+ + | str, unicode | string | + +-------------------+---------------+ + | int, long, float | number | + +-------------------+---------------+ + | True | true | + +-------------------+---------------+ + | False | false | + +-------------------+---------------+ + | None | null | + +-------------------+---------------+ + + To extend this to recognize other objects, subclass and implement a + ``.default()`` method with another method that returns a serializable + object for ``o`` if possible, otherwise it should call the superclass + implementation (to raise ``TypeError``). + """ + __all__ = ['__init__', 'default', 'encode', 'iterencode'] + def __init__(self, skipkeys=False, ensure_ascii=True, + check_circular=True, allow_nan=True, sort_keys=False): + """ + Constructor for JSONEncoder, with sensible defaults. + + If skipkeys is False, then it is a TypeError to attempt + encoding of keys that are not str, int, long, float or None. If + skipkeys is True, such items are simply skipped. + + If ensure_ascii is True, the output is guaranteed to be str + objects with all incoming unicode characters escaped. If + ensure_ascii is false, the output will be unicode object. + + If check_circular is True, then lists, dicts, and custom encoded + objects will be checked for circular references during encoding to + prevent an infinite recursion (which would cause an OverflowError). + Otherwise, no such check takes place. + + If allow_nan is True, then NaN, Infinity, and -Infinity will be + encoded as such. This behavior is not JSON specification compliant, + but is consistent with most JavaScript based encoders and decoders. + Otherwise, it will be a ValueError to encode such floats. + + If sort_keys is True, then the output of dictionaries will be + sorted by key; this is useful for regression tests to ensure + that JSON serializations can be compared on a day-to-day basis. + """ + + self.skipkeys = skipkeys + self.ensure_ascii = ensure_ascii + self.check_circular = check_circular + self.allow_nan = allow_nan + self.sort_keys = sort_keys + + def _iterencode_list(self, lst, markers=None): + if not lst: + yield '[]' + return + if markers is not None: + markerid = id(lst) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = lst + yield '[' + first = True + for value in lst: + if first: + first = False + else: + yield ', ' + for chunk in self._iterencode(value, markers): + yield chunk + yield ']' + if markers is not None: + del markers[markerid] + + def _iterencode_dict(self, dct, markers=None): + if not dct: + yield '{}' + return + if markers is not None: + markerid = id(dct) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = dct + yield '{' + first = True + if self.ensure_ascii: + encoder = encode_basestring_ascii + else: + encoder = encode_basestring + allow_nan = self.allow_nan + if self.sort_keys: + keys = dct.keys() + keys.sort() + items = [(k,dct[k]) for k in keys] + else: + items = dct.iteritems() + for key, value in items: + if isinstance(key, basestring): + pass + # JavaScript is weakly typed for these, so it makes sense to + # also allow them. Many encoders seem to do something like this. + elif isinstance(key, float): + key = floatstr(key, allow_nan) + elif isinstance(key, (int, long)): + key = str(key) + elif key is True: + key = 'true' + elif key is False: + key = 'false' + elif key is None: + key = 'null' + elif self.skipkeys: + continue + else: + raise TypeError("key %r is not a string" % (key,)) + if first: + first = False + else: + yield ', ' + yield encoder(key) + yield ': ' + for chunk in self._iterencode(value, markers): + yield chunk + yield '}' + if markers is not None: + del markers[markerid] + + def _iterencode(self, o, markers=None): + if isinstance(o, basestring): + if self.ensure_ascii: + encoder = encode_basestring_ascii + else: + encoder = encode_basestring + yield encoder(o) + elif o is None: + yield 'null' + elif o is True: + yield 'true' + elif o is False: + yield 'false' + elif isinstance(o, (int, long)): + yield str(o) + elif isinstance(o, float): + yield floatstr(o, self.allow_nan) + elif isinstance(o, (list, tuple)): + for chunk in self._iterencode_list(o, markers): + yield chunk + elif isinstance(o, dict): + for chunk in self._iterencode_dict(o, markers): + yield chunk + else: + if markers is not None: + markerid = id(o) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = o + for chunk in self._iterencode_default(o, markers): + yield chunk + if markers is not None: + del markers[markerid] + + def _iterencode_default(self, o, markers=None): + newobj = self.default(o) + return self._iterencode(newobj, markers) + + def default(self, o): + """ + Implement this method in a subclass such that it returns + a serializable object for ``o``, or calls the base implementation + (to raise a ``TypeError``). + + For example, to support arbitrary iterators, you could + implement default like this:: + + def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + return JSONEncoder.default(self, o) + """ + raise TypeError("%r is not JSON serializable" % (o,)) + + def encode(self, o): + """ + Return a JSON string representation of a Python data structure. + + >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) + '{"foo":["bar", "baz"]}' + """ + # This doesn't pass the iterator directly to ''.join() because it + # sucks at reporting exceptions. It's going to do this internally + # anyway because it uses PySequence_Fast or similar. + chunks = list(self.iterencode(o)) + return ''.join(chunks) + + def iterencode(self, o): + """ + Encode the given object and yield each string + representation as available. + + For example:: + + for chunk in JSONEncoder().iterencode(bigobject): + mysocket.write(chunk) + """ + if self.check_circular: + markers = {} + else: + markers = None + return self._iterencode(o, markers) + +__all__ = ['JSONEncoder'] diff --git a/django/utils/simplejson/scanner.py b/django/utils/simplejson/scanner.py new file mode 100644 index 0000000000..c2e9b6eb89 --- /dev/null +++ b/django/utils/simplejson/scanner.py @@ -0,0 +1,63 @@ +""" +Iterator based sre token scanner +""" +import sre_parse, sre_compile, sre_constants +from sre_constants import BRANCH, SUBPATTERN +from sre import VERBOSE, MULTILINE, DOTALL +import re + +__all__ = ['Scanner', 'pattern'] + +FLAGS = (VERBOSE | MULTILINE | DOTALL) +class Scanner(object): + def __init__(self, lexicon, flags=FLAGS): + self.actions = [None] + # combine phrases into a compound pattern + s = sre_parse.Pattern() + s.flags = flags + p = [] + for idx, token in enumerate(lexicon): + phrase = token.pattern + try: + subpattern = sre_parse.SubPattern(s, + [(SUBPATTERN, (idx + 1, sre_parse.parse(phrase, flags)))]) + except sre_constants.error: + raise + p.append(subpattern) + self.actions.append(token) + + p = sre_parse.SubPattern(s, [(BRANCH, (None, p))]) + self.scanner = sre_compile.compile(p) + + + def iterscan(self, string, idx=0, context=None): + """ + Yield match, end_idx for each match + """ + match = self.scanner.scanner(string, idx).match + actions = self.actions + lastend = idx + end = len(string) + while True: + m = match() + if m is None: + break + matchbegin, matchend = m.span() + if lastend == matchend: + break + action = actions[m.lastindex] + if action is not None: + rval, next_pos = action(m, context) + if next_pos is not None and next_pos != matchend: + # "fast forward" the scanner + matchend = next_pos + match = self.scanner.scanner(string, matchend).match + yield rval, matchend + lastend = matchend + +def pattern(pattern, flags=FLAGS): + def decorator(fn): + fn.pattern = pattern + fn.regex = re.compile(pattern, flags) + return fn + return decorator diff --git a/django/utils/synch.py b/django/utils/synch.py index 1e6b546a78..6fcd81390e 100644 --- a/django/utils/synch.py +++ b/django/utils/synch.py @@ -6,7 +6,10 @@ Synchronization primitives: (Contributed to Django by eugene@lazutkin.com) """ -import threading +try: + import threading +except ImportError: + import dummy_threading as threading class RWLock: """ diff --git a/django/utils/termcolors.py b/django/utils/termcolors.py index 3ce1d5bb6b..17a600f899 100644 --- a/django/utils/termcolors.py +++ b/django/utils/termcolors.py @@ -2,8 +2,6 @@ termcolors.py """ -import types - color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white') foreground = dict([(color_names[x], '3%s' % x) for x in range(8)]) background = dict([(color_names[x], '4%s' % x) for x in range(8)]) diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py new file mode 100644 index 0000000000..276e59f4bd --- /dev/null +++ b/django/utils/translation/__init__.py @@ -0,0 +1,8 @@ +from django.conf import settings + +if settings.USE_I18N: + from trans_real import * +else: + from trans_null import * + +del settings diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py new file mode 100644 index 0000000000..75ad573357 --- /dev/null +++ b/django/utils/translation/trans_null.py @@ -0,0 +1,30 @@ +# These are versions of the functions in django.utils.translation.trans_real +# that don't actually do anything. This is purely for performance, so that +# settings.USE_I18N = False can use this module rather than trans_real.py. + +from django.conf import settings + +def ngettext(singular, plural, number): + if number == 1: return singular + return plural +ngettext_lazy = ngettext + +gettext = gettext_noop = gettext_lazy = _ = lambda x: x +string_concat = lambda *strings: ''.join([str(el) for el in strings]) +activate = lambda x: None +deactivate = install = lambda: None +get_language = lambda: settings.LANGUAGE_CODE +get_language_bidi = lambda: settings.LANGUAGE_CODE in settings.LANGUAGES_BIDI +get_date_formats = lambda: (settings.DATE_FORMAT, settings.DATETIME_FORMAT, settings.TIME_FORMAT) +get_partial_date_formats = lambda: (settings.YEAR_MONTH_FORMAT, settings.MONTH_DAY_FORMAT) +check_for_language = lambda x: True + +def to_locale(language): + p = language.find('-') + if p >= 0: + return language[:p].lower()+'_'+language[p+1:].upper() + else: + return language.lower() + +def get_language_from_request(request): + return settings.LANGUAGE_CODE diff --git a/django/utils/translation.py b/django/utils/translation/trans_real.py index a73c43c257..94df23a8e9 100644 --- a/django/utils/translation.py +++ b/django/utils/translation/trans_real.py @@ -1,4 +1,4 @@ -"translation helper functions" +"Translation helper functions" import os, re, sys import gettext as gettext_module @@ -221,7 +221,6 @@ def get_language_bidi(): False = left-to-right layout True = right-to-left layout """ - from django.conf import settings return get_language() in settings.LANGUAGES_BIDI @@ -389,7 +388,7 @@ def get_partial_date_formats(): def install(): """ Installs the gettext function as the default translation function under - the name _. + the name '_'. """ __builtins__['_'] = gettext diff --git a/django/views/debug.py b/django/views/debug.py index 6cbbde987b..6934360afd 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -3,8 +3,6 @@ from django.template import Template, Context, TemplateDoesNotExist from django.utils.html import escape from django.http import HttpResponseServerError, HttpResponseNotFound import os, re -from itertools import count, izip -from os.path import dirname, join as pathjoin HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD') @@ -124,7 +122,7 @@ def technical_500_response(request, exc_type, exc_value, tb): 'frames': frames, 'lastframe': frames[-1], 'request': request, - 'request_protocol': os.environ.get("HTTPS") == "on" and "https" or "http", + 'request_protocol': request.is_secure() and "https" or "http", 'settings': get_safe_settings(), 'template_info': template_info, 'template_does_not_exist': template_does_not_exist, @@ -149,7 +147,7 @@ def technical_404_response(request, exception): 'urlpatterns': tried, 'reason': str(exception), 'request': request, - 'request_protocol': os.environ.get("HTTPS") == "on" and "https" or "http", + 'request_protocol': request.is_secure() and "https" or "http", 'settings': get_safe_settings(), }) return HttpResponseNotFound(t.render(c), mimetype='text/html') diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py index 5467ff501e..b04cc2340b 100644 --- a/django/views/decorators/cache.py +++ b/django/views/decorators/cache.py @@ -10,7 +10,6 @@ example, as that is unique across a Django project. Additionally, all headers from the response's Vary header will be taken into account on caching -- just like the middleware does. """ -import re from django.utils.decorators import decorator_from_middleware from django.utils.cache import patch_cache_control, add_never_cache_headers diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py index b5fdd3e4cc..27ba327ff5 100644 --- a/django/views/generic/create_update.py +++ b/django/views/generic/create_update.py @@ -4,7 +4,6 @@ from django import forms from django.db.models import FileField from django.contrib.auth.views import redirect_to_login from django.template import RequestContext -from django.core.paginator import ObjectPaginator, InvalidPage from django.http import Http404, HttpResponse, HttpResponseRedirect from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured @@ -20,7 +19,7 @@ def create_object(request, model, template_name=None, the form wrapper for the object """ if extra_context is None: extra_context = {} - if login_required and request.user.is_anonymous(): + if login_required and not request.user.is_authenticated(): return redirect_to_login(request.path) manipulator = model.AddManipulator(follow=follow) @@ -39,7 +38,7 @@ def create_object(request, model, template_name=None, # No errors -- this means we can save the data! new_object = manipulator.save(new_data) - if not request.user.is_anonymous(): + if request.user.is_authenticated(): request.user.message_set.create(message="The %s was created successfully." % model._meta.verbose_name) # Redirect to the new object: first by trying post_save_redirect, @@ -86,7 +85,7 @@ def update_object(request, model, object_id=None, slug=None, the original object being edited """ if extra_context is None: extra_context = {} - if login_required and request.user.is_anonymous(): + if login_required and not request.user.is_authenticated(): return redirect_to_login(request.path) # Look up the object to be edited @@ -113,7 +112,7 @@ def update_object(request, model, object_id=None, slug=None, if not errors: object = manipulator.save(new_data) - if not request.user.is_anonymous(): + if request.user.is_authenticated(): request.user.message_set.create(message="The %s was updated successfully." % model._meta.verbose_name) # Do a post-after-redirect so that reload works, etc. @@ -162,7 +161,7 @@ def delete_object(request, model, post_delete_redirect, the original object being deleted """ if extra_context is None: extra_context = {} - if login_required and request.user.is_anonymous(): + if login_required and not request.user.is_authenticated(): return redirect_to_login(request.path) # Look up the object to be edited @@ -180,7 +179,7 @@ def delete_object(request, model, post_delete_redirect, if request.method == 'POST': object.delete() - if not request.user.is_anonymous(): + if request.user.is_authenticated(): request.user.message_set.create(message="The %s was deleted." % model._meta.verbose_name) return HttpResponseRedirect(post_delete_redirect) else: diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py index 7084cdfe5e..860199c22e 100644 --- a/django/views/generic/date_based.py +++ b/django/views/generic/date_based.py @@ -7,7 +7,7 @@ import datetime, time def archive_index(request, queryset, date_field, num_latest=15, template_name=None, template_loader=loader, extra_context=None, allow_empty=False, context_processors=None, - mimetype=None): + mimetype=None, allow_future=False): """ Generic top-level archive of date-based objects. @@ -20,7 +20,8 @@ def archive_index(request, queryset, date_field, num_latest=15, """ if extra_context is None: extra_context = {} model = queryset.model - queryset = queryset.filter(**{'%s__lte' % date_field: datetime.datetime.now()}) + if not allow_future: + queryset = queryset.filter(**{'%s__lte' % date_field: datetime.datetime.now()}) date_list = queryset.dates(date_field, 'year')[::-1] if not date_list and not allow_empty: raise Http404, "No %s available" % model._meta.verbose_name @@ -47,7 +48,7 @@ def archive_index(request, queryset, date_field, num_latest=15, def archive_year(request, year, queryset, date_field, template_name=None, template_loader=loader, extra_context=None, allow_empty=False, context_processors=None, template_object_name='object', mimetype=None, - make_object_list=False): + make_object_list=False, allow_future=False): """ Generic yearly archive view. @@ -67,8 +68,8 @@ def archive_year(request, year, queryset, date_field, template_name=None, lookup_kwargs = {'%s__year' % date_field: year} - # Only bother to check current date if the year isn't in the past. - if int(year) >= now.year: + # Only bother to check current date if the year isn't in the past and future objects aren't requested. + if int(year) >= now.year and not allow_future: lookup_kwargs['%s__lte' % date_field] = now date_list = queryset.filter(**lookup_kwargs).dates(date_field, 'month') if not date_list and not allow_empty: @@ -95,7 +96,7 @@ def archive_year(request, year, queryset, date_field, template_name=None, def archive_month(request, year, month, queryset, date_field, month_format='%b', template_name=None, template_loader=loader, extra_context=None, allow_empty=False, context_processors=None, - template_object_name='object', mimetype=None): + template_object_name='object', mimetype=None, allow_future=False): """ Generic monthly archive view. @@ -127,19 +128,28 @@ def archive_month(request, year, month, queryset, date_field, last_day = first_day.replace(month=first_day.month + 1) lookup_kwargs = {'%s__range' % date_field: (first_day, last_day)} - # Only bother to check current date if the month isn't in the past. - if last_day >= now.date(): + # Only bother to check current date if the month isn't in the past and future objects are requested. + if last_day >= now.date() and not allow_future: lookup_kwargs['%s__lte' % date_field] = now object_list = queryset.filter(**lookup_kwargs) if not object_list and not allow_empty: raise Http404 + + # Calculate the next month, if applicable. + if allow_future: + next_month = last_day + datetime.timedelta(days=1) + elif last_day < datetime.date.today(): + next_month = last_day + datetime.timedelta(days=1) + else: + next_month = None + if not template_name: template_name = "%s/%s_archive_month.html" % (model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) c = RequestContext(request, { '%s_list' % template_object_name: object_list, 'month': date, - 'next_month': (last_day < datetime.date.today()) and (last_day + datetime.timedelta(days=1)) or None, + 'next_month': next_month, 'previous_month': first_day - datetime.timedelta(days=1), }, context_processors) for key, value in extra_context.items(): @@ -152,7 +162,7 @@ def archive_month(request, year, month, queryset, date_field, def archive_week(request, year, week, queryset, date_field, template_name=None, template_loader=loader, extra_context=None, allow_empty=True, context_processors=None, - template_object_name='object', mimetype=None): + template_object_name='object', mimetype=None, allow_future=False): """ Generic weekly archive view. @@ -177,8 +187,8 @@ def archive_week(request, year, week, queryset, date_field, last_day = date + datetime.timedelta(days=7) lookup_kwargs = {'%s__range' % date_field: (first_day, last_day)} - # Only bother to check current date if the week isn't in the past. - if last_day >= now.date(): + # Only bother to check current date if the week isn't in the past and future objects aren't requested. + if last_day >= now.date() and not allow_future: lookup_kwargs['%s__lte' % date_field] = now object_list = queryset.filter(**lookup_kwargs) if not object_list and not allow_empty: @@ -201,7 +211,7 @@ def archive_day(request, year, month, day, queryset, date_field, month_format='%b', day_format='%d', template_name=None, template_loader=loader, extra_context=None, allow_empty=False, context_processors=None, template_object_name='object', - mimetype=None): + mimetype=None, allow_future=False): """ Generic daily archive view. @@ -229,12 +239,21 @@ def archive_day(request, year, month, day, queryset, date_field, '%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)), } - # Only bother to check current date if the date isn't in the past. - if date >= now.date(): + # Only bother to check current date if the date isn't in the past and future objects aren't requested. + if date >= now.date() and not allow_future: lookup_kwargs['%s__lte' % date_field] = now object_list = queryset.filter(**lookup_kwargs) if not allow_empty and not object_list: raise Http404 + + # Calculate the next day, if applicable. + if allow_future: + next_day = date + datetime.timedelta(days=1) + elif date < datetime.date.today(): + next_day = date + datetime.timedelta(days=1) + else: + next_day = None + if not template_name: template_name = "%s/%s_archive_day.html" % (model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) @@ -242,7 +261,7 @@ def archive_day(request, year, month, day, queryset, date_field, '%s_list' % template_object_name: object_list, 'day': date, 'previous_day': date - datetime.timedelta(days=1), - 'next_day': (date < datetime.date.today()) and (date + datetime.timedelta(days=1)) or None, + 'next_day': next_day, }, context_processors) for key, value in extra_context.items(): if callable(value): @@ -267,7 +286,7 @@ def object_detail(request, year, month, day, queryset, date_field, month_format='%b', day_format='%d', object_id=None, slug=None, slug_field=None, template_name=None, template_name_field=None, template_loader=loader, extra_context=None, context_processors=None, - template_object_name='object', mimetype=None): + template_object_name='object', mimetype=None, allow_future=False): """ Generic detail view from year/month/day/slug or year/month/day/id structure. @@ -289,8 +308,8 @@ def object_detail(request, year, month, day, queryset, date_field, '%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)), } - # Only bother to check current date if the date isn't in the past. - if date >= now.date(): + # Only bother to check current date if the date isn't in the past and future objects aren't requested. + if date >= now.date() and not allow_future: lookup_kwargs['%s__lte' % date_field] = now if object_id: lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id diff --git a/django/views/i18n.py b/django/views/i18n.py index a2bc54c9ed..b5eb32bda3 100644 --- a/django/views/i18n.py +++ b/django/views/i18n.py @@ -28,21 +28,9 @@ def set_language(request): NullSource = """ /* gettext identity library */ -function gettext(msgid) { - return msgid; -} - -function ngettext(singular, plural, count) { - if (count == 1) { - return singular; - } else { - return plural; - } -} - -function gettext_noop(msgid) { - return msgid; -} +function gettext(msgid) { return msgid; } +function ngettext(singular, plural, count) { return (count == 1) ? singular : plural; } +function gettext_noop(msgid) { return msgid; } """ LibHead = """ @@ -54,56 +42,47 @@ var catalog = new Array(); LibFoot = """ function gettext(msgid) { - var value = catalog[msgid]; - if (typeof(value) == 'undefined') { - return msgid; - } else { - if (typeof(value) == 'string') { - return value; - } else { - return value[0]; - } - } + var value = catalog[msgid]; + if (typeof(value) == 'undefined') { + return msgid; + } else { + return (typeof(value) == 'string') ? value : value[0]; + } } function ngettext(singular, plural, count) { - value = catalog[singular]; - if (typeof(value) == 'undefined') { - if (count == 1) { - return singular; - } else { - return plural; - } - } else { - return value[pluralidx(count)]; - } + value = catalog[singular]; + if (typeof(value) == 'undefined') { + return (count == 1) ? singular : plural; + } else { + return value[pluralidx(count)]; + } } -function gettext_noop(msgid) { - return msgid; -} +function gettext_noop(msgid) { return msgid; } """ SimplePlural = """ -function pluralidx(count) { - if (count == 1) { - return 0; - } else { - return 1; - } -} +function pluralidx(count) { return (count == 1) ? 0 : 1; } """ InterPolate = r""" function interpolate(fmt, obj, named) { - if (named) { - return fmt.replace(/%\(\w+\)s/, function(match){return String(obj[match.slice(2,-2)])}); - } else { - return fmt.replace(/%s/, function(match){return String(obj.shift())}); - } + if (named) { + return fmt.replace(/%\(\w+\)s/, function(match){return String(obj[match.slice(2,-2)])}); + } else { + return fmt.replace(/%s/, function(match){return String(obj.shift())}); + } } """ +def null_javascript_catalog(request, domain=None, packages=None): + """ + Returns "identity" versions of the JavaScript i18n functions -- i.e., + versions that don't actually do anything. + """ + return http.HttpResponse(NullSource + InterPolate, 'text/javascript') + def javascript_catalog(request, domain='djangojs', packages=None): """ Returns the selected language catalog as a javascript library. @@ -191,4 +170,3 @@ def javascript_catalog(request, domain='djangojs', packages=None): src.append(InterPolate) src = ''.join(src) return http.HttpResponse(src, 'text/javascript') - diff --git a/django/views/static.py b/django/views/static.py index 072a01671e..ac323944d0 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -1,5 +1,4 @@ from django.template import loader -from django.core.exceptions import ImproperlyConfigured from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified from django.template import Template, Context, TemplateDoesNotExist import mimetypes diff --git a/docs/add_ons.txt b/docs/add_ons.txt index d72e92b018..6507f3b139 100644 --- a/docs/add_ons.txt +++ b/docs/add_ons.txt @@ -2,12 +2,15 @@ The "contrib" add-ons ===================== -Django aims to follow Python's "batteries included" philosophy. It ships with a -variety of extra, optional tools that solve common Web-development problems. +Django aims to follow Python's `"batteries included" philosophy`_. It ships +with a variety of extra, optional tools that solve common Web-development +problems. This code lives in ``django/contrib`` in the Django distribution. Here's a rundown of the packages in ``contrib``: +.. _"batteries included" philosophy: http://docs.python.org/tut/node12.html#batteries-included + admin ===== @@ -128,6 +131,8 @@ A collection of template filters that implement these common markup languages: * Markdown * ReST (ReStructured Text) +For documentation, read the source code in django/contrib/markup/templatetags/markup.py. + redirects ========= diff --git a/docs/admin_css.txt b/docs/admin_css.txt index 069012a84b..ec402f7142 100644 --- a/docs/admin_css.txt +++ b/docs/admin_css.txt @@ -118,8 +118,8 @@ additional class on the ``a`` for that tool. These are ``.addlink`` and Example from a changelist page:: <ul class="object-tools"> - <li><a href="/stories/add/" class="addlink">Add redirect</a></li> - </ul> + <li><a href="/stories/add/" class="addlink">Add redirect</a></li> + </ul> .. image:: http://media.djangoproject.com/img/doc/admincss/objecttools_01.gif :alt: Object tools on a changelist page diff --git a/docs/api_stability.txt b/docs/api_stability.txt new file mode 100644 index 0000000000..a9d6904735 --- /dev/null +++ b/docs/api_stability.txt @@ -0,0 +1,123 @@ +============= +API stability +============= + +Although Django has not reached a 1.0 release, the bulk of Django's public APIs are +stable as of the 0.95 release. This document explains which APIs will and will not +change before the 1.0 release. + +What "stable" means +=================== + +In this context, stable means: + + - All the public APIs -- everything documented in the linked documents, and + all methods that don't begin with an underscore -- will not be moved or + renamed without providing backwards-compatible aliases. + + - If new features are added to these APIs -- which is quite possible -- + they will not break or change the meaning of existing methods. In other + words, "stable" does not (necessarily) mean "complete." + + - If, for some reason, an API declared stable must be removed or replaced, it + will be declared deprecated but will remain in the API until at least + version 1.1. Warnings will be issued when the deprecated method is + called. + + - We'll only break backwards compatibility of these APIs if a bug or + security hole makes it completely unavoidable. + +Stable APIs +=========== + +These APIs are stable: + + - `Caching`_. + + - `Custom template tags and libraries`_ (with the possible exception for a + small change in the way templates are registered and loaded). + + - `Database lookup`_ (with the exception of validation; see below). + + - `django-admin utility`_. + + - `FastCGI integration`_. + + - `Flatpages`_. + + - `Generic views`_. + + - `Internationalization`_. + + - `Legacy database integration`_. + + - `Model definition`_ (with the exception of generic relations; see below). + + - `mod_python integration`_. + + - `Redirects`_. + + - `Request/response objects`_. + + - `Sending email`_. + + - `Sessions`_. + + - `Settings`_. + + - `Syndication`_. + + - `Template language`_ (with the exception of some possible disambiguation + of how tag arguments are passed to tags and filters). + + - `Transactions`_. + + - `URL dispatch`_. + +You'll notice that this list comprises the bulk of Django's APIs. That's right +-- most of the changes planned between now and Django 1.0 are either under the +hood, feature additions, or changes to a few select bits. A good estimate is +that 90% of Django can be considered forwards-compatible at this point. + +That said, these APIs should *not* be considered stable, and are likely to +change: + + - `Forms and validation`_ will most likely be compeltely rewritten to + deemphasize Manipulators in favor of validation-aware models. + + - `Serialization`_ is under heavy development; changes are likely. + + - The `authentication`_ framework is changing to be far more flexible, and + API changes may be necessary. + + - Generic relations will most likely be moved out of core and into the + content-types contrib package to avoid core dependacies on optional + components. + + - The comments framework, which is yet undocumented, will likely get a complete + rewrite before Django 1.0. Even if the change isn't quite that drastic, + there will at least be moderate changes. + +.. _caching: http://www.djangoproject.com/documentation/cache/ +.. _custom template tags and libraries: http://www.djangoproject.com/documentation/templates_python/ +.. _database lookup: http://www.djangoproject.com/documentation/db_api/ +.. _django-admin utility: http://www.djangoproject.com/documentation/django_admin/ +.. _fastcgi integration: http://www.djangoproject.com/documentation/fastcgi/ +.. _flatpages: http://www.djangoproject.com/documentation/flatpages/ +.. _generic views: http://www.djangoproject.com/documentation/generic_views/ +.. _internationalization: http://www.djangoproject.com/documentation/i18n/ +.. _legacy database integration: http://www.djangoproject.com/documentation/legacy_databases/ +.. _model definition: http://www.djangoproject.com/documentation/model_api/ +.. _mod_python integration: http://www.djangoproject.com/documentation/modpython/ +.. _redirects: http://www.djangoproject.com/documentation/redirects/ +.. _request/response objects: http://www.djangoproject.com/documentation/request_response/ +.. _sending email: http://www.djangoproject.com/documentation/email/ +.. _sessions: http://www.djangoproject.com/documentation/sessions/ +.. _settings: http://www.djangoproject.com/documentation/settings/ +.. _syndication: http://www.djangoproject.com/documentation/syndication/ +.. _template language: http://www.djangoproject.com/documentation/templates/ +.. _transactions: http://www.djangoproject.com/documentation/transactions/ +.. _url dispatch: http://www.djangoproject.com/documentation/url_dispatch/ +.. _forms and validation: http://www.djangoproject.com/documentation/forms/ +.. _serialization: http://www.djangoproject.com/documentation/serialization/ +.. _authentication: http://www.djangoproject.com/documentation/authentication/ diff --git a/docs/authentication.txt b/docs/authentication.txt index 79a4ed0875..d10dda28ef 100644 --- a/docs/authentication.txt +++ b/docs/authentication.txt @@ -95,7 +95,11 @@ In addition to those automatic API methods, ``User`` objects have the following custom methods: * ``is_anonymous()`` -- Always returns ``False``. This is a way of - comparing ``User`` objects to anonymous users. + differentiating ``User`` and ``AnonymousUser`` objects. Generally, you + should prefer using ``is_authenticated()`` to this method. + + * ``is_authenticated()`` -- Always returns ``True``. This is a way to + tell if the user has been authenticated. * ``get_full_name()`` -- Returns the ``first_name`` plus the ``last_name``, with a space in between. @@ -219,6 +223,7 @@ the ``django.contrib.auth.models.User`` interface, with these differences: * ``id`` is always ``None``. * ``is_anonymous()`` returns ``True`` instead of ``False``. + * ``is_authenticated()`` returns ``False`` instead of ``True``. * ``has_perm()`` always returns ``False``. * ``set_password()``, ``check_password()``, ``save()``, ``delete()``, ``set_groups()`` and ``set_permissions()`` raise ``NotImplementedError``. @@ -254,12 +259,12 @@ Once you have those middlewares installed, you'll be able to access ``request.user`` in views. ``request.user`` will give you a ``User`` object representing the currently logged-in user. If a user isn't currently logged in, ``request.user`` will be set to an instance of ``AnonymousUser`` (see the -previous section). You can tell them apart with ``is_anonymous()``, like so:: +previous section). You can tell them apart with ``is_authenticated()``, like so:: - if request.user.is_anonymous(): - # Do something for anonymous users. + if request.user.is_authenticated(): + # Do something for authenticated users. else: - # Do something for logged-in users. + # Do something for anonymous users. .. _request objects: http://www.djangoproject.com/documentation/request_response/#httprequest-objects .. _session documentation: http://www.djangoproject.com/documentation/sessions/ @@ -267,17 +272,54 @@ previous section). You can tell them apart with ``is_anonymous()``, like so:: How to log a user in -------------------- -To log a user in, do the following within a view:: +Django provides two functions in ``django.contrib.auth``: ``authenticate()`` +and ``login()``. + +To authenticate a given username and password, use ``authenticate()``. It +takes two keyword arguments, ``username`` and ``password``, and it returns +a ``User`` object if the password is valid for the given username. If the +password is invalid, ``authenticate()`` returns ``None``. Example:: + + from django.contrib.auth import authenticate + user = authenticate(username='john', password='secret') + if user is not None: + print "You provided a correct username and password!" + else: + print "Your username and password were incorrect." + +To log a user in, in a view, use ``login()``. It takes an ``HttpRequest`` +object and a ``User`` object. ``login()`` saves the user's ID in the session, +using Django's session framework, so, as mentioned above, you'll need to make +sure to have the session middleware installed. + +This example shows how you might use both ``authenticate()`` and ``login()``:: + + from django.contrib.auth import authenticate, login + + def my_view(request): + username = request.POST['username'] + password = request.POST['password'] + user = authenticate(username=username, password=password) + if user is not None: + login(request, user) + # Redirect to a success page. + else: + # Return an error message. - from django.contrib.auth.models import SESSION_KEY - request.session[SESSION_KEY] = some_user.id +How to log a user out +--------------------- -Because this uses sessions, you'll need to make sure you have -``SessionMiddleware`` enabled. See the `session documentation`_ for more -information. +To log out a user who has been logged in via ``django.contrib.auth.login()``, +use ``django.contrib.auth.logout()`` within your view. It takes an +``HttpRequest`` object and has no return value. Example:: -This assumes ``some_user`` is your ``User`` instance. Depending on your task, -you'll probably want to make sure to validate the user's username and password. + from django.contrib.auth import logout + + def logout_view(request): + logout(request) + # Redirect to a success page. + +Note that ``logout()`` doesn't throw any errors if the user wasn't logged in. Limiting access to logged-in users ---------------------------------- @@ -286,19 +328,19 @@ The raw way ~~~~~~~~~~~ The simple, raw way to limit access to pages is to check -``request.user.is_anonymous()`` and either redirect to a login page:: +``request.user.is_authenticated()`` and either redirect to a login page:: from django.http import HttpResponseRedirect def my_view(request): - if request.user.is_anonymous(): + if not request.user.is_authenticated(): return HttpResponseRedirect('/login/?next=%s' % request.path) # ... ...or display an error message:: def my_view(request): - if request.user.is_anonymous(): + if not request.user.is_authenticated(): return render_to_response('myapp/login_error.html') # ... @@ -402,7 +444,7 @@ For example, this view checks to make sure the user is logged in and has the permission ``polls.can_vote``:: def my_view(request): - if request.user.is_anonymous() or not request.user.has_perm('polls.can_vote'): + if not (request.user.is_authenticated() and request.user.has_perm('polls.can_vote')): return HttpResponse("You can't vote in this poll.") # ... @@ -560,7 +602,7 @@ The currently logged-in user and his/her permissions are made available in the setting contains ``"django.core.context_processors.auth"``, which is default. For more, see the `RequestContext docs`_. - .. _RequestContext docs: http://www.djangoproject.com/documentation/templates_python/#subclassing-context-djangocontext + .. _RequestContext docs: http://www.djangoproject.com/documentation/templates_python/#subclassing-context-requestcontext Users ----- @@ -568,10 +610,10 @@ Users The currently logged-in user, either a ``User`` instance or an``AnonymousUser`` instance, is stored in the template variable ``{{ user }}``:: - {% if user.is_anonymous %} - <p>Welcome, new user. Please log in.</p> + {% if user.is_authenticated %} + <p>Welcome, {{ user.username }}. Thanks for logging in.</p> {% else %} - <p>Welcome, {{ user.username }}. Thanks for logging in.</p> + <p>Welcome, new user. Please log in.</p> {% endif %} Permissions @@ -672,3 +714,117 @@ Finally, note that this messages framework only works with users in the user database. To send messages to anonymous users, use the `session framework`_. .. _session framework: http://www.djangoproject.com/documentation/sessions/ + +Other authentication sources +============================ + +The authentication that comes with Django is good enough for most common cases, +but you may have the need to hook into another authentication source -- that +is, another source of usernames and passwords or authentication methods. + +For example, your company may already have an LDAP setup that stores a username +and password for every employee. It'd be a hassle for both the network +administrator and the users themselves if users had separate accounts in LDAP +and the Django-based applications. + +So, to handle situations like this, the Django authentication system lets you +plug in another authentication sources. You can override Django's default +database-based scheme, or you can use the default system in tandem with other +systems. + +Specifying authentication backends +---------------------------------- + +Behind the scenes, Django maintains a list of "authentication backends" that it +checks for authentication. When somebody calls +``django.contrib.auth.authenticate()`` -- as described in "How to log a user in" +above -- Django tries authenticating across all of its authentication backends. +If the first authentication method fails, Django tries the second one, and so +on, until all backends have been attempted. + +The list of authentication backends to use is specified in the +``AUTHENTICATION_BACKENDS`` setting. This should be a tuple of Python path +names that point to Python classes that know how to authenticate. These classes +can be anywhere on your Python path. + +By default, ``AUTHENTICATION_BACKENDS`` is set to:: + + ('django.contrib.auth.backends.ModelBackend',) + +That's the basic authentication scheme that checks the Django users database. + +The order of ``AUTHENTICATION_BACKENDS`` matters, so if the same username and +password is valid in multiple backends, Django will stop processing at the +first positive match. + +Writing an authentication backend +--------------------------------- + +An authentication backend is a class that implements two methods: +``get_user(id)`` and ``authenticate(**credentials)``. + +The ``get_user`` method takes an ``id`` -- which could be a username, database +ID or whatever -- and returns a ``User`` object. + +The ``authenticate`` method takes credentials as keyword arguments. Most of +the time, it'll just look like this:: + + class MyBackend: + def authenticate(username=None, password=None): + # Check the username/password and return a User. + +But it could also authenticate a token, like so:: + + class MyBackend: + def authenticate(token=None): + # Check the token and return a User. + +Either way, ``authenticate`` should check the credentials it gets, and it +should return a ``User`` object that matches those credentials, if the +credentials are valid. If they're not valid, it should return ``None``. + +The Django admin system is tightly coupled to the Django ``User`` object +described at the beginning of this document. For now, the best way to deal with +this is to create a Django ``User`` object for each user that exists for your +backend (e.g., in your LDAP directory, your external SQL database, etc.) You +can either write a script to do this in advance, or your ``authenticate`` +method can do it the first time a user logs in. + +Here's an example backend that authenticates against a username and password +variable defined in your ``settings.py`` file and creates a Django ``User`` +object the first time a user authenticates:: + + from django.conf import settings + from django.contrib.auth.models import User, check_password + + class SettingsBackend: + """ + Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD. + + Use the login name, and a hash of the password. For example: + + ADMIN_LOGIN = 'admin' + ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de' + """ + def authenticate(self, username=None, password=None): + login_valid = (settings.ADMIN_LOGIN == username) + pwd_valid = check_password(password, settings.ADMIN_PASSWORD) + if login_valid and pwd_valid: + try: + user = User.objects.get(username=username) + except User.DoesNotExist: + # Create a new user. Note that we can set password + # to anything, because it won't be checked; the password + # from settings.py will. + user = User(username=username, password='get from settings.py') + user.is_staff = True + user.is_superuser = True + user.save() + return user + return None + + def get_user(self, user_id): + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + return None diff --git a/docs/cache.txt b/docs/cache.txt index 2ef3d6503f..62fec289b9 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -230,8 +230,13 @@ Then, add the following required settings to your Django settings file: collisions. Use an empty string if you don't care. The cache middleware caches every page that doesn't have GET or POST -parameters. Additionally, ``CacheMiddleware`` automatically sets a few headers -in each ``HttpResponse``: +parameters. Optionally, if the ``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting is +``True``, only anonymous requests (i.e., not those made by a logged-in user) +will be cached. This is a simple and effective way of disabling caching for any +user-specific pages (include Django's admin interface). + +Additionally, ``CacheMiddleware`` automatically sets a few headers in each +``HttpResponse``: * Sets the ``Last-Modified`` header to the current date/time when a fresh (uncached) version of the page is requested. diff --git a/docs/contributing.txt b/docs/contributing.txt index d7552cdc7c..9d116cac10 100644 --- a/docs/contributing.txt +++ b/docs/contributing.txt @@ -168,6 +168,10 @@ Please follow these coding standards when writing code for inclusion in Django: {{foo}} + * Please don't put your name in the code. While we appreciate all + contributions to Django, our policy is not to publish individual + developer names in code -- for instance, at the top of Python modules. + Committing code =============== @@ -212,6 +216,10 @@ repository: first, then the "Fixed #abc." For example: "magic-removal: Fixed #123 -- Added whizbang feature." + For the curious: We're using a `Trac post-commit hook`_ for this. + + .. _Trac post-commit hook: http://trac.edgewall.org/browser/trunk/contrib/trac-post-commit-hook + * If your commit references a ticket in the Django `ticket tracker`_ but does *not* close the ticket, include the phrase "Refs #abc", where "abc" is the number of the ticket your commit references. We've rigged diff --git a/docs/db-api.txt b/docs/db-api.txt index 5108949184..a7cf30813d 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -60,6 +60,10 @@ the database until you explicitly call ``save()``. The ``save()`` method has no return value. +To create an object and save it all in one step see the `create`__ method. + +__ `create(**kwargs)`_ + Auto-incrementing primary keys ------------------------------ @@ -574,6 +578,9 @@ related ``Person`` *and* the related ``City``:: p = b.author # Hits the database. c = p.hometown # Hits the database. +Note that ``select_related()`` does not follow foreign keys that have +``null=True``. + ``extra(select=None, where=None, params=None, tables=None)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -705,6 +712,20 @@ The ``DoesNotExist`` exception inherits from except ObjectDoesNotExist: print "Either the entry or blog doesn't exist." +``create(**kwargs)`` +~~~~~~~~~~~~~~~~~~~~ + +A convenience method for creating an object and saving it all in one step. Thus:: + + p = Person.objects.create(first_name="Bruce", last_name="Springsteen") + +and:: + + p = Person(first_name="Bruce", last_name="Springsteen") + p.save() + +are equivalent. + ``get_or_create(**kwargs)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1103,35 +1124,37 @@ Note this is only available in MySQL and requires direct manipulation of the database to add the full-text index. Default lookups are exact -~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------- If you don't provide a lookup type -- that is, if your keyword argument doesn't contain a double underscore -- the lookup type is assumed to be ``exact``. For example, the following two statements are equivalent:: - Blog.objects.get(id=14) - Blog.objects.get(id__exact=14) + Blog.objects.get(id__exact=14) # Explicit form + Blog.objects.get(id=14) # __exact is implied This is for convenience, because ``exact`` lookups are the common case. The pk lookup shortcut -~~~~~~~~~~~~~~~~~~~~~~ +---------------------- For convenience, Django provides a ``pk`` lookup type, which stands for "primary_key". This is shorthand for "an exact lookup on the primary-key." In the example ``Blog`` model, the primary key is the ``id`` field, so these -two statements are equivalent:: +three statements are equivalent:: - Blog.objects.get(id__exact=14) - Blog.objects.get(pk=14) + Blog.objects.get(id__exact=14) # Explicit form + Blog.objects.get(id=14) # __exact is implied + Blog.objects.get(pk=14) # pk implies id__exact -``pk`` lookups also work across joins. For example, these two statements are +``pk`` lookups also work across joins. For example, these three statements are equivalent:: - Entry.objects.filter(blog__id__exact=3) - Entry.objects.filter(blog__pk=3) + Entry.objects.filter(blog__id__exact=3) # Explicit form + Entry.objects.filter(blog__id=3) # __exact is implied + Entry.objects.filter(blog__pk=3) # __pk implies __id__exact Lookups that span relationships ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1532,6 +1555,21 @@ loaded, Django iterates over every model in ``INSTALLED_APPS`` and creates the backward relationships in memory as needed. Essentially, one of the functions of ``INSTALLED_APPS`` is to tell Django the entire model domain. +Queries over related objects +---------------------------- + +Queries involving related objects follow the same rules as queries involving +normal value fields. When specifying the the value for a query to match, you +may use either an object instance itself, or the primary key value for the +object. + +For example, if you have a Blog object ``b`` with ``id=5``, the following +three queries would be identical:: + + Entry.objects.filter(blog=b) # Query using object instance + Entry.objects.filter(blog=b.id) # Query using id from instance + Entry.objects.filter(blog=5) # Query using id directly + Deleting objects ================ diff --git a/docs/design_philosophies.txt b/docs/design_philosophies.txt index 17ed3ad6da..7fdc7ea01b 100644 --- a/docs/design_philosophies.txt +++ b/docs/design_philosophies.txt @@ -274,8 +274,8 @@ Loose coupling A view shouldn't care about which template system the developer uses -- or even whether a template system is used at all. -Designate between GET and POST ------------------------------- +Differentiate between GET and POST +---------------------------------- GET and POST are distinct; developers should explicitly use one or the other. The framework should make it easy to distinguish between GET and POST data. diff --git a/docs/django-admin.txt b/docs/django-admin.txt index 3334ae4530..eb7b2dccd6 100644 --- a/docs/django-admin.txt +++ b/docs/django-admin.txt @@ -126,8 +126,9 @@ you run it, you'll want to look over the generated models yourself to make customizations. In particular, you'll need to rearrange models' order, so that models that refer to other models are ordered properly. -Primary keys are automatically introspected for PostgreSQL and MySQL, in which -case Django puts in the ``primary_key=True`` where needed. +Primary keys are automatically introspected for PostgreSQL, MySQL and +SQLite, in which case Django puts in the ``primary_key=True`` where +needed. ``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection only works in PostgreSQL and with certain types of MySQL tables. @@ -191,6 +192,14 @@ documentation. .. _serving static files: http://www.djangoproject.com/documentation/static_files/ +Turning off auto-reload +~~~~~~~~~~~~~~~~~~~~~~~ + +To disable auto-reloading of code while the development server is running, use the +``--noreload`` option, like so:: + + django-admin.py runserver --noreload + shell ----- diff --git a/docs/faq.txt b/docs/faq.txt index adcdbaca59..36fb547cf1 100644 --- a/docs/faq.txt +++ b/docs/faq.txt @@ -156,7 +156,7 @@ logical to us. ----------------------------------------------------- We're well aware that there are other awesome Web frameworks out there, and -we're not adverse to borrowing ideas where appropriate. However, Django was +we're not averse to borrowing ideas where appropriate. However, Django was developed precisely because we were unhappy with the status quo, so please be aware that "because <Framework X>" does it is not going to be sufficient reason to add a given feature to Django. @@ -251,6 +251,16 @@ information than the docs that come with the latest Django release. .. _stored in revision control: http://code.djangoproject.com/browser/django/trunk/docs +Where can I find Django developers for hire? +-------------------------------------------- + +Consult our `developers for hire page`_ for a list of Django developers who +would be happy to help you. + +You might also be interested in posting a job to http://www.gypsyjobs.com/ . + +.. _developers for hire page: http://code.djangoproject.com/wiki/DevelopersForHire + Installation questions ====================== @@ -301,16 +311,18 @@ PostgreSQL fans, and MySQL_ and `SQLite 3`_ are also supported. Do I have to use mod_python? ---------------------------- -Not if you just want to play around and develop things on your local computer. -Django comes with its own Web server, and things should Just Work. +Although we recommend mod_python for production use, you don't have to use it, +thanks to the fact that Django uses an arrangement called WSGI_. Django can +talk to any WSGI-enabled server. The most common non-mod_python deployment +setup is FastCGI. See `How to use Django with FastCGI`_ for full information. -For production use, though, we recommend mod_python. The Django developers have -been running it on mod_python for several years, and it's quite stable. +Also, see the `server arrangements wiki page`_ for other deployment strategies. -However, if you don't want to use mod_python, you can use a different server, -as long as that server has WSGI_ hooks. See the `server arrangements wiki page`_. +If you just want to play around and develop things on your local computer, use +the development Web server that comes with Django. Things should Just Work. .. _WSGI: http://www.python.org/peps/pep-0333.html +.. _How to use Django with FastCGI: http://www.djangoproject.com/documentation/fastcgi/ .. _server arrangements wiki page: http://code.djangoproject.com/wiki/ServerArrangements How do I install mod_python on Windows? @@ -409,6 +421,36 @@ Using a ``FileField`` or an ``ImageField`` in a model takes a few steps: absolute URL to your image in a template with ``{{ object.get_mug_shot_url }}``. +Databases and models +==================== + +How can I see the raw SQL queries Django is running? +---------------------------------------------------- + +Make sure your Django ``DEBUG`` setting is set to ``True``. Then, just do +this:: + + >>> from django.db import connection + >>> connection.queries + [{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls', + 'time': '0.002'}] + +``connection.queries`` is only available if ``DEBUG`` is ``True``. It's a list +of dictionaries in order of query execution. Each dictionary has the following:: + + ``sql`` -- The raw SQL statement + ``time`` -- How long the statement took to execute, in seconds. + +``connection.queries`` includes all SQL statements -- INSERTs, UPDATES, +SELECTs, etc. Each time your app hits the database, the query will be recorded. + +Can I use Django with a pre-existing database? +---------------------------------------------- + +Yes. See `Integrating with a legacy database`_. + +.. _`Integrating with a legacy database`: http://www.djangoproject.com/documentation/legacy_databases/ + If I make changes to a model, how do I update the database? ----------------------------------------------------------- @@ -437,35 +479,24 @@ uniqueness at that level. Single-column primary keys are needed for things such as the admin interface to work; e.g., you need a simple way of being able to specify an object to edit or delete. -The database API -================ +How do I add database-specific options to my CREATE TABLE statements, such as specifying MyISAM as the table type? +------------------------------------------------------------------------------------------------------------------ -How can I see the raw SQL queries Django is running? ----------------------------------------------------- +We try to avoid adding special cases in the Django code to accomodate all the +database-specific options such as table type, etc. If you'd like to use any of +these options, create an `SQL initial data file`_ that contains ``ALTER TABLE`` +statements that do what you want to do. The initial data files are executed in +your database after the ``CREATE TABLE`` statements. -Make sure your Django ``DEBUG`` setting is set to ``True``. Then, just do -this:: +For example, if you're using MySQL and want your tables to use the MyISAM table +type, create an initial data file and put something like this in it:: - >>> from django.db import connection - >>> connection.queries - [{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls', - 'time': '0.002'}] + ALTER TABLE myapp_mytable ENGINE=MyISAM; -``connection.queries`` is only available if ``DEBUG`` is ``True``. It's a list -of dictionaries in order of query execution. Each dictionary has the following:: +As explained in the `SQL initial data file`_ documentation, this SQL file can +contain arbitrary SQL, so you can make any sorts of changes you need to make. - ``sql`` -- The raw SQL statement - ``time`` -- How long the statement took to execute, in seconds. - -``connection.queries`` includes all SQL statements -- INSERTs, UPDATES, -SELECTs, etc. Each time your app hits the database, the query will be recorded. - -Can I use Django with a pre-existing database? ----------------------------------------------- - -Yes. See `Integrating with a legacy database`_. - -.. _`Integrating with a legacy database`: http://www.djangoproject.com/documentation/legacy_databases/ +.. _SQL initial data file: http://www.djangoproject.com/documentation/model_api/#providing-initial-sql-data Why is Django leaking memory? ----------------------------- @@ -514,13 +545,26 @@ If you're sure your username and password are correct, make sure your user account has ``is_active`` and ``is_staff`` set to True. The admin site only allows access to users with those two fields both set to True. +How can I prevent the cache middleware from caching the admin site? +------------------------------------------------------------------- + +Set the ``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting to ``True``. See the +`cache documentation`_ for more information. + +.. _cache documentation: ../cache/#the-per-site-cache + How do I automatically set a field's value to the user who last edited the object in the admin? ----------------------------------------------------------------------------------------------- -At this point, you can't do this. But it's an oft-requested feature, so we're -discussing how it can be implemented. The problem is we don't want to couple -the model layer with the admin layer with the request layer (to get the current -user). It's a tricky problem. +At this point, Django doesn't have an official way to do this. But it's an oft-requested +feature, so we're discussing how it can be implemented. The problem is we don't want to couple +the model layer with the admin layer with the request layer (to get the current user). It's a +tricky problem. + +One person hacked up a `solution that doesn't require patching Django`_, but note that it's an +unofficial solution, and there's no guarantee it won't break at some point. + +.. _solution that doesn't require patching Django: http://lukeplant.me.uk/blog.php?id=1107301634 How do I limit admin access so that objects can only be edited by the users who created them? --------------------------------------------------------------------------------------------- @@ -585,3 +629,21 @@ To create a user, you'll have to use the Python API. See `creating users`_ for full info. .. _creating users: http://www.djangoproject.com/documentation/authentication/#creating-users + +Contributing code +================= + +I submitted a bug fix in the ticket system several weeks ago. Why are you ignoring my patch? +-------------------------------------------------------------------------------------------- + +Don't worry: We're not ignoring you! + +It's important to understand there is a difference between "a ticket is being +ignored" and "a ticket has not been attended to yet." Django's ticket system +contains hundreds of open tickets, of various degrees of impact on end-user +functionality, and Django's developers have to review and prioritize. + +Besides, if your feature request stands no chance of inclusion in Django, we +won't ignore it -- we'll just close the ticket. So if your ticket is still +open, it doesn't mean we're ignoring you; it just means we haven't had time to +look at it yet. diff --git a/docs/fastcgi.txt b/docs/fastcgi.txt index 41b9561b6d..41d50d97a1 100644 --- a/docs/fastcgi.txt +++ b/docs/fastcgi.txt @@ -2,7 +2,7 @@ How to use Django with FastCGI ============================== -Although the current preferred setup for running Django is Apache_ with +Although the `current preferred setup`_ for running Django is Apache_ with `mod_python`_, many people use shared hosting, on which FastCGI is the only viable option. In some setups, FastCGI also allows better security -- and, possibly, better performance -- than mod_python. @@ -17,6 +17,7 @@ served with no startup time. Unlike mod_python (or `mod_perl`_), a FastCGI process doesn't run inside the Web server process, but in a separate, persistent process. +.. _current preferred setup: http://www.djangoproject.com/documentation/modpython/ .. _Apache: http://httpd.apache.org/ .. _mod_python: http://www.modpython.org/ .. _mod_perl: http://perl.apache.org/ @@ -35,6 +36,16 @@ persistent process. security benefit on shared systems, because it means you can secure your code from other users. +Prerequisite: flup +================== + +Before you can start using FastCGI with Django, you'll need to install flup_, +which is a Python library for dealing with FastCGI. Make sure to use the latest +Subversion snapshot of flup, as some users have reported stalled pages with +older flup versions. + +.. _flup: http://www.saddi.com/software/flup/ + Starting your FastCGI server ============================ @@ -120,18 +131,53 @@ Apache setup ============ To use Django with Apache and FastCGI, you'll need Apache installed and -configured, with mod_fastcgi installed and enabled. Consult the Apache +configured, with `mod_fastcgi`_ installed and enabled. Consult the Apache documentation for instructions. -Add the following to your ``httpd.conf``:: +Once you've got that set up, point Apache at your Django FastCGI instance by +editing the ``httpd.conf`` (Apache configuration) file. You'll need to do two +things: + + * Use the ``FastCGIExternalServer`` directive to specify the location of + your FastCGI server. + * Use ``mod_rewrite`` to point URLs at FastCGI as appropriate. + +.. _mod_fastcgi: http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html + +Specifying the location of the FastCGI server +--------------------------------------------- + +The ``FastCGIExternalServer`` directive tells Apache how to find your FastCGI +server. As the `FastCGIExternalServer docs`_ explain, you can specify either a +``socket`` or a ``host``. Here are examples of both:: - # Connect to FastCGI via a socket / named pipe + # Connect to FastCGI via a socket / named pipe. FastCGIExternalServer /home/user/public_html/mysite.fcgi -socket /home/user/mysite.sock - # Connect to FastCGI via a TCP host/port - # FastCGIExternalServer /home/user/public_html/mysite.fcgi -host 127.0.0.1:3033 - <VirtualHost 64.92.160.91> - ServerName mysite.com + # Connect to FastCGI via a TCP host/port. + FastCGIExternalServer /home/user/public_html/mysite.fcgi -host 127.0.0.1:3033 + +In either case, the file ``/home/user/public_html/mysite.fcgi`` doesn't +actually have to exist. It's just a URL used by the Web server internally -- a +hook for signifying which requests at a URL should be handled by FastCGI. (More +on this in the next section.) + +.. _FastCGIExternalServer docs: http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html#FastCgiExternalServer + +Using mod_rewrite to point URLs at FastCGI +------------------------------------------ + +The second step is telling Apache to use FastCGI for URLs that match a certain +pattern. To do this, use the `mod_rewrite`_ module and rewrite URLs to +``mysite.fcgi`` (or whatever you specified in the ``FastCGIExternalServer`` +directive, as explained in the previous section). + +In this example, we tell Apache to use FastCGI to handle any request that +doesn't represent a file on the filesystem and doesn't start with ``/media/``. +This is probably the most common case, if you're using Django's admin site:: + + <VirtualHost 12.34.56.78> + ServerName example.com DocumentRoot /home/user/public_html Alias /media /home/user/python/django/contrib/admin/media RewriteEngine On @@ -140,22 +186,18 @@ Add the following to your ``httpd.conf``:: RewriteRule ^/(.*)$ /mysite.fcgi/$1 [QSA,L] </VirtualHost> -Note that while you have to specify a mysite.fcgi, that this file doesn't -actually have to exist. It is just an internal URL to the webserver which -signifies that any requests to that URL will go to the external FastCGI -server. +.. _mod_rewrite: http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html -LigHTTPd Setup +lighttpd setup ============== -LigHTTPd is a light-weight asynchronous web-server, which is commonly used -for serving static files. However, it supports FastCGI natively, and as such -is a very good choice for serving both static and dynamic media, if your site -does not have any apache-specific components. +lighttpd is a lightweight Web server commonly used for serving static files. It +supports FastCGI natively and, thus, is a good choice for serving both static +and dynamic pages, if your site doesn't have any Apache-specific needs. Make sure ``mod_fastcgi`` is in your modules list, somewhere after -mod_rewrite and mod_access, but not after mod_accesslog. You'll probably -want mod_alias as well, for serving admin media. +``mod_rewrite`` and ``mod_access``, but not after ``mod_accesslog``. You'll +probably want ``mod_alias`` as well, for serving admin media. Add the following to your lighttpd config file:: @@ -165,7 +207,7 @@ Add the following to your lighttpd config file:: "main" => ( # Use host / port instead of socket for TCP fastcgi # "host" => "127.0.0.1", - # "port" => 3033, + # "port" => 3033, "socket" => "/home/user/mysite.sock", "check-local" => "disable", ) @@ -181,14 +223,15 @@ Add the following to your lighttpd config file:: "^(/.*)$" => "/mysite.fcgi$1", ) -Running multiple django sites on one LigHTTPd +Running multiple Django sites on one lighttpd --------------------------------------------- -LigHTTPd allows you to use what is called conditional configuration to allow -configuration to be customized per-host. In order to specify multiple fastcgi -sites, simply add a conditional block around your fastcgi config for each site:: +lighttpd lets you use "conditional configuration" to allow configuration to be +customized per host. To specify multiple FastCGI sites, just add a conditional +block around your FastCGI config for each site:: - $HTTP["host"] == "www.website1.com" { + # If the hostname is 'www.example1.com'... + $HTTP["host"] == "www.example1.com" { server.document-root = "/foo/site1" fastcgi.server = ( ... @@ -196,7 +239,8 @@ sites, simply add a conditional block around your fastcgi config for each site:: ... } - $HTTP["host"] == "www.website2.com" { + # If the hostname is 'www.example2.com'... + $HTTP["host"] == "www.example2.com" { server.document-root = "/foo/site2" fastcgi.server = ( ... @@ -204,44 +248,44 @@ sites, simply add a conditional block around your fastcgi config for each site:: ... } -You can also run multiple django installations on the same site simply by -specifying multiple entries in the ``fastcgi.server`` directive, add one -fastcgi host for each. +You can also run multiple Django installations on the same site simply by +specifying multiple entries in the ``fastcgi.server`` directive. Add one +FastCGI host for each. -Running Django on a shared-hosting provider -=========================================== +Running Django on a shared-hosting provider with Apache +======================================================= -For many users on shared-hosting providers, you aren't able to run your own -server daemons nor do they have access to the httpd.conf of their webserver. -However, it is still possible to run Django using webserver-spawned processes. +Many shared-hosting providers don't allow you to run your own server daemons or +edit the ``httpd.conf`` file. In these cases, it's still possible to run Django +using Web server-spawned processes. .. admonition:: Note - If you are using webserver-managed processes, there's no need for you - to start the FastCGI server on your own. Apache will spawn a number - of processes, scaling as it needs to. + If you're using Web server-spawned processes, as explained in this section, + there's no need for you to start the FastCGI server on your own. Apache + will spawn a number of processes, scaling as it needs to. -In your web root directory, add this to a file named .htaccess :: +In your Web root directory, add this to a file named ``.htaccess`` :: AddHandler fastcgi-script .fcgi RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^/(.*)$ /mysite.fcgi/$1 [QSA,L] -Now you must add a small shim script in order for apache to properly -spawn your FastCGI program. Create a mysite.fcgi and place it in your -web directory, making it executable :: +Then, create a small script that tells Apache how to spawn your FastCGI +program. Create a file ``mysite.fcgi`` and place it in your Web directory, and +be sure to make it executable :: #!/usr/bin/python import sys, os - # add a custom pythonpath + # Add a custom Python path. sys.path.insert(0, "/home/user/python") - # switch to the directory of your project. (optional) + # Switch to the directory of your project. (Optional.) # os.chdir("/home/user/myproject") - # change to the name of your app's settings module + # Set the DJANGO_SETTINGS_MODULE environment variable. os.environ['DJANGO_SETTINGS_MODULE'] = "myproject.settings" from django.core.servers.fastcgi import runfastcgi @@ -250,13 +294,13 @@ web directory, making it executable :: Restarting the spawned server ----------------------------- -If you change the code of your site, to make apache re-load your django -application, you do not need to restart the server. Simply re-upload or -edit your ``mysite.fcgi`` in such a way that the timestamp on the file -will change. When apache sees that the file has been updated, it will -restart your django application for you. +If you change any Python code on your site, you'll need to tell FastCGI the +code has changed. But there's no need to restart Apache in this case. Rather, +just reupload ``mysite.fcgi``, or edit the file, so that the timestamp on the +file will change. When Apache sees the file has been updated, it will restart +your Django application for you. -If you have access to a command shell on a unix system, restarting the -server can be done with the ``touch`` command:: +If you have access to a command shell on a Unix system, you can accomplish this +easily by using the ``touch`` command:: touch mysite.fcgi diff --git a/docs/forms.txt b/docs/forms.txt index 5026bc1bab..67408f3c5d 100644 --- a/docs/forms.txt +++ b/docs/forms.txt @@ -50,7 +50,7 @@ model that "knows" how to create or modify instances of that model and how to validate data for the object. Manipulators come in two flavors: ``AddManipulators`` and ``ChangeManipulators``. Functionally they are quite similar, but the former knows how to create new instances of the model, while -the later modifies existing instances. Both types of classes are automatically +the latter modifies existing instances. Both types of classes are automatically created when you define a new class:: >>> from mysite.myapp.models import Place @@ -404,6 +404,43 @@ Here's a simple function that might drive the above form:: errors = new_data = {} form = forms.FormWrapper(manipulator, new_data, errors) return render_to_response('contact_form.html', {'form': form}) + +``FileField`` and ``ImageField`` special cases +============================================== + +Dealing with ``FileField`` and ``ImageField`` objects is a little more +complicated. + +First, you'll need to make sure that your ``<form>`` element correctly defines +the ``enctype`` as ``"multipart/form-data"``, in order to upload files:: + + <form enctype="multipart/form-data" method="post" action="/foo/"> + +Next, you'll need to treat the field in the template slightly differently. A +``FileField`` or ``ImageField`` is represented by *two* HTML form elements. + +For example, given this field in a model:: + + photo = model.ImageField('/path/to/upload/location') + +You'd need to display two formfields in the template:: + + <p><label for="id_photo">Photo:</label> {{ form.photo }}{{ form.photo_file }}</p> + +The first bit (``{{ form.photo }}``) displays the currently-selected file, +while the second (``{{ form.photo_file }}``) actually contains the file upload +form field. Thus, at the validation layer you need to check the ``photo_file`` +key. + +Finally, in your view, make sure to access ``request.FILES``, rather than +``request.POST``, for the uploaded files. This is necessary because +``request.POST`` does not contain file-upload data. + +For example, following the ``new_data`` convention, you might do something like +this:: + + new_data = request.POST.copy() + new_data.update(request.FILES) Validators ========== diff --git a/docs/generic_views.txt b/docs/generic_views.txt index d14fe12418..aab878467f 100644 --- a/docs/generic_views.txt +++ b/docs/generic_views.txt @@ -148,7 +148,8 @@ are views for displaying drilldown pages for date-based data. **Description:** A top-level index page showing the "latest" objects, by date. Objects with -a date in the *future* are not included. +a date in the *future* are not included unless you set ``allow_future`` to +``True``. **Required arguments:** @@ -185,6 +186,11 @@ a date in the *future* are not included. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_MIME_TYPE`` setting. + * ``allow_future``: A boolean specifying whether to include "future" + objects on this page, where "future" means objects in which the field + specified in ``date_field`` is greater than the current date/time. By + default, this is ``False``. + **Template name:** If ``template_name`` isn't specified, this view will use the template @@ -217,7 +223,8 @@ In addition to ``extra_context``, the template's context will be: **Description:** A yearly archive page showing all available months in a given year. Objects -with a date in the *future* are not displayed. +with a date in the *future* are not displayed unless you set ``allow_future`` +to ``True``. **Required arguments:** @@ -265,6 +272,11 @@ with a date in the *future* are not displayed. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_MIME_TYPE`` setting. + * ``allow_future``: A boolean specifying whether to include "future" + objects on this page, where "future" means objects in which the field + specified in ``date_field`` is greater than the current date/time. By + default, this is ``False``. + **Template name:** If ``template_name`` isn't specified, this view will use the template @@ -296,7 +308,8 @@ In addition to ``extra_context``, the template's context will be: **Description:** A monthly archive page showing all objects in a given month. Objects with a -date in the *future* are not displayed. +date in the *future* are not displayed unless you set ``allow_future`` to +``True``. **Required arguments:** @@ -346,6 +359,11 @@ date in the *future* are not displayed. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_MIME_TYPE`` setting. + * ``allow_future``: A boolean specifying whether to include "future" + objects on this page, where "future" means objects in which the field + specified in ``date_field`` is greater than the current date/time. By + default, this is ``False``. + **Template name:** If ``template_name`` isn't specified, this view will use the template @@ -378,7 +396,7 @@ In addition to ``extra_context``, the template's context will be: **Description:** A weekly archive page showing all objects in a given week. Objects with a date -in the *future* are not displayed. +in the *future* are not displayed unless you set ``allow_future`` to ``True``. **Required arguments:** @@ -422,6 +440,11 @@ in the *future* are not displayed. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_MIME_TYPE`` setting. + * ``allow_future``: A boolean specifying whether to include "future" + objects on this page, where "future" means objects in which the field + specified in ``date_field`` is greater than the current date/time. By + default, this is ``False``. + **Template name:** If ``template_name`` isn't specified, this view will use the template @@ -445,7 +468,8 @@ In addition to ``extra_context``, the template's context will be: **Description:** A day archive page showing all objects in a given day. Days in the future throw -a 404 error, regardless of whether any objects exist for future days. +a 404 error, regardless of whether any objects exist for future days, unless +you set ``allow_future`` to ``True``. **Required arguments:** @@ -501,6 +525,11 @@ a 404 error, regardless of whether any objects exist for future days. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_MIME_TYPE`` setting. + * ``allow_future``: A boolean specifying whether to include "future" + objects on this page, where "future" means objects in which the field + specified in ``date_field`` is greater than the current date/time. By + default, this is ``False``. + **Template name:** If ``template_name`` isn't specified, this view will use the template @@ -537,7 +566,9 @@ and today's date is used instead. **Description:** -A page representing an individual object. +A page representing an individual object. If the object has a date value in the +future, the view will throw a 404 error by default, unless you set +``allow_future`` to ``True``. **Required arguments:** @@ -604,6 +635,11 @@ A page representing an individual object. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_MIME_TYPE`` setting. + * ``allow_future``: A boolean specifying whether to include "future" + objects on this page, where "future" means objects in which the field + specified in ``date_field`` is greater than the current date/time. By + default, this is ``False``. + **Template name:** If ``template_name`` isn't specified, this view will use the template diff --git a/docs/i18n.txt b/docs/i18n.txt index 1220ea95b3..e12900c2f9 100644 --- a/docs/i18n.txt +++ b/docs/i18n.txt @@ -35,12 +35,25 @@ How to internationalize your app: in three steps support. 3. Activate the locale middleware in your Django settings. - .. admonition:: Behind the scenes Django's translation machinery uses the standard ``gettext`` module that comes with Python. +If you don't need internationalization +====================================== + +Django's internationalization hooks are on by default, and that means there's a +bit of i18n-related overhead in certain places of the framework. If you don't +use internationalization, you should take the two seconds to set +``USE_I18N = False`` in your settings file. If ``USE_I18N`` is set to +``False``, then Django will make some optimizations so as not to load the +internationalization machinery. + +See the `documentation for USE_I18N`_. + +.. _documentation for USE_I18N: http://www.djangoproject.com/documentation/settings/#use-i18n + How to specify translation strings ================================== @@ -211,11 +224,18 @@ block:: This will have {{ myvar }} inside. {% endblocktrans %} +If you need to bind more than one expression inside a ``blocktrans`` tag, +separate the pieces with ``and``:: + + {% blocktrans with book|title as book_t and author|title as author_t %} + This is {{ book_t }} by {{ author_t }} + {% endblocktrans %} + To pluralize, specify both the singular and plural forms with the ``{% plural %}`` tag, which appears within ``{% blocktrans %}`` and ``{% endblocktrans %}``. Example:: - {% blocktrans count list|counted as counter %} + {% blocktrans count list|count as counter %} There is only one {{ name }} object. {% plural %} There are {{ counter }} {{ name }} objects. @@ -293,7 +313,7 @@ marked for translation. It creates (or updates) a message file in the directory ``conf/locale``. In the ``de`` example, the file will be ``conf/locale/de/LC_MESSAGES/django.po``. -If run over your project source tree or your appliation source tree, it will +If run over your project source tree or your application source tree, it will do the same, but the location of the locale directory is ``locale/LANG/LC_MESSAGES`` (note the missing ``conf`` prefix). @@ -336,7 +356,7 @@ A quick explanation: Long messages are a special case. There, the first string directly after the ``msgstr`` (or ``msgid``) is an empty string. Then the content itself will be written over the next few lines as one string per line. Those strings are -directlyconcatenated. Don't forget trailing spaces within the strings; +directly concatenated. Don't forget trailing spaces within the strings; otherwise, they'll be tacked together without whitespace! .. admonition:: Mind your charset @@ -439,7 +459,7 @@ Notes: ``de``. * Only languages listed in the `LANGUAGES setting`_ can be selected. If you want to restrict the language selection to a subset of provided - languages (because your appliaction doesn't provide all those languages), + languages (because your application doesn't provide all those languages), set ``LANGUAGES`` to a list of languages. For example:: LANGUAGES = ( @@ -452,6 +472,30 @@ Notes: en-us). .. _LANGUAGES setting: http://www.djangoproject.com/documentation/settings/#languages + + * If you define a custom ``LANGUAGES`` setting, as explained in the + previous bullet, it's OK to mark the languages as translation strings + -- but use a "dummy" ``gettext()`` function, not the one in + ``django.utils.translation``. You should *never* import + ``django.utils.translation`` from within your settings file, because that + module in itself depends on the settings, and that would cause a circular + import. + + The solution is to use a "dummy" ``gettext()`` function. Here's a sample + settings file:: + + gettext = lambda s: s + + LANGUAGES = ( + ('de', gettext('German')), + ('en', gettext('English')), + ) + + With this arrangement, ``make-messages.py`` will still find and mark + these strings for translation, but the translation won't happen at + runtime -- so you'll have to remember to wrap the languages in the *real* + ``gettext()`` in any code that uses ``LANGUAGES`` at runtime. + * The ``LocaleMiddleware`` can only select languages for which there is a Django-provided base translation. If you want to provide translations for your application that aren't already in the set of translations @@ -610,7 +654,7 @@ The ``javascript_catalog`` view ------------------------------- The main solution to these problems is the ``javascript_catalog`` view, which -sends out a JavaScript code library with functions that mimick the ``gettext`` +sends out a JavaScript code library with functions that mimic the ``gettext`` interface, plus an array of translation strings. Those translation strings are taken from the application, project or Django core, according to what you specify in either the {{{info_dict}}} or the URL. @@ -628,7 +672,7 @@ You hook it up like this:: Each string in ``packages`` should be in Python dotted-package syntax (the same format as the strings in ``INSTALLED_APPS``) and should refer to a package that contains a ``locale`` directory. If you specify multiple packages, all -those catalogs aremerged into one catalog. This is useful if you have +those catalogs are merged into one catalog. This is useful if you have JavaScript that uses strings from different applications. You can make the view dynamic by putting the packages into the URL pattern:: diff --git a/docs/install.txt b/docs/install.txt index 800c49b596..fb1bd73122 100644 --- a/docs/install.txt +++ b/docs/install.txt @@ -77,9 +77,9 @@ It's easy either way. Installing the official version ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -1. Download Django-0.91.tar.gz from our `download page`_. -2. ``tar xzvf Django-0.91.tar.gz`` -3. ``cd Django-0.91`` +1. Download Django-0.95.tar.gz from our `download page`_. +2. ``tar xzvf Django-0.95.tar.gz`` +3. ``cd Django-0.95`` 4. ``sudo python setup.py install`` Note that the last command will automatically download and install setuptools_ @@ -89,14 +89,6 @@ connection. This will install Django in your Python installation's ``site-packages`` directory. -.. note:: - - Due to recent backwards-incompatible changes, it is strongly recommended - that you use the development version (below) for any new applications or - if you are just starting to work with Django. The 0.91 release is a - dead-end branch that is primarily of use for supporting legacy Django - applications. - .. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools Installing the development version diff --git a/docs/model-api.txt b/docs/model-api.txt index 0dc98416a3..c369508c65 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -1225,6 +1225,51 @@ A few special cases to note about ``list_display``: return self.birthday.strftime('%Y')[:3] + "0's" decade_born_in.short_description = 'Birth decade' + * If the string given is a method of the model, Django will HTML-escape the + output by default. If you'd rather not escape the output of the method, + give the method an ``allow_tags`` attribute whose value is ``True``. + + Here's a full example model:: + + class Person(models.Model): + first_name = models.CharField(maxlength=50) + last_name = models.CharField(maxlength=50) + color_code = models.CharField(maxlength=6) + + class Admin: + list_display = ('first_name', 'last_name', 'colored_name') + + def colored_name(self): + return '<span style="color: #%s;">%s %s</span>' % (self.color_code, self.first_name, self.last_name) + colored_name.allow_tags = True + +``list_display_links`` +---------------------- + +Set ``list_display_links`` to control which fields in ``list_display`` should +be linked to the "change" page for an object. + +By default, the change list page will link the first column -- the first field +specified in ``list_display`` -- to the change page for each item. But +``list_display_links`` lets you change which columns are linked. Set +``list_display_links`` to a list or tuple of field names (in the same format as +``list_display``) to link. + +``list_display_links`` can specify one or many field names. As long as the +field names appear in ``list_display``, Django doesn't care how many (or how +few) fields are linked. The only requirement is: If you want to use +``list_display_links``, you must define ``list_display``. + +In this example, the ``first_name`` and ``last_name`` fields will be linked on +the change list page:: + + class Admin: + list_display = ('first_name', 'last_name', 'birthday') + list_display_links = ('first_name', 'last_name') + +Finally, note that in order to use ``list_display_links``, you must define +``list_display``, too. + ``list_filter`` --------------- diff --git a/docs/modpython.txt b/docs/modpython.txt index 0c0219e2e9..b88874d3d3 100644 --- a/docs/modpython.txt +++ b/docs/modpython.txt @@ -10,15 +10,17 @@ Python code into memory when the server starts. Code stays in memory throughout the life of an Apache process, which leads to significant performance gains over other server arrangements. -Django requires Apache 2.x and mod_python 3.x. +Django requires Apache 2.x and mod_python 3.x, and you should use Apache's +`prefork MPM`_, as opposed to the `worker MPM`_. -We recommend you use Apache's `prefork MPM`_, as opposed to the `worker MPM`_. +You may also be interested in `How to use Django with FastCGI`_. .. _Apache: http://httpd.apache.org/ .. _mod_python: http://www.modpython.org/ .. _mod_perl: http://perl.apache.org/ .. _prefork MPM: http://httpd.apache.org/docs/2.2/mod/prefork.html .. _worker MPM: http://httpd.apache.org/docs/2.2/mod/worker.html +.. _How to use Django with FastCGI: http://www.djangoproject.com/documentation/fastcgi/ Basic configuration =================== diff --git a/docs/outputting_pdf.txt b/docs/outputting_pdf.txt index a58cf2c217..edd34aca24 100644 --- a/docs/outputting_pdf.txt +++ b/docs/outputting_pdf.txt @@ -110,7 +110,7 @@ efficient. Here's the above "Hello World" example rewritten to use from cStringIO import StringIO from reportlab.pdfgen import canvas - from django.utils.httpwrappers import HttpResponse + from django.http import HttpResponse def some_view(request): # Create the HttpResponse object with the appropriate PDF headers. diff --git a/docs/release_notes_0.95.txt b/docs/release_notes_0.95.txt new file mode 100644 index 0000000000..e5b89e5a7a --- /dev/null +++ b/docs/release_notes_0.95.txt @@ -0,0 +1,126 @@ +================================= +Django version 0.95 release notes +================================= + + +Welcome to the Django 0.95 release. + +This represents a significant advance in Django development since the 0.91 +release in January 2006. The details of every change in this release would be +too extensive to list in full, but a summary is presented below. + +Suitability and API stability +============================= + +This release is intended to provide a stable reference point for developers +wanting to work on production-level applications that use Django. + +However, it's not the 1.0 release, and we'll be introducing further changes +before 1.0. For a clear look at which areas of the framework will change (and +which ones will *not* change) before 1.0, see the api-stability.txt file, which +lives in the docs/ directory of the distribution. + +You may have a need to use some of the features that are marked as +"subject to API change" in that document, but that's OK with us as long as it's +OK with you, and as long as you understand APIs may change in the future. + +Fortunately, most of Django's core APIs won't be changing before version 1.0. +There likely won't be as big of a change between 0.95 and 1.0 versions as there +was between 0.91 and 0.95. + +Changes and new features +======================== + +The major changes in this release (for developers currently using the 0.91 +release) are a result of merging the 'magic-removal' branch of development. +This branch removed a number of constraints in the way Django code had to be +written that were a consequence of decisions made in the early days of Django, +prior to its open-source release. It's now possible to write more natural, +Pythonic code that works as expected, and there's less "black magic" happening +behind the scenes. + +Aside from that, another main theme of this release is a dramatic increase in +usability. We've made countless improvements in error messages, documentation, +etc., to improve developers' quality of life. + +The new features and changes introduced in 0.95 include: + + * Django now uses a more consistent and natural filtering interface for + retrieving objects from the database. + + * User-defined models, functions and constants now appear in the module + namespace they were defined in. (Previously everything was magically + transferred to the django.models.* namespace.) + + * Some optional applications, such as the FlatPage, Sites and Redirects + apps, have been decoupled and moved into django.contrib. If you don't + want to use these applications, you no longer have to install their + database tables. + + * Django now has support for managing database transactions. + + * We've added the ability to write custom authentication and authorization + backends for authenticating users against alternate systems, such as + LDAP. + + * We've made it easier to add custom table-level functions to models, + through a new "Manager" API. + + * It's now possible to use Django without a database. This simply means + that the framework no longer requires you to have a working database set + up just to serve dynamic pages. In other words, you can just use + URLconfs/views on their own. Previously, the framework required that a + database be configured, regardless of whether you actually used it. + + * It's now more explicit and natural to override save() and delete() + methods on models, rather than needing to hook into the pre_save() and + post_save() method hooks. + + * Individual pieces of the framework now can be configured without + requiring the setting of an environment variable. This permits use of, + for example, the Django templating system inside other applications. + + * More and more parts of the framework have been internationalized, as + we've expanded internationalization (i18n) support. The Django + codebase, including code and templates, has now been translated, at least + in part, into 31 languages. From Arabic to Chinese to Hungarian to Welsh, + it is now possible to use Django's admin site in your native language. + +The number of changes required to port from 0.91-compatible code to the 0.95 +code base are significant in some cases. However, they are, for the most part, +reasonably routine and only need to be done once. A list of the necessary +changes is described in the `Removing The Magic`_ wiki page. There is also an +easy checklist_ for reference when undertaking the porting operation. + +.. _Removing The Magic: http://code.djangoproject.com/wiki/RemovingTheMagic +.. _checklist: http://code.djangoproject.com/wiki/MagicRemovalCheatSheet1 + +Problem reports and getting help +================================ + +Need help resolving a problem with Django? The documentation in the +distribution is also available online_ at the `Django website`_. The FAQ_ +document is especially recommended, as it contains a number of issues that +come up time and again. + +For more personalized help, the `django-users`_ mailing list is a very active +list, with more than 2,000 subscribers who can help you solve any sort of +Django problem. We recommend you search the archives first, though, because +many common questions appear with some regularity, and any particular problem +may already have been answered. + +Finally, for those who prefer the more immediate feedback offered by IRC, +there's a #django channel or irc.freenode.net that is regularly populated by +Django users and developers from around the world. Friendly people are usually +available at any hour of the day -- to help, or just to chat. + +.. _online: http://www.djangoproject.com/documentation/ +.. _Django website: http://www.djangoproject.com/ +.. _FAQ: http://www.djangoproject.com/documentation/faq/ +.. _django-users: http://groups.google.com/group/django-users + +Thanks for using Django! + +The Django Team +July 2006 + diff --git a/docs/request_response.txt b/docs/request_response.txt index 0bcb3a7f5b..7480a6d3bb 100644 --- a/docs/request_response.txt +++ b/docs/request_response.txt @@ -106,12 +106,12 @@ All attributes except ``session`` should be considered read-only. A ``django.contrib.auth.models.User`` object representing the currently logged-in user. If the user isn't currently logged in, ``user`` will be set to an instance of ``django.contrib.auth.models.AnonymousUser``. You - can tell them apart with ``is_anonymous()``, like so:: + can tell them apart with ``is_authenticated()``, like so:: - if request.user.is_anonymous(): - # Do something for anonymous users. - else: + if request.user.is_authenticated(): # Do something for logged-in users. + else: + # Do something for anonymous users. ``user`` is only available if your Django installation has the ``AuthenticationMiddleware`` activated. For more, see @@ -134,21 +134,25 @@ Methods ------- ``__getitem__(key)`` - Returns the GET/POST value for the given key, checking POST first, then - GET. Raises ``KeyError`` if the key doesn't exist. + Returns the GET/POST value for the given key, checking POST first, then + GET. Raises ``KeyError`` if the key doesn't exist. - This lets you use dictionary-accessing syntax on an ``HttpRequest`` - instance. Example: ``request["foo"]`` would return ``True`` if either - ``request.POST`` or ``request.GET`` had a ``"foo"`` key. + This lets you use dictionary-accessing syntax on an ``HttpRequest`` + instance. Example: ``request["foo"]`` would return ``True`` if either + ``request.POST`` or ``request.GET`` had a ``"foo"`` key. ``has_key()`` - Returns ``True`` or ``False``, designating whether ``request.GET`` or - ``request.POST`` has the given key. + Returns ``True`` or ``False``, designating whether ``request.GET`` or + ``request.POST`` has the given key. ``get_full_path()`` - Returns the ``path``, plus an appended query string, if applicable. + Returns the ``path``, plus an appended query string, if applicable. - Example: ``"/music/bands/the_beatles/?print=true"`` + Example: ``"/music/bands/the_beatles/?print=true"`` + +``is_secure()`` + Returns ``True`` if the request is secure; that is, if it was made with + HTTPS. QueryDict objects ----------------- diff --git a/docs/serialization.txt b/docs/serialization.txt new file mode 100644 index 0000000000..25199e7a50 --- /dev/null +++ b/docs/serialization.txt @@ -0,0 +1,102 @@ +========================== +Serializing Django objects +========================== + +.. note:: + + This API is currently under heavy development and may change -- + perhaps drastically -- in the future. + + You have been warned. + +Django's serialization framework provides a mechanism for "translating" Django +objects into other formats. Usually these other formats will be text-based and +used for sending Django objects over a wire, but it's possible for a +serializer to handle any format (text-based or not). + +Serializing data +---------------- + +At the highest level, serializing data is a very simple operation:: + + from django.core import serializers + data = serializers.serialize("xml", SomeModel.objects.all()) + +The arguments to the ``serialize`` function are the format to serialize the +data to (see `Serialization formats`_) and a QuerySet_ to serialize. +(Actually, the second argument can be any iterator that yields Django objects, +but it'll almost always be a QuerySet). + +.. _QuerySet: ../db_api/#retrieving-objects + +You can also use a serializer object directly:: + + xml_serializer = serializers.get_serializer("xml") + xml_serializer.serialize(queryset) + data = xml_serializer.getvalue() + +This is useful if you want to serialize data directly to a file-like object +(which includes a HTTPResponse_):: + + out = open("file.xml", "w") + xml_serializer.serialize(SomeModel.objects.all(), stream=out) + +.. _HTTPResponse: ../request_response/#httpresponse-objects + +Deserializing data +------------------ + +Deserializing data is also a fairly simple operation:: + + for obj in serializers.deserialize("xml", data): + do_something_with(obj) + +As you can see, the ``deserialize`` function takes the same format argument as +``serialize``, a string or stream of data, and returns an iterator. + +However, here it gets slightly complicated. The objects returned by the +``deserialize`` iterator *aren't* simple Django objects. Instead, they are +special ``DeserializedObject`` instances that wrap a created -- but unsaved -- +object and any associated relationship data. + +Calling ``DeserializedObject.save()`` saves the object to the database. + +This ensures that deserializing is a non-destructive operation even if the +data in your serialized representation doesn't match what's currently in the +database. Usually, working with these ``DeserializedObject`` instances looks +something like:: + + for deserialized_object in serializers.deserialize("xml", data): + if object_should_be_saved(deserialized_object): + obj.save() + +In other words, the usual use is to examine the deserialized objects to make +sure that they are "appropriate" for saving before doing so. Of course, if you trust your data source you could just save the object and move on. + +The Django object itself can be inspected as ``deserialized_object.object``. + +Serialization formats +--------------------- + +Django "ships" with a few included serializers: + + ========== ============================================================== + Identifier Information + ========== ============================================================== + ``xml`` Serializes to and from a simple XML dialect. + + ``json`` Serializes to and from JSON_ (using a version of simplejson_ + bundled with Django). + + ``python`` Translates to and from "simple" Python objects (lists, dicts, + strings, etc.). Not really all that useful on its own, but + used as a base for other serializers. + ========== ============================================================== + +.. _json: http://json.org/ +.. _simplejson: http://undefined.org/python/#simplejson + +Writing custom serializers +`````````````````````````` + +XXX ... diff --git a/docs/sessions.txt b/docs/sessions.txt index 2dba491159..c473d0a3db 100644 --- a/docs/sessions.txt +++ b/docs/sessions.txt @@ -10,18 +10,22 @@ Cookies contain a session ID -- not the data itself. Enabling sessions ================= -Sessions are implemented via middleware_. +Sessions are implemented via a piece of middleware_ and a Django model. -Turn session functionality on and off by editing the ``MIDDLEWARE_CLASSES`` -setting. To activate sessions, make sure ``MIDDLEWARE_CLASSES`` contains -``'django.contrib.sessions.middleware.SessionMiddleware'``. +To enable session functionality, do these two things: -The default ``settings.py`` created by ``django-admin.py startproject`` has -``SessionMiddleware`` activated. + * Edit the ``MIDDLEWARE_CLASSES`` setting and make sure + ``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``. + The default ``settings.py`` created by ``django-admin.py startproject`` has + ``SessionMiddleware`` activated. + + * Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, and + run ``manage.py syncdb`` to install the single database table that stores + session data. If you don't want to use sessions, you might as well remove the -``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES``. It'll save you a small -bit of overhead. +``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'`` +from your ``INSTALLED_APPS``. It'll save you a small bit of overhead. .. _middleware: http://www.djangoproject.com/documentation/middleware/ diff --git a/docs/settings.txt b/docs/settings.txt index 553736b280..099196e56e 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -107,15 +107,20 @@ For more, see the `diffsettings documentation`_. Using settings in Python code ============================= -In your Django apps, use settings by importing them from +In your Django apps, use settings by importing the object ``django.conf.settings``. Example:: - from django.conf.settings import DEBUG + from django.conf import settings - if DEBUG: + if settings.DEBUG: # Do something -Note that your code should *not* import from either ``global_settings`` or +Note that ``django.conf.settings`` isn't a module -- it's an object. So +importing individual settings is not possible:: + + from django.conf.settings import DEBUG # This won't work. + +Also note that your code should *not* import from either ``global_settings`` or your own settings file. ``django.conf.settings`` abstracts the concepts of default settings and site-specific settings; it presents a single interface. It also decouples the code that uses settings from the location of your @@ -127,9 +132,9 @@ Altering settings at runtime You shouldn't alter settings in your applications at runtime. For example, don't do this in a view:: - from django.conf.settings import DEBUG + from django.conf import settings - DEBUG = True # Don't do this! + settings.DEBUG = True # Don't do this! The only place you should assign to settings is in a settings file. @@ -496,6 +501,28 @@ specifies which languages are available for language selection. See the Generally, the default value should suffice. Only set this setting if you want to restrict language selection to a subset of the Django-provided languages. +If you define a custom ``LANGUAGES`` setting, it's OK to mark the languages as +translation strings (as in the default value displayed above) -- but use a +"dummy" ``gettext()`` function, not the one in ``django.utils.translation``. +You should *never* import ``django.utils.translation`` from within your +settings file, because that module in itself depends on the settings, and that +would cause a circular import. + +The solution is to use a "dummy" ``gettext()`` function. Here's a sample +settings file:: + + gettext = lambda s: s + + LANGUAGES = ( + ('de', gettext('German')), + ('en', gettext('English')), + ) + +With this arrangement, ``make-messages.py`` will still find and mark these +strings for translation, but the translation won't happen at runtime -- so +you'll have to remember to wrap the languages in the *real* ``gettext()`` in +any code that uses ``LANGUAGES`` at runtime. + MANAGERS -------- @@ -724,11 +751,22 @@ TIME_ZONE Default: ``'America/Chicago'`` A string representing the time zone for this installation. `See available choices`_. +(Note that list of available choices lists more than one on the same line; +you'll want to use just one of the choices for a given time zone. For instance, +one line says ``'Europe/London GB GB-Eire'``, but you should use the first bit +of that -- ``'Europe/London'`` -- as your ``TIME_ZONE`` setting.) Note that this is the time zone to which Django will convert all dates/times -- not necessarily the timezone of the server. For example, one server may serve multiple Django-powered sites, each with a separate time-zone setting. +Normally, Django sets the ``os.environ['TZ']`` variable to the time zone you +specify in the ``TIME_ZONE`` setting. Thus, all your views and models will +automatically operate in the correct time zone. However, if you're using the +manual configuration option (see below), Django will *not* touch the ``TZ`` +environment variable, and it'll be up to you to ensure your processes are +running in the correct environment. + USE_ETAGS --------- @@ -738,6 +776,16 @@ A boolean that specifies whether to output the "Etag" header. This saves bandwidth but slows down performance. This is only used if ``CommonMiddleware`` is installed (see the `middleware docs`_). +USE_I18N +-------- + +Default: ``True`` + +A boolean that specifies whether Django's internationalization system should be +enabled. This provides an easy way to turn it off, for performance. If this is +set to ``False``, Django will make some optimizations so as not to load the +internationalization machinery. + YEAR_MONTH_FORMAT ----------------- @@ -796,6 +844,15 @@ uppercase, with the same name as the settings described above. If a particular setting is not passed to ``configure()`` and is needed at some later point, Django will use the default setting value. +Configuring Django in this fashion is mostly necessary -- and, indeed, +recommended -- when you're using a piece of the framework inside a larger +application. + +Consequently, when configured via ``settings.configure()``, Django will not +make any modifications to the process environment variables. (See the +explanation of ``TIME_ZONE``, above, for why this would normally occur.) It's +assumed that you're already in full control of your environment in these cases. + Custom default settings ----------------------- diff --git a/docs/syndication_feeds.txt b/docs/syndication_feeds.txt index 4f77c4ff21..b00af200a0 100644 --- a/docs/syndication_feeds.txt +++ b/docs/syndication_feeds.txt @@ -134,7 +134,9 @@ put into those elements. If you don't create a template for either the title or description, the framework will use the template ``"{{ obj }}"`` by default -- that is, - the normal string representation of the object. + the normal string representation of the object. You can also change the + names of these two templates by specifying ``title_template`` and + ``description_template`` as attributes of your ``Feed`` class. * To specify the contents of ``<link>``, you have two options. For each item in ``items()``, Django first tries executing a ``get_absolute_url()`` method on that object. If that method doesn't @@ -342,6 +344,16 @@ This example illustrates all possible attributes and methods for a ``Feed`` clas feed_type = feedgenerator.Rss201rev2Feed + # TEMPLATE NAMES -- Optional. These should be strings representing + # names of Django templates that the system should use in rendering the + # title and description of your feed items. Both are optional. + # If you don't specify one, or either, Django will use the template + # 'feeds/SLUG_title.html' and 'feeds/SLUG_description.html', where SLUG + # is the slug you specify in the URL. + + title_template = None + description_template = None + # TITLE -- One of the following three is required. The framework looks # for them in this order. @@ -415,7 +427,7 @@ This example illustrates all possible attributes and methods for a ``Feed`` clas author's e-mail as a normal Python string. """ - def author_name(self): + def author_email(self): """ Returns the feed's author's e-mail as a normal Python string. """ diff --git a/docs/templates.txt b/docs/templates.txt index 42947510d1..49d30018fe 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -47,7 +47,7 @@ explained later in this document.:: JavaScript and CSV. You can use the template language for any text-based format. - Oh, and one more thing: Making humans edit XML is masochistic! + Oh, and one more thing: Making humans edit XML is sadistic! Variables ========= @@ -363,10 +363,15 @@ extends Signal that this template extends a parent template. -This tag may be used in two ways: ``{% extends "base.html" %}`` (with quotes) -uses the literal value "base.html" as the name of the parent template to -extend, or ``{% extends variable %}`` uses the value of ``variable`` as the -name of the parent template to extend. +This tag can be used in two ways: + + * ``{% extends "base.html" %}`` (with quotes) uses the literal value + ``"base.html"`` as the name of the parent template to extend. + + * ``{% extends variable %}`` uses the value of ``variable``. If the variable + evaluates to a string, Django will use that string as the name of the + parent template. If the variable evaluates to a ``Template`` object, + Django will use that object as the parent template. See `Template inheritance`_ for more information. @@ -493,6 +498,11 @@ If you need to combine ``and`` and ``or`` to do advanced logic, just use nested {% endif %} {% endif %} +Multiple uses of the same logical operator are fine, as long as you use the +same operator. For example, this is valid:: + + {% if athlete_list or coach_list or parent_list or teacher_list %} + ifchanged ~~~~~~~~~ @@ -951,12 +961,26 @@ any string. pluralize ~~~~~~~~~ -Returns ``'s'`` if the value is not 1. +Returns a plural suffix if the value is not 1. By default, this suffix is ``'s'``. Example:: You have {{ num_messages }} message{{ num_messages|pluralize }}. +For words that require a suffix other than ``'s'``, you can provide an alternate +suffix as a parameter to the filter. + +Example:: + + You have {{ num_walruses }} walrus{{ num_walrus|pluralize:"es" }}. + +For words that don't pluralize by simple suffix, you can specify both a +singular and plural suffix, separated by a comma. + +Example:: + + You have {{ num_cherries }} cherr{{ num_cherries|pluralize:"y,ies" }}. + pprint ~~~~~~ diff --git a/docs/templates_python.txt b/docs/templates_python.txt index 5e3038ebb4..95ccfb3eab 100644 --- a/docs/templates_python.txt +++ b/docs/templates_python.txt @@ -198,21 +198,6 @@ some things to keep in mind: How invalid variables are handled ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In Django 0.91, if a variable doesn't exist, the template system fails -silently. The variable is replaced with an empty string:: - - >>> t = Template("My name is {{ my_name }}.") - >>> c = Context({"foo": "bar"}) - >>> t.render(c) - "My name is ." - -This applies to any level of lookup:: - - >>> t = Template("My name is {{ person.fname }} {{ person.lname }}.") - >>> c = Context({"person": {"fname": "Stan"}}) - >>> t.render(c) - "My name is Stan ." - If a variable doesn't exist, the template system inserts the value of the ``TEMPLATE_STRING_IF_INVALID`` setting, which is set to ``''`` (the empty string) by default. @@ -357,7 +342,7 @@ django.core.context_processors.request ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every -``DjangoContext`` will contain a variable ``request``, which is the current +``RequestContext`` will contain a variable ``request``, which is the current `HttpRequest object`_. Note that this processor is not enabled by default; you'll have to activate it. @@ -643,7 +628,7 @@ the current date/time, formatted according to a parameter given in the tag, in `strftime syntax`_. It's a good idea to decide the tag syntax before anything else. In our case, let's say the tag should be used like this:: - <p>The time is {% current_time "%Y-%M-%d %I:%M %p" %}.</p> + <p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p> .. _`strftime syntax`: http://www.python.org/doc/current/lib/module-time.html#l2h-1941 @@ -653,10 +638,10 @@ object:: from django import template def do_current_time(parser, token): try: - # Splitting by None == splitting by spaces. - tag_name, format_string = token.contents.split(None, 1) + # split_contents() knows not to split quoted strings. + tag_name, format_string = token.split_contents() except ValueError: - raise template.TemplateSyntaxError, "%r tag requires an argument" % token.contents[0] + raise template.TemplateSyntaxError, "%r tag requires a single argument" % token.contents[0] if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name return CurrentTimeNode(format_string[1:-1]) @@ -667,7 +652,13 @@ Notes: example. * ``token.contents`` is a string of the raw contents of the tag. In our - example, it's ``'current_time "%Y-%M-%d %I:%M %p"'``. + example, it's ``'current_time "%Y-%m-%d %I:%M %p"'``. + + * The ``token.split_contents()`` method separates the arguments on spaces + while keeping quoted strings together. The more straightforward + ``token.contents.split()`` wouldn't be as robust, as it would naively + split on *all* spaces, including those within quoted strings. It's a good + idea to always use ``token.split_contents()``. * This function is responsible for raising ``django.template.TemplateSyntaxError``, with helpful messages, for @@ -681,7 +672,7 @@ Notes: * The function returns a ``CurrentTimeNode`` with everything the node needs to know about this tag. In this case, it just passes the argument -- - ``"%Y-%M-%d %I:%M %p"``. The leading and trailing quotes from the + ``"%Y-%m-%d %I:%M %p"``. The leading and trailing quotes from the template tag are removed in ``format_string[1:-1]``. * The parsing is very low-level. The Django developers have experimented @@ -766,27 +757,24 @@ registers it with the template system. Our earlier ``current_time`` function could thus be written like this:: - # This version of do_current_time takes only a single argument and returns - # a string. - - def do_current_time(token): - try: - # Splitting by None == splitting by spaces. - tag_name, format_string = token.contents.split(None, 1) - except ValueError: - raise template.TemplateSyntaxError, "%r tag requires an argument" % token.contents[0] - if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): - raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name - return datetime.datetime.now().strftime(self.format_string[1:-1]) + def current_time(format_string): + return datetime.datetime.now().strftime(format_string) - register.simple_tag(do_current_time) + register.simple_tag(current_time) In Python 2.4, the decorator syntax also works:: - @simple_tag - def do_current_time(token): + @register.simple_tag + def current_time(token): ... +A couple of things to note about the ``simple_tag`` helper function: + * Only the (single) argument is passed into our function. + * Checking for the required number of arguments, etc, has already been + done by the time our function is called, so we don't need to do that. + * The quotes around the argument (if any) have already been stripped away, + so we just receive a plain string. + Inclusion tags ~~~~~~~~~~~~~~ @@ -844,7 +832,7 @@ loader, we'd register the tag like this:: As always, Python 2.4 decorator syntax works as well, so we could have written:: - @inclusion_tag('results.html') + @register.inclusion_tag('results.html') def show_results(poll): ... diff --git a/docs/transactions.txt b/docs/transactions.txt index c1cd5aa984..2b0755a257 100644 --- a/docs/transactions.txt +++ b/docs/transactions.txt @@ -2,7 +2,8 @@ Managing database transactions ============================== -Django gives you a few ways to control how database transactions are managed. +Django gives you a few ways to control how database transactions are managed, +if you're using a database that supports transactions. Django's default transaction behavior ===================================== @@ -144,3 +145,19 @@ Thus, this is best used in situations where you want to run your own transaction-controlling middleware or do something really strange. In almost all situations, you'll be better off using the default behavior, or the transaction middleware, and only modify selected functions as needed. + +Transactions in MySQL +===================== + +If you're using MySQL, your tables may or may not support transactions; it +depends on your MySQL version and the table types you're using. (By +"table types," we mean something like "InnoDB" or "MyISAM".) MySQL transaction +peculiarities are outside the scope of this article, but the MySQL site has +`information on MySQL transactions`_. + +If your MySQL setup does *not* support transactions, then Django will function +in auto-commit mode: Statements will be executed and committed as soon as +they're called. If your MySQL setup *does* support transactions, Django will +handle transactions as explained in this document. + +.. _information on MySQL transactions: http://dev.mysql.com/books/mysqlpress/mysql-tutorial/ch10.html diff --git a/docs/tutorial01.txt b/docs/tutorial01.txt index c353e1ab4b..1113b603da 100644 --- a/docs/tutorial01.txt +++ b/docs/tutorial01.txt @@ -81,7 +81,7 @@ the following output on the command line:: Validating models... 0 errors found. - Django version 0.95 (post-magic-removal), using settings 'mysite.settings' + Django version 0.95, using settings 'mysite.settings' Development server is running at http://127.0.0.1:8000/ Quit the server with CONTROL-C (Unix) or CTRL-BREAK (Windows). @@ -346,6 +346,8 @@ Note the following: the SQL to the database. If you're interested, also run the following commands: + * ``python manage.py validate polls`` -- Checks for any errors in the + construction of your models. * ``python manage.py sqlinitialdata polls`` -- Outputs any initial data required for Django's admin framework and your models. diff --git a/docs/tutorial02.txt b/docs/tutorial02.txt index 84eae3eb83..bc1717e67c 100644 --- a/docs/tutorial02.txt +++ b/docs/tutorial02.txt @@ -54,7 +54,8 @@ http://127.0.0.1:8000/admin/. You should see the admin's login screen: Enter the admin site ==================== -Now, try logging in. You should see the Django admin index page: +Now, try logging in. (You created a superuser account in the first part of this +tutorial, remember?) You should see the Django admin index page: .. image:: http://media.djangoproject.com/img/doc/tutorial/admin02t.png :alt: Django admin index page diff --git a/docs/tutorial03.txt b/docs/tutorial03.txt index 3a830eb76f..248d234043 100644 --- a/docs/tutorial03.txt +++ b/docs/tutorial03.txt @@ -189,7 +189,7 @@ publication date:: from django.http import HttpResponse def index(request): - latest_poll_list = Poll.objects.all().order_by('-pub_date') + latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] output = ', '.join([p.question for p in latest_poll_list]) return HttpResponse(output) @@ -202,7 +202,7 @@ So let's use Django's template system to separate the design from Python:: from django.http import HttpResponse def index(request): - latest_poll_list = Poll.objects.all().order_by('-pub_date') + latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] t = loader.get_template('polls/index.html') c = Context({ 'latest_poll_list': latest_poll_list, @@ -288,7 +288,7 @@ exception if a poll with the requested ID doesn't exist. A shortcut: get_object_or_404() ------------------------------- -It's a very common idiom to use ``get_object()`` and raise ``Http404`` if the +It's a very common idiom to use ``get()`` and raise ``Http404`` if the object doesn't exist. Django provides a shortcut. Here's the ``detail()`` view, rewritten:: @@ -313,8 +313,8 @@ exist. foremost design goals of Django is to maintain loose coupling. There's also a ``get_list_or_404()`` function, which works just as -``get_object_or_404()`` -- except using ``get_list()`` instead of -``get_object()``. It raises ``Http404`` if the list is empty. +``get_object_or_404()`` -- except using ``filter()`` instead of +``get()``. It raises ``Http404`` if the list is empty. Write a 404 (page not found) view ================================= diff --git a/docs/tutorial04.txt b/docs/tutorial04.txt index 8ef4a03c6d..f234ed0ce1 100644 --- a/docs/tutorial04.txt +++ b/docs/tutorial04.txt @@ -198,7 +198,7 @@ By default, the ``object_detail`` generic view uses a template called ``vote()``. Similarly, the ``object_list`` generic view uses a template called -``<app name>/<module name>_list.html``. Thus, rename ``poll/index.html`` to +``<app name>/<module name>_list.html``. Thus, rename ``polls/index.html`` to ``polls/poll_list.html``. Because we have more than one entry in the URLconf that uses ``object_detail`` @@ -206,7 +206,7 @@ for the polls app, we manually specify a template name for the results view: ``template_name='polls/results.html'``. Otherwise, both views would use the same template. Note that we use ``dict()`` to return an altered dictionary in place. -In previous versions of the tutorial, the templates have been provided with a context +In previous parts of the tutorial, the templates have been provided with a context that contains the ``poll` and ``latest_poll_list`` context variables. However, the generic views provide the variables ``object`` and ``object_list`` as context. Therefore, you need to change your templates to match the new context variables. diff --git a/docs/url_dispatch.txt b/docs/url_dispatch.txt index 498a906d5e..6158014fc8 100644 --- a/docs/url_dispatch.txt +++ b/docs/url_dispatch.txt @@ -263,12 +263,12 @@ Here's the example URLconf from the `Django overview`_:: from django.conf.urls.defaults import * urlpatterns = patterns('', - (r'^articles/(\d{4})/$', 'myproject.news.views.year_archive'), - (r'^articles/(\d{4})/(\d{2})/$', 'myproject.news.views.month_archive'), - (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'myproject.news.views.article_detail'), + (r'^articles/(\d{4})/$', 'mysite.news.views.year_archive'), + (r'^articles/(\d{4})/(\d{2})/$', 'mysite.news.views.month_archive'), + (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'mysite.news.views.article_detail'), ) -In this example, each view has a common prefix -- ``'myproject.news.views'``. +In this example, each view has a common prefix -- ``'mysite.news.views'``. Instead of typing that out for each entry in ``urlpatterns``, you can use the first argument to the ``patterns()`` function to specify a prefix to apply to each view function. @@ -277,7 +277,7 @@ With this in mind, the above example can be written more concisely as:: from django.conf.urls.defaults import * - urlpatterns = patterns('myproject.news.views', + urlpatterns = patterns('mysite.news.views', (r'^articles/(\d{4})/$', 'year_archive'), (r'^articles/(\d{4})/(\d{2})/$', 'month_archive'), (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'article_detail'), @@ -389,3 +389,45 @@ to pass metadata and options to views. .. _generic views: http://www.djangoproject.com/documentation/generic_views/ .. _syndication framework: http://www.djangoproject.com/documentation/syndication/ + +Passing extra options to ``include()`` +-------------------------------------- + +**New in the Django development version.** + +Similarly, you can pass extra options to ``include()``. When you pass extra +options to ``include()``, *each* line in the included URLconf will be passed +the extra options. + +For example, these two URLconf sets are functionally identical: + +Set one:: + + # main.py + urlpatterns = patterns('', + (r'^blog/', include('inner'), {'blogid': 3}), + ) + + # inner.py + urlpatterns = patterns('', + (r'^archive/$', 'mysite.views.archive'), + (r'^about/$', 'mysite.views.about'), + ) + +Set two:: + + # main.py + urlpatterns = patterns('', + (r'^blog/', include('inner')), + ) + + # inner.py + urlpatterns = patterns('', + (r'^archive/$', 'mysite.views.archive', {'blogid': 3}), + (r'^about/$', 'mysite.views.about', {'blogid': 3}), + ) + +Note that extra options will *always* be passed to *every* line in the included +URLconf, regardless of whether the line's view actually accepts those options +as valid. For this reason, this technique is only useful if you're certain that +every view in the the included URLconf accepts the extra options you're passing. diff --git a/ez_setup.py b/ez_setup.py index fe3983fef0..33675107b2 100644 --- a/ez_setup.py +++ b/ez_setup.py @@ -14,34 +14,20 @@ the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import sys -DEFAULT_VERSION = "0.6a10" +DEFAULT_VERSION = "0.6c1" DEFAULT_URL = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3] md5_data = { - 'setuptools-0.5a13-py2.3.egg': '85edcf0ef39bab66e130d3f38f578c86', - 'setuptools-0.5a13-py2.4.egg': 'ede4be600e3890e06d4ee5e0148e092a', - 'setuptools-0.6a1-py2.3.egg': 'ee819a13b924d9696b0d6ca6d1c5833d', - 'setuptools-0.6a1-py2.4.egg': '8256b5f1cd9e348ea6877b5ddd56257d', - 'setuptools-0.6a10-py2.3.egg': '162d8357f1aff2b0349c6c247ee62987', - 'setuptools-0.6a10-py2.4.egg': '803a2d8db501c1ac3b5b6fb4e907f788', - 'setuptools-0.6a10dev_r42346-py2.3.egg': 'a7899272cfceb6aa60094ae8928b8077', - 'setuptools-0.6a10dev_r42346-py2.4.egg': '5d42a64adca9aedb409f83ecf22156a5', - 'setuptools-0.6a2-py2.3.egg': 'b98da449da411267c37a738f0ab625ba', - 'setuptools-0.6a2-py2.4.egg': 'be5b88bc30aed63fdefd2683be135c3b', - 'setuptools-0.6a3-py2.3.egg': 'ee0e325de78f23aab79d33106dc2a8c8', - 'setuptools-0.6a3-py2.4.egg': 'd95453d525a456d6c23e7a5eea89a063', - 'setuptools-0.6a4-py2.3.egg': 'e958cbed4623bbf47dd1f268b99d7784', - 'setuptools-0.6a4-py2.4.egg': '7f33c3ac2ef1296f0ab4fac1de4767d8', - 'setuptools-0.6a5-py2.3.egg': '748408389c49bcd2d84f6ae0b01695b1', - 'setuptools-0.6a5-py2.4.egg': '999bacde623f4284bfb3ea77941d2627', - 'setuptools-0.6a6-py2.3.egg': '7858139f06ed0600b0d9383f36aca24c', - 'setuptools-0.6a6-py2.4.egg': 'c10d20d29acebce0dc76219dc578d058', - 'setuptools-0.6a7-py2.3.egg': 'cfc4125ddb95c07f9500adc5d6abef6f', - 'setuptools-0.6a7-py2.4.egg': 'c6d62dab4461f71aed943caea89e6f20', - 'setuptools-0.6a8-py2.3.egg': '2f18eaaa3f544f5543ead4a68f3b2e1a', - 'setuptools-0.6a8-py2.4.egg': '799018f2894f14c9f8bcb2b34e69b391', - 'setuptools-0.6a9-py2.3.egg': '8e438ad70438b07b0d8f82cae42b278f', - 'setuptools-0.6a9-py2.4.egg': '8f6e01fc12fb1cd006dc0d6c04327ec1', + 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', + 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', + 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', + 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', + 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', + 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', + 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', + 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', + 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', + 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', } import sys, os @@ -56,7 +42,7 @@ def _validate_md5(egg_name, data): % egg_name ) sys.exit(2) - return data + return data def use_setuptools( @@ -72,7 +58,7 @@ def use_setuptools( be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in - an attempt to abort the calling script. + an attempt to abort the calling script. """ try: import setuptools @@ -159,7 +145,7 @@ def main(argv, version=DEFAULT_VERSION): egg = download_setuptools(version, to_dir=tmpdir, delay=0) sys.path.insert(0,egg) from setuptools.command.easy_install import main - main(list(argv)+[egg]) + return main(list(argv)+[egg]) # we're done here finally: shutil.rmtree(tmpdir) else: @@ -187,7 +173,7 @@ def main(argv, version=DEFAULT_VERSION): print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' - + def update_md5(filenames): """Update our built-in md5 registry""" @@ -196,7 +182,7 @@ def update_md5(filenames): for name in filenames: base = os.path.basename(name) - f = open(name,'rb') + f = open(name,'rb') md5_data[base] = md5(f.read()).hexdigest() f.close() @@ -14,35 +14,7 @@ setup( packages = find_packages(exclude=['examples', 'examples.*']), package_data = { '': ['*.TXT'], - 'django.conf': ['locale/bn/LC_MESSAGES/*', - 'locale/cs/LC_MESSAGES/*', - 'locale/cy/LC_MESSAGES/*', - 'locale/da/LC_MESSAGES/*', - 'locale/de/LC_MESSAGES/*', - 'locale/el/LC_MESSAGES/*', - 'locale/en/LC_MESSAGES/*', - 'locale/es/LC_MESSAGES/*', - 'locale/es_AR/LC_MESSAGES/*', - 'locale/fr/LC_MESSAGES/*', - 'locale/gl/LC_MESSAGES/*', - 'locale/hu/LC_MESSAGES/*', - 'locale/he/LC_MESSAGES/*', - 'locale/is/LC_MESSAGES/*', - 'locale/it/LC_MESSAGES/*', - 'locale/ja/LC_MESSAGES/*', - 'locale/nl/LC_MESSAGES/*', - 'locale/no/LC_MESSAGES/*', - 'locale/pl/LC_MESSAGES/*', - 'locale/pt_BR/LC_MESSAGES/*', - 'locale/ro/LC_MESSAGES/*', - 'locale/ru/LC_MESSAGES/*', - 'locale/sk/LC_MESSAGES/*', - 'locale/sl/LC_MESSAGES/*', - 'locale/sr/LC_MESSAGES/*', - 'locale/sv/LC_MESSAGES/*', - 'locale/uk/LC_MESSAGES/*', - 'locale/zh_CN/LC_MESSAGES/*', - 'locale/zh_TW/LC_MESSAGES/*'], + 'django.conf': ['locale/*/LC_MESSAGES/*'], 'django.contrib.admin': ['templates/admin/*.html', 'templates/admin_doc/*.html', 'templates/registration/*.html', diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index a4de0f9a81..78d943eb97 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -347,4 +347,9 @@ API_TESTS += """ >>> a101 = Article.objects.get(pk=101) >>> a101.headline 'Article 101' + +# You can create saved objects in a single step +>>> a10 = Article.objects.create(headline="Article 10", pub_date=datetime(2005, 7, 31, 12, 30, 45)) +>>> Article.objects.get(headline="Article 10") +<Article: Article 10> """ diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py index 1720dd96d3..eb305b4e92 100644 --- a/tests/modeltests/invalid_models/models.py +++ b/tests/modeltests/invalid_models/models.py @@ -17,17 +17,19 @@ class FieldErrors(models.Model): class Target(models.Model): tgt_safe = models.CharField(maxlength=10) + clash1 = models.CharField(maxlength=10) + clash2 = models.CharField(maxlength=10) clash1_set = models.CharField(maxlength=10) class Clash1(models.Model): - src_safe = models.CharField(maxlength=10) + src_safe = models.CharField(maxlength=10, core=True) foreign = models.ForeignKey(Target) m2m = models.ManyToManyField(Target) class Clash2(models.Model): - src_safe = models.CharField(maxlength=10) + src_safe = models.CharField(maxlength=10, core=True) foreign_1 = models.ForeignKey(Target, related_name='id') foreign_2 = models.ForeignKey(Target, related_name='src_safe') @@ -36,6 +38,7 @@ class Clash2(models.Model): m2m_2 = models.ManyToManyField(Target, related_name='src_safe') class Target2(models.Model): + clash3 = models.CharField(maxlength=10) foreign_tgt = models.ForeignKey(Target) clashforeign_set = models.ForeignKey(Target) @@ -43,6 +46,8 @@ class Target2(models.Model): clashm2m_set = models.ManyToManyField(Target) class Clash3(models.Model): + src_safe = models.CharField(maxlength=10, core=True) + foreign_1 = models.ForeignKey(Target2, related_name='foreign_tgt') foreign_2 = models.ForeignKey(Target2, related_name='m2m_tgt') @@ -56,7 +61,8 @@ class ClashM2M(models.Model): m2m = models.ManyToManyField(Target2) class SelfClashForeign(models.Model): - src_safe = models.CharField(maxlength=10) + src_safe = models.CharField(maxlength=10, core=True) + selfclashforeign = models.CharField(maxlength=10) selfclashforeign_set = models.ForeignKey("SelfClashForeign") foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id') @@ -64,11 +70,14 @@ class SelfClashForeign(models.Model): class SelfClashM2M(models.Model): src_safe = models.CharField(maxlength=10) + selfclashm2m = models.CharField(maxlength=10) selfclashm2m_set = models.ManyToManyField("SelfClashM2M") m2m_1 = models.ManyToManyField("SelfClashM2M", related_name='id') m2m_2 = models.ManyToManyField("SelfClashM2M", related_name='src_safe') + + error_log = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute. invalid_models.fielderrors: "floatfield": FloatFields require a "decimal_places" attribute. invalid_models.fielderrors: "floatfield": FloatFields require a "max_digits" attribute. @@ -78,42 +87,69 @@ invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tup invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples. invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples. invalid_models.fielderrors: "index": "db_index" should be either None, True or False. -invalid_models.clash1: 'foreign' accessor name 'Target.clash1_set' clashes with another field. Add a related_name argument to the definition for 'foreign'. -invalid_models.clash1: 'foreign' accessor name 'Target.clash1_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign'. -invalid_models.clash1: 'm2m' m2m accessor name 'Target.clash1_set' clashes with another field. Add a related_name argument to the definition for 'm2m'. -invalid_models.clash1: 'm2m' m2m accessor name 'Target.clash1_set' clashes with another related field. Add a related_name argument to the definition for 'm2m'. -invalid_models.clash2: 'foreign_1' accessor name 'Target.id' clashes with another field. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash2: 'foreign_1' accessor name 'Target.id' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash2: 'foreign_2' accessor name 'Target.src_safe' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.clash2: 'm2m_1' m2m accessor name 'Target.id' clashes with another field. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash2: 'm2m_1' m2m accessor name 'Target.id' clashes with another related field. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash2: 'm2m_2' m2m accessor name 'Target.src_safe' clashes with another related field. Add a related_name argument to the definition for 'm2m_2'. -invalid_models.clash3: 'foreign_1' accessor name 'Target2.foreign_tgt' clashes with another field. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash3: 'foreign_1' accessor name 'Target2.foreign_tgt' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.clash3: 'foreign_2' accessor name 'Target2.m2m_tgt' clashes with a m2m field. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.clash3: 'foreign_2' accessor name 'Target2.m2m_tgt' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.clash3: 'm2m_1' m2m accessor name 'Target2.foreign_tgt' clashes with another field. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash3: 'm2m_1' m2m accessor name 'Target2.foreign_tgt' clashes with another related field. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.clash3: 'm2m_2' m2m accessor name 'Target2.m2m_tgt' clashes with a m2m field. Add a related_name argument to the definition for 'm2m_2'. -invalid_models.clash3: 'm2m_2' m2m accessor name 'Target2.m2m_tgt' clashes with another related field. Add a related_name argument to the definition for 'm2m_2'. -invalid_models.clashforeign: 'foreign' accessor name 'Target2.clashforeign_set' clashes with another field. Add a related_name argument to the definition for 'foreign'. -invalid_models.clashm2m: 'm2m' m2m accessor name 'Target2.clashm2m_set' clashes with a m2m field. Add a related_name argument to the definition for 'm2m'. -invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_tgt'. -invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_tgt'. -invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'foreign_tgt'. -invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashforeign_set'. -invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashforeign_set'. -invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashforeign_set'. -invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'm2m_tgt'. -invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'm2m_tgt'. -invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'm2m_tgt'. -invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashm2m_set'. -invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashm2m_set'. -invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashm2m_set'. -invalid_models.selfclashforeign: 'selfclashforeign_set' accessor name 'SelfClashForeign.selfclashforeign_set' clashes with another field. Add a related_name argument to the definition for 'selfclashforeign_set'. -invalid_models.selfclashforeign: 'foreign_1' accessor name 'SelfClashForeign.id' clashes with another field. Add a related_name argument to the definition for 'foreign_1'. -invalid_models.selfclashforeign: 'foreign_2' accessor name 'SelfClashForeign.src_safe' clashes with another field. Add a related_name argument to the definition for 'foreign_2'. -invalid_models.selfclashm2m: 'selfclashm2m_set' m2m accessor name 'SelfClashM2M.selfclashm2m_set' clashes with a m2m field. Add a related_name argument to the definition for 'selfclashm2m_set'. -invalid_models.selfclashm2m: 'm2m_1' m2m accessor name 'SelfClashM2M.id' clashes with another field. Add a related_name argument to the definition for 'm2m_1'. -invalid_models.selfclashm2m: 'm2m_2' m2m accessor name 'SelfClashM2M.src_safe' clashes with another field. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash1: Accessor for field 'foreign' clashes with field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'. +invalid_models.clash1: Accessor for field 'foreign' clashes with related m2m field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'. +invalid_models.clash1: Reverse query name for field 'foreign' clashes with field 'Target.clash1'. Add a related_name argument to the definition for 'foreign'. +invalid_models.clash1: Accessor for m2m field 'm2m' clashes with field 'Target.clash1_set'. Add a related_name argument to the definition for 'm2m'. +invalid_models.clash1: Accessor for m2m field 'm2m' clashes with related field 'Target.clash1_set'. Add a related_name argument to the definition for 'm2m'. +invalid_models.clash1: Reverse query name for m2m field 'm2m' clashes with field 'Target.clash1'. Add a related_name argument to the definition for 'm2m'. +invalid_models.clash2: Accessor for field 'foreign_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash2: Accessor for field 'foreign_1' clashes with related m2m field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash2: Reverse query name for field 'foreign_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash2: Reverse query name for field 'foreign_1' clashes with related m2m field 'Target.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash2: Accessor for field 'foreign_2' clashes with related m2m field 'Target.src_safe'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash2: Reverse query name for field 'foreign_2' clashes with related m2m field 'Target.src_safe'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash2: Accessor for m2m field 'm2m_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash2: Accessor for m2m field 'm2m_1' clashes with related field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash2: Reverse query name for m2m field 'm2m_1' clashes with field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash2: Reverse query name for m2m field 'm2m_1' clashes with related field 'Target.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash2: Accessor for m2m field 'm2m_2' clashes with related field 'Target.src_safe'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash2: Reverse query name for m2m field 'm2m_2' clashes with related field 'Target.src_safe'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash3: Accessor for field 'foreign_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash3: Accessor for field 'foreign_1' clashes with related m2m field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash3: Reverse query name for field 'foreign_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash3: Reverse query name for field 'foreign_1' clashes with related m2m field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash3: Accessor for field 'foreign_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash3: Accessor for field 'foreign_2' clashes with related m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash3: Reverse query name for field 'foreign_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash3: Reverse query name for field 'foreign_2' clashes with related m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash3: Accessor for m2m field 'm2m_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash3: Accessor for m2m field 'm2m_1' clashes with related field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash3: Reverse query name for m2m field 'm2m_1' clashes with field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash3: Reverse query name for m2m field 'm2m_1' clashes with related field 'Target2.foreign_tgt'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash3: Accessor for m2m field 'm2m_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash3: Accessor for m2m field 'm2m_2' clashes with related field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash3: Reverse query name for m2m field 'm2m_2' clashes with m2m field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash3: Reverse query name for m2m field 'm2m_2' clashes with related field 'Target2.m2m_tgt'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clashforeign: Accessor for field 'foreign' clashes with field 'Target2.clashforeign_set'. Add a related_name argument to the definition for 'foreign'. +invalid_models.clashm2m: Accessor for m2m field 'm2m' clashes with m2m field 'Target2.clashm2m_set'. Add a related_name argument to the definition for 'm2m'. +invalid_models.target2: Accessor for field 'foreign_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'foreign_tgt'. +invalid_models.target2: Accessor for field 'foreign_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'foreign_tgt'. +invalid_models.target2: Accessor for field 'foreign_tgt' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'foreign_tgt'. +invalid_models.target2: Accessor for field 'clashforeign_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashforeign_set'. +invalid_models.target2: Accessor for field 'clashforeign_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashforeign_set'. +invalid_models.target2: Accessor for field 'clashforeign_set' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'clashforeign_set'. +invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: Accessor for m2m field 'm2m_tgt' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.target2: Accessor for m2m field 'clashm2m_set' clashes with related m2m field 'Target.target2_set'. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.selfclashforeign: Accessor for field 'selfclashforeign_set' clashes with field 'SelfClashForeign.selfclashforeign_set'. Add a related_name argument to the definition for 'selfclashforeign_set'. +invalid_models.selfclashforeign: Reverse query name for field 'selfclashforeign_set' clashes with field 'SelfClashForeign.selfclashforeign'. Add a related_name argument to the definition for 'selfclashforeign_set'. +invalid_models.selfclashforeign: Accessor for field 'foreign_1' clashes with field 'SelfClashForeign.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.selfclashforeign: Reverse query name for field 'foreign_1' clashes with field 'SelfClashForeign.id'. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.selfclashforeign: Accessor for field 'foreign_2' clashes with field 'SelfClashForeign.src_safe'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.selfclashforeign: Reverse query name for field 'foreign_2' clashes with field 'SelfClashForeign.src_safe'. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.selfclashm2m: Accessor for m2m field 'selfclashm2m_set' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'selfclashm2m_set'. +invalid_models.selfclashm2m: Reverse query name for m2m field 'selfclashm2m_set' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'selfclashm2m_set'. +invalid_models.selfclashm2m: Accessor for m2m field 'm2m_1' clashes with field 'SelfClashM2M.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.selfclashm2m: Accessor for m2m field 'm2m_2' clashes with field 'SelfClashM2M.src_safe'. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_1' clashes with field 'SelfClashM2M.id'. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_2' clashes with field 'SelfClashM2M.src_safe'. Add a related_name argument to the definition for 'm2m_2'. """ + diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index e0c4850ba2..a2c0a14158 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -58,6 +58,10 @@ Article 4 >>> Article.objects.filter(headline__startswith='Blah blah').count() 0L +# Date and date/time lookups can also be done with strings. +>>> Article.objects.filter(pub_date__exact='2005-07-27 00:00:00').count() +3L + # in_bulk() takes a list of IDs and returns a dictionary mapping IDs # to objects. >>> Article.objects.in_bulk([1, 2]) diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py index 7a685ecbd2..f43fb12d9e 100644 --- a/tests/modeltests/m2m_and_m2o/models.py +++ b/tests/modeltests/m2m_and_m2o/models.py @@ -28,24 +28,23 @@ API_TESTS = """ >>> r.save() >>> g = User(username='gustav') >>> g.save() + >>> i = Issue(num=1) >>> i.client = r ->>> i.validate() -{} >>> i.save() + >>> i2 = Issue(num=2) >>> i2.client = r ->>> i2.validate() -{} >>> i2.save() >>> i2.cc.add(r) + >>> i3 = Issue(num=3) >>> i3.client = g ->>> i3.validate() -{} >>> i3.save() >>> i3.cc.add(r) + >>> from django.db.models.query import Q + >>> Issue.objects.filter(client=r.id) [<Issue: 1>, <Issue: 2>] >>> Issue.objects.filter(client=g.id) @@ -55,8 +54,8 @@ API_TESTS = """ >>> Issue.objects.filter(cc__id__exact=r.id) [<Issue: 2>, <Issue: 3>] -# Queries that combine results from the m2m and the m2o relationship. -# 3 ways of saying the same thing: +# These queries combine results from the m2m and the m2o relationships. +# They're three ways of saying the same thing. >>> Issue.objects.filter(Q(cc__id__exact=r.id) | Q(client=r.id)) [<Issue: 1>, <Issue: 2>, <Issue: 3>] >>> Issue.objects.filter(cc__id__exact=r.id) | Issue.objects.filter(client=r.id) diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/modeltests/m2m_recursive/models.py index c109b9cc2c..dace32d565 100644 --- a/tests/modeltests/m2m_recursive/models.py +++ b/tests/modeltests/m2m_recursive/models.py @@ -2,7 +2,7 @@ 27. Many-to-many relationships between the same two tables In this example, A Person can have many friends, who are also people. Friendship is a -symmetrical relationshiup - if I am your friend, you are my friend. +symmetrical relationship - if I am your friend, you are my friend. A person can also have many idols - but while I may idolize you, you may not think the same of me. 'Idols' is an example of a non-symmetrical m2m field. Only recursive diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py index e80afece46..0e989a0fbe 100644 --- a/tests/modeltests/many_to_many/models.py +++ b/tests/modeltests/many_to_many/models.py @@ -75,6 +75,10 @@ API_TESTS = """ [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>] >>> Article.objects.filter(publications__pk=1) [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>] +>>> Article.objects.filter(publications=1) +[<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>] +>>> Article.objects.filter(publications=p1) +[<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>] >>> Article.objects.filter(publications__title__startswith="Science") [<Article: NASA uses Python>, <Article: NASA uses Python>] @@ -89,6 +93,13 @@ API_TESTS = """ >>> Article.objects.filter(publications__title__startswith="Science").distinct().count() 1 +>>> Article.objects.filter(publications__in=[1,2]).distinct() +[<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>] +>>> Article.objects.filter(publications__in=[1,p2]).distinct() +[<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>] +>>> Article.objects.filter(publications__in=[p1,p2]).distinct() +[<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>] + # Reverse m2m queries are supported (i.e., starting at the table that doesn't # have a ManyToManyField). >>> Publication.objects.filter(id__exact=1) @@ -101,9 +112,19 @@ API_TESTS = """ >>> Publication.objects.filter(article__id__exact=1) [<Publication: The Python Journal>] - >>> Publication.objects.filter(article__pk=1) [<Publication: The Python Journal>] +>>> Publication.objects.filter(article=1) +[<Publication: The Python Journal>] +>>> Publication.objects.filter(article=a1) +[<Publication: The Python Journal>] + +>>> Publication.objects.filter(article__in=[1,2]).distinct() +[<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>] +>>> Publication.objects.filter(article__in=[1,a2]).distinct() +[<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>] +>>> Publication.objects.filter(article__in=[a1,a2]).distinct() +[<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>] # If we delete a Publication, its Articles won't be able to access it. >>> p1.delete() diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index a830ffbdc2..d202975128 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -136,6 +136,10 @@ False >>> Article.objects.filter(reporter__first_name__exact='John') [<Article: John's second story>, <Article: This is a test>] +# Check that implied __exact also works +>>> Article.objects.filter(reporter__first_name='John') +[<Article: John's second story>, <Article: This is a test>] + # Query twice over the related field. >>> Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') [<Article: John's second story>, <Article: This is a test>] @@ -151,10 +155,20 @@ False [<Article: John's second story>, <Article: This is a test>] # Find all Articles for the Reporter whose ID is 1. +# Use direct ID check, pk check, and object comparison >>> Article.objects.filter(reporter__id__exact=1) [<Article: John's second story>, <Article: This is a test>] >>> Article.objects.filter(reporter__pk=1) [<Article: John's second story>, <Article: This is a test>] +>>> Article.objects.filter(reporter=1) +[<Article: John's second story>, <Article: This is a test>] +>>> Article.objects.filter(reporter=r) +[<Article: John's second story>, <Article: This is a test>] + +>>> Article.objects.filter(reporter__in=[1,2]).distinct() +[<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>] +>>> Article.objects.filter(reporter__in=[r,r2]).distinct() +[<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>] # You need two underscores between "reporter" and "id" -- not one. >>> Article.objects.filter(reporter_id__exact=1) @@ -168,10 +182,6 @@ Traceback (most recent call last): ... TypeError: Cannot resolve keyword 'reporter_id' into field -# "pk" shortcut syntax works in a related context, too. ->>> Article.objects.filter(reporter__pk=1) -[<Article: John's second story>, <Article: This is a test>] - # You can also instantiate an Article by passing # the Reporter's ID instead of a Reporter object. >>> a3 = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id=r.id) @@ -200,6 +210,18 @@ TypeError: Cannot resolve keyword 'reporter_id' into field [<Reporter: John Smith>] >>> Reporter.objects.filter(article__pk=1) [<Reporter: John Smith>] +>>> Reporter.objects.filter(article=1) +[<Reporter: John Smith>] +>>> Reporter.objects.filter(article=a) +[<Reporter: John Smith>] + +>>> Reporter.objects.filter(article__in=[1,4]).distinct() +[<Reporter: John Smith>] +>>> Reporter.objects.filter(article__in=[1,a3]).distinct() +[<Reporter: John Smith>] +>>> Reporter.objects.filter(article__in=[a,a3]).distinct() +[<Reporter: John Smith>] + >>> Reporter.objects.filter(article__headline__startswith='This') [<Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John Smith>] >>> Reporter.objects.filter(article__headline__startswith='This').distinct() @@ -216,6 +238,12 @@ TypeError: Cannot resolve keyword 'reporter_id' into field [<Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John Smith>, <Reporter: John Smith>] >>> Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct() [<Reporter: John Smith>] +>>> Reporter.objects.filter(article__reporter__exact=r).distinct() +[<Reporter: John Smith>] + +# Check that implied __exact also works +>>> Reporter.objects.filter(article__reporter=r).distinct() +[<Reporter: John Smith>] # If you delete a reporter, his articles will be deleted. >>> Article.objects.all() diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py index 27c16b5a34..f95556c08d 100644 --- a/tests/modeltests/one_to_one/models.py +++ b/tests/modeltests/one_to_one/models.py @@ -94,6 +94,12 @@ DoesNotExist: Restaurant matching query does not exist. <Restaurant: Demon Dogs the restaurant> >>> Restaurant.objects.get(place__exact=1) <Restaurant: Demon Dogs the restaurant> +>>> Restaurant.objects.get(place__exact=p1) +<Restaurant: Demon Dogs the restaurant> +>>> Restaurant.objects.get(place=1) +<Restaurant: Demon Dogs the restaurant> +>>> Restaurant.objects.get(place=p1) +<Restaurant: Demon Dogs the restaurant> >>> Restaurant.objects.get(place__pk=1) <Restaurant: Demon Dogs the restaurant> >>> Restaurant.objects.get(place__name__startswith="Demon") @@ -105,8 +111,18 @@ DoesNotExist: Restaurant matching query does not exist. <Place: Demon Dogs the place> >>> Place.objects.get(restaurant__place__exact=1) <Place: Demon Dogs the place> +>>> Place.objects.get(restaurant__place__exact=p1) +<Place: Demon Dogs the place> >>> Place.objects.get(restaurant__pk=1) <Place: Demon Dogs the place> +>>> Place.objects.get(restaurant=1) +<Place: Demon Dogs the place> +>>> Place.objects.get(restaurant=r) +<Place: Demon Dogs the place> +>>> Place.objects.get(restaurant__exact=1) +<Place: Demon Dogs the place> +>>> Place.objects.get(restaurant__exact=r) +<Place: Demon Dogs the place> # Add a Waiter to the Restaurant. >>> w = r.waiter_set.create(name='Joe') @@ -115,14 +131,22 @@ DoesNotExist: Restaurant matching query does not exist. <Waiter: Joe the waiter at Demon Dogs the restaurant> # Query the waiters +>>> Waiter.objects.filter(restaurant__place__pk=1) +[<Waiter: Joe the waiter at Demon Dogs the restaurant>] >>> Waiter.objects.filter(restaurant__place__exact=1) [<Waiter: Joe the waiter at Demon Dogs the restaurant>] +>>> Waiter.objects.filter(restaurant__place__exact=p1) +[<Waiter: Joe the waiter at Demon Dogs the restaurant>] >>> Waiter.objects.filter(restaurant__pk=1) [<Waiter: Joe the waiter at Demon Dogs the restaurant>] >>> Waiter.objects.filter(id__exact=1) [<Waiter: Joe the waiter at Demon Dogs the restaurant>] >>> Waiter.objects.filter(pk=1) [<Waiter: Joe the waiter at Demon Dogs the restaurant>] +>>> Waiter.objects.filter(restaurant=1) +[<Waiter: Joe the waiter at Demon Dogs the restaurant>] +>>> Waiter.objects.filter(restaurant=r) +[<Waiter: Joe the waiter at Demon Dogs the restaurant>] # Delete the restaurant; the waiter should also be removed >>> r = Restaurant.objects.get(pk=1) diff --git a/tests/modeltests/serializers/__init__.py b/tests/modeltests/serializers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/serializers/__init__.py diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py new file mode 100644 index 0000000000..ccf565c365 --- /dev/null +++ b/tests/modeltests/serializers/models.py @@ -0,0 +1,121 @@ +""" +XXX. Serialization + +``django.core.serializers`` provides interfaces to converting Django querysets +to and from "flat" data (i.e. strings). +""" + +from django.db import models + +class Category(models.Model): + name = models.CharField(maxlength=20) + + class Meta: + ordering = ('name',) + + def __str__(self): + return self.name + +class Author(models.Model): + name = models.CharField(maxlength=20) + + class Meta: + ordering = ('name',) + + def __str__(self): + return self.name + +class Article(models.Model): + author = models.ForeignKey(Author) + headline = models.CharField(maxlength=50) + pub_date = models.DateTimeField() + categories = models.ManyToManyField(Category) + + class Meta: + ordering = ('pub_date',) + + def __str__(self): + return self.headline + +API_TESTS = """ +# Create some data: +>>> from datetime import datetime +>>> sports = Category(name="Sports") +>>> music = Category(name="Music") +>>> op_ed = Category(name="Op-Ed") +>>> sports.save(); music.save(); op_ed.save() + +>>> joe = Author(name="Joe") +>>> jane = Author(name="Jane") +>>> joe.save(); jane.save() + +>>> a1 = Article( +... author = jane, +... headline = "Poker has no place on ESPN", +... pub_date = datetime(2006, 6, 16, 11, 00)) +>>> a2 = Article( +... author = joe, +... headline = "Time to reform copyright", +... pub_date = datetime(2006, 6, 16, 13, 00)) +>>> a1.save(); a2.save() +>>> a1.categories = [sports, op_ed] +>>> a2.categories = [music, op_ed] + +# Serialize a queryset to XML +>>> from django.core import serializers +>>> xml = serializers.serialize("xml", Article.objects.all()) + +# The output is valid XML +>>> from xml.dom import minidom +>>> dom = minidom.parseString(xml) + +# Deserializing has a similar interface, except that special DeserializedObject +# instances are returned. This is because data might have changed in the +# database since the data was serialized (we'll simulate that below). +>>> for obj in serializers.deserialize("xml", xml): +... print obj +<DeserializedObject: Poker has no place on ESPN> +<DeserializedObject: Time to reform copyright> + +# Deserializing data with different field values doesn't change anything in the +# database until we call save(): +>>> xml = xml.replace("Poker has no place on ESPN", "Poker has no place on television") +>>> objs = list(serializers.deserialize("xml", xml)) + +# Even those I deserialized, the database hasn't been touched +>>> Article.objects.all() +[<Article: Poker has no place on ESPN>, <Article: Time to reform copyright>] + +# But when I save, the data changes as you might except. +>>> objs[0].save() +>>> Article.objects.all() +[<Article: Poker has no place on television>, <Article: Time to reform copyright>] + +# Django also ships with a built-in JSON serializers +>>> json = serializers.serialize("json", Category.objects.filter(pk=2)) +>>> json +'[{"pk": "2", "model": "serializers.category", "fields": {"name": "Music"}}]' + +# You can easily create new objects by deserializing data with an empty PK +# (It's easier to demo this with JSON...) +>>> new_author_json = '[{"pk": null, "model": "serializers.author", "fields": {"name": "Bill"}}]' +>>> for obj in serializers.deserialize("json", new_author_json): +... obj.save() +>>> Author.objects.all() +[<Author: Bill>, <Author: Jane>, <Author: Joe>] + +# All the serializers work the same +>>> json = serializers.serialize("json", Article.objects.all()) +>>> for obj in serializers.deserialize("json", json): +... print obj +<DeserializedObject: Poker has no place on television> +<DeserializedObject: Time to reform copyright> + +>>> json = json.replace("Poker has no place on television", "Just kidding; I love TV poker") +>>> for obj in serializers.deserialize("json", json): +... obj.save() + +>>> Article.objects.all() +[<Article: Just kidding; I love TV poker>, <Article: Time to reform copyright>] + +""" diff --git a/tests/othertests/defaultfilters.py b/tests/othertests/defaultfilters.py index 46f2519285..1636b948d0 100644 --- a/tests/othertests/defaultfilters.py +++ b/tests/othertests/defaultfilters.py @@ -313,6 +313,36 @@ False >>> pluralize(2) 's' +>>> pluralize([1]) +'' + +>>> pluralize([]) +'s' + +>>> pluralize([1,2,3]) +'s' + +>>> pluralize(1,'es') +'' + +>>> pluralize(0,'es') +'es' + +>>> pluralize(2,'es') +'es' + +>>> pluralize(1,'y,ies') +'y' + +>>> pluralize(0,'y,ies') +'ies' + +>>> pluralize(2,'y,ies') +'ies' + +>>> pluralize(0,'y,ies,error') +'' + >>> phone2numeric('0800 flowers') '0800 3569377' diff --git a/tests/othertests/markup.py b/tests/othertests/markup.py index 3fa5a3a883..2b00a8c7a5 100644 --- a/tests/othertests/markup.py +++ b/tests/othertests/markup.py @@ -1,6 +1,7 @@ # Quick tests for the markup templatetags (django.contrib.markup) from django.template import Template, Context, add_to_builtins +import re add_to_builtins('django.contrib.markup.templatetags.markup') @@ -47,7 +48,8 @@ markdown_content = """Paragraph 1 t = Template("{{ markdown_content|markdown }}") rendered = t.render(Context(locals())).strip() if markdown: - assert rendered == """<p>Paragraph 1</p><h2>An h2</h2>""" + pattern = re.compile("""<p>Paragraph 1\s*</p>\s*<h2>\s*An h2</h2>""") + assert pattern.match(rendered) else: assert rendered == markdown_content diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py index c0333c90a6..9975f3b05c 100644 --- a/tests/othertests/templates.py +++ b/tests/othertests/templates.py @@ -1,5 +1,9 @@ from django.conf import settings +if __name__ == '__main__': + # When running this file in isolation, we need to set up the configuration + # before importing 'template'. + settings.configure() from django import template from django.template import loader @@ -78,7 +82,7 @@ TEMPLATE_TESTS = { 'basic-syntax03': ("{{ first }} --- {{ second }}", {"first" : 1, "second" : 2}, "1 --- 2"), # Fail silently when a variable is not found in the current context - 'basic-syntax04': ("as{{ missing }}df", {}, "asdf"), + 'basic-syntax04': ("as{{ missing }}df", {}, "asINVALIDdf"), # A variable may not contain more than one word 'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError), @@ -94,7 +98,7 @@ TEMPLATE_TESTS = { 'basic-syntax10': ("{{ var.otherclass.method }}", {"var": SomeClass()}, "OtherClass.method"), # Fail silently when a variable's attribute isn't found - 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, ""), + 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, "INVALID"), # Raise TemplateSyntaxError when trying to access a variable beginning with an underscore 'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError), @@ -110,10 +114,10 @@ TEMPLATE_TESTS = { 'basic-syntax18': ("{{ foo.bar }}", {"foo" : {"bar" : "baz"}}, "baz"), # Fail silently when a variable's dictionary key isn't found - 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, ""), + 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, "INVALID"), # Fail silently when accessing a non-simple method - 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, ""), + 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, "INVALID"), # Basic filter usage 'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"), @@ -152,7 +156,7 @@ TEMPLATE_TESTS = { 'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'), # Fail silently for methods that raise an exception with a "silent_variable_failure" attribute - 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "12"), + 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "1INVALID2"), # In methods that raise an exception without a "silent_variable_attribute" set to True, # the exception propogates @@ -406,6 +410,12 @@ TEMPLATE_TESTS = { # Three-level inheritance with {{ block.super }} from parent and grandparent 'inheritance23': ("{% extends 'inheritance20' %}{% block first %}{{ block.super }}b{% endblock %}", {}, '1_ab3_'), + # Inheritance from local context without use of template loader + 'inheritance24': ("{% extends context_template %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {'context_template': template.Template("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}")}, '1234'), + + # Inheritance from local context with variable parent template + 'inheritance25': ("{% extends context_template.1 %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {'context_template': [template.Template("Wrong"), template.Template("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}")]}, '1234'), + ### I18N ################################################################## # {% spaceless %} tag @@ -495,7 +505,7 @@ TEMPLATE_TESTS = { '{{ item.foo }}' + \ '{% endfor %},' + \ '{% endfor %}', - {}, ''), + {}, 'INVALID:INVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALID,'), ### TEMPLATETAG TAG ####################################################### 'templatetag01': ('{% templatetag openblock %}', {}, '{%'), @@ -538,10 +548,10 @@ TEMPLATE_TESTS = { ### TIMESINCE TAG ################################################## # Default compare with datetime.now() - 'timesince01' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=-1)}, '1 minute'), - 'timesince02' : ('{{ a|timesince }}', {'a':(datetime.now() - timedelta(days=1))}, '1 day'), + 'timesince01' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'), + 'timesince02' : ('{{ a|timesince }}', {'a':(datetime.now() - timedelta(days=1, minutes = 1))}, '1 day'), 'timesince03' : ('{{ a|timesince }}', {'a':(datetime.now() - - timedelta(hours=1, minutes=25))}, '1 hour, 25 minutes'), + timedelta(hours=1, minutes=25, seconds = 10))}, '1 hour, 25 minutes'), # Compare to a given parameter 'timesince04' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2), 'b':NOW + timedelta(days=1)}, '1 day'), @@ -552,9 +562,9 @@ TEMPLATE_TESTS = { ### TIMEUNTIL TAG ################################################## # Default compare with datetime.now() - 'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2)}, '2 minutes'), - 'timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1))}, '1 day'), - 'timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10))}, '8 hours, 10 minutes'), + 'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'), + 'timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'), + 'timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'), # Compare to a given parameter 'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'), @@ -579,6 +589,9 @@ def run_tests(verbosity=0, standalone=False): # Turn TEMPLATE_DEBUG off, because tests assume that. old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False + # Set TEMPLATE_STRING_IF_INVALID to a known string + old_invalid, settings.TEMPLATE_STRING_IF_INVALID = settings.TEMPLATE_STRING_IF_INVALID, 'INVALID' + for name, vals in tests: install() if 'LANGUAGE_CODE' in vals[1]: @@ -609,6 +622,7 @@ def run_tests(verbosity=0, standalone=False): loader.template_source_loaders = old_template_loaders deactivate() settings.TEMPLATE_DEBUG = old_td + settings.TEMPLATE_STRING_IF_INVALID = old_invalid if failed_tests and not standalone: msg = "Template tests %s failed." % failed_tests @@ -617,5 +631,4 @@ def run_tests(verbosity=0, standalone=False): raise Exception, msg if __name__ == "__main__": - settings.configure() run_tests(1, True) diff --git a/tests/othertests/urlpatterns_reverse.py b/tests/othertests/urlpatterns_reverse.py new file mode 100644 index 0000000000..236944d49f --- /dev/null +++ b/tests/othertests/urlpatterns_reverse.py @@ -0,0 +1,47 @@ +"Unit tests for reverse URL lookup" + +from django.core.urlresolvers import reverse_helper, NoReverseMatch +import re + +test_data = ( + ('^places/(\d+)/$', 'places/3/', [3], {}), + ('^places/(\d+)/$', 'places/3/', ['3'], {}), + ('^places/(\d+)/$', NoReverseMatch, ['a'], {}), + ('^places/(\d+)/$', NoReverseMatch, [], {}), + ('^places/(?P<id>\d+)/$', 'places/3/', [], {'id': 3}), + ('^people/(?P<name>\w+)/$', 'people/adrian/', ['adrian'], {}), + ('^people/(?P<name>\w+)/$', 'people/adrian/', [], {'name': 'adrian'}), + ('^people/(?P<name>\w+)/$', NoReverseMatch, ['name with spaces'], {}), + ('^people/(?P<name>\w+)/$', NoReverseMatch, [], {'name': 'name with spaces'}), + ('^people/(?P<name>\w+)/$', NoReverseMatch, [], {}), + ('^hardcoded/$', 'hardcoded/', [], {}), + ('^hardcoded/$', 'hardcoded/', ['any arg'], {}), + ('^hardcoded/$', 'hardcoded/', [], {'kwarg': 'foo'}), + ('^people/(?P<state>\w\w)/(?P<name>\w+)/$', 'people/il/adrian/', [], {'state': 'il', 'name': 'adrian'}), + ('^people/(?P<state>\w\w)/(?P<name>\d)/$', NoReverseMatch, [], {'state': 'il', 'name': 'adrian'}), + ('^people/(?P<state>\w\w)/(?P<name>\w+)/$', NoReverseMatch, [], {'state': 'il'}), + ('^people/(?P<state>\w\w)/(?P<name>\w+)/$', NoReverseMatch, [], {'name': 'adrian'}), + ('^people/(?P<state>\w\w)/(\w+)/$', NoReverseMatch, ['il'], {'name': 'adrian'}), + ('^people/(?P<state>\w\w)/(\w+)/$', 'people/il/adrian/', ['adrian'], {'state': 'il'}), +) + +def run_tests(verbosity=0): + for regex, expected, args, kwargs in test_data: + passed = True + try: + got = reverse_helper(re.compile(regex), *args, **kwargs) + except NoReverseMatch, e: + if expected != NoReverseMatch: + passed, got = False, str(e) + else: + if got != expected: + passed, got = False, got + if passed and verbosity: + print "Passed: %s" % regex + elif not passed: + print "REVERSE LOOKUP FAILED: %s" % regex + print " Got: %s" % got + print " Expected: %r" % expected + +if __name__ == "__main__": + run_tests(1) diff --git a/tests/regressiontests/many_to_one_regress/__init__.py b/tests/regressiontests/many_to_one_regress/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/regressiontests/many_to_one_regress/__init__.py diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py new file mode 100644 index 0000000000..485e928777 --- /dev/null +++ b/tests/regressiontests/many_to_one_regress/models.py @@ -0,0 +1,13 @@ +from django.db import models + +class First(models.Model): + second = models.IntegerField() + +class Second(models.Model): + first = models.ForeignKey(First, related_name = 'the_first') + +# If ticket #1578 ever slips back in, these models will not be able to be +# created (the field names being lower-cased versions of their opposite +# classes is important here). + +API_TESTS = "" diff --git a/tests/runtests.py b/tests/runtests.py index e5e9c18fca..b98a739249 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -99,8 +99,9 @@ class TestRunner: # Manually set INSTALLED_APPS to point to the test models. settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS + ['.'.join(a) for a in get_test_models()] - # Manually set DEBUG = False. + # Manually set DEBUG and USE_I18N. settings.DEBUG = False + settings.USE_I18N = True from django.db import connection from django.core import management |
