1
0
mirror of https://github.com/django/django.git synced 2025-07-03 17:29:12 +00:00

gis: Merged revisions 7981-8001,8003-8011,8013-8033,8035-8036,8038-8039,8041-8063,8065-8076,8078-8139,8141-8154,8156-8214 via svnmerge from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@8215 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2008-08-05 17:15:33 +00:00
parent 45b73c9a46
commit aa239e3e54
269 changed files with 21956 additions and 14800 deletions

13
AUTHORS
View File

@ -41,6 +41,7 @@ And here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS --
people who have submitted patches, reported bugs, added translations, helped
answer newbie questions, and generally made Django that much better:
ajs <adi@sieker.info>
alang@bright-green.com
Marty Alchin <gulopine@gamemusic.org>
atlithorn <atlithorn@gmail.com>
@ -70,7 +71,7 @@ answer newbie questions, and generally made Django that much better:
Esdras Beleza <linux@esdrasbeleza.com>
Chris Bennett <chrisrbennett@yahoo.com>
James Bennett
Ben Godfrey <http://aftnn.org>
Julian Bez
Arvis Bickovskis <viestards.lists@gmail.com>
Paul Bissex <http://e-scribe.com/>
Simon Blanchard
@ -150,8 +151,10 @@ answer newbie questions, and generally made Django that much better:
Stefane Fermgier <sf@fermigier.com>
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
J. Pablo Fernandez <pupeno@pupeno.com>
Maciej Fijalkowski
Matthew Flanagan <http://wadofstuff.blogspot.com>
Eric Floehr <eric@intellovations.com>
Eric Florenzano <floguy@gmail.com>
Vincent Foley <vfoleybourgon@yahoo.ca>
Rudolph Froger <rfroger@estrate.nl>
Jorge Gajon <gajon@gajon.org>
@ -164,6 +167,7 @@ answer newbie questions, and generally made Django that much better:
glin@seznam.cz
martin.glueck@gmail.com
Artyom Gnilov <boobsd@gmail.com>
Ben Godfrey <http://aftnn.org>
GomoX <gomo@datafull.com>
Guilherme Mesquita Gondim <semente@taurinus.org>
Mario Gonzalez <gonzalemario@gmail.com>
@ -172,6 +176,7 @@ answer newbie questions, and generally made Django that much better:
Owen Griffiths
Espen Grindhaug <http://grindhaug.org/>
Thomas Güttler <hv@tbz-pariv.de>
Horst Gutmann <zerok@zerokspot.com>
dAniel hAhler
hambaloney
Brian Harring <ferringb@gmail.com>
@ -234,6 +239,7 @@ answer newbie questions, and generally made Django that much better:
Stuart Langridge <http://www.kryogenix.org/>
Paul Lanier <planier@google.com>
Nicola Larosa <nico@teknico.net>
Lau Bech Lauritzen
Rune Rønde Laursen <runerl@skjoldhoej.dk>
Eugene Lazutkin <http://lazutkin.com/blog/>
lcordier@point45.com
@ -276,6 +282,7 @@ answer newbie questions, and generally made Django that much better:
Eric Moritz <http://eric.themoritzfamily.com/>
mrmachine <real.human@mrmachine.net>
Robin Munn <http://www.geekforgod.com/>
James Murty
msundstr
Robert Myers <myer0052@gmail.com>
Nebojša Dorđević
@ -301,6 +308,7 @@ answer newbie questions, and generally made Django that much better:
phil@produxion.net
phil.h.smith@gmail.com
Gustavo Picon
Michael Placentra II <someone@michaelplacentra2.net>
Luke Plant <http://lukeplant.me.uk/>
plisk
Mihai Preda <mihai_preda@yahoo.com>
@ -338,8 +346,10 @@ answer newbie questions, and generally made Django that much better:
Pete Shinners <pete@shinners.org>
Leo Shklovskii
jason.sidabras@gmail.com
Brenton Simpson <http://theillustratedlife.com>
Jozko Skrablin <jozko.skrablin@gmail.com>
Ben Slavin <benjamin.slavin@gmail.com>
sloonz <simon.lipp@insa-lyon.fr>
SmileyChris <smileychris@gmail.com>
smurf@smurf.noris.de
Vsevolod Solovyov
@ -398,6 +408,7 @@ answer newbie questions, and generally made Django that much better:
charly.wilhelm@gmail.com
Rachel Willmer <http://www.willmer.com/kb/>
Gary Wilson <gary.wilson@gmail.com>
Jakub Wilk <ubanus@users.sf.net>
Jakub Wiśniowski <restless.being@gmail.com>
Maciej Wiśniowski <pigletto@gmail.com>
wojtek

View File

@ -1,4 +1,4 @@
VERSION = (0, 97, 'pre')
VERSION = (1, 0, 'alpha')
def get_version():
"Returns the version as a human-format string."

View File

