diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 332af199f8..a1dff3c815 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -203,6 +203,16 @@ DATETIME_FORMAT = 'N j, Y, P' # http://www.djangoproject.com/documentation/templates/#now TIME_FORMAT = 'P' +# Default formatting for date objects when only the year and month are relevant. +# See all available format strings here: +# http://www.djangoproject.com/documentation/templates/#now +YEAR_MONTH_FORMAT = 'F Y' + +# Default formatting for date objects when only the month and day are relevant. +# See all available format strings here: +# http://www.djangoproject.com/documentation/templates/#now +MONTH_DAY_FORMAT = 'F j' + # Whether to enable Psyco, which optimizes Python code. Requires Psyco. # http://psyco.sourceforge.net/ ENABLE_PSYCO = False diff --git a/django/conf/locale/nl/LC_MESSAGES/django.mo b/django/conf/locale/nl/LC_MESSAGES/django.mo index fe87810b97..abc387aeea 100644 Binary files a/django/conf/locale/nl/LC_MESSAGES/django.mo and b/django/conf/locale/nl/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/nl/LC_MESSAGES/django.po b/django/conf/locale/nl/LC_MESSAGES/django.po index b137909b3a..bba721b56a 100644 --- a/django/conf/locale/nl/LC_MESSAGES/django.po +++ b/django/conf/locale/nl/LC_MESSAGES/django.po @@ -72,7 +72,7 @@ msgstr "Dit veld mag niet leeg zijn." #: db/models/fields/__init__.py:468 core/validators.py:132 msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." -msgstr "Geef geldige datum/tijd in JJJJ-MM-DD HH:MM formaat." +msgstr "Geef geldige datum/tijd in JJJJ-MM-DD UU:MM formaat." #: db/models/fields/__init__.py:562 msgid "Enter a valid filename." @@ -116,7 +116,7 @@ msgstr "Frans" #: conf/global_settings.py:46 msgid "Galician" -msgstr "Galisisch" +msgstr "Galicisch" #: conf/global_settings.py:47 msgid "Hungarian" @@ -176,7 +176,7 @@ msgstr "Zweeds" #: conf/global_settings.py:61 msgid "Ukrainian" -msgstr "Ukraiens" +msgstr "Oekraïens" #: conf/global_settings.py:62 msgid "Simplified Chinese" @@ -220,11 +220,11 @@ msgstr "Geef een geldig IP adres op." #: core/validators.py:103 msgid "Empty values are not allowed here." -msgstr "Lege waarden niet toegestaan." +msgstr "Lege waarden zijn hier niet toegestaan." #: core/validators.py:107 msgid "Non-numeric characters aren't allowed here." -msgstr "Niet-numerieke karakters niet toegestaan." +msgstr "Niet-numerieke karakters zijn hier niet toegestaan." #: core/validators.py:111 msgid "This value can't be comprised solely of digits." @@ -244,7 +244,7 @@ msgstr "Geef een geldige datum in JJJJ-MM-DD formaat." #: core/validators.py:128 msgid "Enter a valid time in HH:MM format." -msgstr "Geef een geldige tijd in HH:MM formaat." +msgstr "Geef een geldige tijd in UU:MM formaat." #: core/validators.py:136 msgid "Enter a valid e-mail address." @@ -321,7 +321,7 @@ msgstr "Dit veld moet overeenkomen met het '%s' veld." #: core/validators.py:255 msgid "Please enter something for at least one field." -msgstr "Geef in minimaal één veld een waarde." +msgstr "Voer tenminste één veld in." #: core/validators.py:264 core/validators.py:275 msgid "Please enter both fields or leave them both empty." @@ -355,26 +355,26 @@ msgstr "Geef een geldig decimaal getal." 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] "Geef een geldig decimaal getal met maximaal %s cijfer." -msgstr[1] "Geef een geldig decimaal getal met maximaal %s cijfers." +msgstr[0] "Geef een geldig decimaal getal met hooguit %s cijfer." +msgstr[1] "Geef een geldig decimaal getal met hooguit %s cijfers." #: 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] "Geef een decimaal getal met maximaal %s cijfer achter de komma." -msgstr[1] "Geef een decimaal getal met maximaal %s cijfers achter de komma." +msgstr[0] "Geef een decimaal getal met hooguit %s cijfer achter de komma." +msgstr[1] "Geef een decimaal getal met hooguit %s cijfers achter de komma." #: core/validators.py:362 #, python-format msgid "Make sure your uploaded file is at least %s bytes big." -msgstr "Zorg ervoor dat het bestand minimaal %s bytes groot is." +msgstr "Zorg ervoor dat het bestand minstens %s bytes groot is." #: core/validators.py:363 #, python-format msgid "Make sure your uploaded file is at most %s bytes big." -msgstr "Zorg ervoor dat het bestand maximaal %s bytes groot is." +msgstr "Zorg ervoor dat het bestand hoogstens %s bytes groot is." #: core/validators.py:376 msgid "The format for this field is wrong." @@ -456,7 +456,7 @@ msgid "" "Your Web browser doesn't appear to have cookies enabled. Cookies are " "required for logging in." msgstr "" -"Het lijkt erop dat uw browser geen cookies accepteerd. Om u aan te melden " +"Het lijkt erop dat uw browser geen cookies accepteerd. Om aan te melden " "moeten cookies worden geaccepteerd." #: contrib/auth/forms.py:36 contrib/auth/forms.py:41 @@ -465,7 +465,7 @@ msgid "" "Please enter a correct username and password. Note that both fields are case-" "sensitive." msgstr "" -"Geef een correcte gebruikersnaam en wachtwoord. Let op de velden zijn " +"Voer een correcte gebruikersnaam en wachtwoord in. Let op, de velden zijn " "hoofdletter-gevoelig." #: contrib/auth/models.py:13 contrib/auth/models.py:26 @@ -522,7 +522,7 @@ msgstr "staf status" #: contrib/auth/models.py:60 msgid "Designates whether the user can log into this admin site." -msgstr "Bepaalt of de gebruiker kan inloggen op deze admin site" +msgstr "Bepaalt of de gebruiker kan inloggen op deze admin site." #: contrib/auth/models.py:61 msgid "active" @@ -682,7 +682,7 @@ msgstr "Gebruikersnamen mogen geen '@' bevatten." #: contrib/admin/views/decorators.py:84 #, python-format msgid "Your e-mail address is not your username. Try '%s' instead." -msgstr "Uw e-mail adres is niet uw gebruikersnaam. Probeer '%s' eens." +msgstr "Uw e-mailadres is niet uw gebruikersnaam. Probeer '%s' eens." #: contrib/admin/views/main.py:226 msgid "Site administration" @@ -734,13 +734,13 @@ msgstr "Geen velden gewijzigd." #: contrib/admin/views/main.py:346 #, python-format msgid "The %(name)s \"%(obj)s\" was changed successfully." -msgstr "Wijzigen %(name)s \"%(obj)s\" is geslaagd." +msgstr "Het wijzigen van %(name)s \"%(obj)s\" is geslaagd." #: contrib/admin/views/main.py:354 #, python-format msgid "" "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." -msgstr "De %(name)s \"%(obj)s\" toegevoegd. U kunt het hieronder wijzigen." +msgstr "De %(name)s \"%(obj)s\" was toegevoegd. U kunt het hieronder wijzigen." #: contrib/admin/views/main.py:392 #, python-format @@ -760,7 +760,7 @@ msgstr "Een of meer %(fieldname)s in %(name)s:" #: contrib/admin/views/main.py:508 #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." -msgstr "Verwijdering %(name)s \"%(obj)s\" is geslaagd." +msgstr "De verwijdering van %(name)s \"%(obj)s\" is geslaagd." #: contrib/admin/views/main.py:511 msgid "Are you sure?" @@ -769,7 +769,7 @@ msgstr "Weet u het zeker?" #: contrib/admin/views/main.py:533 #, python-format msgid "Change history: %s" -msgstr "Wijzigingshistorie: %s" +msgstr "Wijzigingsgeschiedenis: %s" #: contrib/admin/views/main.py:565 #, python-format @@ -794,7 +794,7 @@ msgstr "Boolean (True of False)" #: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 #, python-format msgid "String (up to %(maxlength)s)" -msgstr "Karakterreeks (maximaal %(maxlength)s)" +msgstr "Karakterreeks (hooguit %(maxlength)s)" #: contrib/admin/views/doc.py:280 msgid "Comma-separated integers" @@ -939,9 +939,9 @@ msgid "" "objects, but your account doesn't have permission to delete the following " "types of objects:" msgstr "" -"Verwijderen van %(object_name)s '%(object)s' zal ook gerelateerde objecten " -"verwijderen. Echter u heeft geen rechten om de volgende typen objecten te " -"verwijderen:" +"Het verwijderen van %(object_name)s '%(object)s' zal ook gerelateerde " +"objecten verwijderen. Echter u heeft geen rechten om de volgende typen " +"objecten te verwijderen:" #: contrib/admin/templates/admin/delete_confirmation.html:21 #, python-format @@ -963,7 +963,7 @@ msgstr "Pagina niet gevonden" #: contrib/admin/templates/admin/404.html:10 msgid "We're sorry, but the requested page could not be found." -msgstr "De gevraagde pagina komt niet voor." +msgstr "Onze excuses, maar de gevraagde pagina komt niet voor." #: contrib/admin/templates/admin/change_form.html:15 #: contrib/admin/templates/admin/index.html:28 @@ -1073,8 +1073,8 @@ msgid "" "This object doesn't have a change history. It probably wasn't added via this " "admin site." msgstr "" -"Dit object heeft geen geschiedenis. Mogelijk niet via de admin site " -"toegevoegd." +"Dit object heeft geen wijzigingsgeschiedenis. Het is mogelijk niet via de " +"admin site toegevoegd." #: contrib/admin/templates/admin/500.html:4 msgid "Server error" @@ -1093,9 +1093,8 @@ 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 "" -"Er is een fout opgetreden. Dit is inmiddels doorgegevens aan de " -"sitebeheerder via e-mail en zal binnenkort worden gerepareerd. Bedankt voor " -"uw geduld" +"Er is een fout opgetreden. Dit is inmiddels doorgegeven aan de sitebeheerder " +"via e-mail en zal spoedig worden gerepareerd. Bedankt voor uw geduld." #: contrib/admin/templates/admin/search_form.html:8 msgid "Go" @@ -1188,7 +1187,7 @@ msgstr "Bewerk dit object (nieuwe pagina)" #: contrib/admin/templates/admin_doc/bookmarklets.html:29 msgid "As above, but opens the admin page in a new window." -msgstr "Als boven, maar opent de beheerpagina in een nieuw venster." +msgstr "Zoals hierboven, maar opent de beheerpagina in een nieuw venster." #: contrib/admin/templates/widget/date_time.html:3 msgid "Date:" @@ -1218,8 +1217,8 @@ 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 "" -"Wachtwoord vergeten? Geef u e-mail adres op en we herstellen uw wachtwoord " -"en zullen u het nieuwe wachtwoord per e-mail toesturen." +"Uw wachtwoord vergeten? Geef uw e-mailadres op en er zal een nieuw " +"wachtwoord worden toegekend en aan u worden toegezonden." #: contrib/admin/templates/registration/password_reset_form.html:16 msgid "E-mail address:" @@ -1231,7 +1230,7 @@ msgstr "Herstel mijn wachtwoord" #: contrib/admin/templates/registration/password_reset_email.html:2 msgid "You're receiving this e-mail because you requested a password reset" -msgstr "U krijgt een e-mail omdat u om een nieuw wachtwoord heeft gevraagd" +msgstr "U krijgt deze e-mail omdat u om een nieuw wachtwoord heeft gevraagd" #: contrib/admin/templates/registration/password_reset_email.html:3 #, python-format @@ -1278,8 +1277,7 @@ msgid "" "We've e-mailed a new password to the e-mail address you submitted. You " "should be receiving it shortly." msgstr "" -"Een nieuw wachtwoord is per e-mail verstuurd. U zult het binnenkort " -"ontvangen." +"Een nieuw wachtwoord is per e-mail verstuurd. U zult het spoedig ontvangen." #: contrib/admin/templates/registration/password_change_form.html:4 #: contrib/admin/templates/registration/password_change_form.html:6 @@ -1293,8 +1291,8 @@ 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 "" -"Geef voor de veiligheid uw oude wachtwoord op en twee keer een nieuw " -"wachtwoord, zodat we kunnen controleren of er geen typefouten zijn gemaakt." +"Vanwege de beveiliging moet u uw oude en twee keer een nieuw wachtwoord" +"invoeren, zodat we kunnen controleren of er geen typefouten zijn gemaakt." #: contrib/admin/templates/registration/password_change_form.html:17 msgid "Old password:" @@ -1340,7 +1338,8 @@ msgstr "sites" #: contrib/flatpages/models.py:8 msgid "" "Example: '/about/contact/'. Make sure to have leading and trailing slashes." -msgstr "Voorbeeld: '/about/contact/'. Zorg voor slashes aan begin en eind" +msgstr "" +"Voorbeeld: '/about/contact/'. Zorg voor slashes aan het begin en eind." #: contrib/flatpages/models.py:9 msgid "title" @@ -1373,8 +1372,8 @@ msgstr "registratie verplicht" #: contrib/flatpages/models.py:14 msgid "If this is checked, only logged-in users will be able to view the page." msgstr "" -"Alleen ingelogde gebruikers kunnen deze pagina zien, indien dit is " -"aangekruist." +"Indien dit is aangekruist kunnen alleen ingelogde gebruikers deze pagina " +"bekijken." #: contrib/flatpages/models.py:18 msgid "flat page" @@ -1405,8 +1404,8 @@ msgid "" "This can be either an absolute path (as above) or a full URL starting with " "'http://'." msgstr "" -"Dit kan een absoluut pad zijn (zoals boven) of een volledige URL beginnend " -"met 'http://'." +"Dit kan een absoluut pad (zoals hierboven) zijn of een volledige URL " +"beginnend met 'http://'." #: contrib/redirects/models.py:12 msgid "redirect" @@ -1471,7 +1470,7 @@ msgstr "datum/tijd toegevoegd" #: contrib/comments/models.py:84 contrib/comments/models.py:170 msgid "is public" -msgstr "is publiek" +msgstr "is openbaar" #: contrib/comments/models.py:86 msgid "is removed" @@ -1518,7 +1517,7 @@ msgstr "ip adres" #: contrib/comments/models.py:173 msgid "approved by staff" -msgstr "goegekeurd door de staf" +msgstr "goedgekeurd door de staf" #: contrib/comments/models.py:176 msgid "free comment" @@ -1604,14 +1603,14 @@ msgstr "Ongeldige opmerkingen ID" #: contrib/comments/views/karma.py:25 msgid "No voting for yourself" -msgstr "Niet op jezelf stemmen" +msgstr "Niet op uzelf stemmen" #: contrib/comments/views/comments.py:28 msgid "" "This rating is required because you've entered at least one other rating." msgstr "" -"Deze waardering is verplicht omdat je op zijn minst een andere waardering " -"hebt ingevoerd." +"Deze waardering is verplicht omdat u tenminste één andere waardering hebt " +"ingevoerd." #: contrib/comments/views/comments.py:112 #, python-format @@ -1626,13 +1625,13 @@ msgid_plural "" "\n" "%(text)s" msgstr[0] "" -"Deze opmerking is gepost door een gebruiker welke minder dan %(count)s opmerking " -"heeft gepost:\n" +"Deze opmerking is gepost door een gebruiker die minder dan %(count)s " +"opmerking heeft gepost:\n" "\n" "%(text)s" msgstr[1] "" -"Deze opmerking is gepost door een gebruiker welke minder dan %(count)s opmerkingen " -"heeft gepost:\n" +"Deze opmerking is gepost door een gebruiker die minder dan %(count)s " +"opmerkingen heeft gepost:\n" "\n" "%(text)s" @@ -1655,7 +1654,7 @@ msgstr "Alleen POSTs zijn toegestaan" #: contrib/comments/views/comments.py:193 #: contrib/comments/views/comments.py:284 msgid "One or more of the required fields wasn't submitted" -msgstr "Een of meerdere verplichte velden is niet ingevuld" +msgstr "Een of meerdere verplichte velden zijn niet ingevuld" #: contrib/comments/views/comments.py:197 #: contrib/comments/views/comments.py:286 @@ -1710,7 +1709,7 @@ msgstr "Concept opmerking" #: contrib/comments/templates/comments/freeform.html:4 msgid "Your name:" -msgstr "Uw gebruikernaam:" +msgstr "Uw gebruikersnaam:" #: contrib/sessions/models.py:35 msgid "session key" @@ -1738,18 +1737,18 @@ msgstr "python model-class-naam" #: contrib/contenttypes/models.py:28 msgid "content type" -msgstr "inhoudtype" +msgstr "inhoudstype" #: contrib/contenttypes/models.py:29 msgid "content types" -msgstr "inhoudtypen" +msgstr "inhoudstypen" #: 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] "Zorg ervoor dat uw tekst korter is dan %s teken." -msgstr[1] "Zorg ervoor dat uw tekst korter is dan %S tekens." +msgstr[0] "Zorg ervoor dat uw tekst korter is dan %s karakter." +msgstr[1] "Zorg ervoor dat uw tekst korter is dan %s karakters." #: forms/__init__.py:385 msgid "Line breaks are not allowed here." @@ -1778,31 +1777,31 @@ msgstr "Geef een geheel getal op tussen 0 en 32.767." #: utils/dates.py:6 msgid "Monday" -msgstr "Maandag" +msgstr "maandag" #: utils/dates.py:6 msgid "Tuesday" -msgstr "Dinsdag" +msgstr "dinsdag" #: utils/dates.py:6 msgid "Wednesday" -msgstr "Woensdag" +msgstr "woensdag" #: utils/dates.py:6 msgid "Thursday" -msgstr "Donderdag" +msgstr "donderdag" #: utils/dates.py:6 msgid "Friday" -msgstr "Vrijdag" +msgstr "vrijdag" #: utils/dates.py:7 msgid "Saturday" -msgstr "Zaterdag" +msgstr "zaterdag" #: utils/dates.py:7 msgid "Sunday" -msgstr "Zondag" +msgstr "zondag" #: utils/dates.py:14 msgid "January" @@ -1890,7 +1889,7 @@ msgstr "sep" #: utils/dates.py:20 msgid "oct" -msgstr "oct" +msgstr "okt" #: utils/dates.py:20 msgid "nov" diff --git a/django/conf/urls/registration.py b/django/conf/urls/registration.py index 2d733a898b..9b41e434f9 100644 --- a/django/conf/urls/registration.py +++ b/django/conf/urls/registration.py @@ -1,7 +1,7 @@ from django.conf.urls.defaults import * urlpatterns = patterns('', - (r'^login/$', 'django.contrib.auth.view.login'), + (r'^login/$', 'django.contrib.auth.views.login'), (r'^logout/$', 'django.contrib.auth.views.logout'), (r'^login_another/$', 'django.contrib.auth.views.logout_then_login'), diff --git a/django/contrib/admin/media/css/changelists.css b/django/contrib/admin/media/css/changelists.css index 2269c9fe20..fbcbe56f06 100644 --- a/django/contrib/admin/media/css/changelists.css +++ b/django/contrib/admin/media/css/changelists.css @@ -4,7 +4,7 @@ #changelist { position:relative; width:100%; } #changelist table { width:100%; } .change-list .filtered table { border-right:1px solid #ddd; } -.change-list .filtered { min-height:400px; _height:400px; } +.change-list .filtered { min-height:400px; } .change-list .filtered { background:white url(../img/admin/changelist-bg.gif) top right repeat-y !important; } .change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { margin-right:160px !important; width:auto !important; } .change-list .filtered table tbody th { padding-right:1em; } @@ -27,7 +27,7 @@ #changelist-filter { position:absolute; top:0; right:0; z-index:1000; width:160px; border-left:1px solid #ddd; background:#efefef; margin:0; } #changelist-filter h2 { font-size:11px; padding:2px 5px; border-bottom:1px solid #ddd; } #changelist-filter h3 { font-size:12px; margin-bottom:0; } -#changelist-filter ul { padding-left:0;margin-left:10px;_margin-right:-10px; } +#changelist-filter ul { padding-left:0;margin-left:10px; } #changelist-filter li { list-style-type:none; margin-left:0; padding-left:0; } #changelist-filter a { color:#999; } #changelist-filter a:hover { color:#036; } diff --git a/django/contrib/admin/media/css/global.css b/django/contrib/admin/media/css/global.css index 67e37324e5..a87931681c 100644 --- a/django/contrib/admin/media/css/global.css +++ b/django/contrib/admin/media/css/global.css @@ -86,7 +86,7 @@ textarea { vertical-align:top !important; } input[type=text], input[type=password], textarea, select, .vTextField { border:1px solid #ccc; } /* FORM BUTTONS */ -input[type=submit], input[type=button], .submit-row input { background:white url(../img/admin/nav-bg.gif) bottom repeat-x; padding:3px; color:black; } +input[type=submit], input[type=button], .submit-row input { background:white url(../img/admin/nav-bg.gif) bottom repeat-x; padding:3px; color:black; border:1px solid #bbb; border-color:#ddd #aaa #aaa #ddd; } input[type=submit]:active, input[type=button]:active { background-image:url(../img/admin/nav-bg-reverse.gif); background-position:top; } input[type=submit].default, .submit-row input.default { border:2px solid #5b80b2; background:#7CA0C7 url(../img/admin/default-bg.gif) bottom repeat-x; font-weight:bold; color:white; } input[type=submit].default:active { background-image:url(../img/admin/default-bg-reverse.gif); background-position:top; } diff --git a/django/contrib/admin/media/css/layout.css b/django/contrib/admin/media/css/layout.css index c8eb42650f..befe4fc1ca 100644 --- a/django/contrib/admin/media/css/layout.css +++ b/django/contrib/admin/media/css/layout.css @@ -3,13 +3,13 @@ #content { margin:10px 15px; } #header { width:100%; } #content-main { float:left; width:100%; } -#content-related { float:right; width:220px; position:relative; margin-right:-230px; } +#content-related { float:right; width:18em; position:relative; margin-right:-19em; } #footer { clear:both; padding:10px; } /* COLUMN TYPES */ -.colMS { margin-right:245px !important; } -.colSM { margin-left:245px !important; } -.colSM #content-related { float:left; margin-right:0; margin-left:-230px; } +.colMS { margin-right:20em !important; } +.colSM { margin-left:20em !important; } +.colSM #content-related { float:left; margin-right:0; margin-left:-19em; } .colSM #content-main { float:right; } .popup .colM { width:95%; } .subcol { float:left; width:46%; margin-right:15px; } diff --git a/django/contrib/admin/media/css/patch-iewin.css b/django/contrib/admin/media/css/patch-iewin.css index b2e6a4c560..2de1305e44 100644 --- a/django/contrib/admin/media/css/patch-iewin.css +++ b/django/contrib/admin/media/css/patch-iewin.css @@ -3,4 +3,6 @@ * html .colSM #content-related { margin-right:10px; margin-left:-115px; position:static; } /* put the left sidebars back on the page */ * html .form-row { height:1%; } * html .dashboard #content { width:768px; } /* proper fixed width for dashboard in IE6 */ -* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */ \ No newline at end of file +* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */ +* html #changelist-filter ul { margin-right:-10px; } /* fix right margin for changelist filters in IE6 */ +* html .change-list .filtered { height:400px; } /* IE ignores min-height, but treats height as if it were min-height */ \ No newline at end of file diff --git a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js index cb84790f44..cf57fc4dfb 100644 --- a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js +++ b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js @@ -3,6 +3,8 @@ function showRelatedObjectLookupPopup(triggeringLink) { var name = triggeringLink.id.replace(/^lookup_/, ''); + // IE doesn't like periods in the window name, so convert temporarily. + name = name.replace(/\./g, '___'); var href; if (triggeringLink.href.search(/\?/) >= 0) { href = triggeringLink.href + '&pop=1'; @@ -15,11 +17,12 @@ function showRelatedObjectLookupPopup(triggeringLink) { } function dismissRelatedLookupPopup(win, chosenId) { - var elem = document.getElementById(win.name); + var name = win.name.replace(/___/g, '.'); + var elem = document.getElementById(name); if (elem.className.indexOf('vRawIdAdminField') != -1 && elem.value) { elem.value += ',' + chosenId; } else { - document.getElementById(win.name).value = chosenId; + document.getElementById(name).value = chosenId; } win.close(); } diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index 912a620010..a8c1c9c101 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -19,9 +19,9 @@
{% block branding %}{% endblock %}
- {% if not user.is_anonymous %} + {% if not user.is_anonymous %}{% if user.is_staff %}
{% trans 'Welcome,' %} {% if user.first_name %}{{ user.first_name }}{% else %}{{ user.username }}{% endif %}. {% block userlinks %}{% trans 'Documentation' %} / {% trans 'Change password' %} / {% trans 'Log out' %}{% endblock %}
- {% endif %} + {% endif %}{% endif %} {% block nav-global %}{% endblock %} diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 8efef6b542..d5f8df9dfd 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -2,14 +2,15 @@ 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 ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR -from django.contrib.admin.views.main import IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE, MONTHS +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 from django.utils.html import escape from django.utils.text import capfirst -from django.utils.translation import get_date_formats +from django.utils.translation import get_date_formats, get_partial_date_formats from django.template import Library +import datetime register = Library() @@ -193,18 +194,19 @@ def date_hierarchy(cl): year_lookup = cl.params.get(year_field) month_lookup = cl.params.get(month_field) day_lookup = cl.params.get(day_field) + year_month_format, month_day_format = get_partial_date_formats() link = lambda d: cl.get_query_string(d, [field_generic]) if year_lookup and month_lookup and day_lookup: - month_name = MONTHS[int(month_lookup)] + day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup)) return { 'show': True, 'back': { 'link': link({year_field: year_lookup, month_field: month_lookup}), - 'title': "%s %s" % (month_name, year_lookup) + 'title': dateformat.format(day, year_month_format) }, - 'choices': [{'title': "%s %s" % (month_name, day_lookup)}] + 'choices': [{'title': dateformat.format(day, month_day_format)}] } elif year_lookup and month_lookup: days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day') @@ -216,7 +218,7 @@ def date_hierarchy(cl): }, 'choices': [{ 'link': link({year_field: year_lookup, month_field: month_lookup, day_field: day.day}), - 'title': day.strftime('%B %d') + 'title': dateformat.format(day, month_day_format) } for day in days] } elif year_lookup: @@ -229,7 +231,7 @@ def date_hierarchy(cl): }, 'choices': [{ 'link': link({year_field: year_lookup, month_field: month.month}), - 'title': "%s %s" % (month.strftime('%B'), month.year) + 'title': dateformat.format(month, year_month_format) } for month in months] } else: diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py index c6c794c75e..0242b2f32e 100644 --- a/django/contrib/admin/utils.py +++ b/django/contrib/admin/utils.py @@ -81,12 +81,16 @@ ROLES = { } def create_reference_role(rolename, urlbase): - def _role(name, rawtext, text, lineno, inliner, options={}, content=[]): + def _role(name, rawtext, text, lineno, inliner, options=None, content=None): + if options is None: options = {} + if content is None: content = [] node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % (inliner.document.settings.link_base, text.lower())), **options) return [node], [] docutils.parsers.rst.roles.register_canonical_role(rolename, _role) -def default_reference_role(name, rawtext, text, lineno, inliner, options={}, content=[]): +def default_reference_role(name, rawtext, text, lineno, inliner, options=None, content=None): + if options is None: options = {} + if content is None: content = [] context = inliner.document.settings.default_reference_context node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % (inliner.document.settings.link_base, text.lower())), **options) return [node], [] diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 8fe3c95178..af55587d23 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -579,7 +579,9 @@ class ChangeList(object): filter_specs.append(spec) return filter_specs, bool(filter_specs) - def get_query_string(self, new_params={}, remove=[]): + def get_query_string(self, new_params=None, remove=None): + if new_params is None: new_params = {} + if remove is None: remove = [] p = self.params.copy() for r in remove: for k in p.keys(): diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index f5f622665c..ef81268e2a 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -35,6 +35,8 @@ class AuthenticationForm(forms.Manipulator): self.user_cache = authenticate(username=username, password=password) if self.user_cache is 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.") def get_user_id(self): if self.user_cache: diff --git a/tests/modeltests/repr/__init__.py b/django/contrib/humanize/__init__.py similarity index 100% rename from tests/modeltests/repr/__init__.py rename to django/contrib/humanize/__init__.py diff --git a/django/contrib/humanize/templatetags/__init__.py b/django/contrib/humanize/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py new file mode 100644 index 0000000000..b2d28a0ab4 --- /dev/null +++ b/django/contrib/humanize/templatetags/humanize.py @@ -0,0 +1,64 @@ +from django import template +import re + +register = template.Library() + +def ordinal(value): + """ + Converts an integer to its ordinal as a string. 1 is '1st', 2 is '2nd', + 3 is '3rd', etc. Works for any integer. + """ + try: + value = int(value) + except ValueError: + return value + t = ('th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th') + if value % 100 in (11, 12, 13): # special case + return '%dth' % value + return '%d%s' % (value, t[value % 10]) +register.filter(ordinal) + +def intcomma(value): + """ + Converts an integer to a string containing commas every three digits. + For example, 3000 becomes '3,000' and 45000 becomes '45,000'. + """ + orig = str(value) + new = re.sub("^(-?\d+)(\d{3})", '\g<1>,\g<2>', str(value)) + if orig == new: + return new + else: + return intcomma(new) +register.filter(intcomma) + +def intword(value): + """ + Converts a large integer to a friendly text representation. Works best for + numbers over 1 million. For example, 1000000 becomes '1.0 million', 1200000 + becomes '1.2 million' and '1200000000' becomes '1.2 billion'. + """ + value = int(value) + if value < 1000000: + return value + if value < 1000000000: + return '%.1f million' % (value / 1000000.0) + if value < 1000000000000: + return '%.1f billion' % (value / 1000000000.0) + if value < 1000000000000000: + return '%.1f trillion' % (value / 1000000000000.0) + return value +register.filter(intword) + +def apnumber(value): + """ + For numbers 1-9, returns the number spelled out. Otherwise, returns the + number. This follows Associated Press style. + """ + try: + value = int(value) + except ValueError: + return value + if not 0 < value < 10: + return value + return ('one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine')[value-1] +register.filter(apnumber) diff --git a/django/core/management.py b/django/core/management.py index f7bbf29227..931372cc5e 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -1055,7 +1055,9 @@ def run_shell(use_plain=False): # Don't bother loading IPython, because the user wants plain Python. raise ImportError import IPython - shell = IPython.Shell.IPShell() + # Explicitly pass an empty list as arguments, because otherwise IPython + # would use sys.argv from this script. + shell = IPython.Shell.IPShell(argv=[]) shell.mainloop() except ImportError: import code @@ -1137,7 +1139,11 @@ def print_error(msg, cmd): sys.stderr.write(style.ERROR('Error: %s' % msg) + '\nRun "%s --help" for help.\n' % cmd) sys.exit(1) -def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING): +def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None): + # Use sys.argv if we've not passed in a custom argv + if argv is None: + argv = sys.argv + # Parse the command-line arguments. optparse handles the dirty work. parser = DjangoOptionParser(usage=get_usage(action_mapping), version=get_version()) parser.add_option('--settings', @@ -1146,7 +1152,7 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING): 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.') - options, args = parser.parse_args() + options, args = parser.parse_args(argv[1:]) # Take care of options. if options.settings: @@ -1161,7 +1167,7 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING): except IndexError: parser.print_usage_and_exit() if not action_mapping.has_key(action): - print_error("Your action, %r, was invalid." % action, sys.argv[0]) + print_error("Your action, %r, was invalid." % action, argv[0]) # Switch to English, because django-admin.py creates database content # like permissions, and those shouldn't contain any translations. @@ -1220,7 +1226,7 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING): if action not in NO_SQL_TRANSACTION: print style.SQL_KEYWORD("COMMIT;") -def execute_manager(settings_mod): +def execute_manager(settings_mod, argv=None): # Add this project to sys.path so that it's importable in the conventional # way. For example, if this file (manage.py) lives in a directory # "myproject", this code would add "/path/to/myproject" to sys.path. @@ -1247,4 +1253,4 @@ def execute_manager(settings_mod): action_mapping['startapp'].args = startapp.args # Run the django-admin.py command. - execute_from_command_line(action_mapping) + execute_from_command_line(action_mapping, argv) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index db4967e1c5..91e999f802 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -23,6 +23,25 @@ def get_mod_func(callback): dot = callback.rindex('.') return callback[:dot], callback[dot+1:] +def reverse_helper(regex, *args, **kwargs): + """ + Does a "reverse" lookup -- returns the URL for the given args/kwargs. + The args/kwargs are applied to the given compiled regular expression. + For example: + + >>> reverse_helper(re.compile('^places/(\d+)/$'), 3) + 'places/3/' + >>> reverse_helper(re.compile('^places/(?P\d+)/$'), id=3) + 'places/3/' + >>> reverse_helper(re.compile('^people/(?P\w\w)/(\w+)/$'), 'adrian', state='il') + 'people/il/adrian/' + + Raises NoReverseMatch if the args/kwargs aren't valid for the regex. + """ + # TODO: Handle nested parenthesis in the following regex. + result = re.sub(r'\(([^)]+)\)', MatchChecker(args, kwargs), regex.pattern) + return result.replace('^', '').replace('$', '') + class MatchChecker(object): "Class used in reverse RegexURLPattern lookup." def __init__(self, args, kwargs): @@ -108,23 +127,7 @@ class RegexURLPattern: return self.reverse_helper(*args, **kwargs) def reverse_helper(self, *args, **kwargs): - """ - Does a "reverse" lookup -- returns the URL for the given args/kwargs. - The args/kwargs are applied to the regular expression in this - RegexURLPattern. For example: - - >>> RegexURLPattern('^places/(\d+)/$').reverse_helper(3) - 'places/3/' - >>> RegexURLPattern('^places/(?P\d+)/$').reverse_helper(id=3) - 'places/3/' - >>> RegexURLPattern('^people/(?P\w\w)/(\w+)/$').reverse_helper('adrian', state='il') - 'people/il/adrian/' - - Raises NoReverseMatch if the args/kwargs aren't valid for the RegexURLPattern. - """ - # TODO: Handle nested parenthesis in the following regex. - result = re.sub(r'\(([^)]+)\)', MatchChecker(args, kwargs), self.regex.pattern) - return result.replace('^', '').replace('$', '') + return reverse_helper(self.regex, *args, **kwargs) class RegexURLResolver(object): def __init__(self, regex, urlconf_name): @@ -182,9 +185,19 @@ class RegexURLResolver(object): def reverse(self, viewname, *args, **kwargs): for pattern in self.urlconf_module.urlpatterns: - if pattern.callback == viewname: + if isinstance(pattern, RegexURLResolver): + try: + return pattern.reverse_helper(viewname, *args, **kwargs) + except NoReverseMatch: + continue + elif pattern.callback == viewname: try: return pattern.reverse_helper(*args, **kwargs) except NoReverseMatch: continue raise NoReverseMatch + + def reverse_helper(self, viewname, *args, **kwargs): + sub_match = self.reverse(viewname, *args, **kwargs) + result = reverse_helper(self.regex, *args, **kwargs) + return result + sub_match diff --git a/django/core/validators.py b/django/core/validators.py index 27505b7d5a..a2e9bfaf89 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -399,7 +399,8 @@ class AnyValidator: as a validation error. The message is rather unspecific, so it's best to specify one on instantiation. """ - def __init__(self, validator_list=[], error_message=gettext_lazy("This field is invalid.")): + def __init__(self, validator_list=None, error_message=gettext_lazy("This field is invalid.")): + if validator_list is None: validator_list = [] self.validator_list = validator_list self.error_message = error_message for v in validator_list: diff --git a/django/db/backends/ado_mssql/base.py b/django/db/backends/ado_mssql/base.py index c5a3b2cc33..b645b053bf 100644 --- a/django/db/backends/ado_mssql/base.py +++ b/django/db/backends/ado_mssql/base.py @@ -125,6 +125,9 @@ def get_limit_offset_sql(limit, offset=None): def get_random_function_sql(): return "RAND()" +def get_fulltext_search_sql(field_name): + raise NotImplementedError + def get_drop_foreignkey_sql(): return "DROP CONSTRAINT" diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py index 89fec00c1d..985fe96469 100644 --- a/django/db/backends/dummy/base.py +++ b/django/db/backends/dummy/base.py @@ -33,5 +33,6 @@ get_date_extract_sql = complain get_date_trunc_sql = complain get_limit_offset_sql = complain get_random_function_sql = complain +get_fulltext_search_sql = complain get_drop_foreignkey_sql = complain OPERATOR_MAPPING = {} diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 6a53956cad..4a13450c67 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -152,6 +152,9 @@ def get_limit_offset_sql(limit, offset=None): def get_random_function_sql(): return "RAND()" +def get_fulltext_search_sql(field_name): + return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name + def get_drop_foreignkey_sql(): return "DROP FOREIGN KEY" diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index a7c866484e..e981805108 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -65,11 +65,13 @@ class FormatStylePlaceholderCursor(Database.Cursor): This fixes it -- but note that if you want to use a literal "%s" in a query, you'll need to use "%%s". """ - def execute(self, query, params=[]): + def execute(self, query, params=None): + if params is None: params = [] query = self.convert_arguments(query, len(params)) return Database.Cursor.execute(self, query, params) - def executemany(self, query, params=[]): + def executemany(self, query, params=None): + if params is None: params = [] query = self.convert_arguments(query, len(params[0])) return Database.Cursor.executemany(self, query, params) @@ -106,6 +108,9 @@ def get_limit_offset_sql(limit, offset=None): def get_random_function_sql(): return "DBMS_RANDOM.RANDOM" +def get_fulltext_search_sql(field_name): + raise NotImplementedError + def get_drop_foreignkey_sql(): return "DROP FOREIGN KEY" diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index a365434318..decb160ee9 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -102,6 +102,9 @@ def get_limit_offset_sql(limit, offset=None): def get_random_function_sql(): return "RANDOM()" +def get_fulltext_search_sql(field_name): + raise NotImplementedError + def get_drop_foreignkey_sql(): return "DROP CONSTRAINT" diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 13e7be7a98..697a33bb76 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -108,6 +108,9 @@ def get_limit_offset_sql(limit, offset=None): def get_random_function_sql(): return "RANDOM()" +def get_fulltext_search_sql(field_name): + raise NotImplementedError + def get_drop_foreignkey_sql(): return "DROP CONSTRAINT" diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 7c3018aed9..7b51967416 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -124,6 +124,9 @@ def get_limit_offset_sql(limit, offset=None): def get_random_function_sql(): return "RANDOM()" +def get_fulltext_search_sql(field_name): + raise NotImplementedError + def get_drop_foreignkey_sql(): return "" diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index b5245d6624..2f8a8651a1 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -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'): + if lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'ne', 'year', 'month', 'day', 'search'): return [value] elif lookup_type in ('range', 'in'): return value diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 5f6ec83bb6..6e0fb6d2a8 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -198,7 +198,7 @@ class ForeignRelatedObjectsDescriptor(object): setattr(obj, rel_field.name, None) obj.save() else: - raise rel_field.rel.to.DoesNotExist, "'%s' is not related to '%s'." % (obj, instance) + raise rel_field.rel.to.DoesNotExist, "%r is not related to %r." % (obj, instance) remove.alters_data = True def clear(self): @@ -712,7 +712,7 @@ class ManyToManyRel: self.related_name = related_name self.filter_interface = filter_interface if limit_choices_to is None: - limit_choices_to = {} + limit_choices_to = {} self.limit_choices_to = limit_choices_to self.edit_inline = False self.raw_id_admin = raw_id_admin diff --git a/django/db/models/query.py b/django/db/models/query.py index 3517d6bed5..4bd9b3b9fe 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -615,6 +615,8 @@ def get_where_clause(lookup_type, table_prefix, field_name, value): return "%s = %%s" % backend.get_date_extract_sql(lookup_type, table_prefix + field_name) elif lookup_type == 'isnull': return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or '')) + elif lookup_type == 'search': + return backend.get_fulltext_search_sql(table_prefix + field_name) raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type) def get_cached_row(klass, row, index_start): diff --git a/django/forms/__init__.py b/django/forms/__init__.py index b67e1d0f84..cea3d22310 100644 --- a/django/forms/__init__.py +++ b/django/forms/__init__.py @@ -367,7 +367,8 @@ class FormField: class TextField(FormField): input_type = "text" - def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[], member_name=None): + def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None): + if validator_list is None: validator_list = [] self.field_name = field_name self.length, self.maxlength = length, maxlength self.is_required = is_required @@ -404,7 +405,8 @@ class PasswordField(TextField): input_type = "password" class LargeTextField(TextField): - def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=[], maxlength=None): + def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None): + if validator_list is None: validator_list = [] self.field_name = field_name self.rows, self.cols, self.is_required = rows, cols, is_required self.validator_list = validator_list[:] @@ -422,7 +424,8 @@ class LargeTextField(TextField): self.field_name, self.rows, self.cols, escape(data)) class HiddenField(FormField): - def __init__(self, field_name, is_required=False, validator_list=[]): + def __init__(self, field_name, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] self.field_name, self.is_required = field_name, is_required self.validator_list = validator_list[:] @@ -452,7 +455,9 @@ class CheckboxField(FormField): html2python = staticmethod(html2python) class SelectField(FormField): - def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[], member_name=None): + def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None): + if validator_list is None: validator_list = [] + if choices is None: choices = [] self.field_name = field_name # choices is a list of (value, human-readable key) tuples because order matters self.choices, self.size, self.is_required = choices, size, is_required @@ -488,7 +493,9 @@ class NullSelectField(SelectField): html2python = staticmethod(html2python) class RadioSelectField(FormField): - def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[], member_name=None): + def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None): + if validator_list is None: validator_list = [] + if choices is None: choices = [] self.field_name = field_name # choices is a list of (value, human-readable key) tuples because order matters self.choices, self.is_required = choices, is_required @@ -552,7 +559,8 @@ class RadioSelectField(FormField): class NullBooleanField(SelectField): "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None" - def __init__(self, field_name, is_required=False, validator_list=[]): + def __init__(self, field_name, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')], is_required=is_required, validator_list=validator_list) @@ -605,7 +613,9 @@ class CheckboxSelectMultipleField(SelectMultipleField): back into the single list that validators, renderers and save() expect. """ requires_data_list = True - def __init__(self, field_name, choices=[], validator_list=[]): + def __init__(self, field_name, choices=None, validator_list=None): + if validator_list is None: validator_list = [] + if choices is None: choices = [] SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list) def prepare(self, new_data): @@ -636,7 +646,8 @@ class CheckboxSelectMultipleField(SelectMultipleField): #################### class FileUploadField(FormField): - def __init__(self, field_name, is_required=False, validator_list=[]): + def __init__(self, field_name, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] self.field_name, self.is_required = field_name, is_required self.validator_list = [self.isNonEmptyFile] + validator_list @@ -675,7 +686,8 @@ class ImageUploadField(FileUploadField): #################### class IntegerField(TextField): - def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[], member_name=None): + def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None): + if validator_list is None: validator_list = [] validator_list = [self.isInteger] + validator_list if member_name is not None: self.member_name = member_name @@ -694,7 +706,8 @@ class IntegerField(TextField): html2python = staticmethod(html2python) class SmallIntegerField(IntegerField): - def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=[]): + def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] validator_list = [self.isSmallInteger] + validator_list IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list) @@ -703,7 +716,8 @@ class SmallIntegerField(IntegerField): raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.") class PositiveIntegerField(IntegerField): - def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[]): + def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] validator_list = [self.isPositive] + validator_list IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list) @@ -712,7 +726,8 @@ class PositiveIntegerField(IntegerField): raise validators.CriticalValidationError, gettext("Enter a positive number.") class PositiveSmallIntegerField(IntegerField): - def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=[]): + def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] validator_list = [self.isPositiveSmall] + validator_list IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list) @@ -721,7 +736,8 @@ class PositiveSmallIntegerField(IntegerField): raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.") class FloatField(TextField): - def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=[]): + def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] self.max_digits, self.decimal_places = max_digits, decimal_places validator_list = [self.isValidFloat] + validator_list TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list) @@ -746,7 +762,8 @@ class FloatField(TextField): class DatetimeField(TextField): """A FormField that automatically converts its data to a datetime.datetime object. The data should be in the format YYYY-MM-DD HH:MM:SS.""" - def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[]): + def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] self.field_name = field_name self.length, self.maxlength = length, maxlength self.is_required = is_required @@ -769,7 +786,8 @@ class DatetimeField(TextField): class DateField(TextField): """A FormField that automatically converts its data to a datetime.date object. The data should be in the format YYYY-MM-DD.""" - def __init__(self, field_name, is_required=False, validator_list=[]): + def __init__(self, field_name, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] validator_list = [self.isValidDate] + validator_list TextField.__init__(self, field_name, length=10, maxlength=10, is_required=is_required, validator_list=validator_list) @@ -793,7 +811,8 @@ class DateField(TextField): class TimeField(TextField): """A FormField that automatically converts its data to a datetime.time object. The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm.""" - def __init__(self, field_name, is_required=False, validator_list=[]): + def __init__(self, field_name, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] validator_list = [self.isValidTime] + validator_list TextField.__init__(self, field_name, length=8, maxlength=8, is_required=is_required, validator_list=validator_list) @@ -827,7 +846,8 @@ class TimeField(TextField): class EmailField(TextField): "A convenience FormField for validating e-mail addresses" - def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=[]): + def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] validator_list = [self.isValidEmail] + validator_list TextField.__init__(self, field_name, length, maxlength=maxlength, is_required=is_required, validator_list=validator_list) @@ -840,7 +860,8 @@ class EmailField(TextField): class URLField(TextField): "A convenience FormField for validating URLs" - def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=[]): + def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] validator_list = [self.isValidURL] + validator_list TextField.__init__(self, field_name, length=length, maxlength=maxlength, is_required=is_required, validator_list=validator_list) @@ -852,7 +873,8 @@ class URLField(TextField): raise validators.CriticalValidationError, e.messages class IPAddressField(TextField): - def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=[]): + def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] validator_list = [self.isValidIPAddress] + validator_list TextField.__init__(self, field_name, length=length, maxlength=maxlength, is_required=is_required, validator_list=validator_list) @@ -873,7 +895,7 @@ class IPAddressField(TextField): class FilePathField(SelectField): "A SelectField whose choices are the files in a given directory." - def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=[]): + def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None): import os if match is not None: import re @@ -896,7 +918,8 @@ class FilePathField(SelectField): class PhoneNumberField(TextField): "A convenience FormField for validating phone numbers (e.g. '630-555-1234')" - def __init__(self, field_name, is_required=False, validator_list=[]): + def __init__(self, field_name, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] validator_list = [self.isValidPhone] + validator_list TextField.__init__(self, field_name, length=12, maxlength=12, is_required=is_required, validator_list=validator_list) @@ -909,7 +932,8 @@ class PhoneNumberField(TextField): class USStateField(TextField): "A convenience FormField for validating U.S. states (e.g. 'IL')" - def __init__(self, field_name, is_required=False, validator_list=[]): + def __init__(self, field_name, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] validator_list = [self.isValidUSState] + validator_list TextField.__init__(self, field_name, length=2, maxlength=2, is_required=is_required, validator_list=validator_list) @@ -929,7 +953,8 @@ class USStateField(TextField): class CommaSeparatedIntegerField(TextField): "A convenience FormField for validating comma-separated integer fields" - def __init__(self, field_name, maxlength=None, is_required=False, validator_list=[]): + def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None): + if validator_list is None: validator_list = [] validator_list = [self.isCommaSeparatedIntegerList] + validator_list TextField.__init__(self, field_name, length=20, maxlength=maxlength, is_required=is_required, validator_list=validator_list) diff --git a/django/template/__init__.py b/django/template/__init__.py index 18dceff45c..b526863fbf 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -227,7 +227,8 @@ class Parser(object): for lib in builtins: self.add_library(lib) - def parse(self, parse_until=[]): + def parse(self, parse_until=None): + if parse_until is None: parse_until = [] nodelist = self.create_nodelist() while self.tokens: token = self.next_token() diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index bc8fb07ef5..3d7c4275bb 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -42,7 +42,8 @@ class MergeDict: class SortedDict(dict): "A dictionary that keeps its keys in the order in which they're inserted." - def __init__(self, data={}): + def __init__(self, data=None): + if data is None: data = {} dict.__init__(self, data) self.keyOrder = data.keys() @@ -123,8 +124,9 @@ class MultiValueDict(dict): def __copy__(self): return self.__class__(dict.items(self)) - def __deepcopy__(self, memo={}): + def __deepcopy__(self, memo=None): import copy + if memo is None: memo = {} result = self.__class__() memo[id(self)] = result for key, value in dict.items(self): diff --git a/django/utils/translation.py b/django/utils/translation.py index 81cd8e2992..a73c43c257 100644 --- a/django/utils/translation.py +++ b/django/utils/translation.py @@ -221,10 +221,10 @@ 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 - + def catalog(): """ This function returns the current active catalog for further processing. @@ -369,7 +369,22 @@ def get_date_formats(): datetime_format = settings.DATETIME_FORMAT if time_format == 'TIME_FORMAT': time_format = settings.TIME_FORMAT - return (date_format, datetime_format, time_format) + return date_format, datetime_format, time_format + +def get_partial_date_formats(): + """ + This function checks whether translation files provide a translation for some + technical message ID to store partial date formats. If it doesn't contain + one, the formats provided in the settings will be used. + """ + from django.conf import settings + year_month_format = _('YEAR_MONTH_FORMAT') + month_day_format = _('MONTH_DAY_FORMAT') + if year_month_format == 'YEAR_MONTH_FORMAT': + year_month_format = settings.YEAR_MONTH_FORMAT + if month_day_format == 'MONTH_DAY_FORMAT': + month_day_format = settings.MONTH_DAY_FORMAT + return year_month_format, month_day_format def install(): """ diff --git a/django/utils/xmlutils.py b/django/utils/xmlutils.py index 6638573857..a1eb5fb753 100644 --- a/django/utils/xmlutils.py +++ b/django/utils/xmlutils.py @@ -5,8 +5,9 @@ Utilities for XML generation/parsing. from xml.sax.saxutils import XMLGenerator class SimplerXMLGenerator(XMLGenerator): - def addQuickElement(self, name, contents=None, attrs={}): + def addQuickElement(self, name, contents=None, attrs=None): "Convenience method for adding an element with no children" + if attrs is None: attrs = {} self.startElement(name, attrs) if contents is not None: self.characters(contents) diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py index f73aae26dd..0804cdf30b 100644 --- a/django/views/generic/create_update.py +++ b/django/views/generic/create_update.py @@ -9,7 +9,7 @@ from django.http import Http404, HttpResponse, HttpResponseRedirect from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured def create_object(request, model, template_name=None, - template_loader=loader, extra_context={}, post_save_redirect=None, + template_loader=loader, extra_context=None, post_save_redirect=None, login_required=False, follow=None, context_processors=None): """ Generic object-creation function. @@ -19,6 +19,7 @@ def create_object(request, model, template_name=None, form the form wrapper for the object """ + if extra_context is None: extra_context = {} if login_required and request.user.is_anonymous(): return redirect_to_login(request.path) @@ -71,7 +72,7 @@ def create_object(request, model, template_name=None, def update_object(request, model, object_id=None, slug=None, slug_field=None, template_name=None, template_loader=loader, - extra_context={}, post_save_redirect=None, + extra_context=None, post_save_redirect=None, login_required=False, follow=None, context_processors=None, template_object_name='object'): """ @@ -84,6 +85,7 @@ def update_object(request, model, object_id=None, slug=None, object the original object being edited """ + if extra_context is None: extra_context = {} if login_required and request.user.is_anonymous(): return redirect_to_login(request.path) @@ -143,7 +145,7 @@ def update_object(request, model, object_id=None, slug=None, def delete_object(request, model, post_delete_redirect, object_id=None, slug=None, slug_field=None, template_name=None, - template_loader=loader, extra_context={}, + template_loader=loader, extra_context=None, login_required=False, context_processors=None, template_object_name='object'): """ Generic object-delete function. @@ -157,6 +159,7 @@ def delete_object(request, model, post_delete_redirect, object the original object being deleted """ + if extra_context is None: extra_context = {} if login_required and request.user.is_anonymous(): return redirect_to_login(request.path) diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py index 0fc657d2c1..7084cdfe5e 100644 --- a/django/views/generic/date_based.py +++ b/django/views/generic/date_based.py @@ -6,7 +6,7 @@ import datetime, time def archive_index(request, queryset, date_field, num_latest=15, template_name=None, template_loader=loader, - extra_context={}, allow_empty=False, context_processors=None, + extra_context=None, allow_empty=False, context_processors=None, mimetype=None): """ Generic top-level archive of date-based objects. @@ -18,6 +18,7 @@ def archive_index(request, queryset, date_field, num_latest=15, latest Latest N (defaults to 15) objects by date """ + if extra_context is None: extra_context = {} model = queryset.model queryset = queryset.filter(**{'%s__lte' % date_field: datetime.datetime.now()}) date_list = queryset.dates(date_field, 'year')[::-1] @@ -44,7 +45,7 @@ def archive_index(request, queryset, date_field, num_latest=15, return HttpResponse(t.render(c), mimetype=mimetype) def archive_year(request, year, queryset, date_field, template_name=None, - template_loader=loader, extra_context={}, allow_empty=False, + template_loader=loader, extra_context=None, allow_empty=False, context_processors=None, template_object_name='object', mimetype=None, make_object_list=False): """ @@ -60,6 +61,7 @@ def archive_year(request, year, queryset, date_field, template_name=None, List of objects published in the given month (Only available if make_object_list argument is True) """ + if extra_context is None: extra_context = {} model = queryset.model now = datetime.datetime.now() @@ -92,7 +94,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={}, allow_empty=False, context_processors=None, + extra_context=None, allow_empty=False, context_processors=None, template_object_name='object', mimetype=None): """ Generic monthly archive view. @@ -108,6 +110,7 @@ def archive_month(request, year, month, queryset, date_field, object_list: list of objects published in the given month """ + if extra_context is None: extra_context = {} try: date = datetime.date(*time.strptime(year+month, '%Y'+month_format)[:3]) except ValueError: @@ -148,7 +151,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={}, allow_empty=True, context_processors=None, + extra_context=None, allow_empty=True, context_processors=None, template_object_name='object', mimetype=None): """ Generic weekly archive view. @@ -160,6 +163,7 @@ def archive_week(request, year, week, queryset, date_field, object_list: list of objects published in the given week """ + if extra_context is None: extra_context = {} try: date = datetime.date(*time.strptime(year+'-0-'+week, '%Y-%w-%U')[:3]) except ValueError: @@ -195,7 +199,7 @@ def archive_week(request, year, week, queryset, date_field, def archive_day(request, year, month, day, queryset, date_field, month_format='%b', day_format='%d', template_name=None, - template_loader=loader, extra_context={}, allow_empty=False, + template_loader=loader, extra_context=None, allow_empty=False, context_processors=None, template_object_name='object', mimetype=None): """ @@ -212,6 +216,7 @@ def archive_day(request, year, month, day, queryset, date_field, next_day (datetime) the next day, or None if the current day is today """ + if extra_context is None: extra_context = {} try: date = datetime.date(*time.strptime(year+month+day, '%Y'+month_format+day_format)[:3]) except ValueError: @@ -261,7 +266,7 @@ def archive_today(request, **kwargs): 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={}, context_processors=None, + template_loader=loader, extra_context=None, context_processors=None, template_object_name='object', mimetype=None): """ Generic detail view from year/month/day/slug or year/month/day/id structure. @@ -271,6 +276,7 @@ def object_detail(request, year, month, day, queryset, date_field, object: the object to be detailed """ + if extra_context is None: extra_context = {} try: date = datetime.date(*time.strptime(year+month+day, '%Y'+month_format+day_format)[:3]) except ValueError: diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py index 77b97ca711..bd0f17c56a 100644 --- a/django/views/generic/list_detail.py +++ b/django/views/generic/list_detail.py @@ -4,9 +4,9 @@ from django.core.xheaders import populate_xheaders from django.core.paginator import ObjectPaginator, InvalidPage from django.core.exceptions import ObjectDoesNotExist -def object_list(request, queryset, paginate_by=None, allow_empty=False, - template_name=None, template_loader=loader, - extra_context={}, context_processors=None, template_object_name='object', +def object_list(request, queryset, paginate_by=None, page=None, + allow_empty=False, template_name=None, template_loader=loader, + extra_context=None, context_processors=None, template_object_name='object', mimetype=None): """ Generic list of objects. @@ -34,10 +34,12 @@ def object_list(request, queryset, paginate_by=None, allow_empty=False, hits number of objects, total """ + if extra_context is None: extra_context = {} queryset = queryset._clone() if paginate_by: paginator = ObjectPaginator(queryset, paginate_by) - page = request.GET.get('page', 1) + if not page: + page = request.GET.get('page', 1) try: page = int(page) object_list = paginator.get_page(page - 1) @@ -78,7 +80,7 @@ def object_list(request, queryset, paginate_by=None, allow_empty=False, def object_detail(request, queryset, object_id=None, slug=None, slug_field=None, template_name=None, template_name_field=None, - template_loader=loader, extra_context={}, + template_loader=loader, extra_context=None, context_processors=None, template_object_name='object', mimetype=None): """ @@ -89,6 +91,7 @@ def object_detail(request, queryset, object_id=None, slug=None, object the object """ + if extra_context is None: extra_context = {} model = queryset.model if object_id: queryset = queryset.filter(pk=object_id) diff --git a/docs/add_ons.txt b/docs/add_ons.txt index f7b3056ef0..9f5dc640da 100644 --- a/docs/add_ons.txt +++ b/docs/add_ons.txt @@ -45,6 +45,71 @@ See the `csrf documentation`_. .. _csrf documentation: http://www.djangoproject.com/documentation/csrf/ +humanize +======== + +A set of Django template filters useful for adding a "human touch" to data. +To activate these filters, add ``'django.contrib.english'`` to your +``INSTALLED_APPS`` setting. Once you've done that, use ``{% load english %}`` +in a template, and you'll have access to these filters: + +apnumber +-------- + +For numbers 1-9, returns the number spelled out. Otherwise, returns the +number. This follows Associated Press style. + +Examples: + + * ``1`` becomes ``'one'``. + * ``2`` becomes ``'two'``. + * ``10`` becomes ``10``. + +You can pass in either an integer or a string representation of an integer. + +intcomma +-------- + +Converts an integer to a string containing commas every three digits. + +Examples: + + * ``4500`` becomes ``'4,500'``. + * ``45000`` becomes ``'45,000'``. + * ``450000`` becomes ``'450,000'``. + * ``4500000`` becomes ``'4,500,000'``. + +You can pass in either an integer or a string representation of an integer. + +intword +------- + +Converts a large integer to a friendly text representation. Works best for +numbers over 1 million. + +Examples: + + * ``1000000`` becomes ``'1.0 million'``. + * ``1200000`` becomes ``'1.2 million'``. + * ``1200000000`` becomes ``'1.2 billion'``. + +Values up to 1000000000000000 (one quadrillion) are supported. + +You can pass in either an integer or a string representation of an integer. + +ordinal +------- + +Converts an integer to its ordinal as a string. + +Examples: + + * ``1`` becomes ``'1st'``. + * ``2`` becomes ``'2nd'``. + * ``3`` becomes ``'3rd'``. + +You can pass in either an integer or a string representation of an integer. + flatpages ========= diff --git a/docs/authentication.txt b/docs/authentication.txt index 1be64c045a..c1a1f8494d 100644 --- a/docs/authentication.txt +++ b/docs/authentication.txt @@ -82,14 +82,14 @@ Methods ``user_permissions``. ``User`` objects can access their related objects in the same way as any other `Django model`_:: - ``myuser.objects.groups = [group_list]`` - ``myuser.objects.groups.add(group, group,...)`` - ``myuser.objects.groups.remove(group, group,...)`` - ``myuser.objects.groups.clear()`` - ``myuser.objects.permissions = [permission_list]`` - ``myuser.objects.permissions.add(permission, permission, ...)`` - ``myuser.objects.permissions.remove(permission, permission, ...]`` - ``myuser.objects.permissions.clear()`` + myuser.objects.groups = [group_list] + myuser.objects.groups.add(group, group,...) + myuser.objects.groups.remove(group, group,...) + myuser.objects.groups.clear() + myuser.objects.permissions = [permission_list] + myuser.objects.permissions.add(permission, permission, ...) + myuser.objects.permissions.remove(permission, permission, ...] + myuser.objects.permissions.clear() In addition to those automatic API methods, ``User`` objects have the following custom methods: diff --git a/docs/db-api.txt b/docs/db-api.txt index 4442a75125..3624620609 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -1035,6 +1035,15 @@ SQL equivalent:: SELECT ... WHERE pub_date IS NULL; +search +~~~~~~ + +A boolean full-text search, taking advantage of full-text indexing. This is +like ``contains`` but is significantly faster due to full-text indexing. + +Note this is only available in MySQL and requires direct manipulation of the +database to add the full-text index. + Default lookups are exact ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1578,3 +1587,23 @@ get_FOO_height() and get_FOO_width() For every ``ImageField``, the object will have ``get_FOO_height()`` and ``get_FOO_width()`` methods, where ``FOO`` is the name of the field. This returns the height (or width) of the image, as an integer, in pixels. + +Falling back to raw SQL +======================= + +If you find yourself needing to write an SQL query that is too complex for +Django's database-mapper to handle, you can fall back into raw-SQL statement +mode. + +The preferred way to do this is by giving your model custom methods or custom +manager methods that execute queries. Although there's nothing in Django that +*requires* database queries to live in the model layer, this approach keeps all +your data-access logic in one place, which is smart from an code-organization +standpoint. For instructions, see `Executing custom SQL`_. + +Finally, it's important to note that the Django database layer is merely an +interface to your database. You can access your database via other tools, +programming languages or database frameworks; there's nothing Django-specific +about your database. + +.. _Executing custom SQL: http://www.djangoproject.com/documentation/model_api/#executing-custom-sql diff --git a/docs/django-admin.txt b/docs/django-admin.txt index 90f5f5e4ed..3334ae4530 100644 --- a/docs/django-admin.txt +++ b/docs/django-admin.txt @@ -148,7 +148,11 @@ If you run this script as a user with normal privileges (recommended), you might not have access to start a port on a low port number. Low port numbers are reserved for the superuser (root). -DO NOT USE THIS SERVER IN A PRODUCTION SETTING. +DO NOT USE THIS SERVER IN A PRODUCTION SETTING. It has not gone through +security audits or performance tests. (And that's how it's gonna stay. We're in +the business of making Web frameworks, not Web servers, so improving this +server to be able to handle a production environment is outside the scope of +Django.) The development server automatically reloads Python code for each request, as needed. You don't need to restart the server for code changes to take effect. diff --git a/docs/faq.txt b/docs/faq.txt index d63c02550e..a2c069f0ca 100644 --- a/docs/faq.txt +++ b/docs/faq.txt @@ -112,7 +112,7 @@ Lawrence, Kansas, USA. .. _`Simon Willison`: http://simon.incutio.com/ .. _`simon.incutio.com`: http://simon.incutio.com/ .. _`Jacob Kaplan-Moss`: http://www.jacobian.org/ -.. _`Wilson Miner`: http://www.wilsonminer.com/live/ +.. _`Wilson Miner`: http://www.wilsonminer.com/ Which sites use Django? ----------------------- diff --git a/docs/generic_views.txt b/docs/generic_views.txt index 317828a2b2..d14fe12418 100644 --- a/docs/generic_views.txt +++ b/docs/generic_views.txt @@ -641,8 +641,10 @@ A page representing a list of objects. * ``paginate_by``: An integer specifying how many objects should be displayed per page. If this is given, the view will paginate objects with - ``paginate_by`` objects per page. The view will expect a ``page`` query - string (GET) parameter containing a zero-indexed page number. + ``paginate_by`` objects per page. The view will expect either a ``page`` + query string parameter (via ``GET``) containing a zero-indexed page + number, or a ``page`` variable specified in the URLconf. See + "Notes on pagination" below. * ``template_name``: The full name of a template to use in rendering the page. This lets you override the default template name (see below). @@ -711,6 +713,25 @@ If the results are paginated, the context will contain these extra variables: * ``hits``: The total number of objects across *all* pages, not just this page. +Notes on pagination +~~~~~~~~~~~~~~~~~~~ + +If ``paginate_by`` is specified, Django will paginate the results. You can +specify the page number in the URL in one of two ways: + + * Use the ``page`` parameter in the URLconf. For example, this is what + your URLconf might look like:: + + (r'^objects/page(?P[0-9]+)/$', 'object_list', dict(info_dict)) + + * Pass the page number via the ``page`` query-string parameter. For + example, a URL would look like this: + + /objects/?page=3 + +In both cases, ``page`` is 1-based, not 0-based, so the first page would be +represented as page ``1``. + ``django.views.generic.list_detail.object_detail`` -------------------------------------------------- diff --git a/docs/model-api.txt b/docs/model-api.txt index 9cc5b8f203..c6707a691b 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -1239,6 +1239,12 @@ The above code results in an admin change list page that looks like this: (This example also has ``search_fields`` defined. See below.) +``list_per_page`` +----------------- + +Set ``list_per_page`` to control how many items appear on each paginated admin +change list page. By default, this is set to ``100``. + ``list_select_related`` ----------------------- diff --git a/docs/settings.txt b/docs/settings.txt index 26d5930f21..553736b280 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -291,7 +291,7 @@ The default formatting to use for date fields on Django admin change-list pages -- and, possibly, by other parts of the system. See `allowed date format strings`_. -See also DATETIME_FORMAT and TIME_FORMAT. +See also DATETIME_FORMAT, TIME_FORMAT, YEAR_MONTH_FORMAT and MONTH_DAY_FORMAT. .. _allowed date format strings: http://www.djangoproject.com/documentation/templates/#now @@ -304,7 +304,7 @@ The default formatting to use for datetime fields on Django admin change-list pages -- and, possibly, by other parts of the system. See `allowed date format strings`_. -See also DATE_FORMAT and TIME_FORMAT. +See also DATE_FORMAT, DATETIME_FORMAT, TIME_FORMAT, YEAR_MONTH_FORMAT and MONTH_DAY_FORMAT. .. _allowed date format strings: http://www.djangoproject.com/documentation/templates/#now @@ -532,6 +532,23 @@ Default:: A tuple of middleware classes to use. See the `middleware docs`_. +MONTH_DAY_FORMAT +---------------- + +Default: ``'F j'`` + +The default formatting to use for date fields on Django admin change-list +pages -- and, possibly, by other parts of the system -- in cases when only the +month and day are displayed. + +For example, when a Django admin change-list page is being filtered by a date +drilldown, the header for a given day displays the day and month. Different +locales have different formats. For example, U.S. English would say +"January 1," whereas Spanish might say "1 Enero." + +See `allowed date format strings`_. See also DATE_FORMAT, DATETIME_FORMAT, +TIME_FORMAT and YEAR_MONTH_FORMAT. + PREPEND_WWW ----------- @@ -696,7 +713,8 @@ The default formatting to use for time fields on Django admin change-list pages -- and, possibly, by other parts of the system. See `allowed date format strings`_. -See also DATE_FORMAT and DATETIME_FORMAT. +See also DATE_FORMAT, DATETIME_FORMAT, TIME_FORMAT, YEAR_MONTH_FORMAT and +MONTH_DAY_FORMAT. .. _allowed date format strings: http://www.djangoproject.com/documentation/templates/#now @@ -720,6 +738,23 @@ 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`_). +YEAR_MONTH_FORMAT +----------------- + +Default: ``'F Y'`` + +The default formatting to use for date fields on Django admin change-list +pages -- and, possibly, by other parts of the system -- in cases when only the +year and month are displayed. + +For example, when a Django admin change-list page is being filtered by a date +drilldown, the header for a given month displays the month and the year. +Different locales have different formats. For example, U.S. English would say +"January 2006," whereas another locale might say "2006/January." + +See `allowed date format strings`_. See also DATE_FORMAT, DATETIME_FORMAT, +TIME_FORMAT and MONTH_DAY_FORMAT. + .. _cache docs: http://www.djangoproject.com/documentation/cache/ .. _middleware docs: http://www.djangoproject.com/documentation/middleware/ .. _session docs: http://www.djangoproject.com/documentation/sessions/ diff --git a/docs/templates.txt b/docs/templates.txt index 8d5a383b8c..c191b409f4 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -1091,3 +1091,27 @@ Value Argument Outputs ``None`` ``"yeah,no"`` ``"no"`` (converts None to False if no mapping for None is given) ========== ====================== ================================== + +Other tags and filter libraries +=============================== + +Django comes with a couple of other template-tag libraries that you have to +enable explicitly in your ``INSTALLED_APPS`` setting and enable in your +template with the ``{% load %}`` tag. + +django.contrib.humanize +----------------------- + +A set of Django template filters useful for adding a "human touch" to data. See +the `humanize documentation`_. + +.. _humanize documentation: http://www.djangoproject.com/documentation/add_ons/#humanize + +django.contrib.markup +--------------------- + +A collection of template filters that implement these common markup languages: + + * Textile + * Markdown + * ReST (ReStructured Text) diff --git a/docs/templates_python.txt b/docs/templates_python.txt index 6c33052792..dea5bcbee6 100644 --- a/docs/templates_python.txt +++ b/docs/templates_python.txt @@ -368,6 +368,11 @@ Generally, you'll store templates in files on your filesystem rather than using the low-level ``Template`` API yourself. Save templates in a directory specified as a **template directory**. +Django searches for template directories in a number of places, depending on +your template-loader settings (see "Loader types" below), but the most basic +way of specifying template directories is by using the ``TEMPLATE_DIRS`` +setting. + The TEMPLATE_DIRS setting ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/setup.py b/setup.py index a872eb10cc..90448b1ddf 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from setuptools import setup, find_packages setup( name = "Django", - version = "0.91", + version = "0.95", url = 'http://www.djangoproject.com/', author = 'Lawrence Journal-World', author_email = 'holovaty@gmail.com', @@ -52,6 +52,7 @@ setup( 'media/img/admin/*.png', 'media/js/*.js', 'media/js/admin/*js'], + 'django.contrib.comments': ['templates/comments/*.html'], }, scripts = ['django/bin/django-admin.py'], zip_safe = False, diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index f60c3777e7..a4de0f9a81 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -9,9 +9,10 @@ from django.db import models class Article(models.Model): headline = models.CharField(maxlength=100, default='Default headline') pub_date = models.DateTimeField() - - def __repr__(self): + + def __str__(self): return self.headline + API_TESTS = """ # No articles are in the system yet. @@ -39,34 +40,34 @@ datetime.datetime(2005, 7, 28, 0, 0) >>> a.headline = 'Area woman programs in Python' >>> a.save() -# Article.objects.all() returns all the articles in the database. +# Article.objects.all() returns all the articles in the database. >>> Article.objects.all() -[Area woman programs in Python] +[] # Django provides a rich database lookup API. >>> Article.objects.get(id__exact=1) -Area woman programs in Python + >>> Article.objects.get(headline__startswith='Area woman') -Area woman programs in Python + >>> Article.objects.get(pub_date__year=2005) -Area woman programs in Python + >>> Article.objects.get(pub_date__year=2005, pub_date__month=7) -Area woman programs in Python + >>> Article.objects.get(pub_date__year=2005, pub_date__month=7, pub_date__day=28) -Area woman programs in Python + # The "__exact" lookup type can be omitted, as a shortcut. >>> Article.objects.get(id=1) -Area woman programs in Python + >>> Article.objects.get(headline='Area woman programs in Python') -Area woman programs in Python + >>> Article.objects.filter(pub_date__year=2005) -[Area woman programs in Python] +[] >>> Article.objects.filter(pub_date__year=2004) [] >>> Article.objects.filter(pub_date__year=2005, pub_date__month=7) -[Area woman programs in Python] +[] # Django raises an Article.DoesNotExist exception for get() if the parameters # don't match any object. @@ -84,7 +85,7 @@ DoesNotExist: Article matching query does not exist. # shortcut for primary-key exact lookups. # The following is identical to articles.get(id=1). >>> Article.objects.get(pk=1) -Area woman programs in Python + # Model instances of the same type and same ID are considered equal. >>> a = Article.objects.get(pk=1) @@ -222,7 +223,7 @@ datetime.datetime(2005, 7, 28, 0, 0) >>> s1 = Article.objects.filter(id__exact=1) >>> s2 = Article.objects.filter(id__exact=2) >>> s1 | s2 -[Area woman programs in Python, Second article] +[, ] >>> s1 & s2 [] @@ -232,34 +233,34 @@ datetime.datetime(2005, 7, 28, 0, 0) # You can get items using index and slice notation. >>> Article.objects.all()[0] -Area woman programs in Python + >>> Article.objects.all()[1:3] -[Second article, Third article] +[, ] >>> s3 = Article.objects.filter(id__exact=3) >>> (s1 | s2 | s3)[::2] -[Area woman programs in Python, Third article] +[, ] # Slices (without step) are lazy: >>> Article.objects.all()[0:5].filter() -[Area woman programs in Python, Second article, Third article, Fourth article, Article 6] +[, , , , ] # Slicing again works: >>> Article.objects.all()[0:5][0:2] -[Area woman programs in Python, Second article] +[, ] >>> Article.objects.all()[0:5][:2] -[Area woman programs in Python, Second article] +[, ] >>> Article.objects.all()[0:5][4:] -[Article 6] +[] >>> Article.objects.all()[0:5][5:] [] # Some more tests! >>> Article.objects.all()[2:][0:2] -[Third article, Fourth article] +[, ] >>> Article.objects.all()[2:][:2] -[Third article, Fourth article] +[, ] >>> Article.objects.all()[2:][2:3] -[Article 6] +[] # Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work: >>> Article.objects.all()[2:] @@ -308,10 +309,10 @@ AttributeError: Manager isn't accessible via Article instances # Bulk delete test: How many objects before and after the delete? >>> Article.objects.all() -[Area woman programs in Python, Second article, Third article, Fourth article, Article 6, Default headline, Article 7, Updated article 8] +[, , , , , , , ] >>> Article.objects.filter(id__lte=4).delete() >>> Article.objects.all() -[Article 6, Default headline, Article 7, Updated article 8] +[, , , ] """ diff --git a/tests/modeltests/choices/models.py b/tests/modeltests/choices/models.py index 38dcb934c5..881fb29fd2 100644 --- a/tests/modeltests/choices/models.py +++ b/tests/modeltests/choices/models.py @@ -20,7 +20,7 @@ class Person(models.Model): name = models.CharField(maxlength=20) gender = models.CharField(maxlength=1, choices=GENDER_CHOICES) - def __repr__(self): + def __str__(self): return self.name API_TESTS = """ diff --git a/tests/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py index 4958517e69..7d8c52d137 100644 --- a/tests/modeltests/custom_columns/models.py +++ b/tests/modeltests/custom_columns/models.py @@ -12,7 +12,7 @@ class Person(models.Model): first_name = models.CharField(maxlength=30, db_column='firstname') last_name = models.CharField(maxlength=30, db_column='last') - def __repr__(self): + def __str__(self): return '%s %s' % (self.first_name, self.last_name) API_TESTS = """ @@ -24,13 +24,13 @@ API_TESTS = """ 1 >>> Person.objects.all() -[John Smith] +[] >>> Person.objects.filter(first_name__exact='John') -[John Smith] +[] >>> Person.objects.get(first_name__exact='John') -John Smith + >>> Person.objects.filter(firstname__exact='John') Traceback (most recent call last): diff --git a/tests/modeltests/custom_managers/models.py b/tests/modeltests/custom_managers/models.py index ceecea2fc6..1c4e91b526 100644 --- a/tests/modeltests/custom_managers/models.py +++ b/tests/modeltests/custom_managers/models.py @@ -23,7 +23,7 @@ class Person(models.Model): fun = models.BooleanField() objects = PersonManager() - def __repr__(self): + def __str__(self): return "%s %s" % (self.first_name, self.last_name) # An example of a custom manager that sets get_query_set(). @@ -39,7 +39,7 @@ class Book(models.Model): published_objects = PublishedBookManager() authors = models.ManyToManyField(Person, related_name='books') - def __repr__(self): + def __str__(self): return self.title # An example of providing multiple custom managers. @@ -55,7 +55,7 @@ class Car(models.Model): cars = models.Manager() fast_cars = FastCarManager() - def __repr__(self): + def __str__(self): return self.name API_TESTS = """ @@ -64,7 +64,7 @@ API_TESTS = """ >>> p2 = Person(first_name='Droopy', last_name='Dog', fun=False) >>> p2.save() >>> Person.objects.get_fun_people() -[Bugs Bunny] +[] # The RelatedManager used on the 'books' descriptor extends the default manager >>> from modeltests.custom_managers.models import PublishedBookManager @@ -89,19 +89,19 @@ AttributeError: type object 'Book' has no attribute 'objects' True >>> Book.published_objects.all() -[How to program] +[] >>> c1 = Car(name='Corvette', mileage=21, top_speed=180) >>> c1.save() >>> c2 = Car(name='Neon', mileage=31, top_speed=100) >>> c2.save() >>> Car.cars.order_by('name') -[Corvette, Neon] +[, ] >>> Car.fast_cars.all() -[Corvette] +[] # Each model class gets a "_default_manager" attribute, which is a reference # to the first manager defined in the class. In this case, it's "cars". >>> Car._default_manager.order_by('name') -[Corvette, Neon] +[, ] """ diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py index 6cc3fe8548..e314d97264 100644 --- a/tests/modeltests/custom_methods/models.py +++ b/tests/modeltests/custom_methods/models.py @@ -11,16 +11,16 @@ class Article(models.Model): headline = models.CharField(maxlength=100) pub_date = models.DateField() - def __repr__(self): + def __str__(self): return self.headline def was_published_today(self): return self.pub_date == datetime.date.today() - def get_articles_from_same_day_1(self): + def articles_from_same_day_1(self): return Article.objects.filter(pub_date=self.pub_date).exclude(id=self.id) - def get_articles_from_same_day_2(self): + def articles_from_same_day_2(self): """ Verbose version of get_articles_from_same_day_1, which does a custom database query for the sake of demonstration. @@ -47,12 +47,12 @@ API_TESTS = """ # Test the custom methods. >>> a.was_published_today() False ->>> a.get_articles_from_same_day_1() -[Beatles reunite] ->>> a.get_articles_from_same_day_2() -[Beatles reunite] ->>> b.get_articles_from_same_day_1() -[Area man programs in Python] ->>> b.get_articles_from_same_day_2() -[Area man programs in Python] +>>> a.articles_from_same_day_1() +[] +>>> a.articles_from_same_day_2() +[] +>>> b.articles_from_same_day_1() +[] +>>> b.articles_from_same_day_2() +[] """ diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py index fed2c25515..6193852adf 100644 --- a/tests/modeltests/custom_pk/models.py +++ b/tests/modeltests/custom_pk/models.py @@ -14,7 +14,7 @@ class Employee(models.Model): class Meta: ordering = ('last_name', 'first_name') - def __repr__(self): + def __str__(self): return "%s %s" % (self.first_name, self.last_name) class Business(models.Model): @@ -23,24 +23,24 @@ class Business(models.Model): class Meta: verbose_name_plural = 'businesses' - def __repr__(self): + def __str__(self): return self.name API_TESTS = """ >>> dan = Employee(employee_code='ABC123', first_name='Dan', last_name='Jones') >>> dan.save() >>> Employee.objects.all() -[Dan Jones] +[] >>> fran = Employee(employee_code='XYZ456', first_name='Fran', last_name='Bones') >>> fran.save() >>> Employee.objects.all() -[Fran Bones, Dan Jones] +[, ] >>> Employee.objects.get(pk='ABC123') -Dan Jones + >>> Employee.objects.get(pk='XYZ456') -Fran Bones + >>> Employee.objects.get(pk='foo') Traceback (most recent call last): ... @@ -48,43 +48,43 @@ DoesNotExist: Employee matching query does not exist. # Use the name of the primary key, rather than pk. >>> Employee.objects.get(employee_code__exact='ABC123') -Dan Jones + # Fran got married and changed her last name. >>> fran = Employee.objects.get(pk='XYZ456') >>> fran.last_name = 'Jones' >>> fran.save() >>> Employee.objects.filter(last_name__exact='Jones') -[Dan Jones, Fran Jones] +[, ] >>> Employee.objects.in_bulk(['ABC123', 'XYZ456']) -{'XYZ456': Fran Jones, 'ABC123': Dan Jones} +{'XYZ456': , 'ABC123': } >>> b = Business(name='Sears') >>> b.save() >>> b.employees.add(dan, fran) >>> b.employees.all() -[Dan Jones, Fran Jones] +[, ] >>> fran.business_set.all() -[Sears] +[] >>> Business.objects.in_bulk(['Sears']) -{'Sears': Sears} +{'Sears': } >>> Business.objects.filter(name__exact='Sears') -[Sears] +[] >>> Business.objects.filter(pk='Sears') -[Sears] +[] # Queries across tables, involving primary key >>> Employee.objects.filter(business__name__exact='Sears') -[Dan Jones, Fran Jones] +[, ] >>> Employee.objects.filter(business__pk='Sears') -[Dan Jones, Fran Jones] +[, ] >>> Business.objects.filter(employees__employee_code__exact='ABC123') -[Sears] +[] >>> Business.objects.filter(employees__pk='ABC123') -[Sears] +[] >>> Business.objects.filter(employees__first_name__startswith='Fran') -[Sears] +[] """ diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py index 0af2146c64..42e7a14ec7 100644 --- a/tests/modeltests/get_latest/models.py +++ b/tests/modeltests/get_latest/models.py @@ -17,7 +17,7 @@ class Article(models.Model): class Meta: get_latest_by = 'pub_date' - def __repr__(self): + def __str__(self): return self.headline class Person(models.Model): @@ -26,7 +26,7 @@ class Person(models.Model): # Note that this model doesn't have "get_latest_by" set. - def __repr__(self): + def __str__(self): return self.name API_TESTS = """ @@ -49,19 +49,19 @@ DoesNotExist: Article matching query does not exist. # Get the latest Article. >>> Article.objects.latest() -Article 4 + # Get the latest Article that matches certain filters. >>> Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest() -Article 1 + # Pass a custom field name to latest() to change the field that's used to # determine the latest object. >>> Article.objects.latest('expire_date') -Article 1 + >>> Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date') -Article 3 + # You can still use latest() with a model that doesn't have "get_latest_by" # set -- just pass in the field name manually. @@ -75,5 +75,5 @@ Traceback (most recent call last): AssertionError: latest() requires either a field_name parameter or 'get_latest_by' in the model >>> Person.objects.latest('birthday') -Stephanie + """ diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index 4b8454f855..9ac53b1677 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -12,7 +12,7 @@ class Article(models.Model): class Meta: ordering = ('-pub_date', 'headline') - def __repr__(self): + def __str__(self): return self.headline API_TESTS = """ @@ -61,9 +61,9 @@ Article 4 # in_bulk() takes a list of IDs and returns a dictionary mapping IDs # to objects. >>> Article.objects.in_bulk([1, 2]) -{1: Article 1, 2: Article 2} +{1: , 2: } >>> Article.objects.in_bulk([3]) -{3: Article 3} +{3: } >>> Article.objects.in_bulk([1000]) {} >>> Article.objects.in_bulk([]) @@ -125,55 +125,55 @@ True # In the case of identical date values, these methods will use the ID as a # fallback check. This guarantees that no records are skipped or duplicated. >>> a1.get_next_by_pub_date() -Article 2 + >>> a2.get_next_by_pub_date() -Article 3 + >>> a3.get_next_by_pub_date() -Article 7 + >>> a4.get_next_by_pub_date() -Article 6 + >>> a5.get_next_by_pub_date() Traceback (most recent call last): ... DoesNotExist: Article matching query does not exist. >>> a6.get_next_by_pub_date() -Article 5 + >>> a7.get_next_by_pub_date() -Article 4 + >>> a7.get_previous_by_pub_date() -Article 3 + >>> a6.get_previous_by_pub_date() -Article 4 + >>> a5.get_previous_by_pub_date() -Article 6 + >>> a4.get_previous_by_pub_date() -Article 7 + >>> a3.get_previous_by_pub_date() -Article 2 + >>> a2.get_previous_by_pub_date() -Article 1 + # Underscores and percent signs have special meaning in the underlying # database library, but Django handles the quoting of them automatically. >>> a8 = Article(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20)) >>> a8.save() >>> Article.objects.filter(headline__startswith='Article') -[Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1] +[, , , , , , , ] >>> Article.objects.filter(headline__startswith='Article_') -[Article_ with underscore] +[] >>> a9 = Article(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21)) >>> a9.save() >>> Article.objects.filter(headline__startswith='Article') -[Article% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1] +[, , , , , , , , ] >>> Article.objects.filter(headline__startswith='Article%') -[Article% with percent sign] +[] # exclude() is the opposite of filter() when doing lookups: >>> Article.objects.filter(headline__contains='Article').exclude(headline__contains='with') -[Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1] +[, , , , , , ] >>> Article.objects.exclude(headline__startswith="Article_") -[Article% with percent sign, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1] +[, , , , , , , ] >>> Article.objects.exclude(headline="Article 7") -[Article% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 1] +[, , , , , , , ] """ diff --git a/tests/modeltests/m2m_intermediary/models.py b/tests/modeltests/m2m_intermediary/models.py index 869b188521..848d035c39 100644 --- a/tests/modeltests/m2m_intermediary/models.py +++ b/tests/modeltests/m2m_intermediary/models.py @@ -16,14 +16,14 @@ class Reporter(models.Model): first_name = models.CharField(maxlength=30) last_name = models.CharField(maxlength=30) - def __repr__(self): + def __str__(self): return "%s %s" % (self.first_name, self.last_name) class Article(models.Model): headline = models.CharField(maxlength=100) pub_date = models.DateField() - def __repr__(self): + def __str__(self): return self.headline class Writer(models.Model): @@ -31,8 +31,8 @@ class Writer(models.Model): article = models.ForeignKey(Article) position = models.CharField(maxlength=100) - def __repr__(self): - return '%r (%s)' % (self.reporter, self.position) + def __str__(self): + return '%s (%s)' % (self.reporter, self.position) API_TESTS = """ # Create a few Reporters. @@ -54,15 +54,15 @@ API_TESTS = """ # Play around with the API. >>> a.writer_set.select_related().order_by('-position') -[John Smith (Main writer), Jane Doe (Contributor)] +[, ] >>> w1.reporter -John Smith + >>> w2.reporter -Jane Doe + >>> w1.article -This is a test + >>> w2.article -This is a test + >>> r1.writer_set.all() -[John Smith (Main writer)] +[] """ diff --git a/tests/modeltests/m2m_multiple/models.py b/tests/modeltests/m2m_multiple/models.py index 3fec427c1d..e4fef75f19 100644 --- a/tests/modeltests/m2m_multiple/models.py +++ b/tests/modeltests/m2m_multiple/models.py @@ -14,7 +14,7 @@ class Category(models.Model): class Meta: ordering = ('name',) - def __repr__(self): + def __str__(self): return self.name class Article(models.Model): @@ -25,7 +25,7 @@ class Article(models.Model): class Meta: ordering = ('pub_date',) - def __repr__(self): + def __str__(self): return self.headline API_TESTS = """ @@ -51,29 +51,29 @@ API_TESTS = """ >>> a2.secondary_categories.add(c4) >>> a1.primary_categories.all() -[Crime, News] +[, ] >>> a2.primary_categories.all() -[News, Sports] +[, ] >>> a1.secondary_categories.all() -[Life] +[] >>> c1.primary_article_set.all() -[Area man runs] +[] >>> c1.secondary_article_set.all() [] >>> c2.primary_article_set.all() -[Area man steals, Area man runs] +[, ] >>> c2.secondary_article_set.all() [] >>> c3.primary_article_set.all() -[Area man steals] +[] >>> c3.secondary_article_set.all() [] >>> c4.primary_article_set.all() [] >>> c4.secondary_article_set.all() -[Area man steals, Area man runs] +[, ] """ diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/modeltests/m2m_recursive/models.py index 877a41fd14..c109b9cc2c 100644 --- a/tests/modeltests/m2m_recursive/models.py +++ b/tests/modeltests/m2m_recursive/models.py @@ -19,7 +19,7 @@ class Person(models.Model): friends = models.ManyToManyField('self') idols = models.ManyToManyField('self', symmetrical=False, related_name='stalkers') - def __repr__(self): + def __str__(self): return self.name API_TESTS = """ @@ -41,37 +41,37 @@ API_TESTS = """ # Who is friends with Anne? >>> a.friends.all() -[Bill, Chuck, David] +[, , ] # Who is friends with Bill? >>> b.friends.all() -[Anne] +[] # Who is friends with Chuck? >>> c.friends.all() -[Anne, David] +[, ] # Who is friends with David? >>> d.friends.all() -[Anne, Chuck] +[, ] # Bill is already friends with Anne - add Anne again, but in the reverse direction >>> b.friends.add(a) # Who is friends with Anne? >>> a.friends.all() -[Bill, Chuck, David] +[, , ] # Who is friends with Bill? >>> b.friends.all() -[Anne] +[] # Remove Anne from Bill's friends >>> b.friends.remove(a) # Who is friends with Anne? >>> a.friends.all() -[Chuck, David] +[, ] # Who is friends with Bill? >>> b.friends.all() @@ -87,11 +87,11 @@ API_TESTS = """ # Reverse relationships should also be gone # Who is friends with Chuck? >>> c.friends.all() -[David] +[] # Who is friends with David? >>> d.friends.all() -[Chuck] +[] # Add some idols in the direction of field definition @@ -106,27 +106,27 @@ API_TESTS = """ # Who are Anne's idols? >>> a.idols.all() -[Bill, Chuck, David] +[, , ] # Who is stalking Anne? >>> a.stalkers.all() -[Bill] +[] # Who are Bill's idols? >>> b.idols.all() -[Anne] +[] # Who is stalking Bill? >>> b.stalkers.all() -[Anne] +[] # Who are Chuck's idols? >>> c.idols.all() -[David] +[] # Who is stalking Chuck? >>> c.stalkers.all() -[Anne] +[] # Who are David's idols? >>> d.idols.all() @@ -134,40 +134,40 @@ API_TESTS = """ # Who is stalking David >>> d.stalkers.all() -[Anne, Chuck] +[, ] # Bill is already being stalked by Anne - add Anne again, but in the reverse direction >>> b.stalkers.add(a) # Who are Anne's idols? >>> a.idols.all() -[Bill, Chuck, David] +[, , ] # Who is stalking Anne? -[Bill] +[] # Who are Bill's idols >>> b.idols.all() -[Anne] +[] # Who is stalking Bill? >>> b.stalkers.all() -[Anne] +[] # Remove Anne from Bill's list of stalkers >>> b.stalkers.remove(a) # Who are Anne's idols? >>> a.idols.all() -[Chuck, David] +[, ] # Who is stalking Anne? >>> a.stalkers.all() -[Bill] +[] # Who are Bill's idols? >>> b.idols.all() -[Anne] +[] # Who is stalking Bill? >>> b.stalkers.all() @@ -187,6 +187,6 @@ API_TESTS = """ # Who is friends with David? >>> d.stalkers.all() -[Chuck] +[] """ diff --git a/tests/modeltests/m2o_recursive/models.py b/tests/modeltests/m2o_recursive/models.py index e7996bc15f..44881b5a2f 100644 --- a/tests/modeltests/m2o_recursive/models.py +++ b/tests/modeltests/m2o_recursive/models.py @@ -16,7 +16,7 @@ class Category(models.Model): name = models.CharField(maxlength=20) parent = models.ForeignKey('self', null=True, related_name='child_set') - def __repr__(self): + def __str__(self): return self.name API_TESTS = """ @@ -27,14 +27,14 @@ API_TESTS = """ >>> c.save() >>> r.child_set.all() -[Child category] +[] >>> r.child_set.get(name__startswith='Child') -Child category + >>> print r.parent None >>> c.child_set.all() [] >>> c.parent -Root category + """ diff --git a/tests/modeltests/m2o_recursive2/models.py b/tests/modeltests/m2o_recursive2/models.py index 40b842dc92..93185c6a4c 100644 --- a/tests/modeltests/m2o_recursive2/models.py +++ b/tests/modeltests/m2o_recursive2/models.py @@ -14,7 +14,7 @@ class Person(models.Model): mother = models.ForeignKey('self', null=True, related_name='mothers_child_set') father = models.ForeignKey('self', null=True, related_name='fathers_child_set') - def __repr__(self): + def __str__(self): return self.full_name API_TESTS = """ @@ -29,13 +29,13 @@ API_TESTS = """ >>> kid.save() >>> kid.mother -Jane Smith + >>> kid.father -John Smith Senior + >>> dad.fathers_child_set.all() -[John Smith Junior] +[] >>> mom.mothers_child_set.all() -[John Smith Junior] +[] >>> kid.mothers_child_set.all() [] >>> kid.fathers_child_set.all() diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py index cf833cc468..f7b20d52ac 100644 --- a/tests/modeltests/manipulators/models.py +++ b/tests/modeltests/manipulators/models.py @@ -10,7 +10,7 @@ class Musician(models.Model): first_name = models.CharField(maxlength=30) last_name = models.CharField(maxlength=30) - def __repr__(self): + def __str__(self): return "%s %s" % (self.first_name, self.last_name) class Album(models.Model): @@ -18,7 +18,7 @@ class Album(models.Model): musician = models.ForeignKey(Musician) release_date = models.DateField(blank=True, null=True) - def __repr__(self): + def __str__(self): return self.name API_TESTS = """ @@ -35,7 +35,7 @@ API_TESTS = """ # Verify it worked. >>> Musician.objects.all() -[Ella Fitzgerald] +[] >>> [m1] == list(Musician.objects.all()) True @@ -69,9 +69,9 @@ True # Verify it worked. >>> Album.objects.all() -[Ella and Basie] +[] >>> Album.objects.get().musician -Ella Fitzgerald + # Create an Album with a release_date. >>> data = MultiValueDict({'name': ['Ultimate Ella'], 'musician': ['1'], 'release_date': ['2005-02-13']}) @@ -82,10 +82,10 @@ Ella Fitzgerald # Verify it worked. >>> Album.objects.order_by('name') -[Ella and Basie, Ultimate Ella] +[, ] >>> a2 = Album.objects.get(pk=2) >>> a2 -Ultimate Ella + >>> a2.release_date datetime.date(2005, 2, 13) """ diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py index 4422cb1a6a..e80afece46 100644 --- a/tests/modeltests/many_to_many/models.py +++ b/tests/modeltests/many_to_many/models.py @@ -12,7 +12,7 @@ from django.db import models class Publication(models.Model): title = models.CharField(maxlength=30) - def __repr__(self): + def __str__(self): return self.title class Meta: @@ -22,7 +22,7 @@ class Article(models.Model): headline = models.CharField(maxlength=100) publications = models.ManyToManyField(Publication) - def __repr__(self): + def __str__(self): return self.headline class Meta: @@ -58,29 +58,29 @@ API_TESTS = """ # Article objects have access to their related Publication objects. >>> a1.publications.all() -[The Python Journal] +[] >>> a2.publications.all() -[Highlights for Children, Science News, Science Weekly, The Python Journal] +[, , , ] # Publication objects have access to their related Article objects. >>> p2.article_set.all() -[NASA uses Python] +[] >>> p1.article_set.all() -[Django lets you build Web apps easily, NASA uses Python] +[, ] >>> Publication.objects.get(id=4).article_set.all() -[NASA uses Python] +[] # We can perform kwarg queries across m2m relationships >>> Article.objects.filter(publications__id__exact=1) -[Django lets you build Web apps easily, NASA uses Python] +[, ] >>> Article.objects.filter(publications__pk=1) -[Django lets you build Web apps easily, NASA uses Python] +[, ] >>> Article.objects.filter(publications__title__startswith="Science") -[NASA uses Python, NASA uses Python] +[, ] >>> Article.objects.filter(publications__title__startswith="Science").distinct() -[NASA uses Python] +[] # The count() function respects distinct() as well. >>> Article.objects.filter(publications__title__startswith="Science").count() @@ -92,23 +92,23 @@ API_TESTS = """ # Reverse m2m queries are supported (i.e., starting at the table that doesn't # have a ManyToManyField). >>> Publication.objects.filter(id__exact=1) -[The Python Journal] +[] >>> Publication.objects.filter(pk=1) -[The Python Journal] +[] >>> Publication.objects.filter(article__headline__startswith="NASA") -[Highlights for Children, Science News, Science Weekly, The Python Journal] +[, , , ] >>> Publication.objects.filter(article__id__exact=1) -[The Python Journal] +[] >>> Publication.objects.filter(article__pk=1) -[The Python Journal] +[] # If we delete a Publication, its Articles won't be able to access it. >>> p1.delete() >>> Publication.objects.all() -[Highlights for Children, Science News, Science Weekly] +[, , ] >>> a1 = Article.objects.get(pk=1) >>> a1.publications.all() [] @@ -116,7 +116,7 @@ API_TESTS = """ # If we delete an Article, its Publications won't be able to access it. >>> a2.delete() >>> Article.objects.all() -[Django lets you build Web apps easily] +[] >>> p2.article_set.all() [] @@ -125,22 +125,22 @@ API_TESTS = """ >>> a4.save() >>> p2.article_set.add(a4) >>> p2.article_set.all() -[NASA finds intelligent life on Earth] +[] >>> a4.publications.all() -[Science News] +[] # Adding via the other end using keywords >>> new_article = p2.article_set.create(headline='Oxygen-free diet works wonders') >>> p2.article_set.all() -[NASA finds intelligent life on Earth, Oxygen-free diet works wonders] +[, ] >>> a5 = p2.article_set.all()[1] >>> a5.publications.all() -[Science News] +[] # Removing publication from an article: >>> a4.publications.remove(p2) >>> p2.article_set.all() -[Oxygen-free diet works wonders] +[] >>> a4.publications.all() [] @@ -154,33 +154,33 @@ API_TESTS = """ # Relation sets can be assigned. Assignment clears any existing set members >>> p2.article_set = [a4, a5] >>> p2.article_set.all() -[NASA finds intelligent life on Earth, Oxygen-free diet works wonders] +[, ] >>> a4.publications.all() -[Science News] +[] >>> a4.publications = [p3] >>> p2.article_set.all() -[Oxygen-free diet works wonders] +[] >>> a4.publications.all() -[Science Weekly] +[] # Relation sets can be cleared: >>> p2.article_set.clear() >>> p2.article_set.all() [] >>> a4.publications.all() -[Science Weekly] +[] # And you can clear from the other end >>> p2.article_set.add(a4, a5) >>> p2.article_set.all() -[NASA finds intelligent life on Earth, Oxygen-free diet works wonders] +[, ] >>> a4.publications.all() -[Science News, Science Weekly] +[, ] >>> a4.publications.clear() >>> a4.publications.all() [] >>> p2.article_set.all() -[Oxygen-free diet works wonders] +[] # Recreate the article and Publication we just deleted. >>> p1 = Publication(id=None, title='The Python Journal') @@ -192,22 +192,22 @@ API_TESTS = """ # Bulk delete some Publications - references to deleted publications should go >>> Publication.objects.filter(title__startswith='Science').delete() >>> Publication.objects.all() -[Highlights for Children, The Python Journal] +[, ] >>> Article.objects.all() -[Django lets you build Web apps easily, NASA finds intelligent life on Earth, NASA uses Python, Oxygen-free diet works wonders] +[, , , ] >>> a2.publications.all() -[The Python Journal] +[] # Bulk delete some articles - references to deleted objects should go >>> q = Article.objects.filter(headline__startswith='Django') >>> print q -[Django lets you build Web apps easily] +[] >>> q.delete() # After the delete, the QuerySet cache needs to be cleared, and the referenced objects should be gone >>> print q [] >>> p1.article_set.all() -[NASA uses Python] +[] """ diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index 3a0972b793..a830ffbdc2 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -11,7 +11,7 @@ class Reporter(models.Model): last_name = models.CharField(maxlength=30) email = models.EmailField() - def __repr__(self): + def __str__(self): return "%s %s" % (self.first_name, self.last_name) class Article(models.Model): @@ -19,7 +19,7 @@ class Article(models.Model): pub_date = models.DateField() reporter = models.ForeignKey(Reporter) - def __repr__(self): + def __str__(self): return self.headline class Meta: @@ -42,7 +42,7 @@ API_TESTS = """ 1 >>> a.reporter -John Smith + # Article objects have access to their related Reporter objects. >>> r = a.reporter @@ -52,7 +52,7 @@ John Smith # Create an Article via the Reporter object. >>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29)) >>> new_article -John's second story + >>> new_article.reporter.id 1 @@ -62,43 +62,43 @@ John's second story >>> new_article2.reporter.id 1 >>> r.article_set.all() -[John's second story, Paul's story, This is a test] +[, , ] # Add the same article to a different article set - check that it moves. >>> r2.article_set.add(new_article2) >>> new_article2.reporter.id 2 >>> r.article_set.all() -[John's second story, This is a test] +[, ] >>> r2.article_set.all() -[Paul's story] +[] # Assign the article to the reporter directly using the descriptor >>> new_article2.reporter = r >>> new_article2.save() >>> new_article2.reporter -John Smith + >>> new_article2.reporter.id 1 >>> r.article_set.all() -[John's second story, Paul's story, This is a test] +[, , ] >>> r2.article_set.all() [] # Set the article back again using set descriptor. >>> r2.article_set = [new_article, new_article2] >>> r.article_set.all() -[This is a test] +[] >>> r2.article_set.all() -[John's second story, Paul's story] +[, ] -# Funny case - assignment notation can only go so far; because the +# Funny case - assignment notation can only go so far; because the # ForeignKey cannot be null, existing members of the set must remain >>> r.article_set = [new_article] >>> r.article_set.all() -[John's second story, This is a test] +[, ] >>> r2.article_set.all() -[Paul's story] +[] # Reporter cannot be null - there should not be a clear or remove method >>> hasattr(r2.article_set, 'remove') @@ -108,10 +108,10 @@ False # Reporter objects have access to their related Article objects. >>> r.article_set.all() -[John's second story, This is a test] +[, ] >>> r.article_set.filter(headline__startswith='This') -[This is a test] +[] >>> r.article_set.count() 2 @@ -121,24 +121,24 @@ False # Get articles by id >>> Article.objects.filter(id__exact=1) -[This is a test] +[] >>> Article.objects.filter(pk=1) -[This is a test] +[] # Query on an article property >>> Article.objects.filter(headline__startswith='This') -[This is a test] +[] # The API automatically follows relationships as far as you need. # Use double underscores to separate relationships. # This works as many levels deep as you want. There's no limit. # Find all Articles for any Reporter whose first name is "John". >>> Article.objects.filter(reporter__first_name__exact='John') -[John's second story, This is a test] +[, ] # Query twice over the related field. >>> Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') -[John's second story, This is a test] +[, ] # The underlying query only makes one join when a related table is referenced twice. >>> query = Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') @@ -148,13 +148,13 @@ False # The automatically joined table has a predictable name. >>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_article__reporter.last_name='Smith'"]) -[John's second story, This is a test] +[, ] # Find all Articles for the Reporter whose ID is 1. >>> Article.objects.filter(reporter__id__exact=1) -[John's second story, This is a test] +[, ] >>> Article.objects.filter(reporter__pk=1) -[John's second story, This is a test] +[, ] # You need two underscores between "reporter" and "id" -- not one. >>> Article.objects.filter(reporter_id__exact=1) @@ -170,7 +170,7 @@ TypeError: Cannot resolve keyword 'reporter_id' into field # "pk" shortcut syntax works in a related context, too. >>> Article.objects.filter(reporter__pk=1) -[John's second story, This is a test] +[, ] # You can also instantiate an Article by passing # the Reporter's ID instead of a Reporter object. @@ -179,31 +179,31 @@ TypeError: Cannot resolve keyword 'reporter_id' into field >>> a3.reporter.id 1 >>> a3.reporter -John Smith + # Similarly, the reporter ID can be a string. >>> a4 = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id="1") >>> a4.save() >>> a4.reporter -John Smith + # Reporters can be queried >>> Reporter.objects.filter(id__exact=1) -[John Smith] +[] >>> Reporter.objects.filter(pk=1) -[John Smith] +[] >>> Reporter.objects.filter(first_name__startswith='John') -[John Smith] +[] # Reporters can query in opposite direction of ForeignKey definition >>> Reporter.objects.filter(article__id__exact=1) -[John Smith] +[] >>> Reporter.objects.filter(article__pk=1) -[John Smith] +[] >>> Reporter.objects.filter(article__headline__startswith='This') -[John Smith, John Smith, John Smith] +[, , ] >>> Reporter.objects.filter(article__headline__startswith='This').distinct() -[John Smith] +[] # Counting in the opposite direction works in conjunction with distinct() >>> Reporter.objects.filter(article__headline__startswith='This').count() @@ -213,20 +213,20 @@ John Smith # Queries can go round in circles. >>> Reporter.objects.filter(article__reporter__first_name__startswith='John') -[John Smith, John Smith, John Smith, John Smith] +[, , , ] >>> Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct() -[John Smith] +[] # If you delete a reporter, his articles will be deleted. >>> Article.objects.all() -[John's second story, Paul's story, This is a test, This is a test, This is a test] +[, , , , ] >>> Reporter.objects.order_by('first_name') -[John Smith, Paul Jones] +[, ] >>> r2.delete() >>> Article.objects.all() -[John's second story, This is a test, This is a test, This is a test] +[, , , ] >>> Reporter.objects.order_by('first_name') -[John Smith] +[] # Deletes using a join in the query >>> Reporter.objects.filter(article__headline__startswith='This').delete() diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py index 638382107a..b1936b9861 100644 --- a/tests/modeltests/many_to_one_null/models.py +++ b/tests/modeltests/many_to_one_null/models.py @@ -10,19 +10,19 @@ from django.db import models class Reporter(models.Model): name = models.CharField(maxlength=30) - def __repr__(self): + def __str__(self): return self.name class Article(models.Model): headline = models.CharField(maxlength=100) reporter = models.ForeignKey(Reporter, null=True) - def __repr__(self): - return self.headline - class Meta: ordering = ('headline',) - + + def __str__(self): + return self.headline + API_TESTS = """ # Create a Reporter. >>> r = Reporter(name='John Smith') @@ -36,7 +36,7 @@ API_TESTS = """ 1 >>> a.reporter -John Smith + # Article objects have access to their related Reporter objects. >>> r = a.reporter @@ -44,15 +44,15 @@ John Smith # Create an Article via the Reporter object. >>> a2 = r.article_set.create(headline="Second") >>> a2 -Second + >>> a2.reporter.id 1 # Reporter objects have access to their related Article objects. >>> r.article_set.all() -[First, Second] +[, ] >>> r.article_set.filter(headline__startswith='Fir') -[First] +[] >>> r.article_set.count() 2 @@ -78,47 +78,47 @@ None # To retrieve the articles with no reporters set, use "reporter__isnull=True". >>> Article.objects.filter(reporter__isnull=True) -[Third] +[] # Set the reporter for the Third article >>> r.article_set.add(a3) >>> r.article_set.all() -[First, Second, Third] +[, , ] # Remove an article from the set, and check that it was removed. >>> r.article_set.remove(a3) >>> r.article_set.all() -[First, Second] +[, ] >>> Article.objects.filter(reporter__isnull=True) -[Third] +[] # Create another article and reporter >>> r2 = Reporter(name='Paul Jones') >>> r2.save() >>> a4 = r2.article_set.create(headline='Fourth') >>> r2.article_set.all() -[Fourth] +[] # Try to remove a4 from a set it does not belong to >>> r.article_set.remove(a4) Traceback (most recent call last): ... -DoesNotExist: 'Article object' is not related to 'Reporter object'. +DoesNotExist: is not related to . >>> r2.article_set.all() -[Fourth] +[] # Use descriptor assignment to allocate ForeignKey. Null is legal, so # existing members of set that are not in the assignment set are set null >>> r2.article_set = [a2, a3] >>> r2.article_set.all() -[Second, Third] +[, ] # Clear the rest of the set >>> r.article_set.clear() >>> r.article_set.all() [] >>> Article.objects.filter(reporter__isnull=True) -[First, Fourth] +[, ] """ diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py index f2d184017c..473cf24a9f 100644 --- a/tests/modeltests/model_inheritance/models.py +++ b/tests/modeltests/model_inheritance/models.py @@ -10,20 +10,20 @@ class Place(models.Model): name = models.CharField(maxlength=50) address = models.CharField(maxlength=80) - def __repr__(self): + def __str__(self): return "%s the place" % self.name class Restaurant(Place): serves_hot_dogs = models.BooleanField() serves_pizza = models.BooleanField() - def __repr__(self): + def __str__(self): return "%s the restaurant" % self.name class ItalianRestaurant(Restaurant): serves_gnocchi = models.BooleanField() - def __repr__(self): + def __str__(self): return "%s the italian restaurant" % self.name API_TESTS = """ diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py index 6aaa3e586d..27c16b5a34 100644 --- a/tests/modeltests/one_to_one/models.py +++ b/tests/modeltests/one_to_one/models.py @@ -12,7 +12,7 @@ class Place(models.Model): name = models.CharField(maxlength=50) address = models.CharField(maxlength=80) - def __repr__(self): + def __str__(self): return "%s the place" % self.name class Restaurant(models.Model): @@ -20,15 +20,15 @@ class Restaurant(models.Model): serves_hot_dogs = models.BooleanField() serves_pizza = models.BooleanField() - def __repr__(self): + def __str__(self): return "%s the restaurant" % self.place.name class Waiter(models.Model): restaurant = models.ForeignKey(Restaurant) name = models.CharField(maxlength=50) - def __repr__(self): - return "%s the waiter at %r" % (self.name, self.restaurant) + def __str__(self): + return "%s the waiter at %s" % (self.name, self.restaurant) API_TESTS = """ # Create a couple of Places. @@ -43,11 +43,11 @@ API_TESTS = """ # A Restaurant can access its place. >>> r.place -Demon Dogs the place + # A Place can access its restaurant, if available. >>> p1.restaurant -Demon Dogs the restaurant + # p2 doesn't have an associated restaurant. >>> p2.restaurant @@ -60,69 +60,69 @@ DoesNotExist: Restaurant matching query does not exist. >>> r.place = p2 >>> r.save() >>> p2.restaurant -Ace Hardware the restaurant + >>> r.place -Ace Hardware the place + # Set the place back again, using assignment in the reverse direction -# Need to reget restaurant object first, because the reverse set +# Need to reget restaurant object first, because the reverse set # can't update the existing restaurant instance >>> p1.restaurant = r >>> r.save() >>> p1.restaurant -Demon Dogs the restaurant + >>> r = Restaurant.objects.get(pk=1) >>> r.place -Demon Dogs the place + # Restaurant.objects.all() just returns the Restaurants, not the Places. # Note that there are two restaurants - Ace Hardware the Restaurant was created -# in the call to r.place = p2. This means there are multiple restaurants referencing +# in the call to r.place = p2. This means there are multiple restaurants referencing # a single place... >>> Restaurant.objects.all() -[Demon Dogs the restaurant, Ace Hardware the restaurant] +[, ] # Place.objects.all() returns all Places, regardless of whether they have # Restaurants. >>> Place.objects.order_by('name') -[Ace Hardware the place, Demon Dogs the place] +[, ] >>> Restaurant.objects.get(place__id__exact=1) -Demon Dogs the restaurant + >>> Restaurant.objects.get(pk=1) -Demon Dogs the restaurant + >>> Restaurant.objects.get(place__exact=1) -Demon Dogs the restaurant + >>> Restaurant.objects.get(place__pk=1) -Demon Dogs the restaurant + >>> Restaurant.objects.get(place__name__startswith="Demon") -Demon Dogs the restaurant + >>> Place.objects.get(id__exact=1) -Demon Dogs the place + >>> Place.objects.get(pk=1) -Demon Dogs the place + >>> Place.objects.get(restaurant__place__exact=1) -Demon Dogs the place + >>> Place.objects.get(restaurant__pk=1) -Demon Dogs the place + # Add a Waiter to the Restaurant. >>> w = r.waiter_set.create(name='Joe') >>> w.save() >>> w -Joe the waiter at Demon Dogs the restaurant + # Query the waiters >>> Waiter.objects.filter(restaurant__place__exact=1) -[Joe the waiter at Demon Dogs the restaurant] +[] >>> Waiter.objects.filter(restaurant__pk=1) -[Joe the waiter at Demon Dogs the restaurant] +[] >>> Waiter.objects.filter(id__exact=1) -[Joe the waiter at Demon Dogs the restaurant] +[] >>> Waiter.objects.filter(pk=1) -[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/or_lookups/models.py b/tests/modeltests/or_lookups/models.py index ef4d4dc5ca..80dc35f2b1 100644 --- a/tests/modeltests/or_lookups/models.py +++ b/tests/modeltests/or_lookups/models.py @@ -1,10 +1,10 @@ """ 19. OR lookups -To perform an OR lookup, or a lookup that combines ANDs and ORs, +To perform an OR lookup, or a lookup that combines ANDs and ORs, combine QuerySet objects using & and | operators. -Alternatively, use positional arguments, and pass one or more expressions +Alternatively, use positional arguments, and pass one or more expressions of clauses using the variable ``django.db.models.Q`` (or any object with a get_sql method). @@ -16,10 +16,11 @@ from django.db import models class Article(models.Model): headline = models.CharField(maxlength=50) pub_date = models.DateTimeField() + class Meta: ordering = ('pub_date',) - def __repr__(self): + def __str__(self): return self.headline API_TESTS = """ @@ -36,10 +37,10 @@ API_TESTS = """ >>> a3.save() >>> Article.objects.filter(headline__startswith='Hello') | Article.objects.filter(headline__startswith='Goodbye') -[Hello, Goodbye, Hello and goodbye] +[, , ] >>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__startswith='Goodbye')) -[Hello, Goodbye, Hello and goodbye] +[, , ] >>> Article.objects.filter(Q(headline__startswith='Hello') & Q(headline__startswith='Goodbye')) [] @@ -51,34 +52,34 @@ API_TESTS = """ [] >>> articles.filter(headline__startswith='Hello') & articles.filter(headline__contains='bye') -[Hello and goodbye] +[] >>> Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello') -[Hello and goodbye] +[] >>> Article.objects.filter(headline__contains='Hello') | Article.objects.filter(headline__contains='bye') -[Hello, Goodbye, Hello and goodbye] +[, , ] >>> Article.objects.filter(headline__iexact='Hello') | Article.objects.filter(headline__contains='ood') -[Hello, Goodbye, Hello and goodbye] +[, , ] >>> Article.objects.filter(Q(pk=1) | Q(pk=2)) -[Hello, Goodbye] +[, ] >>> Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3)) -[Hello, Goodbye, Hello and goodbye] +[, , ] -# Q arg objects are ANDed +# Q arg objects are ANDed >>> Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')) -[Hello and goodbye] +[] # Q arg AND order is irrelevant >>> Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello') -[Hello and goodbye] +[] # Try some arg queries with operations other than get_list >>> Article.objects.get(Q(headline__startswith='Hello'), Q(headline__contains='bye')) -Hello and goodbye + >>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__contains='bye')).count() 3 @@ -87,17 +88,17 @@ Hello and goodbye [{'headline': 'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}] >>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2]) -{1: Hello} +{1: } # Demonstrating exclude with a Q object >>> Article.objects.exclude(Q(headline__startswith='Hello')) -[Goodbye] +[] -# The 'complex_filter' method supports framework features such as +# The 'complex_filter' method supports framework features such as # 'limit_choices_to' which normally take a single dictionary of lookup arguments # but need to support arbitrary queries via Q objects too. >>> Article.objects.complex_filter({'pk': 1}) -[Hello] +[] >>> Article.objects.complex_filter(Q(pk=1) | Q(pk=2)) -[Hello, Goodbye] +[, ] """ diff --git a/tests/modeltests/ordering/models.py b/tests/modeltests/ordering/models.py index 8b56731c55..b22568c900 100644 --- a/tests/modeltests/ordering/models.py +++ b/tests/modeltests/ordering/models.py @@ -21,7 +21,7 @@ class Article(models.Model): class Meta: ordering = ('-pub_date', 'headline') - def __repr__(self): + def __str__(self): return self.headline API_TESTS = """ @@ -39,26 +39,26 @@ API_TESTS = """ # By default, Article.objects.all() orders by pub_date descending, then # headline ascending. >>> Article.objects.all() -[Article 4, Article 2, Article 3, Article 1] +[, , , ] # Override ordering with order_by, which is in the same format as the ordering # attribute in models. >>> Article.objects.order_by('headline') -[Article 1, Article 2, Article 3, Article 4] +[, , , ] >>> Article.objects.order_by('pub_date', '-headline') -[Article 1, Article 3, Article 2, Article 4] +[, , , ] # Use the 'stop' part of slicing notation to limit the results. >>> Article.objects.order_by('headline')[:2] -[Article 1, Article 2] +[, ] # Use the 'stop' and 'start' parts of slicing notation to offset the result list. >>> Article.objects.order_by('headline')[1:3] -[Article 2, Article 3] +[, ] # Getting a single item should work too: >>> Article.objects.all()[0] -Article 4 + # Use '?' to order randomly. (We're using [...] in the output to indicate we # don't know what order the output will be in. diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py index 3667c84d69..165b251d35 100644 --- a/tests/modeltests/pagination/models.py +++ b/tests/modeltests/pagination/models.py @@ -12,7 +12,7 @@ class Article(models.Model): headline = models.CharField(maxlength=100, default='Default headline') pub_date = models.DateTimeField() - def __repr__(self): + def __str__(self): return self.headline API_TESTS = """ @@ -35,11 +35,11 @@ API_TESTS = """ # get the first page (zero-based) >>> paginator.get_page(0) -[Article 1, Article 2, Article 3, Article 4, Article 5] +[, , , , ] # get the second page >>> paginator.get_page(1) -[Article 6, Article 7, Article 8, Article 9] +[, , , ] # does the first page have a next or previous page? >>> paginator.has_next_page(0) diff --git a/tests/modeltests/reserved_names/models.py b/tests/modeltests/reserved_names/models.py index 51d32e22c5..db9196bebe 100644 --- a/tests/modeltests/reserved_names/models.py +++ b/tests/modeltests/reserved_names/models.py @@ -21,7 +21,7 @@ class Thing(models.Model): class Meta: db_table = 'select' - def __repr__(self): + def __str__(self): return self.when API_TESTS = """ @@ -39,18 +39,18 @@ a h >>> Thing.objects.order_by('when') -[a, h] +[, ] >>> v = Thing.objects.get(pk='a') >>> print v.join b >>> print v.where 2005-01-01 >>> Thing.objects.order_by('select.when') -[a, h] +[, ] >>> Thing.objects.dates('where', 'year') [datetime.datetime(2005, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)] >>> Thing.objects.filter(where__month=1) -[a] +[] """ diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py index 2fe77bdbeb..b8c4466021 100644 --- a/tests/modeltests/reverse_lookup/models.py +++ b/tests/modeltests/reverse_lookup/models.py @@ -8,20 +8,23 @@ from django.db import models class User(models.Model): name = models.CharField(maxlength=200) - def __repr__(self): + + def __str__(self): return self.name class Poll(models.Model): question = models.CharField(maxlength=200) creator = models.ForeignKey(User) - def __repr__(self): + + def __str__(self): return self.question class Choice(models.Model): name = models.CharField(maxlength=100) poll = models.ForeignKey(Poll, related_name="poll_choice") related_poll = models.ForeignKey(Poll, related_name="related_choice") - def __repr(self): + + def __str(self): return self.name API_TESTS = """ @@ -38,15 +41,15 @@ API_TESTS = """ >>> # Reverse lookups by field name: >>> User.objects.get(poll__question__exact="What's the first question?") -John Doe + >>> User.objects.get(poll__question__exact="What's the second question?") -Jim Bo + >>> # Reverse lookups by related_name: >>> Poll.objects.get(poll_choice__name__exact="This is the answer.") -What's the first question? + >>> Poll.objects.get(related_choice__name__exact="This is the answer.") -What's the second question? + >>> # If a related_name is given you can't use the field name instead: >>> Poll.objects.get(choice__name__exact="This is the answer") diff --git a/tests/modeltests/save_delete_hooks/models.py b/tests/modeltests/save_delete_hooks/models.py index 47748082da..f01a2932d3 100644 --- a/tests/modeltests/save_delete_hooks/models.py +++ b/tests/modeltests/save_delete_hooks/models.py @@ -11,7 +11,7 @@ class Person(models.Model): first_name = models.CharField(maxlength=20) last_name = models.CharField(maxlength=20) - def __repr__(self): + def __str__(self): return "%s %s" % (self.first_name, self.last_name) def save(self): @@ -31,7 +31,7 @@ Before save After save >>> Person.objects.all() -[John Smith] +[] >>> p1.delete() Before deletion diff --git a/tests/modeltests/str/__init__.py b/tests/modeltests/str/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/modeltests/repr/models.py b/tests/modeltests/str/models.py similarity index 85% rename from tests/modeltests/repr/models.py rename to tests/modeltests/str/models.py index 7e5b98c4a5..4e4228ac89 100644 --- a/tests/modeltests/repr/models.py +++ b/tests/modeltests/str/models.py @@ -1,7 +1,7 @@ """ -2. Adding __repr__() to models +2. Adding __str__() to models -Although it's not a strict requirement, each model should have a ``__repr__()`` +Although it's not a strict requirement, each model should have a ``__str__()`` method to return a "human-readable" representation of the object. Do this not only for your own sanity when dealing with the interactive prompt, but also because objects' representations are used throughout Django's @@ -14,7 +14,7 @@ class Article(models.Model): headline = models.CharField(maxlength=100) pub_date = models.DateTimeField() - def __repr__(self): + def __str__(self): return self.headline API_TESTS = """ @@ -23,9 +23,9 @@ API_TESTS = """ >>> a = Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28)) >>> a.save() ->>> repr(a) +>>> str(a) 'Area man programs in Python' >>> a -Area man programs in Python + """ diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py index ef7791c3f3..92e0f38f47 100644 --- a/tests/modeltests/transactions/models.py +++ b/tests/modeltests/transactions/models.py @@ -14,12 +14,19 @@ class Reporter(models.Model): last_name = models.CharField(maxlength=30) email = models.EmailField() - def __repr__(self): + def __str__(self): return "%s %s" % (self.first_name, self.last_name) API_TESTS = """ >>> from django.db import connection, transaction +""" +from django.conf import settings + +building_docs = getattr(settings, 'BUILDING_DOCS', False) + +if building_docs or settings.DATABASE_ENGINE != 'mysql': + API_TESTS += """ # the default behavior is to autocommit after each save() action >>> def create_a_reporter_then_fail(first, last): ... a = Reporter(first_name=first, last_name=last) @@ -33,7 +40,7 @@ Exception: I meant to do that # The object created before the exception still exists >>> Reporter.objects.all() -[Alice Smith] +[] # the autocommit decorator works exactly the same as the default behavior >>> autocomitted_create_then_fail = transaction.autocommit(create_a_reporter_then_fail) @@ -44,7 +51,7 @@ Exception: I meant to do that # Same behavior as before >>> Reporter.objects.all() -[Alice Smith, Ben Jones] +[, ] # With the commit_on_success decorator, the transaction is only comitted if the # function doesn't throw an exception @@ -56,7 +63,7 @@ Exception: I meant to do that # This time the object never got saved >>> Reporter.objects.all() -[Alice Smith, Ben Jones] +[, ] # If there aren't any exceptions, the data will get saved >>> def remove_a_reporter(): @@ -66,7 +73,7 @@ Exception: I meant to do that >>> remove_comitted_on_success = transaction.commit_on_success(remove_a_reporter) >>> remove_comitted_on_success() >>> Reporter.objects.all() -[Ben Jones] +[] # You can manually manage transactions if you really want to, but you # have to remember to commit/rollback @@ -77,7 +84,7 @@ Exception: I meant to do that >>> manually_managed = transaction.commit_manually(manually_managed) >>> manually_managed() >>> Reporter.objects.all() -[Ben Jones, Carol Doe] +[, ] # If you forget, you'll get bad errors >>> def manually_managed_mistake(): diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py index 8904c42727..57811f25a5 100644 --- a/tests/modeltests/validation/models.py +++ b/tests/modeltests/validation/models.py @@ -17,7 +17,7 @@ class Person(models.Model): favorite_moment = models.DateTimeField() email = models.EmailField() - def __repr__(self): + def __str__(self): return self.name API_TESTS = """