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:
parent
45b73c9a46
commit
aa239e3e54
13
AUTHORS
13
AUTHORS
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
VERSION = (0, 97, 'pre')
|
||||
VERSION = (1, 0, 'alpha')
|
||||
|
||||
def get_version():
|
||||
"Returns the version as a human-format string."
|
||||
|
@ -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 #
|
||||
###########
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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"
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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 "ბრაზილიური"
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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"
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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"
|
||||
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,16 @@
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
# Uncomment this for 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:
|
||||
# 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),
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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 }
|
||||
|
@ -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.
|
||||
@ -175,15 +197,6 @@ class BaseModelAdmin(object):
|
||||
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)
|
||||
|
||||
@ -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)
|
||||
@ -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,
|
||||
@ -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.
|
||||
@ -653,7 +665,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||
|
||||
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.
|
||||
|
@ -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)
|
||||
@ -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:
|
||||
|
||||
# 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):
|
||||
@ -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):
|
||||
|
@ -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 }}
|
||||
|
@ -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>
|
||||
|
@ -0,0 +1,16 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> › {% 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 %}
|
@ -0,0 +1,32 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> › {% 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 %}
|
@ -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 %}
|
||||
|
@ -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 %}
|
||||
|
@ -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 %}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 _
|
||||
|
@ -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
|
||||
|
@ -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,8 +14,8 @@ 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
|
||||
@ -48,7 +49,7 @@ 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):
|
||||
"""
|
||||
@ -88,7 +89,7 @@ class AuthenticationForm(forms.Form):
|
||||
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):
|
||||
"""
|
||||
@ -99,15 +100,13 @@ class PasswordResetForm(forms.Form):
|
||||
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,35 +115,28 @@ 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)
|
||||
|
||||
def clean_old_password(self):
|
||||
"""
|
||||
Validates that the old_password field is correct.
|
||||
"""
|
||||
old_password = self.cleaned_data["old_password"]
|
||||
if not self.user.check_password(old_password):
|
||||
raise forms.ValidationError(_("Your old password was entered incorrectly. Please enter it again."))
|
||||
return old_password
|
||||
super(SetPasswordForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_new_password2(self):
|
||||
password1 = self.cleaned_data.get('new_password1')
|
||||
@ -160,12 +152,29 @@ class PasswordChangeForm(forms.Form):
|
||||
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.
|
||||
"""
|
||||
old_password = self.cleaned_data["old_password"]
|
||||
if not self.user.check_password(old_password):
|
||||
raise forms.ValidationError(_("Your old password was entered incorrectly. Please enter it again."))
|
||||
return old_password
|
||||
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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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']
|
||||
|
||||
"""
|
||||
|
29
django/contrib/auth/tests/tokens.py
Normal file
29
django/contrib/auth/tests/tokens.py
Normal 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
|
||||
|
||||
"""
|
88
django/contrib/auth/tests/views.py
Normal file
88
django/contrib/auth/tests/views.py
Normal 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)
|
||||
|
66
django/contrib/auth/tokens.py
Normal file
66
django/contrib/auth/tokens.py
Normal 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()
|
@ -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'),
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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")
|
||||
|
@ -3,15 +3,16 @@ Cross Site Request Forgery Middleware.
|
||||
|
||||
This module provides a middleware that implements protection
|
||||
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 = \
|
||||
@ -20,7 +21,7 @@ _POST_FORM_RE = \
|
||||
_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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 #
|
||||
|
@ -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."
|
||||
|
@ -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!"
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
14
django/contrib/localflavor/at/at_states.py
Normal file
14
django/contrib/localflavor/at/at_states.py
Normal 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')),
|
||||
)
|
65
django/contrib/localflavor/at/forms.py
Normal file
65
django/contrib/localflavor/at/forms.py
Normal 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,)
|
||||
|
200
django/contrib/localflavor/ro/forms.py
Normal file
200
django/contrib/localflavor/ro/forms.py
Normal 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)
|
||||
|
52
django/contrib/localflavor/ro/ro_counties.py
Normal file
52
django/contrib/localflavor/ro/ro_counties.py
Normal 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'),
|
||||
)
|
11
django/contrib/redirects/admin.py
Normal file
11
django/contrib/redirects/admin.py
Normal 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)
|
@ -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)
|
||||
|
||||
|
@ -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,7 +117,7 @@ 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),
|
||||
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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
0
django/contrib/sitemaps/management/__init__.py
Normal file
0
django/contrib/sitemaps/management/__init__.py
Normal 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:
|
||||
try:
|
||||
if callable(site):
|
||||
urls.extend(site().get_urls())
|
||||
urls.extend(site().get_urls(page))
|
||||
else:
|
||||
urls.extend(site.get_urls())
|
||||
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')
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
23
django/core/cache/__init__.py
vendored
23
django/core/cache/__init__.py
vendored
@ -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)
|
||||
|
9
django/core/cache/backends/base.py
vendored
9
django/core/cache/backends/base.py
vendored
@ -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)
|
||||
|
9
django/core/cache/backends/filebased.py
vendored
9
django/core/cache/backends/filebased.py
vendored
@ -1,12 +1,14 @@
|
||||
"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):
|
||||
@ -137,7 +139,7 @@ class CacheClass(BaseCache):
|
||||
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)
|
||||
|
||||
|
2
django/core/cache/backends/locmem.py
vendored
2
django/core/cache/backends/locmem.py
vendored
@ -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
58
django/core/files/temp.py
Normal 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
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
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''))
|
||||
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user