@ -188,6 +188,9 @@ APPEND_SLASH = True
# Whether to prepend the "www." subdomain to URLs that don't have it.
PREPEND_WWW = False
# Override the server-derived value of SCRIPT_NAME
FORCE_SCRIPT_NAME = None
# List of compiled regular expression objects representing User-Agent strings
# that are not allowed to visit any page, systemwide. Use this for bad
# robots/crawlers. Here are a few examples:
@ -363,6 +366,9 @@ LOGOUT_URL = '/accounts/logout/'
LOGIN_REDIRECT_URL = '/accounts/profile/'
# The number of days a password reset link is valid for
PASSWORD_RESET_TIMEOUT_DAYS = 3
###########
# TESTING #
###########

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
# translation of django.po to french
# This file is distributed under the same license as the PACKAGE package.
# Copyright (C) 2008 THE PACKAGE'S COPYRIGHT HOLDER.
#
#
msgid ""
msgstr ""
"Project-Id-Version: Django\n"
@ -1066,7 +1066,7 @@ msgstr "Sélectionnez %s"
#: contrib/admin/views/main.py:583
#, python-format
msgid "Select %s to change"
msgstr "Sélectionnez %s pour changer"
msgstr "Sélectionnez l'objet %s à changer"
#: contrib/admin/views/main.py:784
msgid "Database error"
@ -4093,107 +4093,107 @@ msgstr "midi"
#: utils/dates.py:6
msgid "Monday"
msgstr "Lundi"
msgstr "lundi"
#: utils/dates.py:6
msgid "Tuesday"
msgstr "Mardi"
msgstr "mardi"
#: utils/dates.py:6
msgid "Wednesday"
msgstr "Mercredi"
msgstr "mercredi"
#: utils/dates.py:6
msgid "Thursday"
msgstr "Jeudi"
msgstr "jeudi"
#: utils/dates.py:6
msgid "Friday"
msgstr "Vendredi"
msgstr "vendredi"
#: utils/dates.py:7
msgid "Saturday"
msgstr "Samedi"
msgstr "samedi"
#: utils/dates.py:7
msgid "Sunday"
msgstr "Dimanche"
msgstr "dimanche"
#: utils/dates.py:10
msgid "Mon"
msgstr "Lun"
msgstr "lun"
#: utils/dates.py:10
msgid "Tue"
msgstr "Mar"
msgstr "mar"
#: utils/dates.py:10
msgid "Wed"
msgstr "Mer"
msgstr "mer"
#: utils/dates.py:10
msgid "Thu"
msgstr "Jeu"
msgstr "jeu"
#: utils/dates.py:10
msgid "Fri"
msgstr "Ven"
msgstr "ven"
#: utils/dates.py:11
msgid "Sat"
msgstr "Sam"
msgstr "sam"
#: utils/dates.py:11
msgid "Sun"
msgstr "Dim"
msgstr "dim"
#: utils/dates.py:18
msgid "January"
msgstr "Janvier"
msgstr "janvier"
#: utils/dates.py:18
msgid "February"
msgstr "Février"
msgstr "février"
#: utils/dates.py:18 utils/dates.py:31
msgid "March"
msgstr "Mars"
msgstr "mars"
#: utils/dates.py:18 utils/dates.py:31
msgid "April"
msgstr "Avril"
msgstr "avril"
#: utils/dates.py:18 utils/dates.py:31
msgid "May"
msgstr "Mai"
msgstr "mai"
#: utils/dates.py:18 utils/dates.py:31
msgid "June"
msgstr "Juin"
msgstr "juin"
#: utils/dates.py:19 utils/dates.py:31
msgid "July"
msgstr "Juillet"
msgstr "juillet"
#: utils/dates.py:19
msgid "August"
msgstr "Août"
msgstr "août"
#: utils/dates.py:19
msgid "September"
msgstr "Septembre"
msgstr "septembre"
#: utils/dates.py:19
msgid "October"
msgstr "Octobre"
msgstr "octobre"
#: utils/dates.py:19
msgid "November"
msgstr "Novembre"
msgstr "novembre"
#: utils/dates.py:20
msgid "December"
msgstr "Décembre"
msgstr "décembre"
#: utils/dates.py:23
msgid "jan"
@ -4245,31 +4245,31 @@ msgstr "déc"
#: utils/dates.py:31
msgid "Jan."
msgstr "Jan."
msgstr "jan."
#: utils/dates.py:31
msgid "Feb."
msgstr "Fév."
msgstr "fév."
#: utils/dates.py:32
msgid "Aug."
msgstr "Août"
msgstr "août"
#: utils/dates.py:32
msgid "Sept."
msgstr "Sept."
msgstr "sept."
#: utils/dates.py:32
msgid "Oct."
msgstr "Oct."
msgstr "oct."
#: utils/dates.py:32
msgid "Nov."
msgstr "Nov."
msgstr "nov."
#: utils/dates.py:32
msgid "Dec."
msgstr "Déc."
msgstr "déc."
#: utils/text.py:127
msgid "or"

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2008-02-22 21:55+0400\n"
"POT-Creation-Date: 2008-07-15 21:17+0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: David Avsajanishvili <avsd05@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -14,191 +14,199 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: conf/global_settings.py:39
#: conf/global_settings.py:44
msgid "Arabic"
msgstr "არაბული"
#: conf/global_settings.py:40
#: conf/global_settings.py:45
msgid "Bengali"
msgstr "ბენგალიური"
#: conf/global_settings.py:41
#: conf/global_settings.py:46
msgid "Bulgarian"
msgstr "ბულგარული"
#: conf/global_settings.py:42
#: conf/global_settings.py:47
msgid "Catalan"
msgstr "კატალანური"
#: conf/global_settings.py:43
#: conf/global_settings.py:48
msgid "Czech"
msgstr "ჩეხური"
#: conf/global_settings.py:44
#: conf/global_settings.py:49
msgid "Welsh"
msgstr "უელსური"
#: conf/global_settings.py:45
#: conf/global_settings.py:50
msgid "Danish"
msgstr "დანიური"
#: conf/global_settings.py:46
#: conf/global_settings.py:51
msgid "German"
msgstr "გერმანული"
#: conf/global_settings.py:47
#: conf/global_settings.py:52
msgid "Greek"
msgstr "ბერძნული"
#: conf/global_settings.py:48
#: conf/global_settings.py:53
msgid "English"
msgstr "ინგლისური"
#: conf/global_settings.py:49
#: conf/global_settings.py:54
msgid "Spanish"
msgstr "ესპანური"
#: conf/global_settings.py:50
#: conf/global_settings.py:55
msgid "Estonian"
msgstr "ესტონური"
#: conf/global_settings.py:56
msgid "Argentinean Spanish"
msgstr "არგენტინის ესპანური"
#: conf/global_settings.py:51
#: conf/global_settings.py:57
msgid "Basque"
msgstr "ბასკური"
#: conf/global_settings.py:52
#: conf/global_settings.py:58
msgid "Persian"
msgstr "სპარსული"
#: conf/global_settings.py:53
#: conf/global_settings.py:59
msgid "Finnish"
msgstr "ფინური"
#: conf/global_settings.py:54
#: conf/global_settings.py:60
msgid "French"
msgstr "ფრანგული"
#: conf/global_settings.py:55
#: conf/global_settings.py:61
msgid "Irish"
msgstr "ირლანდიური"
#: conf/global_settings.py:56
#: conf/global_settings.py:62
msgid "Galician"
msgstr "გალიციური"
#: conf/global_settings.py:57
#: conf/global_settings.py:63
msgid "Hungarian"
msgstr "უნგრული"
#: conf/global_settings.py:58
#: conf/global_settings.py:64
msgid "Hebrew"
msgstr "ებრაული"
#: conf/global_settings.py:59
#: conf/global_settings.py:65
msgid "Croatian"
msgstr "ხორვატიული"
#: conf/global_settings.py:60
#: conf/global_settings.py:66
msgid "Icelandic"
msgstr "ისლანდიური"
#: conf/global_settings.py:61
#: conf/global_settings.py:67
msgid "Italian"
msgstr "იტალიური"
#: conf/global_settings.py:62
#: conf/global_settings.py:68
msgid "Japanese"
msgstr "იაპონური"
#: conf/global_settings.py:63
#: conf/global_settings.py:69
msgid "Georgian"
msgstr "ქართული"
#: conf/global_settings.py:64
#: conf/global_settings.py:70
msgid "Korean"
msgstr "კორეული"
#: conf/global_settings.py:65
#: conf/global_settings.py:71
msgid "Khmer"
msgstr "ხმერული"
#: conf/global_settings.py:66
#: conf/global_settings.py:72
msgid "Kannada"
msgstr "კანნადა"
#: conf/global_settings.py:67
#: conf/global_settings.py:73
msgid "Latvian"
msgstr "ლატვიური"
#: conf/global_settings.py:68
#: conf/global_settings.py:74
msgid "Lithuanian"
msgstr "ლიტვური"
#: conf/global_settings.py:75
msgid "Macedonian"
msgstr "მაკედონური"
#: conf/global_settings.py:69
#: conf/global_settings.py:76
msgid "Dutch"
msgstr "ჰოლანდიური"
#: conf/global_settings.py:70
#: conf/global_settings.py:77
msgid "Norwegian"
msgstr "ნორვეგიული"
#: conf/global_settings.py:71
#: conf/global_settings.py:78
msgid "Polish"
msgstr "პოლონური"
#: conf/global_settings.py:72
#: conf/global_settings.py:79
msgid "Portugese"
msgstr "პორტუგალიური"
#: conf/global_settings.py:73
msgid "Brazilian"
msgstr "ბრაზილიური"
#: conf/global_settings.py:80
msgid "Brazilian Portuguese"
msgstr "ბრაზილიური პორტუგალიური"
#: conf/global_settings.py:74
#: conf/global_settings.py:81
msgid "Romanian"
msgstr "რუმინული"
#: conf/global_settings.py:75
#: conf/global_settings.py:82
msgid "Russian"
msgstr "რუსული"
#: conf/global_settings.py:76
#: conf/global_settings.py:83
msgid "Slovak"
msgstr "სლოვარური"
#: conf/global_settings.py:77
#: conf/global_settings.py:84
msgid "Slovenian"
msgstr "სლოვენიური"
#: conf/global_settings.py:78
#: conf/global_settings.py:85
msgid "Serbian"
msgstr "სერბული"
#: conf/global_settings.py:79
#: conf/global_settings.py:86
msgid "Swedish"
msgstr "შვედური"
#: conf/global_settings.py:80
#: conf/global_settings.py:87
msgid "Tamil"
msgstr "თამილური"
#: conf/global_settings.py:81
#: conf/global_settings.py:88
msgid "Telugu"
msgstr "ტელუგუ"
#: conf/global_settings.py:82
#: conf/global_settings.py:89
msgid "Turkish"
msgstr "თურქული"
#: conf/global_settings.py:83
#: conf/global_settings.py:90
msgid "Ukrainian"
msgstr "უკრაინული"
#: conf/global_settings.py:84
#: conf/global_settings.py:91
msgid "Simplified Chinese"
msgstr "გამარტივებული ჩინური"
#: conf/global_settings.py:85
#: conf/global_settings.py:92
msgid "Traditional Chinese"
msgstr "ტრადიციული ჩინური"
@ -322,7 +330,7 @@ msgid ""
"There's been an error. It's been reported to the site administrators via e-"
"mail and should be fixed shortly. Thanks for your patience."
msgstr ""
"აქ იყო შეცდომა. იგი გადაგზავნილია საიტის ადმინისტრატორის ელექტრონულ ფოსტაზე "
"სისტემაში მოხდა შეცდომა. იგი გადაგზავნილია საიტის ადმინისტრატორის ელექტრონულ ფოსტაზე "
"და მალე გამოსწორდება. გმადლობთ მოთმინებისათვის."
#: contrib/admin/templates/admin/base.html:26
@ -477,7 +485,7 @@ msgid "Password:"
msgstr "პაროლი:"
#: contrib/admin/templates/admin/login.html:25
#: contrib/admin/views/decorators.py:25
#: contrib/admin/views/decorators.py:31
msgid "Log in"
msgstr "შესვლა"
@ -799,7 +807,7 @@ msgstr "პაროლი წარმატებით შეიცვალ
msgid "Change password: %s"
msgstr "შევცვალოთ პაროლი: %s"
#: contrib/admin/views/decorators.py:11 contrib/auth/forms.py:60
#: contrib/admin/views/decorators.py:17 contrib/auth/forms.py:60
msgid ""
"Please enter a correct username and password. Note that both fields are case-"
"sensitive."
@ -807,7 +815,7 @@ msgstr ""
"გთხოვთ, შეიყვანოთ სწორი მომხმარებლის სახელი და პაროლი. გაითვალისწინეთ, რომ "
"ორივე ველი დამოკიდებულია რეგისტრზე."
#: contrib/admin/views/decorators.py:63
#: contrib/admin/views/decorators.py:69
msgid ""
"Please log in again, because your session has expired. Don't worry: Your "
"submission has been saved."
@ -815,7 +823,7 @@ msgstr ""
"გთხოვთ, შეხვიდეთ კიდევ ერთხელ, რადგანაც თქვენი სესიის დრო ამოიწურა. ნუ "
"ღელავთ: თქვენს მიერ შეტანილი ცვლილებები შენახულია."
#: contrib/admin/views/decorators.py:70
#: contrib/admin/views/decorators.py:76
msgid ""
"Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again."
@ -823,17 +831,17 @@ msgstr ""
"როგორც ჩანს, თქვენი ბროუზერი არ ღებულობს cookie-ებს. გთხოვთ, ჩართოთ cookie-"
"ების მიღების ფუნქცია, განაახლეთ ეს გვერდი და სცადეთ კიდევ ერთხელ."
#: contrib/admin/views/decorators.py:84
msgid "Usernames cannot contain the '@' character."
msgstr "მომხმარებლის სახელი არ უნდა შეიცავდეს სიმბოლოს '@'."
#: contrib/admin/views/decorators.py:86
#: contrib/admin/views/decorators.py:89
#, python-format
msgid "Your e-mail address is not your username. Try '%s' instead."
msgstr ""
"ელ-ფოსტის მისამართი არ არის თქვენი მომხმარებლის სახელი. სცადეთ '%s' მის "
"ნაცვლად."
#: contrib/admin/views/decorators.py:93
msgid "Usernames cannot contain the '@' character."
msgstr "მომხმარებლის სახელი არ უნდა შეიცავდეს სიმბოლოს '@'."
#: contrib/admin/views/doc.py:48 contrib/admin/views/doc.py:50
#: contrib/admin/views/doc.py:52
msgid "tag:"
@ -957,7 +965,7 @@ msgstr "ტექსტი"
msgid "Time"
msgstr "დრო"
#: contrib/admin/views/doc.py:318 contrib/flatpages/models.py:7
#: contrib/admin/views/doc.py:318 contrib/flatpages/models.py:8
msgid "URL"
msgstr ""
@ -1065,7 +1073,7 @@ msgstr "ავირჩიოთ %s"
msgid "Select %s to change"
msgstr "აირჩიეთ %s შესაცვლელად"
#: contrib/admin/views/main.py:784
#: contrib/admin/views/main.py:765
msgid "Database error"
msgstr "მონაცემთა ბაზის შეცდომა"
@ -1129,15 +1137,15 @@ msgstr "უფლებები"
msgid "group"
msgstr "ჯგუფი"
#: contrib/auth/models.py:98 contrib/auth/models.py:141
#: contrib/auth/models.py:98 contrib/auth/models.py:148
msgid "groups"
msgstr "ჯგუფები"
#: contrib/auth/models.py:131
#: contrib/auth/models.py:138
msgid "username"
msgstr "მომხმარებლის სახელი"
#: contrib/auth/models.py:131
#: contrib/auth/models.py:138
msgid ""
"Required. 30 characters or fewer. Alphanumeric characters only (letters, "
"digits and underscores)."
@ -1145,23 +1153,23 @@ msgstr ""
"აუცილებელია. 30 ან ნაკლები სიმბოლო. მხოლოდ ლათინური ასოები, ციფრები და "
"ხაზგასმა."
#: contrib/auth/models.py:132
#: contrib/auth/models.py:139
msgid "first name"
msgstr "სახელი"
#: contrib/auth/models.py:133
#: contrib/auth/models.py:140
msgid "last name"
msgstr "გვარი"
#: contrib/auth/models.py:134
#: contrib/auth/models.py:141
msgid "e-mail address"
msgstr "ელ. ფოსტა"
#: contrib/auth/models.py:135
#: contrib/auth/models.py:142
msgid "password"
msgstr "პაროლი"
#: contrib/auth/models.py:135
#: contrib/auth/models.py:142
msgid ""
"Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change "
"password form</a>."
@ -1169,46 +1177,46 @@ msgstr ""
"გამოიყენეთ '[algo]$[salt]$[hexdigest]' ან <a href=\"password/\">პაროლის "
"შეცვლის ფორმა</a>."
#: contrib/auth/models.py:136
#: contrib/auth/models.py:143
msgid "staff status"
msgstr "თანამშრომლობის სტატუსი"
#: contrib/auth/models.py:136
#: contrib/auth/models.py:143
msgid "Designates whether the user can log into this admin site."
msgstr ""
"განსაზღვრავს, აქვს თუ არა მომხმარებელს ადმინისტრირების საიტზე შესვლის უფლება."
#: contrib/auth/models.py:137
#: contrib/auth/models.py:144
msgid "active"
msgstr "აქტიურია"
#: contrib/auth/models.py:137
#: contrib/auth/models.py:144
msgid ""
"Designates whether this user can log into the Django admin. Unselect this "
"Designates whether this user should be treated as active. Unselect this "
"instead of deleting accounts."
msgstr ""
"განსაზღვრავს, აქვს თუ არა მომხმარებელს Django-ს ადმინისტრირების საიტზე "
"შესვლის შესაძლებლობა. გადანიშნეთ ეს დროშა მომხმარებლის წაშლის მაგივრად."
"განსაზღვრავს, რომ მომხმარებელი გააქტიურებულია. "
"მომხმარებლის წაშლის მაგივრად გადანიშნეთ ეს დროშა."
#: contrib/auth/models.py:138
#: contrib/auth/models.py:145
msgid "superuser status"
msgstr "სუპერმომხმარებლის სტატუსი"
#: contrib/auth/models.py:138
#: contrib/auth/models.py:145
msgid ""
"Designates that this user has all permissions without explicitly assigning "
"them."
msgstr "განსაზღვრავს, რომ ამ მომხმარებელს აქვს ყველა უფლება."
#: contrib/auth/models.py:139
#: contrib/auth/models.py:146
msgid "last login"
msgstr "ბოლო შესვლა"
#: contrib/auth/models.py:140
#: contrib/auth/models.py:147
msgid "date joined"
msgstr "გაწევრიანების თარიღი"
#: contrib/auth/models.py:142
#: contrib/auth/models.py:149
msgid ""
"In addition to the permissions manually assigned, this user will also get "
"all permissions granted to each group he/she is in."
@ -1216,39 +1224,39 @@ msgstr ""
"ინდივიდუალურად მითითებული უფლებების გარდა, ეს მომხმარებელი მიიღებს აგრეთვე "
"ყველა იმ ჯგუფის უფლებას, რომელშიც იგი გაწევრიანებულია."
#: contrib/auth/models.py:143
#: contrib/auth/models.py:150
msgid "user permissions"
msgstr "მომხმარებლის უფლებები"
#: contrib/auth/models.py:147
#: contrib/auth/models.py:154
msgid "user"
msgstr "მომხმარებელი"
#: contrib/auth/models.py:148
#: contrib/auth/models.py:155
msgid "users"
msgstr "მომხმარებლები"
#: contrib/auth/models.py:154
#: contrib/auth/models.py:160
msgid "Personal info"
msgstr "პირადი ინფორმაცია"
#: contrib/auth/models.py:155
#: contrib/auth/models.py:161
msgid "Permissions"
msgstr "უფლებები"
#: contrib/auth/models.py:156
#: contrib/auth/models.py:162
msgid "Important dates"
msgstr "მნიშვნელოვანი თარიღები"
#: contrib/auth/models.py:157
#: contrib/auth/models.py:163
msgid "Groups"
msgstr "ჯგუფები"
#: contrib/auth/models.py:316
#: contrib/auth/models.py:323
msgid "message"
msgstr "შეტყობინება"
#: contrib/auth/views.py:47
#: contrib/auth/views.py:49
msgid "Logged out"
msgstr "გამოსული ხართ"
@ -1546,42 +1554,42 @@ msgstr "კომენტარის ID არასწორია"
msgid "No voting for yourself"
msgstr "საკუთარი თავისათვის ხმის მიცემა აკრძალულია"
#: contrib/contenttypes/models.py:37
#: contrib/contenttypes/models.py:67
msgid "python model class name"
msgstr "python-ის მოდელის კლასის სახელი"
#: contrib/contenttypes/models.py:40
#: contrib/contenttypes/models.py:71
msgid "content type"
msgstr "კონტენტის ტიპი"
#: contrib/contenttypes/models.py:41
#: contrib/contenttypes/models.py:72
msgid "content types"
msgstr "კონტენტის ტიპები"
#: contrib/flatpages/models.py:8
#: contrib/flatpages/models.py:9
msgid ""
"Example: '/about/contact/'. Make sure to have leading and trailing slashes."
msgstr ""
"მაგალითი: '/about/contact/'. ყურადღება მიაქციეთ დახრილ ხაზებს თავში და "
"ბოლოში."
#: contrib/flatpages/models.py:9
#: contrib/flatpages/models.py:10
msgid "title"
msgstr "სათაური"
#: contrib/flatpages/models.py:10
#: contrib/flatpages/models.py:11
msgid "content"
msgstr "კონტენტი"
#: contrib/flatpages/models.py:11
#: contrib/flatpages/models.py:12
msgid "enable comments"
msgstr "ჩავრთოთ კომენტარები"
#: contrib/flatpages/models.py:12
#: contrib/flatpages/models.py:13
msgid "template name"
msgstr "შაბლონის სახელი"
#: contrib/flatpages/models.py:13
#: contrib/flatpages/models.py:14
msgid ""
"Example: 'flatpages/contact_page.html'. If this isn't provided, the system "
"will use 'flatpages/default.html'."
@ -1589,24 +1597,28 @@ msgstr ""
"მაგალითი: 'flatpages/contact_page.html'. თუ იგი მითითებული არ არის, "
"გამოყენებული იქნება 'flatpages/default.html'."
#: contrib/flatpages/models.py:14
#: contrib/flatpages/models.py:15
msgid "registration required"
msgstr "რეგისტრაცია აუცილებელია"
#: contrib/flatpages/models.py:14
#: contrib/flatpages/models.py:15
msgid "If this is checked, only logged-in users will be able to view the page."
msgstr ""
"თუ ეს დროშა ჩართულია, მხოლო შემოსულ მომხმარებლებს ექნებათ გვერდის "
"დათვალიერების საშუალება."
#: contrib/flatpages/models.py:18
#: contrib/flatpages/models.py:20
msgid "flat page"
msgstr "უბრალო გვერდი"
#: contrib/flatpages/models.py:19
#: contrib/flatpages/models.py:21
msgid "flat pages"
msgstr "უბრალო გვერდები"
#: contrib/flatpages/models.py:27
msgid "Advanced options"
msgstr "დამატებითი პარამეტრები"
#: contrib/humanize/templatetags/humanize.py:19
msgid "th"
msgstr ""
@ -3632,23 +3644,23 @@ msgstr "გადამისამართება"
msgid "redirects"
msgstr "გადამისამართებები"
#: contrib/sessions/models.py:41
#: contrib/sessions/models.py:45
msgid "session key"
msgstr "სესიის გასაღები"
#: contrib/sessions/models.py:42
#: contrib/sessions/models.py:47
msgid "session data"
msgstr "სესიის მონაცემები"
#: contrib/sessions/models.py:43
#: contrib/sessions/models.py:48
msgid "expire date"
msgstr "ამოწურვის თარიღი"
#: contrib/sessions/models.py:48
#: contrib/sessions/models.py:53
msgid "session"
msgstr "სესია"
#: contrib/sessions/models.py:49
#: contrib/sessions/models.py:54
msgid "sessions"
msgstr "სესიები"
@ -3720,7 +3732,7 @@ msgstr "არაციფრული სიმბოლოები აქ დ
msgid "This value can't be comprised solely of digits."
msgstr "ეს მნიშვნელობა არ უნდა შედგებოდეს მხოლოდ ციფრებისაგან."
#: core/validators.py:128 newforms/fields.py:151
#: core/validators.py:128 newforms/fields.py:157
msgid "Enter a whole number."
msgstr "შეიყვანეთ მთელი რიცხვი"
@ -3737,7 +3749,7 @@ msgstr "წელი უნდა იყოს 1900 ან მეტი."
msgid "Invalid date: %s"
msgstr "არასწორი თარიღი: %s"
#: core/validators.py:156 db/models/fields/__init__.py:518
#: core/validators.py:156 db/models/fields/__init__.py:565
msgid "Enter a valid date in YYYY-MM-DD format."
msgstr "შეიყვანეთ სწორი თარიღი YYYY-MM-DD ფორმატში."
@ -3745,21 +3757,20 @@ msgstr "შეიყვანეთ სწორი თარიღი YYYY-MM-D
msgid "Enter a valid time in HH:MM format."
msgstr "შეიყვანეთ სწორი დრო HH:MM ფორმატში."
#: core/validators.py:165 db/models/fields/__init__.py:595
#: core/validators.py:165 db/models/fields/__init__.py:642
msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
msgstr "შეიყვანეთ სწორი თარიღი და დრო YYYY-MM-DD HH:MM ფორმატში."
#: core/validators.py:170 newforms/fields.py:402
#: core/validators.py:170 newforms/fields.py:408
msgid "Enter a valid e-mail address."
msgstr "შეიყვანეთ სწორი ელ. ფოსტის მისამართი."
#: core/validators.py:182 core/validators.py:474 newforms/fields.py:432
#: oldforms/__init__.py:687
#: core/validators.py:182 core/validators.py:474 newforms/fields.py:426
msgid "No file was submitted. Check the encoding type on the form."
msgstr ""
"ფაილი არ იყო გამოგზავნილი. შეამოწმეთ კოდირების ტიპი მოცემული ფორმისათვის."
#: core/validators.py:193 newforms/fields.py:458
#: core/validators.py:193 newforms/fields.py:468
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
@ -3878,9 +3889,9 @@ msgid "Please enter a valid decimal number with at most %s total digit."
msgid_plural ""
"Please enter a valid decimal number with at most %s total digits."
msgstr[0] ""
"გთხოვთ, შეიყვანოთ სწორი, არაუმეტეს %s თანრიგისაგან შემდგარი ათობითი რიცხვი"
"გთხოვთ, შეიყვანოთ სწორი, არაუმეტეს %s თანრიგისაგან შემდგარი ათობითი რიცხვი."
msgstr[1] ""
"გთხოვთ, შეიყვანოთ სწორი, არაუმეტეს %s თანრიგისაგან შემდგარი ათობითი რიცხვი"
"გთხოვთ, შეიყვანოთ სწორი, არაუმეტეს %s თანრიგისაგან შემდგარი ათობითი რიცხვი."
#: core/validators.py:447
#, python-format
@ -4009,53 +4020,53 @@ msgstr "%(object)s მოცემული %(type)s-ით უკვე არ
msgid "%(optname)s with this %(fieldname)s already exists."
msgstr "%(optname)s მოცემული %(fieldname)s-ით უკვე არსებობს."
#: db/models/fields/__init__.py:161 db/models/fields/__init__.py:318
#: db/models/fields/__init__.py:750 db/models/fields/__init__.py:761
#: newforms/fields.py:45 oldforms/__init__.py:374
#: db/models/fields/__init__.py:184 db/models/fields/__init__.py:356
#: db/models/fields/__init__.py:799 db/models/fields/__init__.py:810
#: newforms/fields.py:51 oldforms/__init__.py:374
msgid "This field is required."
msgstr "ეს ველი აუცილებელია."
#: db/models/fields/__init__.py:418
#: db/models/fields/__init__.py:465
msgid "This value must be an integer."
msgstr "ეს მნიშვნელობა უნდა იყოს მთელი."
#: db/models/fields/__init__.py:457
#: db/models/fields/__init__.py:504
msgid "This value must be either True or False."
msgstr "ეს მნიშვნელობა უნდა იყოს True ან False."
#: db/models/fields/__init__.py:481
#: db/models/fields/__init__.py:528
msgid "This field cannot be null."
msgstr "ეს მნიშვნელობა არ შეიძლება იყოს null."
#: db/models/fields/__init__.py:659
#: db/models/fields/__init__.py:706
msgid "This value must be a decimal number."
msgstr "ეს მნიშვნელობა უნდა იყოს ათობითი რიცხვი."
#: db/models/fields/__init__.py:770
#: db/models/fields/__init__.py:819
msgid "Enter a valid filename."
msgstr "შეიყვანეთ სწორი ფაილის სახელი."
#: db/models/fields/__init__.py:941
#: db/models/fields/__init__.py:1013
msgid "This value must be either None, True or False."
msgstr "ეს მნიშვნელობა უნდა იყოს None, True ან False."
#: db/models/fields/related.py:55
#: db/models/fields/related.py:94
#, python-format
msgid "Please enter a valid %s."
msgstr "გთხოვთ, შეიყვანოთ სწორი %s."
#: db/models/fields/related.py:658
#: db/models/fields/related.py:756
msgid "Separate multiple IDs with commas."
msgstr "გამოყავით ID-ები მძიმეებით."
#: db/models/fields/related.py:660
#: db/models/fields/related.py:758
msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
"დააჭირეთ \"Control\", ან \"Command\" Mac-ზე, ერთზე მეტი მნიშვნელობის "
"ასარჩევად."
#: db/models/fields/related.py:707
#: db/models/fields/related.py:805
#, python-format
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
msgid_plural ""
@ -4065,99 +4076,99 @@ msgstr[0] ""
msgstr[1] ""
"გთხოვთ, შეიყვანოთ სწორი %(self)s ID-ები. მნიშვნელობები %(value)r არასწორია."
#: newforms/fields.py:46
#: newforms/fields.py:52
msgid "Enter a valid value."
msgstr "შეიყვანეთ სწორი მნიშვნელობა."
#: newforms/fields.py:123
#: newforms/fields.py:129
#, python-format
msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
msgstr ""
"დარწმუნდით, რომ მნიშვნელობა შედგება არაუმეტეს %(max)d სიმბოლოსაგან (ახლა "
"მისი სიგრძეა %(length)d)."
#: newforms/fields.py:124
#: newforms/fields.py:130
#, python-format
msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
msgstr ""
"დარწმუნდით, რომ მნიშვნელობა შედგება არანაკლებ %(min)d სიმბოლოსაგან (ახლა "
"მისი სიგრძეა %(length)d)."
#: newforms/fields.py:152 newforms/fields.py:181 newforms/fields.py:210
#: newforms/fields.py:158 newforms/fields.py:187 newforms/fields.py:216
#, python-format
msgid "Ensure this value is less than or equal to %s."
msgstr "დარწმუნდით, რომ მნიშვნელობა ნაკლებია ან ტოლია %s-ზე."
#: newforms/fields.py:153 newforms/fields.py:182 newforms/fields.py:211
#: newforms/fields.py:159 newforms/fields.py:188 newforms/fields.py:217
#, python-format
msgid "Ensure this value is greater than or equal to %s."
msgstr "დარწმუნდით, რომ მნიშვნელობა მეტია ან ტოლია %s-ზე."
#: newforms/fields.py:180 newforms/fields.py:209
#: newforms/fields.py:186 newforms/fields.py:215
msgid "Enter a number."
msgstr "შეიყვანეთ რიცხვი."
#: newforms/fields.py:212
#: newforms/fields.py:218
#, python-format
msgid "Ensure that there are no more than %s digits in total."
msgstr "დარწმუნდით, რომ მნიშვნელობა %s თანრიგს არ აღემატება."
#: newforms/fields.py:213
#: newforms/fields.py:219
#, python-format
msgid "Ensure that there are no more than %s decimal places."
msgstr "დარწმუნდით, რომ წილადი ნაწილი %s თანრიგს არ აღემატება."
#: newforms/fields.py:214
#: newforms/fields.py:220
#, python-format
msgid "Ensure that there are no more than %s digits before the decimal point."
msgstr "დარწმუნდით, რომ მთელი ნაწილი %s თანრიგს არ აღემატება."
#: newforms/fields.py:262 newforms/fields.py:723
#: newforms/fields.py:268 newforms/fields.py:781
msgid "Enter a valid date."
msgstr "შეიყვანეთ სწორი თარიღი."
#: newforms/fields.py:295 newforms/fields.py:724
#: newforms/fields.py:301 newforms/fields.py:782
msgid "Enter a valid time."
msgstr "შეიყვანეთ სწორი დრო."
#: newforms/fields.py:334
#: newforms/fields.py:340
msgid "Enter a valid date/time."
msgstr "შეიყვანეთ სწორი თარიღი და დრო."
#: newforms/fields.py:433
#: newforms/fields.py:427
msgid "No file was submitted."
msgstr "ფაილი არ იყო გამოგზავნილი."
#: newforms/fields.py:434 oldforms/__init__.py:689
#: newforms/fields.py:428 oldforms/__init__.py:693
msgid "The submitted file is empty."
msgstr "გამოგზავნილი ფაილი ცარიელია."
#: newforms/fields.py:496
#: newforms/fields.py:524
msgid "Enter a valid URL."
msgstr "შეიყვანეთ სწორი URL."
#: newforms/fields.py:497
#: newforms/fields.py:525
msgid "This URL appears to be a broken link."
msgstr "როგორც ჩანს, URL არის გაწყვეტილი ბმული."
#: newforms/fields.py:559 newforms/models.py:300
#: newforms/fields.py:590 newforms/models.py:306
msgid "Select a valid choice. That choice is not one of the available choices."
msgstr "აირჩიეთ დასაშვები მნიშვნელობა. ეს არჩევანი დასაშვები არ არის."
#: newforms/fields.py:598
#: newforms/fields.py:629
#, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr "აირჩიეთ დასაშვები მნიშვნელობა. %(value)s დასაშვები არ არის."
#: newforms/fields.py:599 newforms/fields.py:661 newforms/models.py:360
#: newforms/fields.py:630 newforms/fields.py:692 newforms/models.py:373
msgid "Enter a list of values."
msgstr "შეიყვანეთ მნიშვნელობების სია."
#: newforms/fields.py:752
#: newforms/fields.py:810
msgid "Enter a valid IPv4 address."
msgstr "შეიყვანეთ სწორი IPv4 მისამართი."
#: newforms/models.py:361
#: newforms/models.py:374
#, python-format
msgid "Select a valid choice. %s is not one of the available choices."
msgstr "აირჩიეთ დასაშვები მნიშვნელობა. %s დასაშვები არ არის."
@ -4180,40 +4191,40 @@ msgstr ""
"აირჩიეთ დასაშვები მნიშვნელობა; '%(data)s' არ არის %(choices)s მნიშვნელობების "
"სიაში."
#: oldforms/__init__.py:745
#: oldforms/__init__.py:754
msgid "Enter a whole number between -32,768 and 32,767."
msgstr "შეიყვანეთ მთელი რიცხვი -32,768-დან 32,767-მდე."
#: oldforms/__init__.py:755
#: oldforms/__init__.py:764
msgid "Enter a positive number."
msgstr "შეიყვანეთ დადებითი რიცხვი."
#: oldforms/__init__.py:765
#: oldforms/__init__.py:774
msgid "Enter a whole number between 0 and 32,767."
msgstr "შეიყვანეთ მთელი რიცხვი 0-დან 32,767-მდე."
#: template/defaultfilters.py:691
#: template/defaultfilters.py:698
msgid "yes,no,maybe"
msgstr "კი,არა,შესაძლოა"
#: template/defaultfilters.py:722
#: template/defaultfilters.py:729
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d ბაიტი"
msgstr[1] "%(size)d ბაიტი"
#: template/defaultfilters.py:724
#: template/defaultfilters.py:731
#, python-format
msgid "%.1f KB"
msgstr "%.1f კბაიტი"
#: template/defaultfilters.py:726
#: template/defaultfilters.py:733
#, python-format
msgid "%.1f MB"
msgstr "%.1f მბაიტი"
#: template/defaultfilters.py:727
#: template/defaultfilters.py:734
#, python-format
msgid "%.1f GB"
msgstr "%.1f გბაიტი"
@ -4422,7 +4433,7 @@ msgstr "ნოემ."
msgid "Dec."
msgstr "დეკ."
#: utils/text.py:127
#: utils/text.py:128
msgid "or"
msgstr "ან"
@ -4476,23 +4487,23 @@ msgstr ""
msgid ", %(number)d %(type)s"
msgstr ""
#: utils/translation/trans_real.py:404
#: utils/translation/trans_real.py:412
msgid "DATE_FORMAT"
msgstr "d.m.Y"
#: utils/translation/trans_real.py:405
#: utils/translation/trans_real.py:413
msgid "DATETIME_FORMAT"
msgstr "d.m.Y H:i"
#: utils/translation/trans_real.py:406
#: utils/translation/trans_real.py:414
msgid "TIME_FORMAT"
msgstr "H:i"
#: utils/translation/trans_real.py:422
#: utils/translation/trans_real.py:430
msgid "YEAR_MONTH_FORMAT"
msgstr "d.m.Y"
#: utils/translation/trans_real.py:423
#: utils/translation/trans_real.py:431
msgid "MONTH_DAY_FORMAT"
msgstr "d.m.Y"
@ -4510,3 +4521,6 @@ msgstr "%(verbose_name)s წარმატებით შეიცვალა
#, python-format
msgid "The %(verbose_name)s was deleted."
msgstr "%(verbose_name)s წაიშალა."
#~ msgid "Brazilian"
#~ msgstr "ბრაზილიური"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1358,7 +1358,7 @@ msgstr "aprovado pela equipe"
#: contrib/comments/models.py:187
msgid "free comment"
msgstr "fomentário livre"
msgstr "comentário livre"
#: contrib/comments/models.py:188
msgid "free comments"

