diff --git a/AUTHORS b/AUTHORS index 1bd0863fff..bf7ee55533 100644 --- a/AUTHORS +++ b/AUTHORS @@ -385,6 +385,7 @@ answer newbie questions, and generally made Django that much better: Vinay Sajip Kadesarin Sanjek Massimo Scamarcia + Paulo Scardine David Schein Bernd Schlapsi schwank@gmail.com diff --git a/django/conf/locale/pl/LC_MESSAGES/django.mo b/django/conf/locale/pl/LC_MESSAGES/django.mo index d52de20b5c..06b72035d5 100644 Binary files a/django/conf/locale/pl/LC_MESSAGES/django.mo and b/django/conf/locale/pl/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/pl/LC_MESSAGES/django.po b/django/conf/locale/pl/LC_MESSAGES/django.po index 405084aa29..d964aae958 100644 --- a/django/conf/locale/pl/LC_MESSAGES/django.po +++ b/django/conf/locale/pl/LC_MESSAGES/django.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-12-11 10:11+0100\n" +"POT-Creation-Date: 2009-12-16 19:53+0100\n" "PO-Revision-Date: 2008-02-25 15:53+0100\n" "Last-Translator: Jarek Zgoda \n" "MIME-Version: 1.0\n" @@ -223,7 +223,7 @@ msgstr "chiński tradycyjny" msgid "Successfully deleted %(count)d %(items)s." msgstr "Usunięto %(count)d %(items)s." -#: contrib/admin/actions.py:67 contrib/admin/options.py:1034 +#: contrib/admin/actions.py:67 contrib/admin/options.py:1040 msgid "Are you sure?" msgstr "Jesteś pewien?" @@ -371,26 +371,37 @@ msgid "" msgstr "" "%(name)s \"%(obj)s\" dodane pomyślnie. Możesz edytować ponownie wpis poniżej." -#: contrib/admin/options.py:778 +#: contrib/admin/options.py:696 +msgid "" +"Items must be selected in order to perform actions on them. No items have " +"been changed." +msgstr "" +"Wykonanie akcji wymaga wybrania obiektów. Żaden obiekt nie został zmieniony." + +#: contrib/admin/options.py:710 +msgid "No action selected." +msgstr "Nie wybrano akcji." + +#: contrib/admin/options.py:784 #, python-format msgid "Add %s" msgstr "Dodaj %s" -#: contrib/admin/options.py:810 contrib/admin/options.py:1012 +#: contrib/admin/options.py:816 contrib/admin/options.py:1018 #, python-format msgid "%(name)s object with primary key %(key)r does not exist." msgstr "Obiekt %(name)s o kluczu głównym %(key)r nie istnieje." -#: contrib/admin/options.py:867 +#: contrib/admin/options.py:873 #, python-format msgid "Change %s" msgstr "Zmień %s" -#: contrib/admin/options.py:911 +#: contrib/admin/options.py:917 msgid "Database error" msgstr "Błąd bazy danych" -#: contrib/admin/options.py:947 +#: contrib/admin/options.py:953 #, python-format msgid "%(count)s %(name)s was changed successfully." msgid_plural "%(count)s %(name)s were changed successfully." @@ -398,12 +409,12 @@ msgstr[0] "%(count)s %(name)s został pomyślnie zmieniony." msgstr[1] "%(count)s %(name)s zostały pomyślnie zmienione." msgstr[2] "%(count)s %(name)s zostało pomyślnie zmienionych." -#: contrib/admin/options.py:1027 +#: contrib/admin/options.py:1033 #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" usunięty pomyślnie." -#: contrib/admin/options.py:1064 +#: contrib/admin/options.py:1070 #, python-format msgid "Change history: %s" msgstr "Historia zmian: %s" @@ -1055,88 +1066,7 @@ msgstr "liczba %s" msgid "Fields on %s objects" msgstr "Pola obiektów %s" -#: contrib/admindocs/views.py:334 contrib/admindocs/views.py:345 -#: contrib/admindocs/views.py:347 contrib/admindocs/views.py:353 -#: contrib/admindocs/views.py:354 contrib/admindocs/views.py:356 -msgid "Integer" -msgstr "Liczba całkowita" - -#: contrib/admindocs/views.py:335 -msgid "Boolean (Either True or False)" -msgstr "Wartość logiczna (True, False - prawda lub fałsz)" - -#: contrib/admindocs/views.py:336 contrib/admindocs/views.py:355 -#, python-format -msgid "String (up to %(max_length)s)" -msgstr "Łańcuch (do %(max_length)s znaków)" - -#: contrib/admindocs/views.py:337 -msgid "Comma-separated integers" -msgstr "Liczby całkowite rozdzielone przecinkami" - -#: contrib/admindocs/views.py:338 -msgid "Date (without time)" -msgstr "Data (bez godziny)" - -#: contrib/admindocs/views.py:339 -msgid "Date (with time)" -msgstr "Data (z godziną)" - -#: contrib/admindocs/views.py:340 -msgid "Decimal number" -msgstr "Liczba dziesiętna" - -#: contrib/admindocs/views.py:341 -msgid "E-mail address" -msgstr "Adres e-mail" - -#: contrib/admindocs/views.py:342 contrib/admindocs/views.py:343 -#: contrib/admindocs/views.py:346 -msgid "File path" -msgstr "Ścieżka do pliku" - -#: contrib/admindocs/views.py:344 -msgid "Floating point number" -msgstr "Liczba zmiennoprzecinkowa" - -#: contrib/admindocs/views.py:348 contrib/comments/models.py:60 -msgid "IP address" -msgstr "Adres IP" - -#: contrib/admindocs/views.py:350 -msgid "Boolean (Either True, False or None)" -msgstr "Wartość logiczna (True, False, None - prawda, fałsz lub nic)" - -#: contrib/admindocs/views.py:351 -msgid "Relation to parent model" -msgstr "Relacja do modelu rodzica" - -#: contrib/admindocs/views.py:352 -msgid "Phone number" -msgstr "Numer telefonu" - -#: contrib/admindocs/views.py:357 -msgid "Text" -msgstr "Tekst" - -#: contrib/admindocs/views.py:358 -msgid "Time" -msgstr "Czas" - -#: contrib/admindocs/views.py:359 contrib/comments/forms.py:95 -#: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7 -msgid "URL" -msgstr "URL" - -#: contrib/admindocs/views.py:360 -msgid "U.S. state (two uppercase letters)" -msgstr "Stan USA (dwie duże litery)" - -#: contrib/admindocs/views.py:361 -msgid "XML text" -msgstr "Tekst XML" - -#: contrib/admindocs/views.py:387 +#: contrib/admindocs/views.py:356 #, python-format msgid "%s does not appear to be a urlpattern object" msgstr "%s nie jest obiektem urlpattern" @@ -1239,7 +1169,7 @@ msgid "Change password: %s" msgstr "Zmień hasło: %s" #: contrib/auth/forms.py:15 contrib/auth/forms.py:48 -#: contrib/auth/models.py:129 +#: contrib/auth/models.py:141 msgid "" "Required. 30 characters or fewer. Alphanumeric characters only (letters, " "digits and underscores)." @@ -1309,51 +1239,51 @@ msgstr "Stare hasło" msgid "Your old password was entered incorrectly. Please enter it again." msgstr "Podane stare hasło jest niepoprawne. Proszę podać je jeszcze raz." -#: contrib/auth/models.py:63 contrib/auth/models.py:86 +#: contrib/auth/models.py:70 contrib/auth/models.py:98 msgid "name" msgstr "nazwa" -#: contrib/auth/models.py:65 +#: contrib/auth/models.py:72 msgid "codename" msgstr "nazwa kodowa" -#: contrib/auth/models.py:68 +#: contrib/auth/models.py:76 msgid "permission" msgstr "uprawnienie" -#: contrib/auth/models.py:69 contrib/auth/models.py:87 +#: contrib/auth/models.py:77 contrib/auth/models.py:99 msgid "permissions" msgstr "uprawnienia" -#: contrib/auth/models.py:90 +#: contrib/auth/models.py:102 msgid "group" msgstr "grupa" -#: contrib/auth/models.py:91 contrib/auth/models.py:139 +#: contrib/auth/models.py:103 contrib/auth/models.py:151 msgid "groups" msgstr "grupy" -#: contrib/auth/models.py:129 +#: contrib/auth/models.py:141 msgid "username" msgstr "użytkownik" -#: contrib/auth/models.py:130 +#: contrib/auth/models.py:142 msgid "first name" msgstr "imię" -#: contrib/auth/models.py:131 +#: contrib/auth/models.py:143 msgid "last name" msgstr "nazwisko" -#: contrib/auth/models.py:132 +#: contrib/auth/models.py:144 msgid "e-mail address" msgstr "adres e-mail" -#: contrib/auth/models.py:133 +#: contrib/auth/models.py:145 msgid "password" msgstr "hasło" -#: contrib/auth/models.py:133 +#: contrib/auth/models.py:145 msgid "" "Use '[algo]$[salt]$[hexdigest]' or use the change " "password form." @@ -1361,19 +1291,19 @@ msgstr "" "Użyj '[algo]$[salt]$[hexdigest]' lub formularza zmiany " "hasła." -#: contrib/auth/models.py:134 +#: contrib/auth/models.py:146 msgid "staff status" msgstr "w zespole" -#: contrib/auth/models.py:134 +#: contrib/auth/models.py:146 msgid "Designates whether the user can log into this admin site." msgstr "Oznacza czy użytkownik może zalogować się do panelu admina." -#: contrib/auth/models.py:135 +#: contrib/auth/models.py:147 msgid "active" msgstr "aktywny" -#: contrib/auth/models.py:135 +#: contrib/auth/models.py:147 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -1381,11 +1311,11 @@ msgstr "" "Oznacza czy użytkownika należy uważać za aktywnego. Odznacz to, zamiast " "usuwać konta." -#: contrib/auth/models.py:136 +#: contrib/auth/models.py:148 msgid "superuser status" msgstr "status administratora" -#: contrib/auth/models.py:136 +#: contrib/auth/models.py:148 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -1393,15 +1323,15 @@ msgstr "" "Oznacza, że ten użytkownik ma wszystkie uprawnienia bez jawnego " "przypisywania ich." -#: contrib/auth/models.py:137 +#: contrib/auth/models.py:149 msgid "last login" msgstr "ostatnio zalogowany" -#: contrib/auth/models.py:138 +#: contrib/auth/models.py:150 msgid "date joined" msgstr "data przyłączenia" -#: contrib/auth/models.py:140 +#: contrib/auth/models.py:152 msgid "" "In addition to the permissions manually assigned, this user will also get " "all permissions granted to each group he/she is in." @@ -1409,20 +1339,20 @@ msgstr "" "Oprócz uprawnień przypisanych bezpośrednio użytkownikowi otrzyma on " "uprawnienia grup, do których należy." -#: contrib/auth/models.py:141 +#: contrib/auth/models.py:153 msgid "user permissions" msgstr "uprawnienia użytkownika" -#: contrib/auth/models.py:145 contrib/comments/models.py:50 +#: contrib/auth/models.py:157 contrib/comments/models.py:50 #: contrib/comments/models.py:168 msgid "user" msgstr "użytkownik" -#: contrib/auth/models.py:146 +#: contrib/auth/models.py:158 msgid "users" msgstr "użytkownicy" -#: contrib/auth/models.py:334 +#: contrib/auth/models.py:346 msgid "message" msgstr "wiadomość" @@ -1492,6 +1422,11 @@ msgstr "Nazwa" msgid "Email address" msgstr "Adres e-mail" +#: contrib/comments/forms.py:95 contrib/flatpages/admin.py:8 +#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:917 +msgid "URL" +msgstr "URL" + #: contrib/comments/forms.py:96 msgid "Comment" msgstr "Komentarz" @@ -1510,7 +1445,7 @@ msgid "" msgstr "" "Jeżeli wpiszesz cokolwiek w to pole, Twój komentarz zostanie uznany za spam" -#: contrib/comments/models.py:22 contrib/contenttypes/models.py:74 +#: contrib/comments/models.py:22 contrib/contenttypes/models.py:81 msgid "content type" msgstr "typ zawartości" @@ -1539,6 +1474,10 @@ msgstr "komentarz" msgid "date/time submitted" msgstr "data/czas dodania" +#: contrib/comments/models.py:60 db/models/fields/__init__.py:737 +msgid "IP address" +msgstr "Adres IP" + #: contrib/comments/models.py:61 msgid "is public" msgstr "publicznie dostępny" @@ -1706,11 +1645,11 @@ msgstr "Zapisz swój komentarz" msgid "or make changes" msgstr "lub wprowadź jakieś zmiany" -#: contrib/contenttypes/models.py:70 +#: contrib/contenttypes/models.py:77 msgid "python model class name" msgstr "nazwa pythonowa modelu klasy" -#: contrib/contenttypes/models.py:75 +#: contrib/contenttypes/models.py:82 msgid "content types" msgstr "typy zawartości" @@ -1782,6 +1721,39 @@ msgstr "" "Przepraszamy, ale Twój formularz wygasł. Proszę kontynuować wypełnianie " "formularza od tej strony." +#: contrib/gis/db/models/fields/__init__.py:42 +msgid "The base GIS field -- maps to the OpenGIS Specification Geometry type." +msgstr "" +"Podstawowe pole GIS - odpowiada typowi Geometry w specyfikacji OpenGIS." + +#: contrib/gis/db/models/fields/__init__.py:264 +msgid "Point" +msgstr "Punkt" + +#: contrib/gis/db/models/fields/__init__.py:268 +msgid "Line string" +msgstr "" + +#: contrib/gis/db/models/fields/__init__.py:272 +msgid "Polygon" +msgstr "Wielobok" + +#: contrib/gis/db/models/fields/__init__.py:276 +msgid "Multi-point" +msgstr "" + +#: contrib/gis/db/models/fields/__init__.py:280 +msgid "Multi-line string" +msgstr "" + +#: contrib/gis/db/models/fields/__init__.py:284 +msgid "Multi polygon" +msgstr "" + +#: contrib/gis/db/models/fields/__init__.py:288 +msgid "Geometry collection" +msgstr "" + #: contrib/gis/forms/fields.py:17 msgid "No geometry value provided." msgstr "Brak wartości geometrii." @@ -1994,11 +1966,11 @@ msgstr "Błędny numer CNPJ." msgid "This field requires at least 14 digits" msgstr "To pole musi zawierać co najmniej 14 cyfr" -#: contrib/localflavor/ca/forms.py:17 +#: contrib/localflavor/ca/forms.py:24 msgid "Enter a postal code in the format XXX XXX." msgstr "Wpisz kod pocztowy w formacie XXX XXX." -#: contrib/localflavor/ca/forms.py:88 +#: contrib/localflavor/ca/forms.py:95 msgid "Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format." msgstr "" "Wpisz poprawny numer kanadyjskiego ubezpieczenia w formacie XXX-XXX-XXXX." @@ -3767,6 +3739,14 @@ msgstr "Wpisz kod pocztowy w formacie XXXXX. lub XXXXX-XXXX." msgid "Enter a valid U.S. Social Security number in XXX-XX-XXXX format." msgstr "Wpisz poprawny numer U.S. Social Security w formacie XXX-XX-XXXX." +#: contrib/localflavor/us/models.py:8 +msgid "U.S. state (two uppercase letters)" +msgstr "Stan USA (dwie duże litery)" + +#: contrib/localflavor/us/models.py:17 +msgid "Phone number" +msgstr "Numer telefonu" + #: contrib/localflavor/za/forms.py:20 msgid "Enter a valid South African ID number" msgstr "Wpisz poprawny południowoafrykański numer ID" @@ -3875,62 +3855,139 @@ msgstr "wyświetlana nazwa" msgid "sites" msgstr "strony" -#: db/models/fields/__init__.py:356 db/models/fields/__init__.py:710 +#: db/models/fields/__init__.py:64 +#, python-format +msgid "Field of type: %(field_type)s" +msgstr "Pole typu: %(field_type)s" + +#: db/models/fields/__init__.py:350 db/models/fields/__init__.py:712 +#: db/models/fields/__init__.py:792 db/models/fields/__init__.py:802 +#: db/models/fields/__init__.py:829 +msgid "Integer" +msgstr "Liczba całkowita" + +#: db/models/fields/__init__.py:364 db/models/fields/__init__.py:728 msgid "This value must be an integer." msgstr "Ta wartość musi być liczbą całkowitą." -#: db/models/fields/__init__.py:388 +#: db/models/fields/__init__.py:382 +msgid "Boolean (Either True or False)" +msgstr "Wartość logiczna (True, False - prawda lub fałsz)" + +#: db/models/fields/__init__.py:397 msgid "This value must be either True or False." msgstr "" "Ta wartość musi być wartością logiczną (True, False - prawda lub fałsz)." -#: db/models/fields/__init__.py:427 +#: db/models/fields/__init__.py:425 db/models/fields/__init__.py:812 +#, python-format +msgid "String (up to %(max_length)s)" +msgstr "Łańcuch (do %(max_length)s znaków)" + +#: db/models/fields/__init__.py:437 msgid "This field cannot be null." msgstr "To pole nie może być puste." -#: db/models/fields/__init__.py:443 +#: db/models/fields/__init__.py:447 +msgid "Comma-separated integers" +msgstr "Liczby całkowite rozdzielone przecinkami" + +#: db/models/fields/__init__.py:454 msgid "Enter only digits separated by commas." msgstr "Wpisz tylko cyfry oddzielone przecinkami." -#: db/models/fields/__init__.py:474 +#: db/models/fields/__init__.py:463 +msgid "Date (without time)" +msgstr "Data (bez godziny)" + +#: db/models/fields/__init__.py:486 msgid "Enter a valid date in YYYY-MM-DD format." msgstr "Proszę wpisać poprawną datę w formacie RRRR-MM-DD." -#: db/models/fields/__init__.py:483 +#: db/models/fields/__init__.py:495 #, python-format msgid "Invalid date: %s" msgstr "Niepoprawna data: %s" -#: db/models/fields/__init__.py:547 db/models/fields/__init__.py:565 +#: db/models/fields/__init__.py:539 +msgid "Date (with time)" +msgstr "Data (z godziną)" + +#: db/models/fields/__init__.py:560 db/models/fields/__init__.py:578 msgid "Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format." msgstr "" "Wprowadź poprawną datę i godzinę w formacie YYYY-MM-DD HH:MM[:ss[.uuuuuu]]." -#: db/models/fields/__init__.py:601 +#: db/models/fields/__init__.py:600 +msgid "Decimal number" +msgstr "Liczba dziesiętna" + +#: db/models/fields/__init__.py:615 msgid "This value must be a decimal number." msgstr "Ta wartość musi być liczbą dziesiętną." +#: db/models/fields/__init__.py:654 +msgid "E-mail address" +msgstr "Adres e-mail" + +#: db/models/fields/__init__.py:665 db/models/fields/files.py:219 +#: db/models/fields/files.py:330 +msgid "File path" +msgstr "Ścieżka do pliku" + #: db/models/fields/__init__.py:686 +msgid "Floating point number" +msgstr "Liczba zmiennoprzecinkowa" + +#: db/models/fields/__init__.py:703 msgid "This value must be a float." msgstr "Ta wartość musi być liczbą rzeczywistą." -#: db/models/fields/__init__.py:746 +#: db/models/fields/__init__.py:752 +msgid "Boolean (Either True, False or None)" +msgstr "Wartość logiczna (True, False, None - prawda, fałsz lub nic)" + +#: db/models/fields/__init__.py:766 msgid "This value must be either None, True or False." msgstr "" "Ta wartość musi być jedną z None (nic), True (prawda) lub False (fałsz)." -#: db/models/fields/__init__.py:849 db/models/fields/__init__.py:863 +#: db/models/fields/__init__.py:834 +msgid "Text" +msgstr "Tekst" + +#: db/models/fields/__init__.py:844 +msgid "Time" +msgstr "Czas" + +#: db/models/fields/__init__.py:875 db/models/fields/__init__.py:889 msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format." msgstr "Proszę wpisać poprawną godzinę w formacie HH:MM[:ss[.uuuuuu]]." -#: db/models/fields/related.py:869 +#: db/models/fields/__init__.py:929 +msgid "XML text" +msgstr "Tekst XML" + +#: db/models/fields/related.py:695 +msgid "Foreign Key (type determined by related field)" +msgstr "Klucz obcy (typ określony przez pole powiązane)" + +#: db/models/fields/related.py:798 +msgid "One-to-one relationship" +msgstr "Powiązanie jeden do jednego" + +#: db/models/fields/related.py:852 +msgid "Many-to-many relationship" +msgstr "Powiązanie wiele do wiele" + +#: db/models/fields/related.py:872 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" "Przytrzymaj wciśnięty klawisz \"Ctrl\" lub \"Command\" na Mac'u aby " "zaznaczyć więcej niż jeden wybór." -#: db/models/fields/related.py:930 +#: db/models/fields/related.py:933 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid_plural "" @@ -4054,7 +4111,7 @@ msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "Wybierz poprawną wartość. %(value)s nie jest jednym z dostępnych wyborów." -#: forms/fields.py:703 forms/fields.py:764 forms/models.py:999 +#: forms/fields.py:703 forms/fields.py:764 forms/models.py:1004 msgid "Enter a list of values." msgstr "Podaj listę wartości." @@ -4106,29 +4163,29 @@ msgstr "" msgid "Please correct the duplicate values below." msgstr "Popraw poniższe zduplikowane wartości." -#: forms/models.py:863 +#: forms/models.py:865 msgid "The inline foreign key did not match the parent instance primary key." msgstr "Osadzony klucz obcy nie pasuje do klucza głównego obiektu rodzica." -#: forms/models.py:926 +#: forms/models.py:931 msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Wybierz poprawną wartość. Podana nie jest jednym z dostępnych wyborów." -#: forms/models.py:1000 +#: forms/models.py:1005 #, python-format msgid "Select a valid choice. %s is not one of the available choices." msgstr "Wybierz poprawną wartość. %s nie jest jednym z dostępnych wyborów." -#: forms/models.py:1002 +#: forms/models.py:1007 #, python-format msgid "\"%s\" is not a valid value for a primary key." msgstr "\"%s\" nie jest poprawną wartością klucza głównego." -#: template/defaultfilters.py:767 +#: template/defaultfilters.py:768 msgid "yes,no,maybe" msgstr "tak,nie,może" -#: template/defaultfilters.py:798 +#: template/defaultfilters.py:799 #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" @@ -4136,17 +4193,17 @@ msgstr[0] "%(size)d bajt" msgstr[1] "%(size)d bajty" msgstr[2] "%(size)d bajtów" -#: template/defaultfilters.py:800 +#: template/defaultfilters.py:801 #, python-format msgid "%.1f KB" msgstr "%.1f KB" -#: template/defaultfilters.py:802 +#: template/defaultfilters.py:803 #, python-format msgid "%.1f MB" msgstr "%.1f MB" -#: template/defaultfilters.py:803 +#: template/defaultfilters.py:804 #, python-format msgid "%.1f GB" msgstr "%.1f GB" @@ -4446,6 +4503,9 @@ msgstr "%(verbose_name)s zostało pomyślnie zmienione." msgid "The %(verbose_name)s was deleted." msgstr "%(verbose_name)s zostało usunięte." +#~ msgid "Relation to parent model" +#~ msgstr "Relacja do modelu rodzica" + #~ msgid "Comment moderation queue" #~ msgstr "Kolejka moderacji komentarzy" diff --git a/django/contrib/admindocs/tests/__init__.py b/django/contrib/admindocs/tests/__init__.py index a091ebe122..b4526c6a7e 100644 --- a/django/contrib/admindocs/tests/__init__.py +++ b/django/contrib/admindocs/tests/__init__.py @@ -1,36 +1,30 @@ import unittest -from django.contrib.admindocs import views import fields - +from django.contrib.admindocs import views from django.db.models import fields as builtin_fields + class TestFieldType(unittest.TestCase): def setUp(self): pass - + def test_field_name(self): self.assertRaises(AttributeError, views.get_readable_field_data_type, "NotAField" ) - + def test_builtin_fields(self): self.assertEqual( views.get_readable_field_data_type(builtin_fields.BooleanField()), u'Boolean (Either True or False)' ) - + def test_custom_fields(self): self.assertEqual( views.get_readable_field_data_type(fields.CustomField()), u'A custom field type' ) self.assertEqual( - views.get_readable_field_data_type(fields.DocstringLackingField()), - u'Field of type: DocstringLackingField' - ) - - def test_multiline_custom_field_truncation(self): - self.assertEqual( - views.get_readable_field_data_type(fields.ManyLineDocstringField()), - u'Many-line custom field' + views.get_readable_field_data_type(fields.DescriptionLackingField()), + u'Field of type: DescriptionLackingField' ) diff --git a/django/contrib/admindocs/tests/fields.py b/django/contrib/admindocs/tests/fields.py index 5cab3627c6..e59498afb5 100644 --- a/django/contrib/admindocs/tests/fields.py +++ b/django/contrib/admindocs/tests/fields.py @@ -1,13 +1,7 @@ from django.db import models class CustomField(models.Field): - """A custom field type""" - -class ManyLineDocstringField(models.Field): - """Many-line custom field - - This docstring has many lines. Lorum ipsem etc. etc. Four score - and seven years ago, and so on and so forth.""" + description = "A custom field type" -class DocstringLackingField(models.Field): +class DescriptionLackingField(models.Field): pass diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index d04030e9f0..bcf8353287 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -327,19 +327,11 @@ def get_return_data_type(func_name): return '' def get_readable_field_data_type(field): - """Returns the first line of a doc string for a given field type, if it - exists. Fields' docstrings can contain format strings, which will be - interpolated against the values of Field.__dict__ before being output. - If no docstring is given, a sensible value will be auto-generated from - the field's class name.""" + """Returns the description for a given field type, if it exists, + Fields' descriptions can contain format strings, which will be interpolated + against the values of field.__dict__ before being output.""" - if field.__doc__: - doc = field.__doc__.split('\n')[0] - return _(doc) % field.__dict__ - else: - return _(u'Field of type: %(field_type)s') % { - 'field_type': field.__class__.__name__ - } + return field.description % field.__dict__ def extract_views_from_urlpatterns(urlpatterns, base=''): """ diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index 93e85b7965..2f1b8efd07 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -292,15 +292,27 @@ class BaseGenericInlineFormSet(BaseModelFormSet): ct_field_name = "content_type" ct_fk_field_name = "object_id" - def __init__(self, data=None, files=None, instance=None, save_as_new=None, prefix=None): + def __init__(self, data=None, files=None, instance=None, save_as_new=None, + prefix=None, queryset=None): + # Avoid a circular import. + from django.contrib.contenttypes.models import ContentType opts = self.model._meta self.instance = instance self.rel_name = '-'.join(( opts.app_label, opts.object_name.lower(), self.ct_field.name, self.ct_fk_field.name, )) + if self.instance is None or self.instance.pk is None: + qs = self.model._default_manager.none() + else: + if queryset is None: + queryset = self.model._default_manager + qs = queryset.filter(**{ + self.ct_field.name: ContentType.objects.get_for_model(self.instance), + self.ct_fk_field.name: self.instance.pk, + }) super(BaseGenericInlineFormSet, self).__init__( - queryset=self.get_queryset(), data=data, files=files, + queryset=qs, data=data, files=files, prefix=prefix ) @@ -312,19 +324,6 @@ class BaseGenericInlineFormSet(BaseModelFormSet): )) get_default_prefix = classmethod(get_default_prefix) - def get_queryset(self): - # Avoid a circular import. - from django.contrib.contenttypes.models import ContentType - if self.instance is None or self.instance.pk is None: - return self.model._default_manager.none() - qs = self.model._default_manager.filter(**{ - self.ct_field.name: ContentType.objects.get_for_model(self.instance), - self.ct_fk_field.name: self.instance.pk, - }) - if not qs.ordered: - qs = qs.order_by(self.model._meta.pk.name) - return qs - def save_new(self, form, commit=True): # Avoid a circular import. from django.contrib.contenttypes.models import ContentType diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py index 0a4c46d4ee..65eb1f171c 100644 --- a/django/contrib/gis/db/models/fields.py +++ b/django/contrib/gis/db/models/fields.py @@ -1,4 +1,5 @@ from django.db.models.fields import Field +from django.utils.translation import ugettext_lazy as _ from django.contrib.gis import forms from django.contrib.gis.db.models.proxy import GeometryProxy from django.contrib.gis.geometry.backend import Geometry, GeometryException @@ -49,6 +50,8 @@ class GeometryField(Field): # Geodetic units. geodetic_units = ('Decimal Degree', 'degree') + description = _("The base GIS field -- maps to the OpenGIS Specification Geometry type.") + def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, geography=False, **kwargs): """ @@ -287,22 +290,28 @@ class GeometryField(Field): # The OpenGIS Geometry Type Fields class PointField(GeometryField): geom_type = 'POINT' + description = _("Point") class LineStringField(GeometryField): geom_type = 'LINESTRING' + description = _("Line string") class PolygonField(GeometryField): geom_type = 'POLYGON' + description = _("Polygon") class MultiPointField(GeometryField): geom_type = 'MULTIPOINT' + description = _("Multi-point") class MultiLineStringField(GeometryField): geom_type = 'MULTILINESTRING' + description = _("Multi-line string") class MultiPolygonField(GeometryField): geom_type = 'MULTIPOLYGON' + description = _("Multi polygon") class GeometryCollectionField(GeometryField): geom_type = 'GEOMETRYCOLLECTION' - + description = _("Geometry collection") diff --git a/django/contrib/localflavor/us/models.py b/django/contrib/localflavor/us/models.py index 01157d038b..6ce6d3bec7 100644 --- a/django/contrib/localflavor/us/models.py +++ b/django/contrib/localflavor/us/models.py @@ -1,16 +1,21 @@ from django.conf import settings +from django.utils.translation import ugettext_lazy as _ from django.db.models.fields import Field, CharField from django.contrib.localflavor.us.us_states import STATE_CHOICES - + class USStateField(CharField): - """U.S. state (two uppercase letters)""" + + description = _("U.S. state (two uppercase letters)") + def __init__(self, *args, **kwargs): kwargs['choices'] = STATE_CHOICES kwargs['max_length'] = 2 super(USStateField, self).__init__(*args, **kwargs) - + class PhoneNumberField(Field): - """Phone number""" + + description = _("Phone number") + def get_internal_type(self): return "PhoneNumberField" diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 495dce0993..dd20d44fce 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -143,10 +143,15 @@ def sort_dependencies(app_list): changed = False while model_dependencies: model, deps = model_dependencies.pop() - if all((d not in models or d in model_list) for d in deps): - # If all of the models in the dependency list are either already - # on the final model list, or not on the original serialization list, - # then we've found another model with all it's dependencies satisfied. + + # If all of the models in the dependency list are either already + # on the final model list, or not on the original serialization list, + # then we've found another model with all it's dependencies satisfied. + found = True + for candidate in ((d not in models or d in model_list) for d in deps): + if not candidate: + found = False + if found: model_list.append(model) changed = True else: @@ -158,4 +163,4 @@ def sort_dependencies(app_list): ) model_dependencies = skipped - return model_list + return model_list \ No newline at end of file diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 4b4d982b6b..d8b0c648c8 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -60,6 +60,13 @@ class Field(object): creation_counter = 0 auto_creation_counter = -1 + # Generic field type description, usually overriden by subclasses + def _description(self): + return _(u'Field of type: %(field_type)s') % { + 'field_type': self.__class__.__name__ + } + description = property(_description) + def __init__(self, verbose_name=None, name=None, primary_key=False, max_length=None, unique=False, blank=False, null=False, db_index=False, rel=None, default=NOT_PROVIDED, editable=True, @@ -369,10 +376,9 @@ class Field(object): return getattr(obj, self.attname) class AutoField(Field): - """Integer""" + description = ugettext_lazy("Integer") empty_strings_allowed = False - def __init__(self, *args, **kwargs): assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__ kwargs['blank'] = True @@ -402,10 +408,8 @@ class AutoField(Field): return None class BooleanField(Field): - """Boolean (Either True or False)""" - empty_strings_allowed = False - + description = ugettext_lazy("Boolean (Either True or False)") def __init__(self, *args, **kwargs): kwargs['blank'] = True if 'default' not in kwargs and not kwargs.get('null'): @@ -448,7 +452,7 @@ class BooleanField(Field): return super(BooleanField, self).formfield(**defaults) class CharField(Field): - """String (up to %(max_length)s)""" + description = ugettext_lazy("String (up to %(max_length)s)") def get_internal_type(self): return "CharField" @@ -471,7 +475,7 @@ class CharField(Field): # TODO: Maybe move this into contrib, because it's specialized. class CommaSeparatedIntegerField(CharField): - """Comma-separated integers""" + description = ugettext_lazy("Comma-separated integers") def formfield(self, **kwargs): defaults = { @@ -488,10 +492,9 @@ class CommaSeparatedIntegerField(CharField): ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$') class DateField(Field): - """Date (without time)""" + description = ugettext_lazy("Date (without time)") empty_strings_allowed = False - def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): self.auto_now, self.auto_now_add = auto_now, auto_now_add #HACKs : auto_now_add/auto_now should be done as a default or a pre_save. @@ -571,7 +574,7 @@ class DateField(Field): return super(DateField, self).formfield(**defaults) class DateTimeField(DateField): - """Date (with time)""" + description = ugettext_lazy("Date (with time)") def get_internal_type(self): return "DateTimeField" @@ -637,10 +640,8 @@ class DateTimeField(DateField): return super(DateTimeField, self).formfield(**defaults) class DecimalField(Field): - """Decimal number""" - empty_strings_allowed = False - + description = ugettext_lazy("Decimal number") def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): self.max_digits, self.decimal_places = max_digits, decimal_places Field.__init__(self, verbose_name, name, **kwargs) @@ -694,8 +695,7 @@ class DecimalField(Field): return super(DecimalField, self).formfield(**defaults) class EmailField(CharField): - """E-mail address""" - + description = ugettext_lazy("E-mail address") def __init__(self, *args, **kwargs): kwargs['max_length'] = kwargs.get('max_length', 75) CharField.__init__(self, *args, **kwargs) @@ -706,7 +706,7 @@ class EmailField(CharField): return super(EmailField, self).formfield(**defaults) class FilePathField(Field): - """File path""" + description = ugettext_lazy("File path") def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): self.path, self.match, self.recursive = path, match, recursive @@ -727,9 +727,8 @@ class FilePathField(Field): return "FilePathField" class FloatField(Field): - """Floating point number""" - empty_strings_allowed = False + description = ugettext_lazy("Floating point number") def get_prep_value(self, value): if value is None: @@ -754,9 +753,8 @@ class FloatField(Field): return super(FloatField, self).formfield(**defaults) class IntegerField(Field): - """Integer""" - empty_strings_allowed = False + description = ugettext_lazy("Integer") def get_prep_value(self, value): if value is None: @@ -781,10 +779,8 @@ class IntegerField(Field): return super(IntegerField, self).formfield(**defaults) class IPAddressField(Field): - """IP address""" - empty_strings_allowed = False - + description = ugettext_lazy("IP address") def __init__(self, *args, **kwargs): kwargs['max_length'] = 15 Field.__init__(self, *args, **kwargs) @@ -798,10 +794,8 @@ class IPAddressField(Field): return super(IPAddressField, self).formfield(**defaults) class NullBooleanField(Field): - """Boolean (Either True, False or None)""" - empty_strings_allowed = False - + description = ugettext_lazy("Boolean (Either True, False or None)") def __init__(self, *args, **kwargs): kwargs['null'] = True Field.__init__(self, *args, **kwargs) @@ -841,7 +835,7 @@ class NullBooleanField(Field): return super(NullBooleanField, self).formfield(**defaults) class PositiveIntegerField(IntegerField): - """Integer""" + description = ugettext_lazy("Integer") def get_internal_type(self): return "PositiveIntegerField" @@ -852,8 +846,7 @@ class PositiveIntegerField(IntegerField): return super(PositiveIntegerField, self).formfield(**defaults) class PositiveSmallIntegerField(IntegerField): - """Integer""" - + description = ugettext_lazy("Integer") def get_internal_type(self): return "PositiveSmallIntegerField" @@ -863,8 +856,7 @@ class PositiveSmallIntegerField(IntegerField): return super(PositiveSmallIntegerField, self).formfield(**defaults) class SlugField(CharField): - """String (up to %(max_length)s)""" - + description = ugettext_lazy("String (up to %(max_length)s)") def __init__(self, *args, **kwargs): kwargs['max_length'] = kwargs.get('max_length', 50) # Set db_index=True unless it's been set manually. @@ -881,13 +873,13 @@ class SlugField(CharField): return super(SlugField, self).formfield(**defaults) class SmallIntegerField(IntegerField): - """Integer""" + description = ugettext_lazy("Integer") def get_internal_type(self): return "SmallIntegerField" class TextField(Field): - """Text""" + description = ugettext_lazy("Text") def get_internal_type(self): return "TextField" @@ -898,10 +890,9 @@ class TextField(Field): return super(TextField, self).formfield(**defaults) class TimeField(Field): - """Time""" + description = ugettext_lazy("Time") empty_strings_allowed = False - def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): self.auto_now, self.auto_now_add = auto_now, auto_now_add if auto_now or auto_now_add: @@ -978,7 +969,7 @@ class TimeField(Field): return super(TimeField, self).formfield(**defaults) class URLField(CharField): - """URL""" + description = ugettext_lazy("URL") def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): kwargs['max_length'] = kwargs.get('max_length', 200) @@ -991,7 +982,7 @@ class URLField(CharField): return super(URLField, self).formfield(**defaults) class XMLField(TextField): - """XML text""" + description = ugettext_lazy("XML text") def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs): self.schema_path = schema_path diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index 3debb25cb6..d3ba975412 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -209,8 +209,6 @@ class FileDescriptor(object): instance.__dict__[self.field.name] = value class FileField(Field): - """File path""" - # The class to wrap instance attributes in. Accessing the file object off # the instance will always return an instance of attr_class. attr_class = FieldFile @@ -218,6 +216,8 @@ class FileField(Field): # The descriptor to use for accessing the attribute off of the class. descriptor_class = FileDescriptor + description = ugettext_lazy("File path") + def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs): for arg in ('primary_key', 'unique'): if arg in kwargs: @@ -325,10 +325,9 @@ class ImageFieldFile(ImageFile, FieldFile): super(ImageFieldFile, self).delete(save) class ImageField(FileField): - """File path""" - attr_class = ImageFieldFile descriptor_class = ImageFileDescriptor + description = ugettext_lazy("File path") def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): self.width_field, self.height_field = width_field, height_field diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 7d5a41389a..cc6d7dfef8 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -708,9 +708,8 @@ class ManyToManyRel(object): return self.to._meta.pk class ForeignKey(RelatedField, Field): - """Foreign Key (type determined by related field)""" - empty_strings_allowed = False + description = ugettext_lazy("Foreign Key (type determined by related field)") def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): try: to_name = to._meta.object_name.lower() @@ -808,13 +807,13 @@ class ForeignKey(RelatedField, Field): return rel_field.db_type(connection=connection) class OneToOneField(ForeignKey): - """One-to-one relationship - + """ A OneToOneField is essentially the same as a ForeignKey, with the exception that always carries a "unique" constraint with it and the reverse relation always returns the object pointed to (since there will only ever be one), - rather than returning a list.""" - + rather than returning a list. + """ + description = ugettext_lazy("One-to-one relationship") def __init__(self, to, to_field=None, **kwargs): kwargs['unique'] = True super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs) @@ -868,8 +867,7 @@ def create_many_to_many_intermediary_model(field, klass): }) class ManyToManyField(RelatedField, Field): - """Many-to-many relationship""" - + description = ugettext_lazy("Many-to-many relationship") def __init__(self, to, **kwargs): try: assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 4ab1d5005a..f86b691cc9 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -35,6 +35,7 @@ class AppCache(object): postponed = [], nesting_level = 0, write_lock = threading.RLock(), + _get_models_cache = {}, ) def __init__(self): @@ -140,6 +141,11 @@ class AppCache(object): explicit intermediate table) are not included. However, if you specify include_auto_created=True, they will be. """ + cache_key = (app_mod, include_auto_created) + try: + return self._get_models_cache[cache_key] + except KeyError: + pass self._populate() if app_mod: model_list = self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values() @@ -148,7 +154,8 @@ class AppCache(object): for app_entry in self.app_models.itervalues(): model_list.extend(app_entry.values()) if not include_auto_created: - return filter(lambda o: not o._meta.auto_created, model_list) + model_list = filter(lambda o: not o._meta.auto_created, model_list) + self._get_models_cache[cache_key] = model_list return model_list def get_model(self, app_label, model_name, seed_cache=True): @@ -183,6 +190,7 @@ class AppCache(object): if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]: continue model_dict[model_name] = model + self._get_models_cache.clear() cache = AppCache() diff --git a/django/forms/models.py b/django/forms/models.py index 81b727111e..25e9b01e68 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -704,7 +704,7 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.f class BaseInlineFormSet(BaseModelFormSet): """A formset for child objects related to a parent.""" def __init__(self, data=None, files=None, instance=None, - save_as_new=False, prefix=None): + save_as_new=False, prefix=None, queryset=None): from django.db.models.fields.related import RelatedObject if instance is None: self.instance = self.fk.rel.to() @@ -717,7 +717,9 @@ class BaseInlineFormSet(BaseModelFormSet): backlink_value = self.instance else: backlink_value = getattr(self.instance, self.fk.rel.field_name) - qs = self.model._default_manager.filter(**{self.fk.name: backlink_value}) + if queryset is None: + queryset = self.model._default_manager + qs = queryset.filter(**{self.fk.name: backlink_value}) super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix, queryset=qs) diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 13e4fff36d..04fb9d24be 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -5,6 +5,7 @@ Writing custom model fields =========================== .. versionadded:: 1.0 +.. currentmodule:: django.db.models Introduction ============ @@ -165,7 +166,8 @@ behave like any existing field, so we'll subclass directly from from django.db import models class HandField(models.Field): - """A hand of cards (bridge style)""" + + description = "A hand of cards (bridge style)" def __init__(self, *args, **kwargs): kwargs['max_length'] = 104 @@ -248,7 +250,8 @@ simple: make sure your field subclass uses a special metaclass: For example:: class HandField(models.Field): - """A hand of cards (bridge style)""" + + description = "A hand of cards (bridge style)" __metaclass__ = models.SubfieldBase @@ -262,16 +265,17 @@ called when the attribute is initialized. Documenting your Custom Field ----------------------------- +.. class:: django.db.models.Field + +.. attribute:: description + As always, you should document your field type, so users will know what it is. -The best way to do this is to simply provide a docstring for it. This will -automatically be picked up by ``django.contrib.admindocs``, if you have it -installed, and the first line of it will show up as the field type in the -documentation for any model that uses your field. In the above examples, it -will show up as 'A hand of cards (bridge style)'. Note that if you provide a -more verbose docstring, only the first line will show up in -``django.contrib.admindocs``. The full docstring will, of course, still be -available through ``pydoc`` or the interactive interpreter's ``help()`` -function. +In addition to providing a docstring for it, which is useful for developers, +you can also allow users of the admin app to see a short description of the +field type via the ``django.contrib.admindocs`` application. To do this simply +provide descriptive text in a ``description`` class attribute of your custom field. +In the above example, the type description displayed by the ``admindocs`` application +for a ``HandField`` will be 'A hand of cards (bridge style)'. Useful methods -------------- diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py index d6f541bfd5..5c62885be5 100644 --- a/tests/modeltests/model_formsets/models.py +++ b/tests/modeltests/model_formsets/models.py @@ -644,6 +644,53 @@ True >>> formset.save() [, ] +We can provide a custom queryset to our InlineFormSet: + +>>> custom_qs = Book.objects.order_by('-title') +>>> formset = AuthorBooksFormSet(instance=author, queryset=custom_qs) +>>> for form in formset.forms: +... print form.as_p() +