File diff suppressed because it is too large Load Diff

View File

@ -7,15 +7,17 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2005-12-09 11:51+0100\n"
"PO-Revision-Date: 2007-02-20 18:51+0100\n"
"Last-Translator: Petar Marić <petar.maric@gmail.com>\n"
"Language-Team: Nesh <nesh@studioquatro.co.yu> & Petar <petar.maric@gmail.com> <sr@li.org>\n"
"POT-Creation-Date: 2008-07-29 12:07+0200\n"
"PO-Revision-Date: 2008-07-29 12:53+0100\n"
"Last-Translator: Nebojsa Djordjevic <djnesh@gmail.com>\n"
"Language-Team: Nesh <djnesh@gmail.com> & Petar <petar.maric@gmail.com> <sr@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Poedit-Country: YUGOSLAVIA\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Poedit-Language: Serbian\n"
"X-Poedit-SourceCharset: utf-8\n"
#: contrib/admin/media/js/SelectFilter2.js:33
#, perl-format
@ -47,63 +49,72 @@ msgstr "Izaberite potrebno i kliknite"
msgid "Clear all"
msgstr "Obrišite sve"
#: contrib/admin/media/js/dateparse.js:26
#: contrib/admin/media/js/calendar.js:24
#: contrib/admin/media/js/dateparse.js:32
msgid "January February March April May June July August September October November December"
msgstr "Januar Februar Mart April Maj Jun Jul Avgust Septembar Oktobar Novembar Decembar"
#: contrib/admin/media/js/dateparse.js:27
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
msgstr "Nedelja Ponedeljak Utorak Sreda Četvrtak Petak Subota"
#: contrib/admin/media/js/calendar.js:25
msgid "S M T W T F S"
msgstr "N P U S Č P S"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80
#: contrib/admin/media/js/dateparse.js:33
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
msgstr "Nedelja Ponedeljak Utorak Sreda Četvrtak Petak Subota"
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72
msgid "Show"
msgstr "Prikaži"
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63
msgid "Hide"
msgstr "Sakrij"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
msgid "Now"
msgstr "Sada"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51
msgid "Clock"
msgstr "Sat"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78
msgid "Choose a time"
msgstr "Izaberite vreme"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
msgid "Midnight"
msgstr "Ponoć"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
msgid "6 a.m."
msgstr "6 sati"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
msgid "Noon"
msgstr "Podne"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
msgid "Cancel"
msgstr "Poništi"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177
msgid "Today"
msgstr "Danas"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132
msgid "Calendar"
msgstr "Kalendar"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175
msgid "Yesterday"
msgstr "Juče"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
msgid "Tomorrow"
msgstr "Sutra"

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,16 @@
from django.conf.urls.defaults import *
# Uncomment this for admin:
#from django.contrib import admin
# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()
urlpatterns = patterns('',
# Example:
# (r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')),
# Uncomment this for admin docs:
#(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable admin documentation:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment this for admin:
#('^admin/(.*)', admin.site.root),
# Uncomment the next line for to enable the admin:
# (r'^admin/(.*)', admin.site.root),
)

View File

@ -8,9 +8,12 @@ def autodiscover():
not present. This forces an import on them to register any admin bits they
may want.
"""
import imp
from django.conf import settings
for app in settings.INSTALLED_APPS:
try:
__import__("%s.admin" % app)
imp.find_module("admin", __import__(app, {}, {}, [app.split(".")[-1]]).__path__)
except ImportError:
pass
# there is no app admin.py, skip it
continue
__import__("%s.admin" % app)

View File

@ -44,3 +44,6 @@ div.breadcrumbs { text-align:right; }
.selector { float: right;}
.selector .selector-filter { text-align: right;}
/* x unsorted */
.inline-related h2 { text-align:right }

View File

@ -1,5 +1,4 @@
from django import oldforms, template
from django import forms
from django import forms, template
from django.forms.formsets import all_valid
from django.forms.models import modelform_factory, inlineformset_factory
from django.forms.models import BaseInlineFormset
@ -15,7 +14,10 @@ from django.utils.safestring import mark_safe
from django.utils.text import capfirst, get_text_list
from django.utils.translation import ugettext as _
from django.utils.encoding import force_unicode
import sets
try:
set
except NameError:
from sets import Set as set # Python 2.3 fallback
HORIZONTAL, VERTICAL = 1, 2
# returns the <ul> class for a given radio_admin field
@ -90,7 +92,7 @@ class Fieldline(object):
yield AdminField(self.form, field, is_first=(i == 0))
def errors(self):
return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]))
return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]).strip('\n'))
class AdminField(object):
def __init__(self, form, field, is_first):
@ -130,6 +132,23 @@ class BaseModelAdmin(object):
If kwargs are given, they're passed to the form Field's constructor.
"""
# If the field specifies choices, we don't need to look for special
# admin widgets - we just need to use a select widget of some kind.
if db_field.choices:
if db_field.name in self.radio_fields:
# If the field is named as a radio_field, use a RadioSelect
kwargs['widget'] = widgets.AdminRadioSelect(
choices=db_field.get_choices(include_blank=db_field.blank,
blank_choice=[('', _('None'))]),
attrs={
'class': get_ul_class(self.radio_fields[db_field.name]),
}
)
else:
# Otherwise, use the default select widget.
return db_field.formfield(**kwargs)
# For DateTimeFields, use a special field and widget.
if isinstance(db_field, models.DateTimeField):
kwargs['form_class'] = forms.SplitDateTimeField
@ -162,10 +181,13 @@ class BaseModelAdmin(object):
kwargs['empty_label'] = db_field.blank and _('None') or None
else:
if isinstance(db_field, models.ManyToManyField):
if db_field.name in self.raw_id_fields:
# If it uses an intermediary model, don't show field in admin.
if db_field.rel.through is not None:
return None
elif db_field.name in self.raw_id_fields:
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
kwargs['help_text'] = ''
elif db_field.name in (self.filter_vertical + self.filter_horizontal):
elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
# Wrap the widget's render() method with a method that adds
# extra HTML to the end of the rendered output.
@ -174,15 +196,6 @@ class BaseModelAdmin(object):
if not db_field.name in self.raw_id_fields:
formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
return formfield
if db_field.choices and db_field.name in self.radio_fields:
kwargs['widget'] = widgets.AdminRadioSelect(
choices=db_field.get_choices(include_blank=db_field.blank,
blank_choice=[('', _('None'))]),
attrs={
'class': get_ul_class(self.radio_fields[db_field.name]),
}
)
# For any other type of field, just call its formfield() method.
return db_field.formfield(**kwargs)
@ -210,7 +223,7 @@ class ModelAdmin(BaseModelAdmin):
save_on_top = False
ordering = None
inlines = []
# Custom templates (designed to be over-ridden in subclasses)
change_form_template = None
change_list_template = None
@ -261,7 +274,7 @@ class ModelAdmin(BaseModelAdmin):
js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
if self.filter_vertical or self.filter_horizontal:
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
media = property(_media)
@ -345,7 +358,7 @@ class ModelAdmin(BaseModelAdmin):
pk_value = new_object._get_pk_val()
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, pk_value, force_unicode(new_object), ADDITION)
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': new_object}
# Here, we distinguish between different save types by checking for
# the presence of keys in request.POST.
if request.POST.has_key("_continue"):
@ -359,7 +372,7 @@ class ModelAdmin(BaseModelAdmin):
# escape() calls force_unicode.
(escape(pk_value), escape(new_object)))
elif request.POST.has_key("_addanother"):
request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
return HttpResponseRedirect(request.path)
else:
request.user.message_set.create(message=msg)
@ -378,7 +391,7 @@ class ModelAdmin(BaseModelAdmin):
Saves the object in the "change" stage and returns an HttpResponseRedirect.
`form` is a bound Form instance that's verified to be valid.
`formsets` is a sequence of InlineFormSet instances that are verified to be valid.
"""
from django.contrib.admin.models import LogEntry, CHANGE
@ -394,20 +407,20 @@ class ModelAdmin(BaseModelAdmin):
change_message = []
if form.changed_data:
change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
if formsets:
for formset in formsets:
for added_object in formset.new_objects:
change_message.append(_('Added %(name)s "%(object)s".')
change_message.append(_('Added %(name)s "%(object)s".')
% {'name': added_object._meta.verbose_name,
'object': added_object})
for changed_object, changed_fields in formset.changed_objects:
change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
% {'list': get_text_list(changed_fields, _('and')),
'name': changed_object._meta.verbose_name,
change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
% {'list': get_text_list(changed_fields, _('and')),
'name': changed_object._meta.verbose_name,
'object': changed_object})
for deleted_object in formset.deleted_objects:
change_message.append(_('Deleted %(name)s "%(object)s".')
change_message.append(_('Deleted %(name)s "%(object)s".')
% {'name': deleted_object._meta.verbose_name,
'object': deleted_object})
change_message = ' '.join(change_message)
@ -415,7 +428,7 @@ class ModelAdmin(BaseModelAdmin):
change_message = _('No fields changed.')
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, pk_value, force_unicode(new_object), CHANGE, change_message)
msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object}
msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': new_object}
if request.POST.has_key("_continue"):
request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
if request.REQUEST.has_key('_popup'):
@ -423,10 +436,10 @@ class ModelAdmin(BaseModelAdmin):
else:
return HttpResponseRedirect(request.path)
elif request.POST.has_key("_saveasnew"):
request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': new_object})
return HttpResponseRedirect("../%s/" % pk_value)
elif request.POST.has_key("_addanother"):
request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
return HttpResponseRedirect("../add/")
else:
request.user.message_set.create(message=msg)
@ -504,7 +517,7 @@ class ModelAdmin(BaseModelAdmin):
inline_admin_formsets.append(inline_admin_formset)
context = {
'title': _('Add %s') % opts.verbose_name,
'title': _('Add %s') % force_unicode(opts.verbose_name),
'adminform': adminForm,
'is_popup': request.REQUEST.has_key('_popup'),
'show_delete': False,
@ -534,7 +547,7 @@ class ModelAdmin(BaseModelAdmin):
raise PermissionDenied
if obj is None:
raise Http404('%s object with primary key %r does not exist.' % (opts.verbose_name, escape(object_id)))
raise Http404('%s object with primary key %r does not exist.' % (force_unicode(opts.verbose_name), escape(object_id)))
if request.POST and request.POST.has_key("_saveasnew"):
return self.add_view(request, form_url='../../add/')
@ -557,17 +570,16 @@ class ModelAdmin(BaseModelAdmin):
adminForm = AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
media = self.media + adminForm.media
for fs in inline_formsets:
media = media + fs.media
inline_admin_formsets = []
for inline, formset in zip(self.inline_instances, inline_formsets):
fieldsets = list(inline.get_fieldsets(request, obj))
inline_admin_formset = InlineAdminFormSet(inline, formset, fieldsets)
inline_admin_formsets.append(inline_admin_formset)
media = media + inline_admin_formset.media
context = {
'title': _('Change %s') % opts.verbose_name,
'title': _('Change %s') % force_unicode(opts.verbose_name),
'adminform': adminForm,
'object_id': object_id,
'original': obj,
@ -599,7 +611,7 @@ class ModelAdmin(BaseModelAdmin):
if ERROR_FLAG in request.GET.keys():
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
context = {
'title': cl.title,
'is_popup': cl.is_popup,
@ -632,12 +644,12 @@ class ModelAdmin(BaseModelAdmin):
raise PermissionDenied
if obj is None:
raise Http404('%s object with primary key %r does not exist.' % (opts.verbose_name, escape(object_id)))
raise Http404('%s object with primary key %r does not exist.' % (force_unicode(opts.verbose_name), escape(object_id)))
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []]
perms_needed = sets.Set()
perms_needed = set()
get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
if request.POST: # The user has already confirmed the deletion.
@ -650,10 +662,10 @@ class ModelAdmin(BaseModelAdmin):
if not self.has_change_permission(request, None):
return HttpResponseRedirect("../../../../")
return HttpResponseRedirect("../../")
context = {
"title": _("Are you sure?"),
"object_name": opts.verbose_name,
"object_name": force_unicode(opts.verbose_name),
"object": obj,
"deleted_objects": deleted_objects,
"perms_lacking": perms_needed,
@ -681,7 +693,7 @@ class ModelAdmin(BaseModelAdmin):
context = {
'title': _('Change history: %s') % force_unicode(obj),
'action_list': action_list,
'module_name': capfirst(opts.verbose_name_plural),
'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
'object': obj,
'root_path': self.admin_site.root_path,
}
@ -761,6 +773,13 @@ class InlineAdminFormSet(object):
for field_name in flatten_fieldsets(self.fieldsets):
yield self.formset.form.base_fields[field_name]
def _media(self):
media = self.formset.media
for fs in self:
media = media + fs.media
return media
media = property(_media)
class InlineAdminForm(AdminForm):
"""
A wrapper around an inline form for use in the admin system.

View File

@ -1,3 +1,7 @@
import base64
import cPickle as pickle
import re
from django import http, template
from django.contrib.admin import ModelAdmin
from django.contrib.auth import authenticate, login
@ -8,11 +12,7 @@ from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy, ugettext as _
from django.views.decorators.cache import never_cache
from django.conf import settings
import base64
import cPickle as pickle
import datetime
import md5
import re
from django.utils.hashcompat import md5_constructor
ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
LOGIN_FORM_KEY = 'this_is_the_login_form'
@ -26,16 +26,14 @@ class NotRegistered(Exception):
pass
def _encode_post_data(post_data):
from django.conf import settings
pickled = pickle.dumps(post_data)
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest()
return base64.encodestring(pickled + pickled_md5)
def _decode_post_data(encoded_data):
from django.conf import settings
encoded_data = base64.decodestring(encoded_data)
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
from django.core.exceptions import SuspiciousOperation
raise SuspiciousOperation, "User may have tampered with session cookie."
return pickle.loads(pickled)
@ -47,10 +45,10 @@ class AdminSite(object):
register() method, and the root() method can then be used as a Django view function
that presents a full admin interface for the collection of registered models.
"""
index_template = None
login_template = None
def __init__(self):
self._registry = {} # model_class class -> admin_class instance
@ -66,19 +64,33 @@ class AdminSite(object):
If a model is already registered, this will raise AlreadyRegistered.
"""
do_validate = admin_class and settings.DEBUG
if do_validate:
# don't import the humongous validation code unless required
# Don't import the humongous validation code unless required
if admin_class and settings.DEBUG:
from django.contrib.admin.validation import validate
admin_class = admin_class or ModelAdmin
# TODO: Handle options
else:
validate = lambda model, adminclass: None
if not admin_class:
admin_class = ModelAdmin
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
for model in model_or_iterable:
if model in self._registry:
raise AlreadyRegistered('The model %s is already registered' % model.__name__)
if do_validate:
validate(admin_class, model)
# If we got **options then dynamically construct a subclass of
# admin_class with those **options.
if options:
# For reasons I don't quite understand, without a __module__
# the created class appears to "live" in the wrong place,
# which causes issues later on.
options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
# Validate (which might be a no-op)
validate(admin_class, model)
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
def unregister(self, model_or_iterable):
@ -102,23 +114,23 @@ class AdminSite(object):
return request.user.is_authenticated() and request.user.is_staff
def root(self, request, url):
"""
"""
Handles main URL routing for the admin app.
`url` is the remainder of the URL -- e.g. 'comments/comment/'.
"""
if request.method == 'GET' and not request.path.endswith('/'):
return http.HttpResponseRedirect(request.path + '/')
# Figure out the admin base URL path and stash it for later use
self.root_path = re.sub(re.escape(url) + '$', '', request.path)
url = url.rstrip('/') # Trim trailing slash, if it exists.
# The 'logout' view doesn't require that the person is logged in.
if url == 'logout':
return self.logout(request)
# Check permission to continue or display login form.
if not self.has_permission(request):
return self.login(request)
@ -139,7 +151,7 @@ class AdminSite(object):
match = USER_CHANGE_PASSWORD_URL_RE.match(url)
if match:
return self.user_change_password(request, match.group(1))
if '/' in url:
return self.model_page(request, *url.split('/', 2))
@ -189,7 +201,6 @@ class AdminSite(object):
This takes into account the USE_I18N setting. If it's set to False, the
generated JavaScript will be leaner and faster.
"""
from django.conf import settings
if settings.USE_I18N:
from django.views.i18n import javascript_catalog
else:
@ -249,9 +260,6 @@ class AdminSite(object):
else:
if user.is_active and user.is_staff:
login(request, user)
# TODO: set last_login with an event.
user.last_login = datetime.datetime.now()
user.save()
if request.POST.has_key('post_data'):
post_data = _decode_post_data(request.POST['post_data'])
if post_data and not post_data.has_key(LOGIN_FORM_KEY):
@ -308,14 +316,14 @@ class AdminSite(object):
# Sort the models alphabetically within each app.
for app in app_list:
app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
context = {
'title': _('Site administration'),
'app_list': app_list,
'root_path': self.root_path,
}
context.update(extra_context or {})
return render_to_response(self.index_template or 'admin/index.html', context,
return render_to_response(self.index_template or 'admin/index.html', context,
context_instance=template.RequestContext(request)
)
index = never_cache(index)
@ -330,7 +338,7 @@ class AdminSite(object):
post_data = _encode_post_data(request.POST)
else:
post_data = _encode_post_data({})
context = {
'title': _('Log in'),
'app_path': request.path,

View File

@ -1,6 +1,6 @@
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
{% if fieldset.description %}<div class="description">{{ fieldset.description }}</div>{% endif %}
{% if fieldset.description %}<div class="description">{{ fieldset.description|safe }}</div>{% endif %}
{% for line in fieldset %}
<div class="form-row{% if line.errors %} errors{% endif %} {% for field in line %}{{ field.field.name }} {% endfor %} ">
{{ line.errors }}
@ -14,4 +14,4 @@
{% endfor %}
</div>
{% endfor %}
</fieldset>
</fieldset>

View File

@ -34,7 +34,7 @@
<tr>
<td>{{ field.name }}</td>
<td>{{ field.data_type }}</td>
<td>{% if field.verbose %}{{ field.verbose }}{% endif %}{% if field.help_text %} - {{ field.help_text }}{% endif %}</td>
<td>{% if field.verbose %}{{ field.verbose }}{% endif %}{% if field.help_text %} - {{ field.help_text|safe }}{% endif %}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -0,0 +1,16 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
{% block title %}{% trans 'Password reset complete' %}{% endblock %}
{% block content %}
<h1>{% trans 'Password reset complete' %}</h1>
<p>{% trans "Your password has been set. You may go ahead and log in now." %}</p>
<p><a href="{{ login_url }}">{% trans 'Log in' %}</a></p>
{% endblock %}

View File

@ -0,0 +1,32 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset confirmation' %}</div>{% endblock %}
{% block title %}{% trans 'Password reset' %}{% endblock %}
{% block content %}
{% if validlink %}
<h1>{% trans 'Enter new password' %}</h1>
<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
<form action="" method="post">
{% if form.new_password1.errors %}{{ form.new_password1.errors }}{% endif %}
<p class="aligned wide"><label for="id_new_password1">{% trans 'New password:' %}</label>{{ form.new_password1 }}</p>
{% if form.new_password2.errors %}{{ form.new_password2.errors }}{% endif %}
<p class="aligned wide"><label for="id_new_password2">{% trans 'Confirm password:' %}</label>{{ form.new_password2 }}</p>
<p><input type="submit" value="{% trans 'Change my password' %}" /></p>
</form>
{% else %}
<h1>{% trans 'Password reset unsuccessful' %}</h1>
<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}
{% endif %}
{% endblock %}

View File

@ -9,6 +9,6 @@
<h1>{% trans 'Password reset successful' %}</h1>
<p>{% trans "We've e-mailed a new password to the e-mail address you submitted. You should be receiving it shortly." %}</p>
<p>{% trans "We've e-mailed you instructions for setting your password to the e-mail address you submitted. You should be receiving it shortly." %}</p>
{% endblock %}

View File

@ -1,15 +1,15 @@
{% load i18n %}
{% load i18n %}{% autoescape off %}
{% trans "You're receiving this e-mail because you requested a password reset" %}
{% blocktrans %}for your user account at {{ site_name }}{% endblocktrans %}.
{% blocktrans %}Your new password is: {{ new_password }}{% endblocktrans %}
{% trans "Feel free to change this password by going to this page:" %}
http://{{ domain }}/password_change/
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}/reset/{{ uid }}-{{ token }}/
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
{% trans "Thanks for using our site!" %}
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
{% endautoescape %}