+

+

+

+

+ +>>> data = { +... 'book_set-TOTAL_FORMS': '5', # the number of forms rendered +... 'book_set-INITIAL_FORMS': '3', # the number of forms with initial data +... 'book_set-0-id': '1', +... 'book_set-0-title': 'Les Fleurs du Mal', +... 'book_set-1-id': '2', +... 'book_set-1-title': 'Le Spleen de Paris', +... 'book_set-2-id': '5', +... 'book_set-2-title': 'Flowers of Evil', +... 'book_set-3-title': 'Revue des deux mondes', +... 'book_set-4-title': '', +... } +>>> formset = AuthorBooksFormSet(data, instance=author, queryset=custom_qs) +>>> formset.is_valid() +True + +>>> custom_qs = Book.objects.filter(title__startswith='F') +>>> formset = AuthorBooksFormSet(instance=author, queryset=custom_qs) +>>> for form in formset.forms: +... print form.as_p() +

+

+

+>>> data = { +... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered +... 'book_set-INITIAL_FORMS': '1', # the number of forms with initial data +... 'book_set-0-id': '5', +... 'book_set-0-title': 'Flowers of Evil', +... 'book_set-1-title': 'Revue des deux mondes', +... 'book_set-2-title': '', +... } +>>> formset = AuthorBooksFormSet(data, instance=author, queryset=custom_qs) +>>> formset.is_valid() +True + # Test a custom primary key ################################################### diff --git a/tests/regressiontests/generic_inline_admin/models.py b/tests/regressiontests/generic_inline_admin/models.py index bc6764d8b2..9f1bbadd3e 100644 --- a/tests/regressiontests/generic_inline_admin/models.py +++ b/tests/regressiontests/generic_inline_admin/models.py @@ -20,7 +20,7 @@ class Media(models.Model): class MediaInline(generic.GenericTabularInline): model = Media - + class EpisodeAdmin(admin.ModelAdmin): inlines = [ MediaInline, @@ -56,7 +56,7 @@ class MediaMaxNumInline(generic.GenericTabularInline): model = Media extra = 5 max_num = 2 - + admin.site.register(EpisodeMaxNum, inlines=[MediaMaxNumInline]) # diff --git a/tests/regressiontests/generic_inline_admin/tests.py b/tests/regressiontests/generic_inline_admin/tests.py index d28cd16e1d..83542d1528 100644 --- a/tests/regressiontests/generic_inline_admin/tests.py +++ b/tests/regressiontests/generic_inline_admin/tests.py @@ -17,7 +17,7 @@ class GenericAdminViewTest(TestCase): self.original_template_debug = settings.TEMPLATE_DEBUG settings.TEMPLATE_DEBUG = True self.client.login(username='super', password='secret') - + # Can't load content via a fixture (since the GenericForeignKey # relies on content type IDs, which will vary depending on what # other tests have been run), thus we do it here. @@ -25,26 +25,30 @@ class GenericAdminViewTest(TestCase): self.episode_pk = e.pk m = Media(content_object=e, url='http://example.com/podcast.mp3') m.save() - self.media_pk = m.pk - + self.mp3_media_pk = m.pk + + m = Media(content_object=e, url='http://example.com/logo.png') + m.save() + self.png_media_pk = m.pk + def tearDown(self): self.client.logout() settings.TEMPLATE_DEBUG = self.original_template_debug - + def testBasicAddGet(self): """ A smoke test to ensure GET on the add_view works. """ response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episode/add/') self.failUnlessEqual(response.status_code, 200) - + def testBasicEditGet(self): """ A smoke test to ensure GET on the change_view works. """ response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episode/%d/' % self.episode_pk) self.failUnlessEqual(response.status_code, 200) - + def testBasicAddPost(self): """ A smoke test to ensure POST on add_view works. @@ -57,7 +61,7 @@ class GenericAdminViewTest(TestCase): } response = self.client.post('/generic_inline_admin/admin/generic_inline_admin/episode/add/', post_data) self.failUnlessEqual(response.status_code, 302) # redirect somewhere - + def testBasicEditPost(self): """ A smoke test to ensure POST on edit_view works. @@ -65,17 +69,45 @@ class GenericAdminViewTest(TestCase): post_data = { "name": u"This Week in Django", # inline data - "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"2", - "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"1", - "generic_inline_admin-media-content_type-object_id-0-id": u"%d" % self.media_pk, + "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"3", + "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"2", + "generic_inline_admin-media-content_type-object_id-0-id": u"%d" % self.mp3_media_pk, "generic_inline_admin-media-content_type-object_id-0-url": u"http://example.com/podcast.mp3", - "generic_inline_admin-media-content_type-object_id-1-id": u"", - "generic_inline_admin-media-content_type-object_id-1-url": u"", + "generic_inline_admin-media-content_type-object_id-1-id": u"%d" % self.png_media_pk, + "generic_inline_admin-media-content_type-object_id-1-url": u"http://example.com/logo.png", + "generic_inline_admin-media-content_type-object_id-2-id": u"", + "generic_inline_admin-media-content_type-object_id-2-url": u"", } url = '/generic_inline_admin/admin/generic_inline_admin/episode/%d/' % self.episode_pk response = self.client.post(url, post_data) self.failUnlessEqual(response.status_code, 302) # redirect somewhere - + + def testGenericInlineFormset(self): + EpisodeMediaFormSet = generic_inlineformset_factory(Media, can_delete=False, extra=3) + e = Episode.objects.get(name='This Week in Django') + + # Works with no queryset + formset = EpisodeMediaFormSet(instance=e) + self.assertEquals(len(formset.forms), 5) + self.assertEquals(formset.forms[0].as_p(), '

') + self.assertEquals(formset.forms[1].as_p(), '

') + self.assertEquals(formset.forms[2].as_p(), '

') + + # A queryset can be used to alter display ordering + formset = EpisodeMediaFormSet(instance=e, queryset=Media.objects.order_by('url')) + self.assertEquals(len(formset.forms), 5) + self.assertEquals(formset.forms[0].as_p(), '

') + self.assertEquals(formset.forms[1].as_p(), '

') + self.assertEquals(formset.forms[2].as_p(), '

') + + + # Works with a queryset that omits items + formset = EpisodeMediaFormSet(instance=e, queryset=Media.objects.filter(url__endswith=".png")) + self.assertEquals(len(formset.forms), 4) + self.assertEquals(formset.forms[0].as_p(), '

') + self.assertEquals(formset.forms[1].as_p(), '

') + + def testGenericInlineFormsetFactory(self): # Regression test for #10522. inline_formset = generic_inlineformset_factory(Media,