View File

@ -9,7 +9,7 @@
<h1>{% trans "Password reset" %}</h1>
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." %}</p>
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}</p>
<form action="" method="post">
{% if form.email.errors %}{{ form.email.errors }}{% endif %}

View File

@ -1,3 +1,7 @@
try:
set
except NameError:
from sets import Set as set # Python 2.3 fallback
from django.core.exceptions import ImproperlyConfigured
from django.db import models
@ -165,6 +169,8 @@ def _validate_base(cls, model):
_check_form_field_existsw('fields', field)
if cls.fieldsets:
raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
if len(cls.fields) > len(set(cls.fields)):
raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__)
# fieldsets
if cls.fieldsets: # default value is None
@ -179,7 +185,10 @@ def _validate_base(cls, model):
raise ImproperlyConfigured("`fields` key is required in "
"%s.fieldsets[%d][1] field options dict."
% (cls.__name__, idx))
for field in flatten_fieldsets(cls.fieldsets):
flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
for field in flattened_fieldsets:
_check_form_field_existsw("fieldsets[%d][1]['fields']" % idx, field)
# form

View File

@ -1,5 +1,4 @@
import base64
import md5
import cPickle as pickle
try:
from functools import wraps
@ -12,6 +11,7 @@ from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login
from django.shortcuts import render_to_response
from django.utils.translation import ugettext_lazy, ugettext as _
from django.utils.hashcompat import md5_constructor
ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
LOGIN_FORM_KEY = 'this_is_the_login_form'
@ -35,13 +35,13 @@ def _display_login_form(request, error_message=''):
def _encode_post_data(post_data):
pickled = pickle.dumps(post_data)
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest()
return base64.encodestring(pickled + pickled_md5)
def _decode_post_data(encoded_data):
encoded_data = base64.decodestring(encoded_data)
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
from django.core.exceptions import SuspiciousOperation
raise SuspiciousOperation, "User may have tampered with session cookie."
return pickle.loads(pickled)
@ -87,7 +87,7 @@ def staff_member_required(view_func):
if len(users) == 1:
message = _("Your e-mail address is not your username. Try '%s' instead.") % users[0].username
else:
# Either we cannot find the user, or if more than 1
# Either we cannot find the user, or if more than 1
# we cannot guess which user is the correct one.
message = _("Usernames cannot contain the '@' character.")
return _display_login_form(request, message)

View File

@ -6,7 +6,6 @@ from django.db import models
from django.db.models.query import QuerySet
from django.utils.encoding import force_unicode, smart_str
from django.utils.translation import ugettext
from django.utils.safestring import mark_safe
from django.utils.http import urlencode
import operator

View File

@ -7,8 +7,7 @@ import copy
from django import forms
from django.forms.widgets import RadioFieldRenderer
from django.forms.util import flatatt
from django.utils.datastructures import MultiValueDict
from django.utils.text import capfirst, truncate_words
from django.utils.text import truncate_words
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode

View File

@ -5,7 +5,7 @@ from django.contrib.admin.views.decorators import staff_member_required
from django.db import models
from django.shortcuts import render_to_response
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.http import Http404, get_host
from django.http import Http404
from django.core import urlresolvers
from django.contrib.admindocs import utils
from django.contrib.sites.models import Site

View File

@ -1,6 +1,6 @@
from django.contrib.auth.models import User, Group
from django.core.exceptions import PermissionDenied
from django import oldforms, template
from django import template
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext, ugettext_lazy as _

View File

@ -1,7 +1,7 @@
try:
from functools import wraps, update_wrapper
from functools import update_wrapper
except ImportError:
from django.utils.functional import wraps, update_wrapper # Python 2.3, 2.4 fallback.
from django.utils.functional import update_wrapper # Python 2.3, 2.4 fallback.
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponseRedirect

View File

@ -1,10 +1,11 @@
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.models import Site
from django.template import Context, loader
from django.core import validators
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.http import int_to_base36
class UserCreationForm(forms.ModelForm):
"""
@ -13,13 +14,13 @@ class UserCreationForm(forms.ModelForm):
username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^\w+$',
help_text = _("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."),
error_message = _("This value must contain only letters, numbers and underscores."))
password1 = forms.CharField(label=_("Password"), max_length=60, widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"), max_length=60, widget=forms.PasswordInput)
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput)
class Meta:
model = User
fields = ("username",)
def clean_username(self):
username = self.cleaned_data["username"]
try:
@ -27,14 +28,14 @@ class UserCreationForm(forms.ModelForm):
except User.DoesNotExist:
return username
raise forms.ValidationError(_("A user with that username already exists."))
def clean_password2(self):
password1 = self.cleaned_data["password1"]
password2 = self.cleaned_data["password2"]
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't match."))
return password2
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
@ -48,8 +49,8 @@ class AuthenticationForm(forms.Form):
username/password logins.
"""
username = forms.CharField(label=_("Username"), max_length=30)
password = forms.CharField(label=_("Password"), max_length=30, widget=forms.PasswordInput)
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
def __init__(self, request=None, *args, **kwargs):
"""
If request is passed in, the form will validate that cookies are
@ -60,36 +61,36 @@ class AuthenticationForm(forms.Form):
self.request = request
self.user_cache = None
super(AuthenticationForm, self).__init__(*args, **kwargs)
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username and password:
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive."))
elif not self.user_cache.is_active:
raise forms.ValidationError(_("This account is inactive."))
# TODO: determine whether this should move to its own method.
if self.request:
if not self.request.session.test_cookie_worked():
raise forms.ValidationError(_("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in."))
return self.cleaned_data
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache
class PasswordResetForm(forms.Form):
email = forms.EmailField(label=_("E-mail"), max_length=40)
email = forms.EmailField(label=_("E-mail"), max_length=75)
def clean_email(self):
"""
Validates that a user exists with the given e-mail address.
@ -98,16 +99,14 @@ class PasswordResetForm(forms.Form):
self.users_cache = User.objects.filter(email__iexact=email)
if len(self.users_cache) == 0:
raise forms.ValidationError(_("That e-mail address doesn't have an associated user account. Are you sure you've registered?"))
def save(self, domain_override=None, email_template_name='registration/password_reset_email.html'):
def save(self, domain_override=None, email_template_name='registration/password_reset_email.html',
use_https=False, token_generator=default_token_generator):
"""
Calculates a new password randomly and sends it to the user.
Generates a one-use only link for resetting password and sends to the user
"""
from django.core.mail import send_mail
for user in self.users_cache:
new_pass = User.objects.make_random_password()
user.set_password(new_pass)
user.save()
if not domain_override:
current_site = Site.objects.get_current()
site_name = current_site.name
@ -116,27 +115,50 @@ class PasswordResetForm(forms.Form):
site_name = domain = domain_override
t = loader.get_template(email_template_name)
c = {
'new_password': new_pass,
'email': user.email,
'domain': domain,
'site_name': site_name,
'uid': int_to_base36(user.id),
'user': user,
'token': token_generator.make_token(user),
'protocol': use_https and 'https' or 'http',
}
send_mail(_("Password reset on %s") % site_name,
t.render(Context(c)), None, [user.email])
class PasswordChangeForm(forms.Form):
class SetPasswordForm(forms.Form):
"""
A form that lets a user change his/her password.
A form that lets a user change set his/her password without
entering the old password
"""
old_password = forms.CharField(label=_("Old password"), max_length=30, widget=forms.PasswordInput)
new_password1 = forms.CharField(label=_("New password"), max_length=30, widget=forms.PasswordInput)
new_password2 = forms.CharField(label=_("New password confirmation"), max_length=30, widget=forms.PasswordInput)
new_password1 = forms.CharField(label=_("New password"), widget=forms.PasswordInput)
new_password2 = forms.CharField(label=_("New password confirmation"), widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
self.user = user
super(PasswordChangeForm, self).__init__(*args, **kwargs)
super(SetPasswordForm, self).__init__(*args, **kwargs)
def clean_new_password2(self):
password1 = self.cleaned_data.get('new_password1')
password2 = self.cleaned_data.get('new_password2')
if password1 and password2:
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't match."))
return password2
def save(self, commit=True):
self.user.set_password(self.cleaned_data['new_password1'])
if commit:
self.user.save()
return self.user
class PasswordChangeForm(SetPasswordForm):
"""
A form that lets a user change his/her password by entering
their old password.
"""
old_password = forms.CharField(label=_("Old password"), widget=forms.PasswordInput)
def clean_old_password(self):
"""
Validates that the old_password field is correct.
@ -145,32 +167,19 @@ class PasswordChangeForm(forms.Form):
if not self.user.check_password(old_password):
raise forms.ValidationError(_("Your old password was entered incorrectly. Please enter it again."))
return old_password
def clean_new_password2(self):
password1 = self.cleaned_data.get('new_password1')
password2 = self.cleaned_data.get('new_password2')
if password1 and password2:
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't match."))
return password2
def save(self, commit=True):
self.user.set_password(self.cleaned_data['new_password1'])
if commit:
self.user.save()
return self.user
PasswordChangeForm.base_fields.keyOrder = ['old_password', 'new_password1', 'new_password2']
class AdminPasswordChangeForm(forms.Form):
"""
A form used to change the password of a user in the admin interface.
"""
password1 = forms.CharField(label=_("Password"), max_length=60, widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password (again)"), max_length=60, widget=forms.PasswordInput)
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password (again)"), widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
self.user = user
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
def clean_password2(self):
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
@ -178,7 +187,7 @@ class AdminPasswordChangeForm(forms.Form):
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't match."))
return password2
def save(self, commit=True):
"""
Saves the new password.

View File

@ -7,7 +7,7 @@ import os
import re
import sys
from optparse import make_option
from django.contrib.auth.models import User, UNUSABLE_PASSWORD
from django.contrib.auth.models import User
from django.core import validators
from django.core.management.base import BaseCommand, CommandError

View File

@ -358,6 +358,9 @@ class AnonymousUser(object):
def has_perm(self, perm):
return False
def has_perms(self, perm_list):
return False
def has_module_perms(self, module):
return False

View File

@ -1,8 +1,11 @@
from django.contrib.auth.tests.basic import BASIC_TESTS, PasswordResetTest
from django.contrib.auth.tests.basic import BASIC_TESTS
from django.contrib.auth.tests.views import PasswordResetTest
from django.contrib.auth.tests.forms import FORM_TESTS
from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS
__test__ = {
'BASIC_TESTS': BASIC_TESTS,
'PASSWORDRESET_TESTS': PasswordResetTest,
'FORM_TESTS': FORM_TESTS,
'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS
}

View File

@ -54,24 +54,3 @@ u'joe@somewhere.org'
>>> u.password
u'!'
"""
from django.test import TestCase
from django.core import mail
class PasswordResetTest(TestCase):
fixtures = ['authtestdata.json']
urls = 'django.contrib.auth.urls'
def test_email_not_found(self):
"Error is raised if the provided email address isn't currently registered"
response = self.client.get('/password_reset/')
self.assertEquals(response.status_code, 200)
response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'})
self.assertContains(response, "That e-mail address doesn't have an associated user account")
self.assertEquals(len(mail.outbox), 0)
def test_email_found(self):
"Email is sent if a valid email address is provided for password reset"
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
self.assertEquals(response.status_code, 302)
self.assertEquals(len(mail.outbox), 1)

View File

@ -2,7 +2,7 @@
FORM_TESTS = """
>>> from django.contrib.auth.models import User
>>> from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
>>> from django.contrib.auth.forms import PasswordChangeForm
>>> from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm
The user already exists.
@ -95,6 +95,32 @@ True
>>> form.non_field_errors()
[]
SetPasswordForm:
The two new passwords do not match.
>>> data = {
... 'new_password1': 'abc123',
... 'new_password2': 'abc',
... }
>>> form = SetPasswordForm(user, data)
>>> form.is_valid()
False
>>> form["new_password2"].errors
[u"The two password fields didn't match."]
The success case.
>>> data = {
... 'new_password1': 'abc123',
... 'new_password2': 'abc123',
... }
>>> form = SetPasswordForm(user, data)
>>> form.is_valid()
True
PasswordChangeForm:
The old password is incorrect.
>>> data = {
@ -132,4 +158,9 @@ The success case.
>>> form.is_valid()
True
Regression test - check the order of fields:
>>> PasswordChangeForm(user, {}).fields.keys()
['old_password', 'new_password1', 'new_password2']
"""

View File

@ -0,0 +1,29 @@
TOKEN_GENERATOR_TESTS = """
>>> from django.contrib.auth.models import User, AnonymousUser
>>> from django.contrib.auth.tokens import PasswordResetTokenGenerator
>>> from django.conf import settings
>>> u = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw')
>>> p0 = PasswordResetTokenGenerator()
>>> tk1 = p0.make_token(u)
>>> p0.check_token(u, tk1)
True
Tests to ensure we can use the token after n days, but no greater.
Use a mocked version of PasswordResetTokenGenerator so we can change
the value of 'today'
>>> class Mocked(PasswordResetTokenGenerator):
... def __init__(self, today):
... self._today_val = today
... def _today(self):
... return self._today_val
>>> from datetime import date, timedelta
>>> p1 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS))
>>> p1.check_token(u, tk1)
True
>>> p2 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS + 1))
>>> p2.check_token(u, tk1)
False
"""

View File

@ -0,0 +1,88 @@
import re
from django.contrib.auth.models import User
from django.test import TestCase
from django.core import mail
class PasswordResetTest(TestCase):
fixtures = ['authtestdata.json']
urls = 'django.contrib.auth.urls'
def test_email_not_found(self):
"Error is raised if the provided email address isn't currently registered"
response = self.client.get('/password_reset/')
self.assertEquals(response.status_code, 200)
response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'})
self.assertContains(response, "That e-mail address doesn't have an associated user account")
self.assertEquals(len(mail.outbox), 0)
def test_email_found(self):
"Email is sent if a valid email address is provided for password reset"
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
self.assertEquals(response.status_code, 302)
self.assertEquals(len(mail.outbox), 1)
self.assert_("http://" in mail.outbox[0].body)
def _test_confirm_start(self):
# Start by creating the email
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
self.assertEquals(response.status_code, 302)
self.assertEquals(len(mail.outbox), 1)
return self._read_signup_email(mail.outbox[0])
def _read_signup_email(self, email):
urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body)
self.assert_(urlmatch is not None, "No URL found in sent email")
return urlmatch.group(), urlmatch.groups()[0]
def test_confirm_valid(self):
url, path = self._test_confirm_start()
response = self.client.get(path)
# redirect to a 'complete' page:
self.assertEquals(response.status_code, 200)
self.assert_("Please enter your new password" in response.content)
def test_confirm_invalid(self):
url, path = self._test_confirm_start()
# Lets munge the token in the path, but keep the same length,
# in case the URL conf will reject a different length
path = path[:-5] + ("0"*4) + path[-1]
response = self.client.get(path)
self.assertEquals(response.status_code, 200)
self.assert_("The password reset link was invalid" in response.content)
def test_confirm_invalid_post(self):
# Same as test_confirm_invalid, but trying
# to do a POST instead.
url, path = self._test_confirm_start()
path = path[:-5] + ("0"*4) + path[-1]
response = self.client.post(path, {'new_password1': 'anewpassword',
'new_password2':' anewpassword'})
# Check the password has not been changed
u = User.objects.get(email='staffmember@example.com')
self.assert_(not u.check_password("anewpassword"))
def test_confirm_complete(self):
url, path = self._test_confirm_start()
response = self.client.post(path, {'new_password1': 'anewpassword',
'new_password2': 'anewpassword'})
# It redirects us to a 'complete' page:
self.assertEquals(response.status_code, 302)
# Check the password has been changed
u = User.objects.get(email='staffmember@example.com')
self.assert_(u.check_password("anewpassword"))
# Check we can't use the link again
response = self.client.get(path)
self.assertEquals(response.status_code, 200)
self.assert_("The password reset link was invalid" in response.content)
def test_confirm_different_passwords(self):
url, path = self._test_confirm_start()
response = self.client.post(path, {'new_password1': 'anewpassword',
'new_password2':' x'})
self.assertEquals(response.status_code, 200)
self.assert_("The two password fields didn't match" in response.content)

View File

@ -0,0 +1,66 @@
from datetime import date
from django.conf import settings
from django.utils.http import int_to_base36, base36_to_int
class PasswordResetTokenGenerator(object):
"""
Stratgy object used to generate and check tokens for the password
reset mechanism.
"""
def make_token(self, user):
"""
Returns a token that can be used once to do a password reset
for the given user.
"""
return self._make_token_with_timestamp(user, self._num_days(self._today()))
def check_token(self, user, token):
"""
Check that a password reset token is correct for a given user.
"""
# Parse the tokem
try:
ts_b36, hash = token.split("-")
except ValueError:
return False
try:
ts = base36_to_int(ts_b36)
except ValueError:
return False
# Check that the timestamp/uid has not been tampered with
if self._make_token_with_timestamp(user, ts) != token:
return False
# Check the timestamp is within limit
if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS:
return False
return True
def _make_token_with_timestamp(self, user, timestamp):
# timestamp is number of days since 2001-1-1. Converted to
# base 36, this gives us a 3 digit string until about 2121
ts_b36 = int_to_base36(timestamp)
# By hashing on the internal state of the user and using state
# that is sure to change (the password salt will change as soon as
# the password is set, at least for current Django auth, and
# last_login will also change), we produce a hash that will be
# invalid as soon as it is used.
# We limit the hash to 20 chars to keep URL short
from django.utils.hashcompat import sha_constructor
hash = sha_constructor(settings.SECRET_KEY + unicode(user.id) +
user.password + unicode(user.last_login) +
unicode(timestamp)).hexdigest()[::2]
return "%s-%s" % (ts_b36, hash)
def _num_days(self, dt):
return (dt - date(2001,1,1)).days
def _today(self):
# Used for mocking in tests
return date.today()
default_token_generator = PasswordResetTokenGenerator()

View File

@ -5,9 +5,12 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('',
('^logout/$', 'django.contrib.auth.views.logout'),
('^password_change/$', 'django.contrib.auth.views.password_change'),
('^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
('^password_reset/$', 'django.contrib.auth.views.password_reset')
(r'^logout/$', 'django.contrib.auth.views.logout'),
(r'^password_change/$', 'django.contrib.auth.views.password_change'),
(r'^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
(r'^password_reset/$', 'django.contrib.auth.views.password_reset'),
(r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done'),
(r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', 'django.contrib.auth.views.password_reset_confirm'),
(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete'),
)

View File

@ -1,13 +1,15 @@
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm, AdminPasswordChangeForm
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm, AdminPasswordChangeForm
from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import PermissionDenied
from django.shortcuts import render_to_response, get_object_or_404
from django.contrib.sites.models import Site, RequestSite
from django.http import HttpResponseRedirect
from django.http import HttpResponseRedirect, Http404
from django.template import RequestContext
from django.utils.http import urlquote
from django.utils.http import urlquote, base36_to_int
from django.utils.html import escape
from django.utils.translation import ugettext as _
from django.contrib.auth.models import User
@ -65,19 +67,29 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N
login_url = settings.LOGIN_URL
return HttpResponseRedirect('%s?%s=%s' % (login_url, urlquote(redirect_field_name), urlquote(next)))
# 4 views for password reset:
# - password_reset sends the mail
# - password_reset_done shows a success message for the above
# - password_reset_confirm checks the link the user clicked and
# prompts for a new password
# - password_reset_complete shows a success message for the above
def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html',
password_reset_form=PasswordResetForm):
password_reset_form=PasswordResetForm, token_generator=default_token_generator):
if request.method == "POST":
form = password_reset_form(request.POST)
if form.is_valid():
opts = {}
opts['use_https'] = request.is_secure()
opts['token_generator'] = token_generator
if is_admin_site:
form.save(domain_override=request.META['HTTP_HOST'])
opts['domain_override'] = request.META['HTTP_HOST']
else:
if Site._meta.installed:
form.save(email_template_name=email_template_name)
else:
form.save(domain_override=RequestSite(request).domain, email_template_name=email_template_name)
opts['email_template_name'] = email_template_name
if not Site._meta.installed:
opts['domain_override'] = RequestSite(request).domain
form.save(**opts)
return HttpResponseRedirect('%sdone/' % request.path)
else:
form = password_reset_form()
@ -88,6 +100,40 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas
def password_reset_done(request, template_name='registration/password_reset_done.html'):
return render_to_response(template_name, context_instance=RequestContext(request))
def password_reset_confirm(request, uidb36=None, token=None, template_name='registration/password_reset_confirm.html',
token_generator=default_token_generator, set_password_form=SetPasswordForm):
"""
View that checks the hash in a password reset link and presents a
form for entering a new password.
"""
assert uidb36 is not None and token is not None # checked by URLconf
try:
uid_int = base36_to_int(uidb36)
except ValueError:
raise Http404
user = get_object_or_404(User, id=uid_int)
context_instance = RequestContext(request)
if token_generator.check_token(user, token):
context_instance['validlink'] = True
if request.method == 'POST':
form = set_password_form(user, request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect("../done/")
else:
form = set_password_form(None)
else:
context_instance['validlink'] = False
form = None
context_instance['form'] = form
return render_to_response(template_name, context_instance=context_instance)
def password_reset_complete(request, template_name='registration/password_reset_complete.html'):
return render_to_response(template_name, context_instance=RequestContext(request,
{'login_url': settings.LOGIN_URL}))
def password_change(request, template_name='registration/password_change_form.html'):
if request.method == "POST":
form = PasswordChangeForm(request.user, request.POST)

View File

@ -29,8 +29,8 @@ class CommentManager(models.Manager):
'pa,ra') and target (something like 'lcom.eventtimes:5157'). Used to
validate that submitted form options have not been tampered-with.
"""
import md5
return md5.new(options + photo_options + rating_options + target + settings.SECRET_KEY).hexdigest()
from django.utils.hashcompat import md5_constructor
return md5_constructor(options + photo_options + rating_options + target + settings.SECRET_KEY).hexdigest()
def get_rating_options(self, rating_string):
"""

View File

@ -95,7 +95,7 @@ class GenericForeignKey(object):
setattr(instance, self.cache_attr, value)
class GenericRelation(RelatedField, Field):
"""Provides an accessor to generic related objects (i.e. comments)"""
"""Provides an accessor to generic related objects (e.g. comments)"""
def __init__(self, to, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
@ -104,6 +104,9 @@ class GenericRelation(RelatedField, Field):
limit_choices_to=kwargs.pop('limit_choices_to', None),
symmetrical=kwargs.pop('symmetrical', True))
# By its very nature, a GenericRelation doesn't create a table.
self.creates_table = False
# Override content-type/object-id field names on the related class
self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
self.content_type_field_name = kwargs.pop("content_type_field", "content_type")

View File

@ -2,44 +2,45 @@
Cross Site Request Forgery Middleware.
This module provides a middleware that implements protection
against request forgeries from other sites.
against request forgeries from other sites.
"""
from django.conf import settings
from django.http import HttpResponseForbidden
from django.utils.safestring import mark_safe
import md5
import re
import itertools
from django.conf import settings
from django.http import HttpResponseForbidden
from django.utils.hashcompat import md5_constructor
from django.utils.safestring import mark_safe
_ERROR_MSG = mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>')
_POST_FORM_RE = \
re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
_HTML_TYPES = ('text/html', 'application/xhtml+xml')
_HTML_TYPES = ('text/html', 'application/xhtml+xml')
def _make_token(session_id):
return md5.new(settings.SECRET_KEY + session_id).hexdigest()
return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
class CsrfMiddleware(object):
"""Django middleware that adds protection against Cross Site
Request Forgeries by adding hidden form fields to POST forms and
checking requests for the correct value.
In the list of middlewares, SessionMiddleware is required, and must come
after this middleware. CsrfMiddleWare must come after compression
Request Forgeries by adding hidden form fields to POST forms and
checking requests for the correct value.
In the list of middlewares, SessionMiddleware is required, and must come
after this middleware. CsrfMiddleWare must come after compression
middleware.
If a session ID cookie is present, it is hashed with the SECRET_KEY
setting to create an authentication token. This token is added to all
outgoing POST forms and is expected on all incoming POST requests that
If a session ID cookie is present, it is hashed with the SECRET_KEY
setting to create an authentication token. This token is added to all
outgoing POST forms and is expected on all incoming POST requests that
have a session ID cookie.
If you are setting cookies directly, instead of using Django's session
If you are setting cookies directly, instead of using Django's session
framework, this middleware will not work.
"""
def process_request(self, request):
if request.method == 'POST':
try:
@ -54,10 +55,10 @@ class CsrfMiddleware(object):
request_csrf_token = request.POST['csrfmiddlewaretoken']
except KeyError:
return HttpResponseForbidden(_ERROR_MSG)
if request_csrf_token != csrf_token:
return HttpResponseForbidden(_ERROR_MSG)
return None
def process_response(self, request, response):
@ -66,7 +67,7 @@ class CsrfMiddleware(object):
cookie = response.cookies[settings.SESSION_COOKIE_NAME]
csrf_token = _make_token(cookie.value)
except KeyError:
# No outgoing cookie to set session, but
# No outgoing cookie to set session, but
# a session might already exist.
try:
session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
@ -74,12 +75,12 @@ class CsrfMiddleware(object):
except KeyError:
# no incoming or outgoing cookie
pass
if csrf_token is not None and \
response['Content-Type'].split(';')[0] in _HTML_TYPES:
# ensure we don't add the 'id' attribute twice (HTML validity)
idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
itertools.repeat(''))
def add_csrf_field(match):
"""Returns the matched <form> tag plus the added <input> element"""

View File

@ -4,7 +4,6 @@ from django.contrib.databrowse.datastructures import EasyModel
from django.contrib.databrowse.sites import DatabrowsePlugin
from django.shortcuts import render_to_response
from django.utils.text import capfirst
from django.utils.translation import get_date_formats
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
from django.views.generic import date_based

View File

@ -6,7 +6,6 @@ from django.shortcuts import render_to_response
from django.utils.text import capfirst
from django.utils.encoding import smart_str, force_unicode
from django.utils.safestring import mark_safe
from django.views.generic import date_based
import urllib
class FieldChoicePlugin(DatabrowsePlugin):

View File

@ -1,6 +1,6 @@
from django import http
from django.db import models
from django.contrib.databrowse.datastructures import EasyModel, EasyChoice
from django.contrib.databrowse.datastructures import EasyModel
from django.shortcuts import render_to_response
from django.utils.safestring import mark_safe

View File

@ -1,7 +1,5 @@
from django.db.models import FieldDoesNotExist, DateTimeField
from django.http import Http404
from django.shortcuts import render_to_response
from django.contrib.databrowse.datastructures import EasyModel, EasyChoice
###########
# CHOICES #

View File

@ -2,12 +2,13 @@
Formtools Preview application.
"""
import cPickle as pickle
from django.conf import settings
from django.http import Http404
from django.shortcuts import render_to_response
from django.template.context import RequestContext
import cPickle as pickle
import md5
from django.utils.hashcompat import md5_constructor
AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter.
@ -109,7 +110,7 @@ class FormPreview(object):
# Use HIGHEST_PROTOCOL because it's the most efficient. It requires
# Python 2.3, but Django requires 2.3 anyway, so that's OK.
pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
return md5.new(pickled).hexdigest()
return md5_constructor(pickled).hexdigest()
def failed_hash(self, request):
"Returns an HttpResponse in the case of an invalid security hash."

View File

@ -1,7 +1,6 @@
from django import forms
from django.contrib.formtools import preview
from django import http
from django.conf import settings
from django.test import TestCase
success_string = "Done was called!"

View File

@ -4,13 +4,14 @@ step and storing the form's state as HTML hidden fields so that no state is
stored on the server side.
"""
import cPickle as pickle
from django import forms
from django.conf import settings
from django.http import Http404
from django.shortcuts import render_to_response
from django.template.context import RequestContext
import cPickle as pickle
import md5
from django.utils.hashcompat import md5_constructor
class FormWizard(object):
# Dictionary of extra template context variables.
@ -150,7 +151,7 @@ class FormWizard(object):
# Use HIGHEST_PROTOCOL because it's the most efficient. It requires
# Python 2.3, but Django requires 2.3 anyway, so that's OK.
pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
return md5.new(pickled).hexdigest()
return md5_constructor(pickled).hexdigest()
def determine_step(self, request, *args, **kwargs):
"""

View File

@ -70,7 +70,7 @@ class OracleSpatialField(Field):
style.SQL_TABLE('MDSYS.SPATIAL_INDEX') + ';'
return sql
def _post_create_sql(self, style, db_table):
def post_create_sql(self, style, db_table):
"""
Returns SQL that will be executed after the model has been
created.

View File

@ -50,7 +50,7 @@ class PostGISField(Field):
style.SQL_KEYWORD(index_opts) + ' );'
return sql
def _post_create_sql(self, style, db_table):
def post_create_sql(self, style, db_table):
"""
Returns SQL that will be executed after the model has been
created. Geometry columns must be added after creation with the

View File

@ -4,6 +4,11 @@ from django.contrib.gis.db.models.query import GeoQuerySet
class GeoManager(Manager):
"Overrides Manager to return Geographic QuerySets."
# This manager should be used for queries on related fields
# so that geometry columns on Oracle and MySQL are selected
# properly.
use_for_related_fields = True
def get_query_set(self):
return GeoQuerySet(model=self.model)

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*
from django.utils.translation import ugettext_lazy as _
STATE_CHOICES = (
('BL', _('Burgenland')),
('KA', _('Carinthia')),
('NO', _('Lower Austria')),
('OO', _('Upper Austria')),
('SA', _('Salzburg')),
('ST', _('Styria')),
('TI', _('Tyrol')),
('VO', _('Vorarlberg')),
('WI', _('Vienna')),
)

View File

@ -0,0 +1,65 @@
"""
AT-specific Form helpers
"""
import re
from django.utils.translation import ugettext_lazy as _
from django.forms.fields import Field, RegexField, Select
from django.forms import ValidationError
re_ssn = re.compile(r'^\d{4} \d{6}')
class ATZipCodeField(RegexField):
"""
A form field that validates its input is an Austrian postcode.
Accepts 4 digits.
"""
default_error_messages = {
'invalid': _('Enter a zip code in the format XXXX.'),
}
def __init__(self, *args, **kwargs):
super(ATZipCodeField, self).__init__(r'^\d{4}$',
max_length=None, min_length=None, *args, **kwargs)
class ATStateSelect(Select):
"""
A Select widget that uses a list of AT states as its choices.
"""
def __init__(self, attrs=None):
from django.contrib.localflavor.at.at_states import STATE_CHOICES
super(ATStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
class ATSocialSecurityNumberField(Field):
"""
Austrian Social Security numbers are composed of a 4 digits and 6 digits
field. The latter represents in most cases the person's birthdate while
the first 4 digits represent a 3-digits counter and a one-digit checksum.
The 6-digits field can also differ from the person's birthdate if the
3-digits counter suffered an overflow.
This code is based on information available on
http://de.wikipedia.org/wiki/Sozialversicherungsnummer#.C3.96sterreich
"""
default_error_messages = {
'invalid': _(u'Enter a valid Austrian Social Security Number in XXXX XXXXXX format.'),
}
def clean(self, value):
if not re_ssn.search(value):
raise ValidationError(self.error_messages['invalid'])
sqnr, date = value.split(" ")
sqnr, check = (sqnr[:3], (sqnr[3]))
if int(sqnr) < 100:
raise ValidationError(self.error_messages['invalid'])
res = int(sqnr[0])*3 + int(sqnr[1])*7 + int(sqnr[2])*9 \
+ int(date[0])*5 + int(date[1])*8 + int(date[2])*4 \
+ int(date[3])*2 + int(date[4])*1 + int(date[5])*6
res = res % 11
if res != int(check):
raise ValidationError(self.error_messages['invalid'])
return u'%s%s %s'%(sqnr, check, date,)

View File

@ -0,0 +1,200 @@
# -*- coding: utf-8 -*-
"""
Romanian specific form helpers.
"""
import re
from django.forms import ValidationError, Field, RegexField, Select
from django.forms.fields import EMPTY_VALUES
from django.utils.translation import ugettext_lazy as _
class ROCIFField(RegexField):
"""
A Romanian fiscal identity code (CIF) field
For CIF validation algorithm see http://www.validari.ro/cui.html
"""
default_error_messages = {
'invalid': _("Enter a valid CIF."),
}
def __init__(self, *args, **kwargs):
super(ROCIFField, self).__init__(r'^[0-9]{2,10}', max_length=10,
min_length=2, *args, **kwargs)
def clean(self, value):
"""
CIF validation
"""
value = super(ROCIFField, self).clean(value)
if value in EMPTY_VALUES:
return u''
# strip RO part
if value[0:2] == 'RO':
value = value[2:]
key = '753217532'[::-1]
value = value[::-1]
key_iter = iter(key)
checksum = 0
for digit in value[1:]:
checksum += int(digit) * int(key_iter.next())
checksum = checksum * 10 % 11
if checksum == 10:
checksum = 0
if checksum != int(value[0]):
raise ValidationError(self.error_messages['invalid'])
return value[::-1]
class ROCNPField(RegexField):
"""
A Romanian personal identity code (CNP) field
For CNP validation algorithm see http://www.validari.ro/cnp.html
"""
default_error_messages = {
'invalid': _("Enter a valid CNP."),
}
def __init__(self, *args, **kwargs):
super(ROCNPField, self).__init__(r'^[1-9][0-9]{12}', max_length=13,
min_length=13, *args, **kwargs)
def clean(self, value):
"""
CNP validations
"""
value = super(ROCNPField, self).clean(value)
# check birthdate digits
import datetime
try:
datetime.date(int(value[1:3]),int(value[3:5]),int(value[5:7]))
except:
raise ValidationError(self.error_messages['invalid'])
# checksum
key = '279146358279'
checksum = 0
value_iter = iter(value)
for digit in key:
checksum += int(digit) * int(value_iter.next())
checksum %= 11
if checksum == 10:
checksum = 1
if checksum != int(value[12]):
raise ValidationError(self.error_messages['invalid'])
return value
class ROCountyField(Field):
"""
A form field that validates its input is a Romanian county name or
abbreviation. It normalizes the input to the standard vehicle registration
abbreviation for the given county
WARNING: This field will only accept names written with diacritics; consider
using ROCountySelect if this behavior is unnaceptable for you
Example:
Argeş => valid
Arges => invalid
"""
default_error_messages = {
'invalid': u'Enter a Romanian county code or name.',
}
def clean(self, value):
from ro_counties import COUNTIES_CHOICES
super(ROCountyField, self).clean(value)
if value in EMPTY_VALUES:
return u''
try:
value = value.strip().upper()
except AttributeError:
pass
# search for county code
for entry in COUNTIES_CHOICES:
if value in entry:
return value
# search for county name
normalized_CC = []
for entry in COUNTIES_CHOICES:
normalized_CC.append((entry[0],entry[1].upper()))
for entry in normalized_CC:
if entry[1] == value:
return entry[0]
raise ValidationError(self.error_messages['invalid'])
class ROCountySelect(Select):
"""
A Select widget that uses a list of Romanian counties (judete) as its
choices.
"""
def __init__(self, attrs=None):
from ro_counties import COUNTIES_CHOICES
super(ROCountySelect, self).__init__(attrs, choices=COUNTIES_CHOICES)
class ROIBANField(RegexField):
"""
Romanian International Bank Account Number (IBAN) field
For Romanian IBAN validation algorithm see http://validari.ro/iban.html
"""
default_error_messages = {
'invalid': _('Enter a valid IBAN in ROXX-XXXX-XXXX-XXXX-XXXX-XXXX format'),
}
def __init__(self, *args, **kwargs):
super(ROIBANField, self).__init__(r'^[0-9A-Za-z\-\s]{24,40}$',
max_length=40, min_length=24, *args, **kwargs)
def clean(self, value):
"""
Strips - and spaces, performs country code and checksum validation
"""
value = super(ROIBANField, self).clean(value)
value = value.replace('-','')
value = value.replace(' ','')
value = value.upper()
if value[0:2] != 'RO':
raise ValidationError(self.error_messages['invalid'])
numeric_format = ''
for char in value[4:] + value[0:4]:
if char.isalpha():
numeric_format += str(ord(char) - 55)
else:
numeric_format += char
if int(numeric_format) % 97 != 1:
raise ValidationError(self.error_messages['invalid'])
return value
class ROPhoneNumberField(RegexField):
"""Romanian phone number field"""
default_error_messages = {
'invalid': _('Phone numbers must be in XXXX-XXXXXX format.'),
}
def __init__(self, *args, **kwargs):
super(ROPhoneNumberField, self).__init__(r'^[0-9\-\(\)\s]{10,20}$',
max_length=20, min_length=10, *args, **kwargs)
def clean(self, value):
"""
Strips -, (, ) and spaces. Checks the final length.
"""
value = super(ROPhoneNumberField, self).clean(value)
value = value.replace('-','')
value = value.replace('(','')
value = value.replace(')','')
value = value.replace(' ','')
if len(value) != 10:
raise ValidationError(self.error_messages['invalid'])
return value
class ROPostalCodeField(RegexField):
"""Romanian postal code field."""
default_error_messages = {
'invalid': _('Enter a valid postal code in the format XXXXXX'),
}
def __init__(self, *args, **kwargs):
super(ROPostalCodeField, self).__init__(r'^[0-9][0-8][0-9]{4}$',
max_length=6, min_length=6, *args, **kwargs)

View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
"""
A list of Romanian counties as `choices` in a formfield.
This exists as a standalone file so that it's only imported into memory when
explicitly needed.
"""
COUNTIES_CHOICES = (
('AB', u'Alba'),
('AR', u'Arad'),
('AG', u'Argeş'),
('BC', u'Bacău'),
('BH', u'Bihor'),
('BN', u'Bistriţa-Năsăud'),
('BT', u'Botoşani'),
('BV', u'Braşov'),
('BR', u'Brăila'),
('B', u'Bucureşti'),
('BZ', u'Buzău'),
('CS', u'Caraş-Severin'),
('CL', u'Călăraşi'),
('CJ', u'Cluj'),
('CT', u'Constanţa'),
('CV', u'Covasna'),
('DB', u'Dâmboviţa'),
('DJ', u'Dolj'),
('GL', u'Galaţi'),
('GR', u'Giurgiu'),
('GJ', u'Gorj'),
('HR', u'Harghita'),
('HD', u'Hunedoara'),
('IL', u'Ialomiţa'),
('IS', u'Iaşi'),
('IF', u'Ilfov'),
('MM', u'Maramureş'),
('MH', u'Mehedinţi'),
('MS', u'Mureş'),
('NT', u'Neamţ'),
('OT', u'Olt'),
('PH', u'Prahova'),
('SM', u'Satu Mare'),
('SJ', u'Sălaj'),
('SB', u'Sibiu'),
('SV', u'Suceava'),
('TR', u'Teleorman'),
('TM', u'Timiş'),
('TL', u'Tulcea'),
('VS', u'Vaslui'),
('VL', u'Vâlcea'),
('VN', u'Vrancea'),
)

View File

@ -0,0 +1,11 @@
from django.contrib import admin
from django.contrib.redirects.models import Redirect
class RedirectAdmin(admin.ModelAdmin):
list_display = ('old_path', 'new_path')
list_filter = ('site',)
search_fields = ('old_path', 'new_path')
radio_fields = {'site': admin.VERTICAL}
admin.site.register(Redirect, RedirectAdmin)

View File

@ -18,18 +18,3 @@ class Redirect(models.Model):
def __unicode__(self):
return "%s ---> %s" % (self.old_path, self.new_path)
# Register the admin options for these models.
# TODO: Maybe this should live in a separate module admin.py, but how would we
# ensure that module was loaded?
from django.contrib import admin
class RedirectAdmin(admin.ModelAdmin):
list_display = ('old_path', 'new_path')
list_filter = ('site',)
search_fields = ('old_path', 'new_path')
radio_fields = {'site': admin.VERTICAL}
admin.site.register(Redirect, RedirectAdmin)

View File

@ -1,5 +1,4 @@
import base64
import md5
import os
import random
import sys
@ -12,6 +11,7 @@ except ImportError:
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.utils.hashcompat import md5_constructor
class SessionBase(object):
@ -73,13 +73,13 @@ class SessionBase(object):
def encode(self, session_dict):
"Returns the given session dictionary pickled and encoded as a string."
pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest()
return base64.encodestring(pickled + pickled_md5)
def decode(self, session_data):
encoded_data = base64.decodestring(session_data)
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
raise SuspiciousOperation("User tampered with session cookie.")
try:
return pickle.loads(pickled)
@ -117,8 +117,8 @@ class SessionBase(object):
# No getpid() in Jython, for example
pid = 1
while 1:
session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1),
pid, time.time(), settings.SECRET_KEY)).hexdigest()
session_key = md5_constructor("%s%s%s%s" % (random.randint(0, sys.maxint - 1),
pid, time.time(), settings.SECRET_KEY)).hexdigest()
if not self.exists(session_key):
break
return session_key

View File

@ -1,8 +1,6 @@
from django.conf import settings
from django.contrib.sessions.backends.base import SessionBase
from django.core.cache import cache
class SessionStore(SessionBase):
"""
A cache-based session store.

View File

@ -1,11 +1,8 @@
import datetime
from django.conf import settings
from django.contrib.sessions.models import Session
from django.contrib.sessions.backends.base import SessionBase
from django.core.exceptions import SuspiciousOperation
class SessionStore(SessionBase):
"""
Implements database session store.

View File

@ -4,12 +4,7 @@ from django.conf import settings
from django.utils.cache import patch_vary_headers
from django.utils.http import cookie_date
TEST_COOKIE_NAME = 'testcookie'
TEST_COOKIE_VALUE = 'worked'
class SessionMiddleware(object):
def process_request(self, request):
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)

View File

@ -1,10 +1,10 @@
import base64
import md5
import cPickle as pickle
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.utils.hashcompat import md5_constructor
class SessionManager(models.Manager):
@ -13,7 +13,7 @@ class SessionManager(models.Manager):
Returns the given session dictionary pickled and encoded as a string.
"""
pickled = pickle.dumps(session_dict)
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest()
return base64.encodestring(pickled + pickled_md5)
def save(self, session_key, session_dict, expire_date):
@ -56,7 +56,7 @@ class Session(models.Model):
def get_decoded(self):
encoded_data = base64.decodestring(self.session_data)
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
from django.core.exceptions import SuspiciousOperation
raise SuspiciousOperation, "User tampered with session cookie."
try:

View File

@ -1,4 +1,4 @@
from django.core import urlresolvers
from django.core import urlresolvers, paginator
import urllib
PING_URL = "http://www.google.com/webmasters/tools/ping"
@ -34,6 +34,10 @@ def ping_google(sitemap_url=None, ping_url=PING_URL):
urllib.urlopen("%s?%s" % (ping_url, params))
class Sitemap:
# This limit is defined by Google. See the index documentation at
# http://sitemaps.org/protocol.php#index.
limit = 50000
def __get(self, name, obj, default=None):
try:
attr = getattr(self, name)
@ -49,11 +53,17 @@ class Sitemap:
def location(self, obj):
return obj.get_absolute_url()
def get_urls(self):
def _get_paginator(self):
if not hasattr(self, "paginator"):
self.paginator = paginator.Paginator(self.items(), self.limit)
return self.paginator
paginator = property(_get_paginator)
def get_urls(self, page=1):
from django.contrib.sites.models import Site
current_site = Site.objects.get_current()
urls = []
for item in self.items():
for item in self.paginator.page(page).object_list:
loc = "http://%s%s" % (current_site.domain, self.__get('location', item))
url_info = {
'location': loc,

View File

@ -3,14 +3,22 @@ from django.template import loader
from django.contrib.sites.models import Site
from django.core import urlresolvers
from django.utils.encoding import smart_str
from django.core.paginator import EmptyPage, PageNotAnInteger
def index(request, sitemaps):
current_site = Site.objects.get_current()
sites = []
protocol = request.is_secure() and 'https' or 'http'
for section in sitemaps.keys():
for section, site in sitemaps.items():
if callable(site):
pages = site().paginator.num_pages
else:
pages = site.paginator.num_pages
sitemap_url = urlresolvers.reverse('django.contrib.sitemaps.views.sitemap', kwargs={'section': section})
sites.append('%s://%s%s' % (protocol, current_site.domain, sitemap_url))
if pages > 1:
for page in range(2, pages+1):
sites.append('%s://%s%s?p=%s' % (protocol, current_site.domain, sitemap_url, page))
xml = loader.render_to_string('sitemap_index.xml', {'sitemaps': sites})
return HttpResponse(xml, mimetype='application/xml')
@ -22,10 +30,16 @@ def sitemap(request, sitemaps, section=None):
maps.append(sitemaps[section])
else:
maps = sitemaps.values()
page = request.GET.get("p", 1)
for site in maps:
if callable(site):
urls.extend(site().get_urls())
else:
urls.extend(site.get_urls())
try:
if callable(site):
urls.extend(site().get_urls(page))
else:
urls.extend(site.get_urls(page))
except EmptyPage:
raise Http404("Page %s empty" % page)
except PageNotAnInteger:
raise Http404("No page '%s'" % page)
xml = smart_str(loader.render_to_string('sitemap.xml', {'urlset': urls}))
return HttpResponse(xml, mimetype='application/xml')

View File

@ -1,6 +1,9 @@
"""
>>> # Make sure that get_current() does not return a deleted Site object.
>>> from django.contrib.sites.models import Site
>>> from django.conf import settings
>>> Site(id=settings.SITE_ID, domain="example.com", name="example.com").save()
>>> # Make sure that get_current() does not return a deleted Site object.
>>> s = Site.objects.get_current()
>>> isinstance(s, Site)
True

View File

@ -1,5 +1,5 @@
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.template import Context, loader, Template, TemplateDoesNotExist
from django.template import loader, Template, TemplateDoesNotExist
from django.contrib.sites.models import Site, RequestSite
from django.utils import feedgenerator
from django.utils.encoding import smart_unicode, iri_to_uri

View File

@ -19,8 +19,10 @@ from cgi import parse_qsl
from django.conf import settings
from django.core.cache.backends.base import InvalidCacheBackendError
# Name for use in settings file --> name of module in "backends" directory.
# Any backend scheme that is not in this dictionary is treated as a Python
# import path to a custom backend.
BACKENDS = {
# name for use in settings file --> name of module in "backends" directory
'memcached': 'memcached',
'locmem': 'locmem',
'file': 'filebased',
@ -28,24 +30,12 @@ BACKENDS = {
'dummy': 'dummy',
}
DEPRECATED_BACKENDS = {
# deprecated backend --> replacement module
'simple': 'locmem',
}
def get_cache(backend_uri):
if backend_uri.find(':') == -1:
raise InvalidCacheBackendError, "Backend URI must start with scheme://"
scheme, rest = backend_uri.split(':', 1)
if not rest.startswith('//'):
raise InvalidCacheBackendError, "Backend URI must start with scheme://"
if scheme in DEPRECATED_BACKENDS:
import warnings
warnings.warn("'%s' backend is deprecated. Use '%s' instead." %
(scheme, DEPRECATED_BACKENDS[scheme]), DeprecationWarning)
scheme = DEPRECATED_BACKENDS[scheme]
if scheme not in BACKENDS:
raise InvalidCacheBackendError, "%r is not a valid cache backend" % scheme
host = rest[2:]
qpos = rest.find('?')
@ -57,7 +47,10 @@ def get_cache(backend_uri):
if host.endswith('/'):
host = host[:-1]
cache_class = getattr(__import__('django.core.cache.backends.%s' % BACKENDS[scheme], {}, {}, ['']), 'CacheClass')
return cache_class(host, params)
if scheme in BACKENDS:
module = __import__('django.core.cache.backends.%s' % BACKENDS[scheme], {}, {}, [''])
else:
module = __import__(scheme, {}, {}, [''])
return getattr(module, 'CacheClass')(host, params)
cache = get_cache(settings.CACHE_BACKEND)

View File

@ -63,4 +63,11 @@ class BaseCache(object):
"""
return self.get(key) is not None
__contains__ = has_key
def __contains__(self, key):
"""
Returns True if the key is in the cache and has not expired.
"""
# This is a separate method, rather than just a copy of has_key(),
# so that it always has the same functionality as has_key(), even
# if a subclass overrides it.
return self.has_key(key)

View File

@ -1,29 +1,31 @@
"File-based cache backend"
import md5
import os, time
import os
import time
try:
import cPickle as pickle
except ImportError:
import pickle
from django.core.cache.backends.base import BaseCache
from django.utils.hashcompat import md5_constructor
class CacheClass(BaseCache):
def __init__(self, dir, params):
BaseCache.__init__(self, params)
max_entries = params.get('max_entries', 300)
try:
self._max_entries = int(max_entries)
except (ValueError, TypeError):
self._max_entries = 300
cull_frequency = params.get('cull_frequency', 3)
try:
self._cull_frequency = int(cull_frequency)
except (ValueError, TypeError):
self._cull_frequency = 3
self._dir = dir
if not os.path.exists(self._dir):
self._createdir()
@ -31,7 +33,7 @@ class CacheClass(BaseCache):
def add(self, key, value, timeout=None):
if self.has_key(key):
return None
self.set(key, value, timeout)
def get(self, key, default=None):
@ -52,12 +54,12 @@ class CacheClass(BaseCache):
def set(self, key, value, timeout=None):
fname = self._key_to_file(key)
dirname = os.path.dirname(fname)
if timeout is None:
timeout = self.default_timeout
self._cull()
try:
if not os.path.exists(dirname):
os.makedirs(dirname)
@ -103,12 +105,12 @@ class CacheClass(BaseCache):
def _cull(self):
if int(self._num_entries) < self._max_entries:
return
try:
filelist = os.listdir(self._dir)
except (IOError, OSError):
return
if self._cull_frequency == 0:
doomed = filelist
else:
@ -133,11 +135,11 @@ class CacheClass(BaseCache):
Convert the filename into an md5 string. We'll turn the first couple
bits of the path into directory prefixes to be nice to filesystems
that have problems with large numbers of files in a directory.
Thus, a cache key of "foo" gets turnned into a file named
``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
"""
path = md5.new(key.encode('utf-8')).hexdigest()
path = md5_constructor(key.encode('utf-8')).hexdigest()
path = os.path.join(path[:2], path[2:4], path[4:])
return os.path.join(self._dir, path)
@ -147,4 +149,3 @@ class CacheClass(BaseCache):
count += len(files)
return count
_num_entries = property(_get_num_entries)

View File

@ -107,7 +107,7 @@ class CacheClass(BaseCache):
else:
doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0]
for k in doomed:
self.delete(k)
self._delete(k)
def _delete(self, key):
try:

58
django/core/files/temp.py Normal file
View File

@ -0,0 +1,58 @@
"""
The temp module provides a NamedTemporaryFile that can be re-opened on any
platform. Most platforms use the standard Python tempfile.TemporaryFile class,
but MS Windows users are given a custom class.
This is needed because in Windows NT, the default implementation of
NamedTemporaryFile uses the O_TEMPORARY flag, and thus cannot be reopened [1].
1: http://mail.python.org/pipermail/python-list/2005-December/359474.html
"""
import os
import tempfile
__all__ = ('NamedTemporaryFile', 'gettempdir',)
if os.name == 'nt':
class TemporaryFile(object):
"""
Temporary file object constructor that works in Windows and supports
reopening of the temporary file in windows.
"""
def __init__(self, mode='w+b', bufsize=-1, suffix='', prefix='',
dir=None):
fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
dir=dir)
self.name = name
self._file = os.fdopen(fd, mode, bufsize)
def __del__(self):
try:
self._file.close()
except (OSError, IOError):
pass
try:
os.unlink(self.name)
except (OSError):
pass
try:
super(TemporaryFile, self).__del__()
except AttributeError:
pass
def read(self, *args): return self._file.read(*args)
def seek(self, offset): return self._file.seek(offset)
def write(self, s): return self._file.write(s)
def close(self): return self._file.close()
def __iter__(self): return iter(self._file)
def readlines(self, size=None): return self._file.readlines(size)
def xreadlines(self): return self._file.xreadlines()
NamedTemporaryFile = TemporaryFile
else:
NamedTemporaryFile = tempfile.NamedTemporaryFile
gettempdir = tempfile.gettempdir

View File

@ -3,7 +3,6 @@ Classes representing uploaded files.
"""
import os
import tempfile
import warnings
try:
from cStringIO import StringIO
@ -12,6 +11,8 @@ except ImportError:
from django.conf import settings
from django.core.files import temp as tempfile
__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile', 'SimpleUploadedFile')
# Because we fooled around with it a bunch, UploadedFile has a bunch

View File

@ -1,8 +1,7 @@
"""
Base file upload handler classes, and the built-in concrete subclasses
"""
import os
import tempfile
try:
from cStringIO import StringIO
except ImportError:

View File

@ -3,6 +3,7 @@ import sys
from django import http
from django.core import signals
from django.dispatch import dispatcher
from django.utils.encoding import force_unicode
class BaseHandler(object):
# Changes that are always applied to a response (in this order).
@ -73,7 +74,8 @@ class BaseHandler(object):
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
try:
callback, callback_args, callback_kwargs = resolver.resolve(request.path)
callback, callback_args, callback_kwargs = resolver.resolve(
request.path_info)
# Apply view middleware
for middleware_method in self._view_middleware:
@ -107,8 +109,11 @@ class BaseHandler(object):
from django.views import debug
return debug.technical_404_response(request, e)
else:
callback, param_dict = resolver.resolve404()
return callback(request, **param_dict)
try:
callback, param_dict = resolver.resolve404()
return callback(request, **param_dict)
except:
return self.handle_uncaught_exception(request, resolver, sys.exc_info())
except exceptions.PermissionDenied:
return http.HttpResponseForbidden('<h1>Permission denied</h1>')
except SystemExit:
@ -118,9 +123,6 @@ class BaseHandler(object):
# Get the exception info now, in case another exception is thrown later.
exc_info = sys.exc_info()
receivers = dispatcher.send(signal=signals.got_request_exception, request=request)
if settings.DEBUG_PROPAGATE_EXCEPTIONS:
raise
return self.handle_uncaught_exception(request, resolver, exc_info)
def handle_uncaught_exception(self, request, resolver, exc_info):
@ -136,6 +138,9 @@ class BaseHandler(object):
from django.conf import settings
from django.core.mail import mail_admins
if settings.DEBUG_PROPAGATE_EXCEPTIONS:
raise
if settings.DEBUG:
from django.views import debug
return debug.technical_500_response(request, *exc_info)
@ -167,3 +172,27 @@ class BaseHandler(object):
response = func(request, response)
return response
def get_script_name(environ):
"""
Returns the equivalent of the HTTP request's SCRIPT_NAME environment
variable. If Apache mod_rewrite has been used, returns what would have been
the script name prior to any rewriting (so it's the script name as seen
from the client's perspective), unless DJANGO_USE_POST_REWRITE is set (to
anything).
"""
from django.conf import settings
if settings.FORCE_SCRIPT_NAME is not None:
return force_unicode(settings.FORCE_SCRIPT_NAME)
# If Apache's mod_rewrite had a whack at the URL, Apache set either
# SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any
# rewrites. Unfortunately not every webserver (lighttpd!) passes this
# information through all the time, so FORCE_SCRIPT_NAME, above, is still
# needed.
script_url = environ.get('SCRIPT_URL', u'')
if not script_url:
script_url = environ.get('REDIRECT_URL', u'')
if script_url:
return force_unicode(script_url[:-len(environ.get('PATH_INFO', ''))])
return force_unicode(environ.get('SCRIPT_NAME', u''))

View File

@ -4,6 +4,7 @@ from pprint import pformat
from django import http
from django.core import signals
from django.core.handlers.base import BaseHandler
from django.core.urlresolvers import set_script_prefix
from django.dispatch import dispatcher
from django.utils import datastructures
from django.utils.encoding import force_unicode, smart_str
@ -15,7 +16,26 @@ from django.utils.encoding import force_unicode, smart_str
class ModPythonRequest(http.HttpRequest):
def __init__(self, req):
self._req = req
# FIXME: This isn't ideal. The request URI may be encoded (it's
# non-normalized) slightly differently to the "real" SCRIPT_NAME
# and PATH_INFO values. This causes problems when we compute path_info,
# below. For now, don't use script names that will be subject to
# encoding/decoding.
self.path = force_unicode(req.uri)
root = req.get_options().get('django.root', '')
self.django_root = root
# req.path_info isn't necessarily computed correctly in all
# circumstances (it's out of mod_python's control a bit), so we use
# req.uri and some string manipulations to get the right value.
if root and req.uri.startswith(root):
self.path_info = force_unicode(req.uri[len(root):])
else:
self.path_info = self.path
if not self.path_info:
# Django prefers empty paths to be '/', rather than '', to give us
# a common start character for URL patterns. So this is a little
# naughty, but also pretty harmless.
self.path_info = u'/'
def __repr__(self):
# Since this is called as part of error handling, we need to be very
@ -100,7 +120,7 @@ class ModPythonRequest(http.HttpRequest):
'CONTENT_LENGTH': self._req.clength, # This may be wrong
'CONTENT_TYPE': self._req.content_type, # This may be wrong
'GATEWAY_INTERFACE': 'CGI/1.1',
'PATH_INFO': self._req.path_info,
'PATH_INFO': self.path_info,
'PATH_TRANSLATED': None, # Not supported
'QUERY_STRING': self._req.args,
'REMOTE_ADDR': self._req.connection.remote_ip,
@ -108,7 +128,7 @@ class ModPythonRequest(http.HttpRequest):
'REMOTE_IDENT': self._req.connection.remote_logname,
'REMOTE_USER': self._req.user,
'REQUEST_METHOD': self._req.method,
'SCRIPT_NAME': None, # Not supported
'SCRIPT_NAME': self.django_root,
'SERVER_NAME': self._req.server.server_hostname,
'SERVER_PORT': self._req.server.port,
'SERVER_PROTOCOL': self._req.protocol,
@ -153,6 +173,7 @@ class ModPythonHandler(BaseHandler):
if self._request_middleware is None:
self.load_middleware()
set_script_prefix(req.get_options().get('django.root', ''))
dispatcher.send(signal=signals.request_started)
try:
try:

Some files were not shown because too many files have changed in this diff Show More