diff --git a/AUTHORS b/AUTHORS index 45a0544e65..5fa957885c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,6 +29,8 @@ The PRIMARY AUTHORS are (and/or have been): * Julien Phalip * Aymeric Augustin * Claude Paroz + * Anssi Kääriäinen + * Florian Apolloner More information on the main contributors to Django can be found in docs/internals/committers.txt. @@ -50,6 +52,7 @@ answer newbie questions, and generally made Django that much better: Antoni Aloy Daniel Alves Barbosa de Oliveira Vaz AgarFu + James Aylett Dagur Páll Ammendrup Collin Anderson Jeff Anderson @@ -59,7 +62,6 @@ answer newbie questions, and generally made Django that much better: andy@jadedplanet.net Fabrice Aneche ant9000@netwise.it - Florian Apolloner arien David Ascher atlithorn @@ -84,6 +86,7 @@ answer newbie questions, and generally made Django that much better: Esdras Beleza Chris Bennett James Bennett + Danilo Bargen Shai Berger Julian Bez Arvis Bickovskis @@ -136,9 +139,10 @@ answer newbie questions, and generally made Django that much better: Robert Coup Pete Crosier Matt Croydon - Leah Culver - flavio.curella@gmail.com Jure Cuhalev + Leah Culver + Raúl Cumplido + flavio.curella@gmail.com John D'Agostino dackze+django@gmail.com Jim Dalton @@ -157,6 +161,7 @@ answer newbie questions, and generally made Django that much better: Rajesh Dhawan Sander Dijkhuis Jordan Dimov + Riccardo Di Virgilio Nebojša Dorđević dne@mayonnaise.net dready @@ -171,6 +176,7 @@ answer newbie questions, and generally made Django that much better: Clint Ecker Nick Efford eibaan@gmail.com + David Eklund Julia Elman enlight Enrico @@ -219,6 +225,7 @@ answer newbie questions, and generally made Django that much better: pradeep.gowda@gmail.com Collin Grady Gabriel Grant + Daniel Greenfeld Simon Greenhill Owen Griffiths Espen Grindhaug @@ -229,6 +236,7 @@ answer newbie questions, and generally made Django that much better: Scot Hacker dAniel hAhler hambaloney + Will Hardy Brian Harring Brant Harris Ronny Haryanto @@ -273,7 +281,6 @@ answer newbie questions, and generally made Django that much better: jpellerin@gmail.com junzhang.jn@gmail.com Xia Kai - Anssi Kääriäinen Antti Kaihola Peter van Kampen Bahadır Kandemir @@ -366,6 +373,7 @@ answer newbie questions, and generally made Django that much better: michael.mcewan@gmail.com Paul McLanahan Tobias McNulty + Andrews Medina Zain Memon Christian Metts michal@plovarna.cz @@ -422,6 +430,7 @@ answer newbie questions, and generally made Django that much better: polpak@yahoo.com Ross Poulton Mihai Preda + Daniele Procida Matthias Pronk Jyrki Pulliainen Thejaswi Puthraya @@ -450,6 +459,7 @@ answer newbie questions, and generally made Django that much better: Armin Ronacher Daniel Roseman Rozza + Audrey Roy Oliver Rutherfurd ryankanno Gonzalo Saavedra @@ -458,6 +468,7 @@ answer newbie questions, and generally made Django that much better: Vinay Sajip Bartolome Sanchez Salado Kadesarin Sanjek + Tim Saylor Massimo Scamarcia Paulo Scardine David Schein @@ -502,6 +513,7 @@ answer newbie questions, and generally made Django that much better: Aaron Swartz Ville Säävuori Mart Sõmermaa + Marc Tamlyn Christian Tanzer Tyler Tarabula Tyson Tate @@ -550,6 +562,7 @@ answer newbie questions, and generally made Django that much better: Mike Wiacek Frank Wierzbicki charly.wilhelm@gmail.com + Simon Williams Derek Willis Rachel Willmer Jakub Wilk diff --git a/django/bin/django-2to3.py b/django/bin/django-2to3.py new file mode 100755 index 0000000000..35566abb94 --- /dev/null +++ b/django/bin/django-2to3.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +# This works exactly like 2to3, except that it uses Django's fixers rather +# than 2to3's built-in fixers. + +import sys +from lib2to3.main import main + +sys.exit(main("django.utils.2to3_fixes")) + diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 531eed8658..e1c3fd1808 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -15,6 +15,7 @@ from django.conf import global_settings from django.core.exceptions import ImproperlyConfigured from django.utils.functional import LazyObject, empty from django.utils import importlib +from django.utils import six ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" @@ -73,7 +74,7 @@ class BaseSettings(object): elif name == "ADMIN_MEDIA_PREFIX": warnings.warn("The ADMIN_MEDIA_PREFIX setting has been removed; " "use STATIC_URL instead.", DeprecationWarning) - elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, basestring): + elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, six.string_types): raise ValueError("The ALLOWED_INCLUDE_ROOTS setting must be set " "to a tuple, not a string.") object.__setattr__(self, name, value) @@ -102,7 +103,10 @@ class Settings(BaseSettings): if setting == setting.upper(): setting_value = getattr(mod, setting) if setting in tuple_settings and \ - isinstance(setting_value, basestring): + isinstance(setting_value, six.string_types): + warnings.warn("The %s setting must be a tuple. Please fix your " + "settings, as auto-correction is now deprecated." % setting, + PendingDeprecationWarning) setting_value = (setting_value,) # In case the user forgot the comma. setattr(self, setting, setting_value) @@ -154,7 +158,7 @@ class UserSettingsHolder(BaseSettings): return getattr(self.default_settings, name) def __dir__(self): - return self.__dict__.keys() + dir(self.default_settings) + return list(self.__dict__) + dir(self.default_settings) # For Python < 2.6: __members__ = property(lambda self: self.__dir__()) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index a78b9187d8..4d5dc49ee0 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -270,19 +270,19 @@ SECRET_KEY = '' DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' # Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/home/media/media.lawrence.com/media/" +# Example: "/var/www/example.com/media/" MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. -# Example: "http://media.lawrence.com/media/" +# Examples: "http://example.com/media/", "http://media.example.com/" MEDIA_URL = '' -# Absolute path to the directory that holds static files. -# Example: "/home/media/media.lawrence.com/static/" +# Absolute path to the directory static files should be collected to. +# Example: "/var/www/example.com/static/" STATIC_ROOT = '' # URL that handles the static files served from STATIC_ROOT. -# Example: "http://media.lawrence.com/static/" +# Example: "http://example.com/static/", "http://static.example.com/" STATIC_URL = None # List of upload handler classes to be applied in order. @@ -451,7 +451,7 @@ MIDDLEWARE_CLASSES = ( SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want. SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks). -SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or None for standard domain cookie. +SESSION_COOKIE_DOMAIN = None # A string like ".example.com", or None for standard domain cookie. SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only). SESSION_COOKIE_PATH = '/' # The path of the session cookie. SESSION_COOKIE_HTTPONLY = True # Whether to use the non-RFC standard httpOnly flag (IE, FF3+, others) diff --git a/django/conf/locale/__init__.py b/django/conf/locale/__init__.py index af08c00e98..93e98194a4 100644 --- a/django/conf/locale/__init__.py +++ b/django/conf/locale/__init__.py @@ -1,434 +1,436 @@ +from __future__ import unicode_literals + LANG_INFO = { 'ar': { 'bidi': True, 'code': 'ar', 'name': 'Arabic', - 'name_local': u'\u0627\u0644\u0639\u0631\u0628\u064a\u0651\u0629', + 'name_local': '\u0627\u0644\u0639\u0631\u0628\u064a\u0651\u0629', }, 'az': { 'bidi': True, 'code': 'az', 'name': 'Azerbaijani', - 'name_local': u'az\u0259rbaycan dili', + 'name_local': 'az\u0259rbaycan dili', }, 'bg': { 'bidi': False, 'code': 'bg', 'name': 'Bulgarian', - 'name_local': u'\u0431\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438', + 'name_local': '\u0431\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438', }, 'bn': { 'bidi': False, 'code': 'bn', 'name': 'Bengali', - 'name_local': u'\u09ac\u09be\u0982\u09b2\u09be', + 'name_local': '\u09ac\u09be\u0982\u09b2\u09be', }, 'bs': { 'bidi': False, 'code': 'bs', 'name': 'Bosnian', - 'name_local': u'bosanski', + 'name_local': 'bosanski', }, 'ca': { 'bidi': False, 'code': 'ca', 'name': 'Catalan', - 'name_local': u'catal\xe0', + 'name_local': 'catal\xe0', }, 'cs': { 'bidi': False, 'code': 'cs', 'name': 'Czech', - 'name_local': u'\u010desky', + 'name_local': '\u010desky', }, 'cy': { 'bidi': False, 'code': 'cy', 'name': 'Welsh', - 'name_local': u'Cymraeg', + 'name_local': 'Cymraeg', }, 'da': { 'bidi': False, 'code': 'da', 'name': 'Danish', - 'name_local': u'Dansk', + 'name_local': 'Dansk', }, 'de': { 'bidi': False, 'code': 'de', 'name': 'German', - 'name_local': u'Deutsch', + 'name_local': 'Deutsch', }, 'el': { 'bidi': False, 'code': 'el', 'name': 'Greek', - 'name_local': u'\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac', + 'name_local': '\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac', }, 'en': { 'bidi': False, 'code': 'en', 'name': 'English', - 'name_local': u'English', + 'name_local': 'English', }, 'en-gb': { 'bidi': False, 'code': 'en-gb', 'name': 'British English', - 'name_local': u'British English', + 'name_local': 'British English', }, 'eo': { 'bidi': False, 'code': 'eo', 'name': 'Esperanto', - 'name_local': u'Esperanto', + 'name_local': 'Esperanto', }, 'es': { 'bidi': False, 'code': 'es', 'name': 'Spanish', - 'name_local': u'espa\xf1ol', + 'name_local': 'espa\xf1ol', }, 'es-ar': { 'bidi': False, 'code': 'es-ar', 'name': 'Argentinian Spanish', - 'name_local': u'espa\xf1ol de Argentina', + 'name_local': 'espa\xf1ol de Argentina', }, 'es-mx': { 'bidi': False, 'code': 'es-mx', 'name': 'Mexican Spanish', - 'name_local': u'espa\xf1ol de Mexico', + 'name_local': 'espa\xf1ol de Mexico', }, 'es-ni': { 'bidi': False, 'code': 'es-ni', 'name': 'Nicaraguan Spanish', - 'name_local': u'espa\xf1ol de Nicaragua', + 'name_local': 'espa\xf1ol de Nicaragua', }, 'et': { 'bidi': False, 'code': 'et', 'name': 'Estonian', - 'name_local': u'eesti', + 'name_local': 'eesti', }, 'eu': { 'bidi': False, 'code': 'eu', 'name': 'Basque', - 'name_local': u'Basque', + 'name_local': 'Basque', }, 'fa': { 'bidi': True, 'code': 'fa', 'name': 'Persian', - 'name_local': u'\u0641\u0627\u0631\u0633\u06cc', + 'name_local': '\u0641\u0627\u0631\u0633\u06cc', }, 'fi': { 'bidi': False, 'code': 'fi', 'name': 'Finnish', - 'name_local': u'suomi', + 'name_local': 'suomi', }, 'fr': { 'bidi': False, 'code': 'fr', 'name': 'French', - 'name_local': u'Fran\xe7ais', + 'name_local': 'Fran\xe7ais', }, 'fy-nl': { 'bidi': False, 'code': 'fy-nl', 'name': 'Frisian', - 'name_local': u'Frisian', + 'name_local': 'Frisian', }, 'ga': { 'bidi': False, 'code': 'ga', 'name': 'Irish', - 'name_local': u'Gaeilge', + 'name_local': 'Gaeilge', }, 'gl': { 'bidi': False, 'code': 'gl', 'name': 'Galician', - 'name_local': u'galego', + 'name_local': 'galego', }, 'he': { 'bidi': True, 'code': 'he', 'name': 'Hebrew', - 'name_local': u'\u05e2\u05d1\u05e8\u05d9\u05ea', + 'name_local': '\u05e2\u05d1\u05e8\u05d9\u05ea', }, 'hi': { 'bidi': False, 'code': 'hi', 'name': 'Hindi', - 'name_local': u'Hindi', + 'name_local': 'Hindi', }, 'hr': { 'bidi': False, 'code': 'hr', 'name': 'Croatian', - 'name_local': u'Hrvatski', + 'name_local': 'Hrvatski', }, 'hu': { 'bidi': False, 'code': 'hu', 'name': 'Hungarian', - 'name_local': u'Magyar', + 'name_local': 'Magyar', }, 'id': { 'bidi': False, 'code': 'id', 'name': 'Indonesian', - 'name_local': u'Bahasa Indonesia', + 'name_local': 'Bahasa Indonesia', }, 'is': { 'bidi': False, 'code': 'is', 'name': 'Icelandic', - 'name_local': u'\xcdslenska', + 'name_local': '\xcdslenska', }, 'it': { 'bidi': False, 'code': 'it', 'name': 'Italian', - 'name_local': u'italiano', + 'name_local': 'italiano', }, 'ja': { 'bidi': False, 'code': 'ja', 'name': 'Japanese', - 'name_local': u'\u65e5\u672c\u8a9e', + 'name_local': '\u65e5\u672c\u8a9e', }, 'ka': { 'bidi': False, 'code': 'ka', 'name': 'Georgian', - 'name_local': u'\u10e5\u10d0\u10e0\u10d7\u10e3\u10da\u10d8', + 'name_local': '\u10e5\u10d0\u10e0\u10d7\u10e3\u10da\u10d8', }, 'kk': { 'bidi': False, 'code': 'kk', 'name': 'Kazakh', - 'name_local': u'\u049a\u0430\u0437\u0430\u049b', + 'name_local': '\u049a\u0430\u0437\u0430\u049b', }, 'km': { 'bidi': False, 'code': 'km', 'name': 'Khmer', - 'name_local': u'Khmer', + 'name_local': 'Khmer', }, 'kn': { 'bidi': False, 'code': 'kn', 'name': 'Kannada', - 'name_local': u'Kannada', + 'name_local': 'Kannada', }, 'ko': { 'bidi': False, 'code': 'ko', 'name': 'Korean', - 'name_local': u'\ud55c\uad6d\uc5b4', + 'name_local': '\ud55c\uad6d\uc5b4', }, 'lt': { 'bidi': False, 'code': 'lt', 'name': 'Lithuanian', - 'name_local': u'Lithuanian', + 'name_local': 'Lithuanian', }, 'lv': { 'bidi': False, 'code': 'lv', 'name': 'Latvian', - 'name_local': u'latvie\u0161u', + 'name_local': 'latvie\u0161u', }, 'mk': { 'bidi': False, 'code': 'mk', 'name': 'Macedonian', - 'name_local': u'\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438', + 'name_local': '\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438', }, 'ml': { 'bidi': False, 'code': 'ml', 'name': 'Malayalam', - 'name_local': u'Malayalam', + 'name_local': 'Malayalam', }, 'mn': { 'bidi': False, 'code': 'mn', 'name': 'Mongolian', - 'name_local': u'Mongolian', + 'name_local': 'Mongolian', }, 'nb': { 'bidi': False, 'code': 'nb', 'name': 'Norwegian Bokmal', - 'name_local': u'Norsk (bokm\xe5l)', + 'name_local': 'Norsk (bokm\xe5l)', }, 'ne': { 'bidi': False, 'code': 'ne', 'name': 'Nepali', - 'name_local': u'\u0928\u0947\u092a\u093e\u0932\u0940', + 'name_local': '\u0928\u0947\u092a\u093e\u0932\u0940', }, 'nl': { 'bidi': False, 'code': 'nl', 'name': 'Dutch', - 'name_local': u'Nederlands', + 'name_local': 'Nederlands', }, 'nn': { 'bidi': False, 'code': 'nn', 'name': 'Norwegian Nynorsk', - 'name_local': u'Norsk (nynorsk)', + 'name_local': 'Norsk (nynorsk)', }, 'no': { 'bidi': False, 'code': 'no', 'name': 'Norwegian', - 'name_local': u'Norsk', + 'name_local': 'Norsk', }, 'pa': { 'bidi': False, 'code': 'pa', 'name': 'Punjabi', - 'name_local': u'Punjabi', + 'name_local': 'Punjabi', }, 'pl': { 'bidi': False, 'code': 'pl', 'name': 'Polish', - 'name_local': u'polski', + 'name_local': 'polski', }, 'pt': { 'bidi': False, 'code': 'pt', 'name': 'Portuguese', - 'name_local': u'Portugu\xeas', + 'name_local': 'Portugu\xeas', }, 'pt-br': { 'bidi': False, 'code': 'pt-br', 'name': 'Brazilian Portuguese', - 'name_local': u'Portugu\xeas Brasileiro', + 'name_local': 'Portugu\xeas Brasileiro', }, 'ro': { 'bidi': False, 'code': 'ro', 'name': 'Romanian', - 'name_local': u'Rom\xe2n\u0103', + 'name_local': 'Rom\xe2n\u0103', }, 'ru': { 'bidi': False, 'code': 'ru', 'name': 'Russian', - 'name_local': u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439', + 'name_local': '\u0420\u0443\u0441\u0441\u043a\u0438\u0439', }, 'sk': { 'bidi': False, 'code': 'sk', 'name': 'Slovak', - 'name_local': u'slovensk\xfd', + 'name_local': 'slovensk\xfd', }, 'sl': { 'bidi': False, 'code': 'sl', 'name': 'Slovenian', - 'name_local': u'Sloven\u0161\u010dina', + 'name_local': 'Sloven\u0161\u010dina', }, 'sq': { 'bidi': False, 'code': 'sq', 'name': 'Albanian', - 'name_local': u'Albanian', + 'name_local': 'Albanian', }, 'sr': { 'bidi': False, 'code': 'sr', 'name': 'Serbian', - 'name_local': u'\u0441\u0440\u043f\u0441\u043a\u0438', + 'name_local': '\u0441\u0440\u043f\u0441\u043a\u0438', }, 'sr-latn': { 'bidi': False, 'code': 'sr-latn', 'name': 'Serbian Latin', - 'name_local': u'srpski (latinica)', + 'name_local': 'srpski (latinica)', }, 'sv': { 'bidi': False, 'code': 'sv', 'name': 'Swedish', - 'name_local': u'Svenska', + 'name_local': 'Svenska', }, 'sw': { 'bidi': False, 'code': 'sw', 'name': 'Swahili', - 'name_local': u'Kiswahili', + 'name_local': 'Kiswahili', }, 'ta': { 'bidi': False, 'code': 'ta', 'name': 'Tamil', - 'name_local': u'\u0ba4\u0bae\u0bbf\u0bb4\u0bcd', + 'name_local': '\u0ba4\u0bae\u0bbf\u0bb4\u0bcd', }, 'te': { 'bidi': False, 'code': 'te', 'name': 'Telugu', - 'name_local': u'\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41', + 'name_local': '\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41', }, 'th': { 'bidi': False, 'code': 'th', 'name': 'Thai', - 'name_local': u'Thai', + 'name_local': 'Thai', }, 'tr': { 'bidi': False, 'code': 'tr', 'name': 'Turkish', - 'name_local': u'T\xfcrk\xe7e', + 'name_local': 'T\xfcrk\xe7e', }, 'tt': { 'bidi': False, 'code': 'tt', 'name': 'Tatar', - 'name_local': u'\u0422\u0430\u0442\u0430\u0440\u0447\u0430', + 'name_local': '\u0422\u0430\u0442\u0430\u0440\u0447\u0430', }, 'uk': { 'bidi': False, 'code': 'uk', 'name': 'Ukrainian', - 'name_local': u'\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430', + 'name_local': '\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430', }, 'ur': { 'bidi': False, 'code': 'ur', 'name': 'Urdu', - 'name_local': u'\u0627\u0631\u062f\u0648', + 'name_local': '\u0627\u0631\u062f\u0648', }, 'vi': { 'bidi': False, 'code': 'vi', 'name': 'Vietnamese', - 'name_local': u'Vietnamese', + 'name_local': 'Vietnamese', }, 'zh-cn': { 'bidi': False, 'code': 'zh-cn', 'name': 'Simplified Chinese', - 'name_local': u'\u7b80\u4f53\u4e2d\u6587', + 'name_local': '\u7b80\u4f53\u4e2d\u6587', }, 'zh-tw': { 'bidi': False, 'code': 'zh-tw', 'name': 'Traditional Chinese', - 'name_local': u'\u7e41\u9ad4\u4e2d\u6587', + 'name_local': '\u7e41\u9ad4\u4e2d\u6587', } } diff --git a/django/conf/locale/ar/formats.py b/django/conf/locale/ar/formats.py index 3b47429ece..213e5b363f 100644 --- a/django/conf/locale/ar/formats.py +++ b/django/conf/locale/ar/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/bg/formats.py b/django/conf/locale/bg/formats.py index 3dc4704972..c5e89737b6 100644 --- a/django/conf/locale/bg/formats.py +++ b/django/conf/locale/bg/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -19,5 +20,5 @@ SHORT_DATE_FORMAT = 'd.m.Y' # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' # Non-breaking space +THOUSAND_SEPARATOR = ' ' # Non-breaking space # NUMBER_GROUPING = diff --git a/django/conf/locale/cs/formats.py b/django/conf/locale/cs/formats.py index 56e9e73547..f0b674227b 100644 --- a/django/conf/locale/cs/formats.py +++ b/django/conf/locale/cs/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -33,5 +34,5 @@ DATETIME_INPUT_FORMATS = ( '%Y-%m-%d', # '2006-10-25' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/en/formats.py b/django/conf/locale/en/formats.py index 68a9276328..6cf2335f5e 100644 --- a/django/conf/locale/en/formats.py +++ b/django/conf/locale/en/formats.py @@ -37,7 +37,7 @@ DATETIME_INPUT_FORMATS = ( '%m/%d/%y %H:%M', # '10/25/06 14:30' '%m/%d/%y', # '10/25/06' ) -DECIMAL_SEPARATOR = u'.' -THOUSAND_SEPARATOR = u',' +DECIMAL_SEPARATOR = '.' +THOUSAND_SEPARATOR = ',' NUMBER_GROUPING = 3 diff --git a/django/conf/locale/es_MX/formats.py b/django/conf/locale/es_MX/formats.py index af534e60ad..cef6b4e2f9 100644 --- a/django/conf/locale/es_MX/formats.py +++ b/django/conf/locale/es_MX/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i:s' @@ -24,5 +25,5 @@ DATETIME_INPUT_FORMATS = ( '%d/%m/%y %H:%M', ) DECIMAL_SEPARATOR = '.' # ',' is also official (less common): NOM-008-SCFI-2002 -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/et/formats.py b/django/conf/locale/et/formats.py index 7de0cd44b5..dd0d1a696e 100644 --- a/django/conf/locale/et/formats.py +++ b/django/conf/locale/et/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -19,5 +20,5 @@ SHORT_DATE_FORMAT = 'd.m.Y' # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' # Non-breaking space +THOUSAND_SEPARATOR = ' ' # Non-breaking space # NUMBER_GROUPING = diff --git a/django/conf/locale/fa/formats.py b/django/conf/locale/fa/formats.py index 7225a62995..5682c410e2 100644 --- a/django/conf/locale/fa/formats.py +++ b/django/conf/locale/fa/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/fi/formats.py b/django/conf/locale/fi/formats.py index 198ece66b0..e76144a9e4 100644 --- a/django/conf/locale/fi/formats.py +++ b/django/conf/locale/fi/formats.py @@ -1,12 +1,13 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. E Y' TIME_FORMAT = 'G.i.s' -# DATETIME_FORMAT = +DATETIME_FORMAT = r'j. E Y \k\e\l\l\o G.i.s' YEAR_MONTH_FORMAT = 'F Y' MONTH_DAY_FORMAT = 'j. F' SHORT_DATE_FORMAT = 'j.n.Y' @@ -19,5 +20,5 @@ SHORT_DATE_FORMAT = 'j.n.Y' # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' # Non-breaking space +THOUSAND_SEPARATOR = ' ' # Non-breaking space # NUMBER_GROUPING = diff --git a/django/conf/locale/fr/formats.py b/django/conf/locale/fr/formats.py index 6d8e334f09..3b5e8861d8 100644 --- a/django/conf/locale/fr/formats.py +++ b/django/conf/locale/fr/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -37,5 +38,5 @@ DATETIME_INPUT_FORMATS = ( '%Y-%m-%d', # '2006-10-25' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/gl/formats.py b/django/conf/locale/gl/formats.py index 1ff9f34521..ba7f6c52a0 100644 --- a/django/conf/locale/gl/formats.py +++ b/django/conf/locale/gl/formats.py @@ -1,17 +1,18 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date -DATE_FORMAT = 'd F Y' +DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i:s' -# DATETIME_FORMAT = -YEAR_MONTH_FORMAT = 'F Y' -MONTH_DAY_FORMAT = 'j F' -SHORT_DATE_FORMAT = 'j M, Y' -# SHORT_DATETIME_FORMAT = -# FIRST_DAY_OF_WEEK = +DATETIME_FORMAT = r'j \d\e F \d\e Y \á\s H:i' +YEAR_MONTH_FORMAT = r'F \d\e Y' +MONTH_DAY_FORMAT = r'j \d\e F' +SHORT_DATE_FORMAT = 'd-m-Y' +SHORT_DATETIME_FORMAT = 'd-m-Y, H:i' +FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior diff --git a/django/conf/locale/he/formats.py b/django/conf/locale/he/formats.py index e83d7085a9..1c8b1b5566 100644 --- a/django/conf/locale/he/formats.py +++ b/django/conf/locale/he/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/hu/formats.py b/django/conf/locale/hu/formats.py index 53a8fc7cb0..a9298bf022 100644 --- a/django/conf/locale/hu/formats.py +++ b/django/conf/locale/hu/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -28,5 +29,5 @@ DATETIME_INPUT_FORMATS = ( '%Y.%m.%d.', # '2006.10.25.' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' # Non-breaking space +THOUSAND_SEPARATOR = ' ' # Non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/ja/formats.py b/django/conf/locale/ja/formats.py index ce9d1acb9a..263aa0a344 100644 --- a/django/conf/locale/ja/formats.py +++ b/django/conf/locale/ja/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/km/formats.py b/django/conf/locale/km/formats.py index 70afa27c1d..ace2fc1eea 100644 --- a/django/conf/locale/km/formats.py +++ b/django/conf/locale/km/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/ko/formats.py b/django/conf/locale/ko/formats.py index af65392934..3c0431e4bf 100644 --- a/django/conf/locale/ko/formats.py +++ b/django/conf/locale/ko/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/lv/formats.py b/django/conf/locale/lv/formats.py index 316d777e8b..e4ef524adf 100644 --- a/django/conf/locale/lv/formats.py +++ b/django/conf/locale/lv/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -37,5 +38,5 @@ DATETIME_INPUT_FORMATS = ( '%d.%m.%y', # '25.10.06' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' # Non-breaking space +THOUSAND_SEPARATOR = ' ' # Non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/nb/formats.py b/django/conf/locale/nb/formats.py index 8de88512a0..4a896dd80d 100644 --- a/django/conf/locale/nb/formats.py +++ b/django/conf/locale/nb/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -39,5 +40,5 @@ DATETIME_INPUT_FORMATS = ( '%d.%m.%y', # '25.10.06' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/nn/formats.py b/django/conf/locale/nn/formats.py index 8de88512a0..4a896dd80d 100644 --- a/django/conf/locale/nn/formats.py +++ b/django/conf/locale/nn/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -39,5 +40,5 @@ DATETIME_INPUT_FORMATS = ( '%d.%m.%y', # '25.10.06' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/pl/formats.py b/django/conf/locale/pl/formats.py index 1c538b26ab..021063d474 100644 --- a/django/conf/locale/pl/formats.py +++ b/django/conf/locale/pl/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -33,5 +34,5 @@ DATETIME_INPUT_FORMATS = ( '%Y-%m-%d', # '2006-10-25' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' +THOUSAND_SEPARATOR = ' ' NUMBER_GROUPING = 3 diff --git a/django/conf/locale/pt_BR/formats.py b/django/conf/locale/pt_BR/formats.py index 6a44d38651..6d9edeecce 100644 --- a/django/conf/locale/pt_BR/formats.py +++ b/django/conf/locale/pt_BR/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/locale/ru/formats.py b/django/conf/locale/ru/formats.py index e825824751..ec46bff400 100644 --- a/django/conf/locale/ru/formats.py +++ b/django/conf/locale/ru/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -36,5 +37,5 @@ DATETIME_INPUT_FORMATS = ( '%Y-%m-%d', # '2006-10-25' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/sk/formats.py b/django/conf/locale/sk/formats.py index 0443efb376..4b2201f49a 100644 --- a/django/conf/locale/sk/formats.py +++ b/django/conf/locale/sk/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -33,5 +34,5 @@ DATETIME_INPUT_FORMATS = ( '%Y-%m-%d', # '2006-10-25' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/sv/formats.py b/django/conf/locale/sv/formats.py index ad7d3b3fee..767dbe8d3e 100644 --- a/django/conf/locale/sv/formats.py +++ b/django/conf/locale/sv/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date @@ -36,5 +37,5 @@ DATETIME_INPUT_FORMATS = ( '%m/%d/%y', # '10/25/06' ) DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/conf/locale/uk/formats.py b/django/conf/locale/uk/formats.py index 8b4606fb64..11293f047a 100644 --- a/django/conf/locale/uk/formats.py +++ b/django/conf/locale/uk/formats.py @@ -2,6 +2,8 @@ # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals + # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j E Y р.' @@ -19,5 +21,5 @@ SHORT_DATE_FORMAT = 'j M Y' # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u' ' +THOUSAND_SEPARATOR = ' ' # NUMBER_GROUPING = diff --git a/django/conf/locale/vi/formats.py b/django/conf/locale/vi/formats.py index 27b4efca25..b491c2d20c 100644 --- a/django/conf/locale/vi/formats.py +++ b/django/conf/locale/vi/formats.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date diff --git a/django/conf/project_template/project_name/settings.py b/django/conf/project_template/project_name/settings.py index 0eccc4eaf5..99590d6fd5 100644 --- a/django/conf/project_template/project_name/settings.py +++ b/django/conf/project_template/project_name/settings.py @@ -44,22 +44,22 @@ USE_L10N = True USE_TZ = True # Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/home/media/media.lawrence.com/media/" +# Example: "/var/www/example.com/media/" MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash. -# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" +# Examples: "http://example.com/media/", "http://media.example.com/" MEDIA_URL = '' # Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files # in apps' "static/" subdirectories and in STATICFILES_DIRS. -# Example: "/home/media/media.lawrence.com/static/" +# Example: "/var/www/example.com/static/" STATIC_ROOT = '' # URL prefix for static files. -# Example: "http://media.lawrence.com/static/" +# Example: "http://example.com/static/", "http://static.example.com/" STATIC_URL = '/static/' # Additional locations of static files diff --git a/django/conf/urls/__init__.py b/django/conf/urls/__init__.py index 0b6ab6496e..04fb1dff59 100644 --- a/django/conf/urls/__init__.py +++ b/django/conf/urls/__init__.py @@ -2,6 +2,7 @@ from django.core.urlresolvers import (RegexURLPattern, RegexURLResolver, LocaleRegexURLResolver) from django.core.exceptions import ImproperlyConfigured from django.utils.importlib import import_module +from django.utils import six __all__ = ['handler403', 'handler404', 'handler500', 'include', 'patterns', 'url'] @@ -20,7 +21,7 @@ def include(arg, namespace=None, app_name=None): # No namespace hint - use manually provided namespace urlconf_module = arg - if isinstance(urlconf_module, basestring): + if isinstance(urlconf_module, six.string_types): urlconf_module = import_module(urlconf_module) patterns = getattr(urlconf_module, 'urlpatterns', urlconf_module) @@ -52,7 +53,7 @@ def url(regex, view, kwargs=None, name=None, prefix=''): urlconf_module, app_name, namespace = view return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace) else: - if isinstance(view, basestring): + if isinstance(view, six.string_types): if not view: raise ImproperlyConfigured('Empty URL pattern view name not permitted (for pattern %r)' % regex) if prefix: diff --git a/django/conf/urls/i18n.py b/django/conf/urls/i18n.py index 6e56af8271..426c2b2d30 100644 --- a/django/conf/urls/i18n.py +++ b/django/conf/urls/i18n.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.conf.urls import patterns +from django.conf.urls import patterns, url from django.core.urlresolvers import LocaleRegexURLResolver def i18n_patterns(prefix, *args): @@ -16,5 +16,5 @@ def i18n_patterns(prefix, *args): urlpatterns = patterns('', - (r'^setlang/$', 'django.views.i18n.set_language'), + url(r'^setlang/$', 'django.views.i18n.set_language', name='set_language'), ) diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py index 5b56402428..201101736e 100644 --- a/django/contrib/admin/actions.py +++ b/django/contrib/admin/actions.py @@ -7,7 +7,7 @@ from django.contrib.admin import helpers from django.contrib.admin.util import get_deleted_objects, model_ngettext from django.db import router from django.template.response import TemplateResponse -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy, ugettext as _ def delete_selected(modeladmin, request, queryset): @@ -42,7 +42,7 @@ def delete_selected(modeladmin, request, queryset): n = queryset.count() if n: for obj in queryset: - obj_display = force_unicode(obj) + obj_display = force_text(obj) modeladmin.log_deletion(request, obj, obj_display) queryset.delete() modeladmin.message_user(request, _("Successfully deleted %(count)d %(items)s.") % { @@ -52,9 +52,9 @@ def delete_selected(modeladmin, request, queryset): return None if len(queryset) == 1: - objects_name = force_unicode(opts.verbose_name) + objects_name = force_text(opts.verbose_name) else: - objects_name = force_unicode(opts.verbose_name_plural) + objects_name = force_text(opts.verbose_name_plural) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": objects_name} diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index 538bf54df9..cecae216c0 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -9,7 +9,7 @@ import datetime from django.db import models from django.core.exceptions import ImproperlyConfigured -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ from django.utils import timezone @@ -195,7 +195,7 @@ class RelatedFieldListFilter(FieldListFilter): } for pk_val, val in self.lookup_choices: yield { - 'selected': self.lookup_val == smart_unicode(pk_val), + 'selected': self.lookup_val == smart_text(pk_val), 'query_string': cl.get_query_string({ self.lookup_kwarg: pk_val, }, [self.lookup_kwarg_isnull]), @@ -272,7 +272,7 @@ class ChoicesFieldListFilter(FieldListFilter): } for lookup, title in self.field.flatchoices: yield { - 'selected': smart_unicode(lookup) == self.lookup_val, + 'selected': smart_text(lookup) == self.lookup_val, 'query_string': cl.get_query_string({ self.lookup_kwarg: lookup}), 'display': title, @@ -381,7 +381,7 @@ class AllValuesFieldListFilter(FieldListFilter): if val is None: include_none = True continue - val = smart_unicode(val) + val = smart_text(val) yield { 'selected': self.lookup_val == val, 'query_string': cl.get_query_string({ diff --git a/django/contrib/admin/forms.py b/django/contrib/admin/forms.py index 4e828697e8..e10c92afcf 100644 --- a/django/contrib/admin/forms.py +++ b/django/contrib/admin/forms.py @@ -1,9 +1,10 @@ +from __future__ import unicode_literals + from django import forms from django.contrib.auth import authenticate from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.models import User - from django.utils.translation import ugettext_lazy, ugettext as _ ERROR_MESSAGE = ugettext_lazy("Please enter the correct username and password " @@ -26,7 +27,7 @@ class AdminAuthenticationForm(AuthenticationForm): if username and password: self.user_cache = authenticate(username=username, password=password) if self.user_cache is None: - if u'@' in username: + if '@' in username: # Mistakenly entered e-mail address instead of username? Look it up. try: user = User.objects.get(email=username) diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 58130574ee..90370bd978 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import forms from django.contrib.admin.util import (flatten_fieldsets, lookup_field, display_for_field, label_for_field, help_text_for_field) @@ -7,9 +9,10 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models.fields.related import ManyToManyRel from django.forms.util import flatatt from django.template.defaultfilters import capfirst -from django.utils.encoding import force_unicode, smart_unicode -from django.utils.html import escape, conditional_escape +from django.utils.encoding import force_text, smart_text +from django.utils.html import conditional_escape, format_html from django.utils.safestring import mark_safe +from django.utils import six from django.utils.translation import ugettext_lazy as _ from django.conf import settings @@ -47,7 +50,7 @@ class AdminForm(object): try: fieldset_name, fieldset_options = self.fieldsets[0] field_name = fieldset_options['fields'][0] - if not isinstance(field_name, basestring): + if not isinstance(field_name, six.string_types): field_name = field_name[0] return self.form[field_name] except (KeyError, IndexError): @@ -69,7 +72,7 @@ class Fieldset(object): description=None, model_admin=None): self.form = form self.name, self.fields = name, fields - self.classes = u' '.join(classes) + self.classes = ' '.join(classes) self.description = description self.model_admin = model_admin self.readonly_fields = readonly_fields @@ -91,7 +94,7 @@ class Fieldset(object): class Fieldline(object): def __init__(self, form, field, readonly_fields=None, model_admin=None): self.form = form # A django.forms.Form instance - if not hasattr(field, "__iter__"): + if not hasattr(field, "__iter__") or isinstance(field, six.text_type): self.fields = [field] else: self.fields = field @@ -109,7 +112,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 if f not in self.readonly_fields]).strip('\n')) + return mark_safe('\n'.join([self.form[f].errors.as_ul() for f in self.fields if f not in self.readonly_fields]).strip('\n')) class AdminField(object): def __init__(self, form, field, is_first): @@ -119,16 +122,16 @@ class AdminField(object): def label_tag(self): classes = [] - contents = conditional_escape(force_unicode(self.field.label)) + contents = conditional_escape(force_text(self.field.label)) if self.is_checkbox: - classes.append(u'vCheckboxLabel') + classes.append('vCheckboxLabel') else: - contents += u':' + contents += ':' if self.field.field.required: - classes.append(u'required') + classes.append('required') if not self.is_first: - classes.append(u'inline') - attrs = classes and {'class': u' '.join(classes)} or {} + classes.append('inline') + attrs = classes and {'class': ' '.join(classes)} or {} return self.field.label_tag(contents=mark_safe(contents), attrs=attrs) def errors(self): @@ -161,11 +164,9 @@ class AdminReadonlyField(object): if not self.is_first: attrs["class"] = "inline" label = self.field['label'] - contents = capfirst(force_unicode(escape(label))) + u":" - return mark_safe('%(contents)s' % { - "attrs": flatatt(attrs), - "contents": contents, - }) + return format_html('{1}:', + flatatt(attrs), + capfirst(force_text(label))) def contents(self): from django.contrib.admin.templatetags.admin_list import _boolean_icon @@ -181,14 +182,14 @@ class AdminReadonlyField(object): if boolean: result_repr = _boolean_icon(value) else: - result_repr = smart_unicode(value) + result_repr = smart_text(value) if getattr(attr, "allow_tags", False): result_repr = mark_safe(result_repr) else: if value is None: result_repr = EMPTY_CHANGELIST_VALUE elif isinstance(f.rel, ManyToManyRel): - result_repr = ", ".join(map(unicode, value.all())) + result_repr = ", ".join(map(six.text_type, value.all())) else: result_repr = display_for_field(value, f) return conditional_escape(result_repr) @@ -324,11 +325,11 @@ class AdminErrorList(forms.util.ErrorList): """ def __init__(self, form, inline_formsets): if form.is_bound: - self.extend(form.errors.values()) + self.extend(list(six.itervalues(form.errors))) for inline_formset in inline_formsets: self.extend(inline_formset.non_form_errors()) for errors_in_inline_form in inline_formset.errors: - self.extend(errors_in_inline_form.values()) + self.extend(list(six.itervalues(errors_in_inline_form))) def normalize_fieldsets(fieldsets): """ diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py index 9e8ffe47c3..e1d3b40d01 100644 --- a/django/contrib/admin/models.py +++ b/django/contrib/admin/models.py @@ -1,10 +1,12 @@ +from __future__ import unicode_literals + from django.db import models from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib.admin.util import quote from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode -from django.utils.safestring import mark_safe +from django.utils.encoding import smart_text +from django.utils.encoding import python_2_unicode_compatible ADDITION = 1 CHANGE = 2 @@ -13,10 +15,11 @@ DELETION = 3 class LogEntryManager(models.Manager): def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''): - e = self.model(None, None, user_id, content_type_id, smart_unicode(object_id), object_repr[:200], action_flag, change_message) + e = self.model(None, None, user_id, content_type_id, smart_text(object_id), object_repr[:200], action_flag, change_message) e.save() +@python_2_unicode_compatible class LogEntry(models.Model): action_time = models.DateTimeField(_('action time'), auto_now=True) user = models.ForeignKey(settings.AUTH_USER_MODEL) @@ -35,9 +38,9 @@ class LogEntry(models.Model): ordering = ('-action_time',) def __repr__(self): - return smart_unicode(self.action_time) + return smart_text(self.action_time) - def __unicode__(self): + def __str__(self): if self.action_flag == ADDITION: return _('Added "%(object)s".') % {'object': self.object_repr} elif self.action_flag == CHANGE: @@ -66,5 +69,5 @@ class LogEntry(models.Model): This is relative to the Django admin index page. """ if self.content_type and self.object_id: - return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id))) + return "%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id)) return None diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 2071792bdb..081d00121b 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -24,10 +24,11 @@ from django.utils.decorators import method_decorator from django.utils.datastructures import SortedDict from django.utils.html import escape, escapejs from django.utils.safestring import mark_safe +from django.utils import six from django.utils.text import capfirst, get_text_list from django.utils.translation import ugettext as _ from django.utils.translation import ungettext -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text HORIZONTAL, VERTICAL = 1, 2 # returns the
    class for a given radio_admin field @@ -49,7 +50,7 @@ FORMFIELD_FOR_DBFIELD_DEFAULTS = { models.TextField: {'widget': widgets.AdminTextareaWidget}, models.URLField: {'widget': widgets.AdminURLFieldWidget}, models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget}, - models.BigIntegerField: {'widget': widgets.AdminIntegerFieldWidget}, + models.BigIntegerField: {'widget': widgets.AdminBigIntegerFieldWidget}, models.CharField: {'widget': widgets.AdminTextInputWidget}, models.ImageField: {'widget': widgets.AdminFileWidget}, models.FileField: {'widget': widgets.AdminFileWidget}, @@ -57,9 +58,8 @@ FORMFIELD_FOR_DBFIELD_DEFAULTS = { csrf_protect_m = method_decorator(csrf_protect) -class BaseModelAdmin(object): +class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): """Functionality common to both ModelAdmin and InlineAdmin.""" - __metaclass__ = forms.MediaDefiningClass raw_id_fields = () fields = None @@ -425,7 +425,7 @@ class ModelAdmin(BaseModelAdmin): if self.declared_fieldsets: return self.declared_fieldsets form = self.get_form(request, obj) - fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj)) + fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj)) return [(None, {'fields': fields})] def get_form(self, request, obj=None, **kwargs): @@ -520,7 +520,7 @@ class ModelAdmin(BaseModelAdmin): user_id = request.user.pk, content_type_id = ContentType.objects.get_for_model(object).pk, object_id = object.pk, - object_repr = force_unicode(object), + object_repr = force_text(object), action_flag = ADDITION ) @@ -535,7 +535,7 @@ class ModelAdmin(BaseModelAdmin): user_id = request.user.pk, content_type_id = ContentType.objects.get_for_model(object).pk, object_id = object.pk, - object_repr = force_unicode(object), + object_repr = force_text(object), action_flag = CHANGE, change_message = message ) @@ -560,7 +560,7 @@ class ModelAdmin(BaseModelAdmin): """ A list_display column containing a checkbox widget. """ - return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_unicode(obj.pk)) + return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_text(obj.pk)) action_checkbox.short_description = mark_safe('') action_checkbox.allow_tags = True @@ -608,7 +608,7 @@ class ModelAdmin(BaseModelAdmin): tuple (name, description). """ choices = [] + default_choices - for func, name, description in self.get_actions(request).itervalues(): + for func, name, description in six.itervalues(self.get_actions(request)): choice = (name, description % model_format_dict(self.opts)) choices.append(choice) return choices @@ -674,17 +674,17 @@ class ModelAdmin(BaseModelAdmin): for formset in formsets: for added_object in formset.new_objects: change_message.append(_('Added %(name)s "%(object)s".') - % {'name': force_unicode(added_object._meta.verbose_name), - 'object': force_unicode(added_object)}) + % {'name': force_text(added_object._meta.verbose_name), + 'object': force_text(added_object)}) for changed_object, changed_fields in formset.changed_objects: change_message.append(_('Changed %(list)s for %(name)s "%(object)s".') % {'list': get_text_list(changed_fields, _('and')), - 'name': force_unicode(changed_object._meta.verbose_name), - 'object': force_unicode(changed_object)}) + 'name': force_text(changed_object._meta.verbose_name), + 'object': force_text(changed_object)}) for deleted_object in formset.deleted_objects: change_message.append(_('Deleted %(name)s "%(object)s".') - % {'name': force_unicode(deleted_object._meta.verbose_name), - 'object': force_unicode(deleted_object)}) + % {'name': force_text(deleted_object._meta.verbose_name), + 'object': force_text(deleted_object)}) change_message = ' '.join(change_message) return change_message or _('No fields changed.') @@ -745,7 +745,7 @@ class ModelAdmin(BaseModelAdmin): 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField, 'has_absolute_url': hasattr(self.model, 'get_absolute_url'), 'ordered_objects': ordered_objects, - 'form_url': mark_safe(form_url), + 'form_url': form_url, 'opts': opts, 'content_type_id': ContentType.objects.get_for_model(self.model).id, 'save_as': self.save_as, @@ -769,7 +769,7 @@ class ModelAdmin(BaseModelAdmin): opts = obj._meta pk_value = obj._get_pk_val() - msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)} + msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_text(opts.verbose_name), 'obj': force_text(obj)} # Here, we distinguish between different save types by checking for # the presence of keys in request.POST. if "_continue" in request.POST: @@ -782,10 +782,10 @@ class ModelAdmin(BaseModelAdmin): return HttpResponse( '' '' % \ - # escape() calls force_unicode. + # escape() calls force_text. (escape(pk_value), escapejs(obj))) elif "_addanother" in request.POST: - self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) + self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(opts.verbose_name))) return HttpResponseRedirect(request.path) else: self.message_user(request, msg) @@ -819,7 +819,7 @@ class ModelAdmin(BaseModelAdmin): pk_value = obj._get_pk_val() - msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(verbose_name), 'obj': force_unicode(obj)} + msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_text(verbose_name), 'obj': force_text(obj)} if "_continue" in request.POST: self.message_user(request, msg + ' ' + _("You may edit it again below.")) if "_popup" in request.REQUEST: @@ -827,14 +827,14 @@ class ModelAdmin(BaseModelAdmin): else: return HttpResponseRedirect(request.path) elif "_saveasnew" in request.POST: - msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(verbose_name), 'obj': obj} + msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_text(verbose_name), 'obj': obj} self.message_user(request, msg) return HttpResponseRedirect(reverse('admin:%s_%s_change' % (opts.app_label, module_name), args=(pk_value,), current_app=self.admin_site.name)) elif "_addanother" in request.POST: - self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(verbose_name))) + self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(verbose_name))) return HttpResponseRedirect(reverse('admin:%s_%s_add' % (opts.app_label, module_name), current_app=self.admin_site.name)) @@ -995,10 +995,9 @@ class ModelAdmin(BaseModelAdmin): media = media + inline_admin_formset.media context = { - 'title': _('Add %s') % force_unicode(opts.verbose_name), + 'title': _('Add %s') % force_text(opts.verbose_name), 'adminform': adminForm, 'is_popup': "_popup" in request.REQUEST, - 'show_delete': False, 'media': media, 'inline_admin_formsets': inline_admin_formsets, 'errors': helpers.AdminErrorList(form, formsets), @@ -1020,7 +1019,7 @@ class ModelAdmin(BaseModelAdmin): raise PermissionDenied if obj is None: - raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)}) + raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_text(opts.verbose_name), 'key': escape(object_id)}) if request.method == 'POST' and "_saveasnew" in request.POST: return self.add_view(request, form_url=reverse('admin:%s_%s_add' % @@ -1086,7 +1085,7 @@ class ModelAdmin(BaseModelAdmin): media = media + inline_admin_formset.media context = { - 'title': _('Change %s') % force_unicode(opts.verbose_name), + 'title': _('Change %s') % force_text(opts.verbose_name), 'adminform': adminForm, 'object_id': object_id, 'original': obj, @@ -1195,14 +1194,14 @@ class ModelAdmin(BaseModelAdmin): if changecount: if changecount == 1: - name = force_unicode(opts.verbose_name) + name = force_text(opts.verbose_name) else: - name = force_unicode(opts.verbose_name_plural) + name = force_text(opts.verbose_name_plural) msg = ungettext("%(count)s %(name)s was changed successfully.", "%(count)s %(name)s were changed successfully.", changecount) % {'count': changecount, 'name': name, - 'obj': force_unicode(obj)} + 'obj': force_text(obj)} self.message_user(request, msg) return HttpResponseRedirect(request.get_full_path()) @@ -1229,7 +1228,7 @@ class ModelAdmin(BaseModelAdmin): 'All %(total_count)s selected', cl.result_count) context = { - 'module_name': force_unicode(opts.verbose_name_plural), + 'module_name': force_text(opts.verbose_name_plural), 'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)}, 'selection_note_all': selection_note_all % {'total_count': cl.result_count}, 'title': cl.title, @@ -1264,7 +1263,7 @@ class ModelAdmin(BaseModelAdmin): raise PermissionDenied if obj is None: - raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)}) + raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_text(opts.verbose_name), 'key': escape(object_id)}) using = router.db_for_write(self.model) @@ -1276,11 +1275,11 @@ class ModelAdmin(BaseModelAdmin): if request.POST: # The user has already confirmed the deletion. if perms_needed: raise PermissionDenied - obj_display = force_unicode(obj) + obj_display = force_text(obj) self.log_deletion(request, obj, obj_display) self.delete_model(request, obj) - self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)}) + self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_text(opts.verbose_name), 'obj': force_text(obj_display)}) if not self.has_change_permission(request, None): return HttpResponseRedirect(reverse('admin:index', @@ -1289,7 +1288,7 @@ class ModelAdmin(BaseModelAdmin): (opts.app_label, opts.module_name), current_app=self.admin_site.name)) - object_name = force_unicode(opts.verbose_name) + object_name = force_text(opts.verbose_name) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": object_name} @@ -1321,15 +1320,15 @@ class ModelAdmin(BaseModelAdmin): opts = model._meta app_label = opts.app_label action_list = LogEntry.objects.filter( - object_id = object_id, + object_id = unquote(object_id), content_type__id__exact = ContentType.objects.get_for_model(model).id ).select_related().order_by('action_time') # If no history was found, see whether this object even exists. obj = get_object_or_404(model, pk=unquote(object_id)) context = { - 'title': _('Change history: %s') % force_unicode(obj), + 'title': _('Change history: %s') % force_text(obj), 'action_list': action_list, - 'module_name': capfirst(force_unicode(opts.verbose_name_plural)), + 'module_name': capfirst(force_text(opts.verbose_name_plural)), 'object': obj, 'app_label': app_label, 'opts': opts, @@ -1416,7 +1415,7 @@ class InlineModelAdmin(BaseModelAdmin): if self.declared_fieldsets: return self.declared_fieldsets form = self.get_formset(request, obj).form - fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj)) + fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj)) return [(None, {'fields': fields})] def queryset(self, request): diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index e57eafe256..333ac7ea11 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -10,6 +10,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse, NoReverseMatch from django.template.response import TemplateResponse from django.utils.safestring import mark_safe +from django.utils import six from django.utils.text import capfirst from django.utils.translation import ugettext as _ from django.views.decorators.cache import never_cache @@ -136,7 +137,7 @@ class AdminSite(object): """ Get all the enabled actions as an iterable of (name, func). """ - return self._actions.iteritems() + return six.iteritems(self._actions) def has_permission(self, request): """ @@ -234,14 +235,15 @@ class AdminSite(object): wrap(self.i18n_javascript, cacheable=True), name='jsi18n'), url(r'^r/(?P\d+)/(?P.+)/$', - wrap(contenttype_views.shortcut)), + wrap(contenttype_views.shortcut), + name='view_on_site'), url(r'^(?P\w+)/$', wrap(self.app_index), name='app_list') ) # Add in each model's views. - for model, model_admin in self._registry.iteritems(): + for model, model_admin in six.iteritems(self._registry): urlpatterns += patterns('', url(r'^%s/%s/' % (model._meta.app_label, model._meta.module_name), include(model_admin.urls)) @@ -373,7 +375,7 @@ class AdminSite(object): } # Sort the apps alphabetically. - app_list = app_dict.values() + app_list = list(six.itervalues(app_dict)) app_list.sort(key=lambda x: x['name']) # Sort the models alphabetically within each app. diff --git a/django/contrib/admin/static/admin/css/forms.css b/django/contrib/admin/static/admin/css/forms.css index 9d39b3fe89..efec04b670 100644 --- a/django/contrib/admin/static/admin/css/forms.css +++ b/django/contrib/admin/static/admin/css/forms.css @@ -226,6 +226,10 @@ body.popup .submit-row { width: 5em; } +.vBigIntegerField { + width: 10em; +} + .vForeignKeyRawIdAdminField { width: 5em; } diff --git a/django/contrib/admin/static/admin/css/widgets.css b/django/contrib/admin/static/admin/css/widgets.css index 2989f2f4fd..0a7012c7b2 100644 --- a/django/contrib/admin/static/admin/css/widgets.css +++ b/django/contrib/admin/static/admin/css/widgets.css @@ -41,7 +41,8 @@ text-align: left; } -.selector .selector-filter label { +.selector .selector-filter label, +.inline-group .aligned .selector .selector-filter label { width: 16px; padding: 2px; } diff --git a/django/contrib/admin/static/admin/js/actions.min.js b/django/contrib/admin/static/admin/js/actions.min.js index 2c6300094f..6b1947cefe 100644 --- a/django/contrib/admin/static/admin/js/actions.min.js +++ b/django/contrib/admin/static/admin/js/actions.min.js @@ -1,6 +1,6 @@ -(function(a){a.fn.actions=function(m){var b=a.extend({},a.fn.actions.defaults,m),f=a(this),e=!1,j=function(c){c?h():i();a(f).attr("checked",c).parent().parent().toggleClass(b.selectedClass,c)},g=function(){var c=a(f).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},!0));a(b.allToggle).attr("checked",function(){c==f.length?(value=!0,h()):(value=!1,k());return value})},h=function(){a(b.acrossClears).hide(); -a(b.acrossQuestions).show();a(b.allContainer).hide()},l=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},i=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},k=function(){i();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show();a(this).filter(":checked").each(function(){a(this).parent().parent().toggleClass(b.selectedClass); -g();1==a(b.acrossInput).val()&&l()});a(b.allToggle).show().click(function(){j(a(this).attr("checked"));g()});a("div.actions span.question a").click(function(c){c.preventDefault();a(b.acrossInput).val(1);l()});a("div.actions span.clear a").click(function(c){c.preventDefault();a(b.allToggle).attr("checked",!1);k();j(0);g()});lastChecked=null;a(f).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(lastChecked&&a.data(lastChecked)!=a.data(d)&&!0===c.shiftKey){var e=!1;a(lastChecked).attr("checked", -d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(f).each(function(){if(a.data(this)==a.data(lastChecked)||a.data(this)==a.data(d))e=e?false:true;e&&a(this).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);lastChecked=d;g()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){e=!0});a('form#changelist-form button[name="index"]').click(function(){if(e)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))}); -a('form#changelist-form input[name="_save"]').click(function(){var b=!1;a("div.actions select option:selected").each(function(){a(this).val()&&(b=!0)});if(b)return e?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")):confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})}; -a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery); +(function(a){a.fn.actions=function(n){var b=a.extend({},a.fn.actions.defaults,n),e=a(this),g=false,k=function(c){c?i():j();a(e).attr("checked",c).parent().parent().toggleClass(b.selectedClass,c)},f=function(){var c=a(e).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},true));a(b.allToggle).attr("checked",function(){if(c==e.length){value=true;i()}else{value=false;l()}return value})},i= +function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()},m=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},j=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},l=function(){j();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show(); +a(this).filter(":checked").each(function(){a(this).parent().parent().toggleClass(b.selectedClass);f();a(b.acrossInput).val()==1&&m()});a(b.allToggle).show().click(function(){k(a(this).attr("checked"));f()});a("div.actions span.question a").click(function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("div.actions span.clear a").click(function(c){c.preventDefault();a(b.allToggle).attr("checked",false);l();k(0);f()});lastChecked=null;a(e).click(function(c){if(!c)c=window.event;var d=c.target? +c.target:c.srcElement;if(lastChecked&&a.data(lastChecked)!=a.data(d)&&c.shiftKey===true){var h=false;a(lastChecked).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(e).each(function(){if(a.data(this)==a.data(lastChecked)||a.data(this)==a.data(d))h=h?false:true;h&&a(this).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);lastChecked=d;f()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){g= +true});a('form#changelist-form button[name="index"]').click(function(){if(g)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});a('form#changelist-form input[name="_save"]').click(function(){var c=false;a("div.actions select option:selected").each(function(){if(a(this).val())c=true});if(c)return g?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")): +confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})};a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery); diff --git a/django/contrib/admin/static/admin/js/collapse.min.js b/django/contrib/admin/static/admin/js/collapse.min.js index 5e41b3c86c..5bf26ec104 100644 --- a/django/contrib/admin/static/admin/js/collapse.min.js +++ b/django/contrib/admin/static/admin/js/collapse.min.js @@ -1,2 +1,2 @@ -(function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){0==a(b).find("div.errors").length&&a(b).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").toggle(function(){a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]);return!1},function(){a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", -[a(this).attr("id")]);return!1})})})(django.jQuery); +(function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){a(b).find("div.errors").length==0&&a(b).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").toggle(function(){a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]);return false},function(){a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", +[a(this).attr("id")]);return false})})})(django.jQuery); diff --git a/django/contrib/admin/static/admin/js/inlines.js b/django/contrib/admin/static/admin/js/inlines.js index 668e1157c8..c11af82f76 100644 --- a/django/contrib/admin/static/admin/js/inlines.js +++ b/django/contrib/admin/static/admin/js/inlines.js @@ -44,7 +44,7 @@ if ($(this).attr("tagName") == "TR") { // If forms are laid out as table rows, insert the // "add" button in a new table row: - var numCols = this.eq(0).children().length; + var numCols = this.eq(-1).children().length; $(this).parent().append('' + options.addText + ""); addButton = $(this).parent().find("tr:last a"); } else { diff --git a/django/contrib/admin/static/admin/js/inlines.min.js b/django/contrib/admin/static/admin/js/inlines.min.js index 53dd92f2f9..1a7bd2cdf8 100644 --- a/django/contrib/admin/static/admin/js/inlines.min.js +++ b/django/contrib/admin/static/admin/js/inlines.min.js @@ -1,5 +1,5 @@ -(function(b){b.fn.formset=function(c){var a=b.extend({},b.fn.formset.defaults,c),j=function(a,f,d){var e=RegExp("("+f+"-(\\d+|__prefix__))"),f=f+"-"+d;b(a).attr("for")&&b(a).attr("for",b(a).attr("for").replace(e,f));a.id&&(a.id=a.id.replace(e,f));a.name&&(a.name=a.name.replace(e,f))},c=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off"),h=parseInt(c.val(),10),g=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off"),c=""===g.val()||0'+a.addText+""),i=b(this).parent().find("tr:last a")):(b(this).filter(":last").after('"),i=b(this).filter(":last").next().find("a"));i.click(function(c){c.preventDefault(); -var f=b("#id_"+a.prefix+"-TOTAL_FORMS"),c=b("#"+a.prefix+"-empty"),d=c.clone(true);d.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+h);d.is("tr")?d.children(":last").append('"):d.is("ul")||d.is("ol")?d.append('
  • '+a.deleteText+"
  • "):d.children(":first").append(''+ -a.deleteText+"");d.find("*").each(function(){j(this,a.prefix,f.val())});d.insertBefore(b(c));b(f).val(parseInt(f.val(),10)+1);h=h+1;g.val()!==""&&g.val()-f.val()<=0&&i.parent().hide();d.find("a."+a.deleteCssClass).click(function(e){e.preventDefault();e=b(this).parents("."+a.formCssClass);e.remove();h=h-1;a.removed&&a.removed(e);e=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(e.length);(g.val()===""||g.val()-e.length>0)&&i.parent().show();for(var c=0,d=e.length;c0;b(this).each(function(){b(this).not("."+ +a.emptyCssClass).addClass(a.formCssClass)});if(b(this).length&&g){var j;if(b(this).attr("tagName")=="TR"){g=this.eq(-1).children().length;b(this).parent().append(''+a.addText+"");j=b(this).parent().find("tr:last a")}else{b(this).filter(":last").after('");j=b(this).filter(":last").next().find("a")}j.click(function(c){c.preventDefault(); +var f=b("#id_"+a.prefix+"-TOTAL_FORMS");c=b("#"+a.prefix+"-empty");var e=c.clone(true);e.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+l);if(e.is("tr"))e.children(":last").append('");else e.is("ul")||e.is("ol")?e.append('
  • '+a.deleteText+"
  • "):e.children(":first").append(''+ +a.deleteText+"");e.find("*").each(function(){k(this,a.prefix,f.val())});e.insertBefore(b(c));b(f).val(parseInt(f.val(),10)+1);l+=1;h.val()!==""&&h.val()-f.val()<=0&&j.parent().hide();e.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();l-=1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);if(h.val()===""||h.val()-d.length>0)j.parent().show();for(var i=0,m=d.length;i0&&e.push(a(f).val())});b.val(URLify(e.join(" "),g))}};a(d.join(",")).keyup(c).change(c).focus(c)})}})(django.jQuery); diff --git a/django/contrib/admin/templates/admin/auth/user/change_password.html b/django/contrib/admin/templates/admin/auth/user/change_password.html index 4fdccd13d2..f367223bc5 100644 --- a/django/contrib/admin/templates/admin/auth/user/change_password.html +++ b/django/contrib/admin/templates/admin/auth/user/change_password.html @@ -3,8 +3,7 @@ {% load admin_urls %} {% block extrahead %}{{ block.super }} -{% url 'admin:jsi18n' as jsi18nurl %} - + {% endblock %} {% block extrastyle %}{{ block.super }}{% endblock %} {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index 79c4f1bdf4..82d7296c85 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -3,8 +3,7 @@ {% load admin_urls %} {% block extrahead %}{{ block.super }} -{% url 'admin:jsi18n' as jsi18nurl %} - + {{ media }} {% endblock %} @@ -31,7 +30,7 @@ {% endif %}{% endif %} @@ -65,7 +64,7 @@ {% block submit_buttons_bottom %}{% submit_row %}{% endblock %} -{% if adminform and add %} +{% if adminform.first_field and add %} {% endif %} diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index 3c73ac8745..c72b6630a3 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -9,8 +9,7 @@ {% endif %} {% if cl.formset or action_form %} - {% url 'admin:jsi18n' as jsi18nurl %} - + {% endif %} {{ media.css }} {% if not actions_on_top and not actions_on_bottom %} diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html index 71d3eb9f0f..c1a711534d 100644 --- a/django/contrib/admin/templates/admin/delete_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_confirmation.html @@ -7,7 +7,7 @@ {% trans 'Home' %}{{ app_label|capfirst }}{{ opts.verbose_name_plural|capfirst|escape }} -› {{ object|truncatewords:"18" }} +› {{ object|truncatewords:"18" }} › {% trans 'Delete' %} {% endblock %} diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html index 08e8ca306d..d57d3dad49 100644 --- a/django/contrib/admin/templates/admin/edit_inline/stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html @@ -6,7 +6,7 @@ {% for inline_admin_form in inline_admin_formset %}

    {{ inline_admin_formset.opts.verbose_name|title }}: {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %} - {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %} + {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %} {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}{% endif %}

    {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index 9e215afd92..4f49153819 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -27,7 +27,7 @@ {% if inline_admin_form.original or inline_admin_form.show_url %}

    {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %} - {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %} + {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %}

    {% endif %} {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %} {{ inline_admin_form.fk_field.field }} diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html index 2c353374f6..55dd4a3b4c 100644 --- a/django/contrib/admin/templates/admin/object_history.html +++ b/django/contrib/admin/templates/admin/object_history.html @@ -7,7 +7,7 @@ {% trans 'Home' %}{{ app_label|capfirst|escape }}{{ module_name }} -› {{ object|truncatewords:"18" }} +› {{ object|truncatewords:"18" }} › {% trans 'History' %}
    {% endblock %} diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index bda8d4b4cd..1873d44989 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import datetime from django.contrib.admin.util import (lookup_field, display_for_field, @@ -8,11 +10,12 @@ from django.contrib.admin.templatetags.admin_static import static from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.utils import formats -from django.utils.html import escape, conditional_escape +from django.utils.html import format_html from django.utils.safestring import mark_safe +from django.utils import six from django.utils.text import capfirst from django.utils.translation import ugettext as _ -from django.utils.encoding import smart_unicode, force_unicode +from django.utils.encoding import smart_text, force_text from django.template import Library from django.template.loader import get_template from django.template.context import Context @@ -27,11 +30,14 @@ def paginator_number(cl,i): Generates an individual page index link in a paginated list. """ if i == DOT: - return u'... ' + return '... ' elif i == cl.page_num: - return mark_safe(u'%d ' % (i+1)) + return format_html('{0} ', i+1) else: - return mark_safe(u'%d ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) + return format_html('{2} ', + cl.get_query_string({PAGE_VAR: i}), + mark_safe(' class="end"' if i == cl.paginator.num_pages-1 else ''), + i+1) @register.inclusion_tag('admin/pagination.html') def pagination(cl): @@ -120,7 +126,7 @@ def result_headers(cl): if i in ordering_field_columns: sorted = True order_type = ordering_field_columns.get(i).lower() - sort_priority = ordering_field_columns.keys().index(i) + 1 + sort_priority = list(ordering_field_columns).index(i) + 1 th_classes.append('sorted %sending' % order_type) new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type] @@ -157,13 +163,14 @@ def result_headers(cl): "url_primary": cl.get_query_string({ORDER_VAR: '.'.join(o_list_primary)}), "url_remove": cl.get_query_string({ORDER_VAR: '.'.join(o_list_remove)}), "url_toggle": cl.get_query_string({ORDER_VAR: '.'.join(o_list_toggle)}), - "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '') + "class_attrib": format_html(' class="{0}"', ' '.join(th_classes)) + if th_classes else '', } def _boolean_icon(field_val): icon_url = static('admin/img/icon-%s.gif' % {True: 'yes', False: 'no', None: 'unknown'}[field_val]) - return mark_safe(u'%s' % (icon_url, field_val)) + return format_html('{1}', icon_url, field_val) def items_for_result(cl, result, form): """ @@ -179,8 +186,8 @@ def items_for_result(cl, result, form): result_repr = EMPTY_CHANGELIST_VALUE else: if f is None: - if field_name == u'action_checkbox': - row_class = ' class="action-checkbox"' + if field_name == 'action_checkbox': + row_class = mark_safe(' class="action-checkbox"') allow_tags = getattr(attr, 'allow_tags', False) boolean = getattr(attr, 'boolean', False) if boolean: @@ -188,24 +195,22 @@ def items_for_result(cl, result, form): result_repr = display_for_value(value, boolean) # Strip HTML tags in the resulting text, except if the # function has an "allow_tags" attribute set to True. - if not allow_tags: - result_repr = escape(result_repr) - else: + if allow_tags: result_repr = mark_safe(result_repr) if isinstance(value, (datetime.date, datetime.time)): - row_class = ' class="nowrap"' + row_class = mark_safe(' class="nowrap"') else: if isinstance(f.rel, models.ManyToOneRel): field_val = getattr(result, f.name) if field_val is None: result_repr = EMPTY_CHANGELIST_VALUE else: - result_repr = escape(field_val) + result_repr = field_val else: result_repr = display_for_field(value, f) if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)): - row_class = ' class="nowrap"' - if force_unicode(result_repr) == '': + row_class = mark_safe(' class="nowrap"') + if force_text(result_repr) == '': result_repr = mark_safe(' ') # If list_display_links not defined, add the link tag to the first field if (first and not cl.list_display_links) or field_name in cl.list_display_links: @@ -219,9 +224,15 @@ def items_for_result(cl, result, form): else: attr = pk value = result.serializable_value(attr) - result_id = repr(force_unicode(value))[1:] - yield mark_safe(u'<%s%s>%s' % \ - (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag)) + result_id = repr(force_text(value))[1:] + yield format_html('<{0}{1}>{4}', + table_tag, + row_class, + url, + format_html(' onclick="opener.dismissRelatedLookupPopup(window, {0}); return false;"', result_id) + if cl.is_popup else '', + result_repr, + table_tag) else: # By default the fields come from ModelAdmin.list_editable, but if we pull # the fields out of the form instead of list_editable custom admins @@ -230,12 +241,10 @@ def items_for_result(cl, result, form): field_name == cl.model._meta.pk.name and form[cl.model._meta.pk.name].is_hidden)): bf = form[field_name] - result_repr = mark_safe(force_unicode(bf.errors) + force_unicode(bf)) - else: - result_repr = conditional_escape(result_repr) - yield mark_safe(u'%s' % (row_class, result_repr)) + result_repr = mark_safe(force_text(bf.errors) + force_text(bf)) + yield format_html('{1}', row_class, result_repr) if form and not form[cl.model._meta.pk.name].is_hidden: - yield mark_safe(u'%s' % force_unicode(form[cl.model._meta.pk.name])) + yield format_html('{0}', force_text(form[cl.model._meta.pk.name])) class ResultList(list): # Wrapper class used to return items in a list_editable @@ -258,7 +267,7 @@ def result_hidden_fields(cl): if cl.formset: for res, form in zip(cl.result_list, cl.formset.forms): if form[cl.model._meta.pk.name].is_hidden: - yield mark_safe(force_unicode(form[cl.model._meta.pk.name])) + yield mark_safe(force_text(form[cl.model._meta.pk.name])) @register.inclusion_tag("admin/change_list_results.html") def result_list(cl): diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index e55b3bf2bd..c190533f95 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -32,7 +32,7 @@ def submit_row(context): 'onclick_attrib': (opts.get_ordered_objects() and change and 'onclick="submitOrderForm();"' or ''), 'show_delete_link': (not is_popup and context['has_delete_permission'] - and (change or context['show_delete'])), + and change and context.get('show_delete', True)), 'show_save_as_new': not is_popup and change and save_as, 'show_save_and_add_another': context['has_add_permission'] and not is_popup and (not save_as or context['add']), diff --git a/django/contrib/admin/templatetags/admin_urls.py b/django/contrib/admin/templatetags/admin_urls.py index 53dc65b567..90e81b0ef3 100644 --- a/django/contrib/admin/templatetags/admin_urls.py +++ b/django/contrib/admin/templatetags/admin_urls.py @@ -1,8 +1,14 @@ -from django.core.urlresolvers import reverse, NoReverseMatch +from django.core.urlresolvers import reverse from django import template +from django.contrib.admin.util import quote register = template.Library() @register.filter def admin_urlname(value, arg): return 'admin:%s_%s_%s' % (value.app_label, value.module_name, arg) + + +@register.filter +def admin_urlquote(value): + return quote(value) diff --git a/django/contrib/admin/templatetags/log.py b/django/contrib/admin/templatetags/log.py index 888b5ed9c3..463e0792f0 100644 --- a/django/contrib/admin/templatetags/log.py +++ b/django/contrib/admin/templatetags/log.py @@ -17,7 +17,7 @@ class AdminLogNode(template.Node): user_id = self.user if not user_id.isdigit(): user_id = context[self.user].id - context[self.varname] = LogEntry.objects.filter(user__id__exact=user_id).select_related('content_type', 'user')[:self.limit] + context[self.varname] = LogEntry.objects.filter(user__id__exact=user_id).select_related('content_type', 'user')[:int(self.limit)] return '' @register.tag diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 9611b00050..ff90e1d007 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import datetime import decimal @@ -7,11 +9,11 @@ from django.db.models.deletion import Collector from django.db.models.related import RelatedObject from django.forms.forms import pretty_name from django.utils import formats -from django.utils.html import escape -from django.utils.safestring import mark_safe +from django.utils.html import format_html from django.utils.text import capfirst from django.utils import timezone -from django.utils.encoding import force_unicode, smart_unicode, smart_str +from django.utils.encoding import force_text, smart_text, smart_bytes +from django.utils import six from django.utils.translation import ungettext from django.core.urlresolvers import reverse @@ -50,7 +52,7 @@ def quote(s): quoting is slightly different so that it doesn't get automatically unquoted by the Web browser. """ - if not isinstance(s, basestring): + if not isinstance(s, six.string_types): return s res = list(s) for i in range(len(res)): @@ -122,15 +124,15 @@ def get_deleted_objects(objs, opts, user, admin_site, using): if not user.has_perm(p): perms_needed.add(opts.verbose_name) # Display a link to the admin page. - return mark_safe(u'%s: %s' % - (escape(capfirst(opts.verbose_name)), - admin_url, - escape(obj))) + return format_html('{0}: {2}', + capfirst(opts.verbose_name), + admin_url, + obj) else: # Don't display link to edit, because it either has no # admin or is edited inline. - return u'%s: %s' % (capfirst(opts.verbose_name), - force_unicode(obj)) + return '%s: %s' % (capfirst(opts.verbose_name), + force_text(obj)) to_delete = collector.nested(format_callback) @@ -205,8 +207,8 @@ def model_format_dict(obj): else: opts = obj return { - 'verbose_name': force_unicode(opts.verbose_name), - 'verbose_name_plural': force_unicode(opts.verbose_name_plural) + 'verbose_name': force_text(opts.verbose_name), + 'verbose_name_plural': force_text(opts.verbose_name_plural) } @@ -272,11 +274,11 @@ def label_for_field(name, model, model_admin=None, return_attr=False): label = field.verbose_name except models.FieldDoesNotExist: if name == "__unicode__": - label = force_unicode(model._meta.verbose_name) - attr = unicode + label = force_text(model._meta.verbose_name) + attr = six.text_type elif name == "__str__": - label = smart_str(model._meta.verbose_name) - attr = str + label = smart_bytes(model._meta.verbose_name) + attr = bytes else: if callable(name): attr = name @@ -309,7 +311,7 @@ def help_text_for_field(name, model): help_text = model._meta.get_field_by_name(name)[0].help_text except models.FieldDoesNotExist: help_text = "" - return smart_unicode(help_text) + return smart_text(help_text) def display_for_field(value, field): @@ -333,7 +335,7 @@ def display_for_field(value, field): elif isinstance(field, models.FloatField): return formats.number_format(value) else: - return smart_unicode(value) + return smart_text(value) def display_for_value(value, boolean=False): @@ -348,10 +350,10 @@ def display_for_value(value, boolean=False): return formats.localize(timezone.template_localtime(value)) elif isinstance(value, (datetime.date, datetime.time)): return formats.localize(value) - elif isinstance(value, (decimal.Decimal, float, int, long)): + elif isinstance(value, six.integer_types + (decimal.Decimal, float)): return formats.number_format(value) else: - return smart_unicode(value) + return smart_text(value) class NotRelationField(Exception): diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 85e03f3b75..30074f1648 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -6,7 +6,7 @@ from django.core.paginator import InvalidPage from django.db import models from django.db.models.fields import FieldDoesNotExist from django.utils.datastructures import SortedDict -from django.utils.encoding import force_unicode, smart_str +from django.utils.encoding import force_text, smart_bytes from django.utils.translation import ugettext, ugettext_lazy from django.utils.http import urlencode @@ -75,7 +75,7 @@ class ChangeList(object): title = ugettext('Select %s') else: title = ugettext('Select %s to change') - self.title = title % force_unicode(self.opts.verbose_name) + self.title = title % force_text(self.opts.verbose_name) self.pk_attname = self.lookup_opts.pk.attname def get_filters(self, request): @@ -94,7 +94,7 @@ class ChangeList(object): # 'key' will be used as a keyword argument later, so Python # requires it to be a string. del lookup_params[key] - lookup_params[smart_str(key)] = value + lookup_params[smart_bytes(key)] = value if not self.model_admin.lookup_allowed(key, value): raise SuspiciousOperation("Filtering by %s not allowed" % key) @@ -148,7 +148,7 @@ class ChangeList(object): if remove is None: remove = [] p = self.params.copy() for r in remove: - for k in p.keys(): + for k in list(p): if k.startswith(r): del p[k] for k, v in new_params.items(): diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 29958b27a4..1e0bc2d366 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -1,18 +1,21 @@ """ Form Widget classes specific to the Django admin site. """ +from __future__ import unicode_literals import copy + from django import forms from django.contrib.admin.templatetags.admin_static import static from django.core.urlresolvers import reverse from django.forms.widgets import RadioFieldRenderer from django.forms.util import flatatt -from django.utils.html import escape +from django.utils.html import escape, format_html, format_html_join from django.utils.text import Truncator from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text +from django.utils import six class FilteredSelectMultiple(forms.SelectMultiple): @@ -39,12 +42,12 @@ class FilteredSelectMultiple(forms.SelectMultiple): if self.is_stacked: attrs['class'] += 'stacked' output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)] - output.append(u'\n' + output.append('SelectFilter.init("id_%s", "%s", %s, "%s"); });\n' % (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), static('admin/'))) - return mark_safe(u''.join(output)) + return mark_safe(''.join(output)) class AdminDateWidget(forms.DateInput): @@ -83,24 +86,25 @@ class AdminSplitDateTime(forms.SplitDateTimeWidget): forms.MultiWidget.__init__(self, widgets, attrs) def format_output(self, rendered_widgets): - return mark_safe(u'

    %s %s
    %s %s

    ' % \ - (_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1])) + return format_html('

    {0} {1}
    {2} {3}

    ', + _('Date:'), rendered_widgets[0], + _('Time:'), rendered_widgets[1]) class AdminRadioFieldRenderer(RadioFieldRenderer): def render(self): """Outputs a
      for this set of radio fields.""" - return mark_safe(u'\n%s\n
    ' % ( - flatatt(self.attrs), - u'\n'.join([u'
  • %s
  • ' % force_unicode(w) for w in self])) - ) + return format_html('\n{1}\n
', + flatatt(self.attrs), + format_html_join('\n', '
  • {0}
  • ', + ((force_text(w),) for w in self))) class AdminRadioSelect(forms.RadioSelect): renderer = AdminRadioFieldRenderer class AdminFileWidget(forms.ClearableFileInput): - template_with_initial = (u'

    %s

    ' + template_with_initial = ('

    %s

    ' % forms.ClearableFileInput.template_with_initial) - template_with_clear = (u'%s' + template_with_clear = ('%s' % forms.ClearableFileInput.template_with_clear) def url_params_from_lookup_dict(lookups): @@ -113,12 +117,12 @@ def url_params_from_lookup_dict(lookups): items = [] for k, v in lookups.items(): if isinstance(v, (tuple, list)): - v = u','.join([str(x) for x in v]) + v = ','.join([str(x) for x in v]) elif isinstance(v, bool): # See django.db.fields.BooleanField.get_prep_lookup v = ('0', '1')[v] else: - v = unicode(v) + v = six.text_type(v) items.append((k, v)) params.update(dict(items)) return params @@ -148,21 +152,21 @@ class ForeignKeyRawIdWidget(forms.TextInput): params = self.url_parameters() if params: - url = u'?' + u'&'.join([u'%s=%s' % (k, v) for k, v in params.items()]) + url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()]) else: - url = u'' + url = '' if "class" not in attrs: attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript code looks for this hook. # TODO: "lookup_id_" is hard-coded here. This should instead use # the correct API to determine the ID dynamically. - extra.append(u' ' + extra.append(' ' % (related_url, url, name)) - extra.append(u'%s' + extra.append('%s' % (static('admin/img/selector-search.gif'), _('Lookup'))) output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + extra if value: output.append(self.label_for_value(value)) - return mark_safe(u''.join(output)) + return mark_safe(''.join(output)) def base_url_parameters(self): return url_params_from_lookup_dict(self.rel.limit_choices_to) @@ -193,7 +197,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): # The related object is registered with the same AdminSite attrs['class'] = 'vManyToManyRawIdAdminField' if value: - value = ','.join([force_unicode(v) for v in value]) + value = ','.join([force_text(v) for v in value]) else: value = '' return super(ManyToManyRawIdWidget, self).render(name, value, attrs) @@ -217,7 +221,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): if len(initial) != len(data): return True for pk1, pk2 in zip(initial, data): - if force_unicode(pk1) != force_unicode(pk2): + if force_text(pk1) != force_text(pk2): return True return False @@ -261,11 +265,11 @@ class RelatedFieldWidgetWrapper(forms.Widget): related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name) # TODO: "add_id_" is hard-coded here. This should instead use the # correct API to determine the ID dynamically. - output.append(u' ' + output.append(' ' % (related_url, name)) - output.append(u'%s' + output.append('%s' % (static('admin/img/icon_addlink.gif'), _('Add Another'))) - return mark_safe(u''.join(output)) + return mark_safe(''.join(output)) def build_attrs(self, extra_attrs=None, **kwargs): "Helper function for building an attribute dictionary." @@ -303,12 +307,17 @@ class AdminURLFieldWidget(forms.TextInput): super(AdminURLFieldWidget, self).__init__(attrs=final_attrs) class AdminIntegerFieldWidget(forms.TextInput): + class_name = 'vIntegerField' + def __init__(self, attrs=None): - final_attrs = {'class': 'vIntegerField'} + final_attrs = {'class': self.class_name} if attrs is not None: final_attrs.update(attrs) super(AdminIntegerFieldWidget, self).__init__(attrs=final_attrs) +class AdminBigIntegerFieldWidget(AdminIntegerFieldWidget): + class_name = 'vBigIntegerField' + class AdminCommaSeparatedIntegerFieldWidget(forms.TextInput): def __init__(self, attrs=None): final_attrs = {'class': 'vCommaSeparatedIntegerField'} diff --git a/django/contrib/admindocs/templates/admin_doc/bookmarklets.html b/django/contrib/admindocs/templates/admin_doc/bookmarklets.html index cde285481d..819beea326 100644 --- a/django/contrib/admindocs/templates/admin_doc/bookmarklets.html +++ b/django/contrib/admindocs/templates/admin_doc/bookmarklets.html @@ -22,7 +22,7 @@ your computer is "internal").

    {% endblocktrans %}
    -

    {% trans "Documentation for this page" %}

    +

    {% trans "Documentation for this page" %}

    {% trans "Jumps you from any page to the documentation for the view that generates that page." %}

    {% trans "Show object ID" %}

    diff --git a/django/contrib/admindocs/tests/__init__.py b/django/contrib/admindocs/tests/__init__.py index 49a925a453..306475beb1 100644 --- a/django/contrib/admindocs/tests/__init__.py +++ b/django/contrib/admindocs/tests/__init__.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.admindocs import views from django.db.models import fields as builtin_fields @@ -20,17 +20,17 @@ class TestFieldType(unittest.TestCase): def test_builtin_fields(self): self.assertEqual( views.get_readable_field_data_type(builtin_fields.BooleanField()), - _(u'Boolean (Either True or False)') + _('Boolean (Either True or False)') ) def test_custom_fields(self): self.assertEqual( views.get_readable_field_data_type(fields.CustomField()), - _(u'A custom field type') + _('A custom field type') ) self.assertEqual( views.get_readable_field_data_type(fields.DescriptionLackingField()), - _(u'Field of type: %(field_type)s') % { + _('Field of type: %(field_type)s') % { 'field_type': 'DescriptionLackingField' } ) diff --git a/django/contrib/admindocs/utils.py b/django/contrib/admindocs/utils.py index 4bf1250ac6..0e10eb4fa3 100644 --- a/django/contrib/admindocs/utils.py +++ b/django/contrib/admindocs/utils.py @@ -6,7 +6,7 @@ from email.errors import HeaderParseError from django.utils.safestring import mark_safe from django.core.urlresolvers import reverse -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes try: import docutils.core import docutils.nodes @@ -66,7 +66,7 @@ def parse_rst(text, default_reference_context, thing_being_parsed=None): "link_base" : reverse('django-admindocs-docroot').rstrip('/') } if thing_being_parsed: - thing_being_parsed = smart_str("<%s>" % thing_being_parsed) + thing_being_parsed = smart_bytes("<%s>" % thing_being_parsed) parts = docutils.core.publish_parts(text, source_path=thing_being_parsed, destination_path=None, writer_name='html', settings_overrides=overrides) diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index aad698836e..94963b4d39 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -14,6 +14,7 @@ from django.core import urlresolvers from django.contrib.admindocs import utils from django.contrib.sites.models import Site from django.utils.importlib import import_module +from django.utils import six from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe @@ -37,7 +38,7 @@ def bookmarklets(request): admin_root = urlresolvers.reverse('admin:index') return render_to_response('admin_doc/bookmarklets.html', { 'root_path': admin_root, - 'admin_url': mark_safe("%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root)), + 'admin_url': "%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root), }, context_instance=RequestContext(request)) @staff_member_required @@ -48,7 +49,7 @@ def template_tag_index(request): load_all_installed_template_libraries() tags = [] - app_libs = template.libraries.items() + app_libs = list(six.iteritems(template.libraries)) builtin_libs = [(None, lib) for lib in template.builtins] for module_name, library in builtin_libs + app_libs: for tag_name, tag_func in library.tags.items(): @@ -83,7 +84,7 @@ def template_filter_index(request): load_all_installed_template_libraries() filters = [] - app_libs = template.libraries.items() + app_libs = list(six.iteritems(template.libraries)) builtin_libs = [(None, lib) for lib in template.builtins] for module_name, library in builtin_libs + app_libs: for filter_name, filter_func in library.filters.items(): diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py index f14b3d219b..ccf940d16d 100644 --- a/django/contrib/auth/admin.py +++ b/django/contrib/auth/admin.py @@ -12,6 +12,7 @@ from django.template.response import TemplateResponse from django.utils.html import escape from django.utils.decorators import method_decorator from django.utils.safestring import mark_safe +from django.utils import six from django.utils.translation import ugettext, ugettext_lazy as _ from django.views.decorators.csrf import csrf_protect from django.views.decorators.debug import sensitive_post_parameters @@ -128,13 +129,13 @@ class UserAdmin(admin.ModelAdmin): else: form = self.change_password_form(user) - fieldsets = [(None, {'fields': form.base_fields.keys()})] + fieldsets = [(None, {'fields': list(form.base_fields)})] adminForm = admin.helpers.AdminForm(form, fieldsets, {}) context = { 'title': _('Change password: %s') % escape(user.username), 'adminForm': adminForm, - 'form_url': mark_safe(form_url), + 'form_url': form_url, 'form': form, 'is_popup': '_popup' in request.REQUEST, 'add': True, diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py index da4c9c727a..427fc1408d 100644 --- a/django/contrib/auth/backends.py +++ b/django/contrib/auth/backends.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission @@ -40,7 +41,7 @@ class ModelBackend(object): if user_obj.is_anonymous() or obj is not None: return set() if not hasattr(user_obj, '_perm_cache'): - user_obj._perm_cache = set([u"%s.%s" % (p.content_type.app_label, p.codename) for p in user_obj.user_permissions.select_related()]) + user_obj._perm_cache = set(["%s.%s" % (p.content_type.app_label, p.codename) for p in user_obj.user_permissions.select_related()]) user_obj._perm_cache.update(self.get_group_permissions(user_obj)) return user_obj._perm_cache diff --git a/django/contrib/auth/context_processors.py b/django/contrib/auth/context_processors.py index 3ffab01e94..1b6c2eedd0 100644 --- a/django/contrib/auth/context_processors.py +++ b/django/contrib/auth/context_processors.py @@ -11,8 +11,9 @@ class PermLookupDict(object): def __getitem__(self, perm_name): return self.user.has_perm("%s.%s" % (self.module_name, perm_name)) - def __nonzero__(self): + def __bool__(self): return self.user.has_module_perms(self.module_name) + __nonzero__ = __bool__ # Python 2 class PermWrapper(object): diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index 5805a3122c..beeb284998 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -1,9 +1,13 @@ -import urlparse +try: + from urllib.parse import urlparse +except ImportError: # Python 2 + from urlparse import urlparse from functools import wraps from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME from django.core.exceptions import PermissionDenied from django.utils.decorators import available_attrs +from django.utils.encoding import force_str def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): @@ -19,11 +23,12 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE if test_func(request.user): return view_func(request, *args, **kwargs) path = request.build_absolute_uri() + # urlparse chokes on lazy objects in Python 3 + login_url_as_str = force_str(login_url or settings.LOGIN_URL) # If the login url is the same scheme and net location then just # use the path as the "next" url. - login_scheme, login_netloc = urlparse.urlparse(login_url or - settings.LOGIN_URL)[:2] - current_scheme, current_netloc = urlparse.urlparse(path)[:2] + login_scheme, login_netloc = urlparse(login_url_as_str)[:2] + current_scheme, current_netloc = urlparse(path)[:2] if ((not login_scheme or login_scheme == current_scheme) and (not login_netloc or login_netloc == current_netloc)): path = request.get_full_path() diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 2cb7da0772..75b3ca4ece 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -1,17 +1,21 @@ +from __future__ import unicode_literals + from django import forms from django.forms.util import flatatt from django.template import loader -from django.utils.encoding import smart_str +from django.utils.datastructures import SortedDict +from django.utils.html import format_html, format_html_join from django.utils.http import int_to_base36 from django.utils.safestring import mark_safe from django.utils.translation import ugettext, ugettext_lazy as _ from django.contrib.auth import authenticate from django.contrib.auth.models import User -from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, get_hasher +from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, identify_hasher from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.models import get_current_site + UNMASKED_DIGITS_TO_SHOW = 6 mask_password = lambda p: "%s%s" % (p[:UNMASKED_DIGITS_TO_SHOW], "*" * max(len(p) - UNMASKED_DIGITS_TO_SHOW, 0)) @@ -26,23 +30,18 @@ class ReadOnlyPasswordHashWidget(forms.Widget): final_attrs = self.build_attrs(attrs) - encoded = smart_str(encoded) - - if len(encoded) == 32 and '$' not in encoded: - algorithm = 'unsalted_md5' - else: - algorithm = encoded.split('$', 1)[0] - try: - hasher = get_hasher(algorithm) + hasher = identify_hasher(encoded) except ValueError: - summary = "Invalid password format or unknown hashing algorithm." + summary = mark_safe("Invalid password format or unknown hashing algorithm.") else: - summary = "" - for key, value in hasher.safe_summary(encoded).iteritems(): - summary += "%(key)s: %(value)s " % {"key": ugettext(key), "value": value} + summary = format_html_join('', + "{0}: {1} ", + ((ugettext(key), value) + for key, value in hasher.safe_summary(encoded).items()) + ) - return mark_safe("%(summary)s
    " % {"attrs": flatatt(final_attrs), "summary": summary}) + return format_html("{1}", flatatt(final_attrs), summary) class ReadOnlyPasswordHashField(forms.Field): @@ -90,9 +89,9 @@ class UserCreationForm(forms.ModelForm): raise forms.ValidationError(self.error_messages['duplicate_username']) def clean_password2(self): - password1 = self.cleaned_data.get("password1", "") - password2 = self.cleaned_data["password2"] - if password1 != password2: + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: raise forms.ValidationError( self.error_messages['password_mismatch']) return password2 @@ -296,8 +295,11 @@ class PasswordChangeForm(SetPasswordForm): raise forms.ValidationError( self.error_messages['password_incorrect']) return old_password -PasswordChangeForm.base_fields.keyOrder = ['old_password', 'new_password1', - 'new_password2'] + +PasswordChangeForm.base_fields = SortedDict([ + (k, PasswordChangeForm.base_fields[k]) + for k in ['old_password', 'new_password1', 'new_password2'] +]) class AdminPasswordChangeForm(forms.Form): diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 52d204cce6..cdaf75636f 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -1,3 +1,6 @@ +from __future__ import unicode_literals + +import base64 import hashlib from django.dispatch import receiver @@ -5,7 +8,7 @@ from django.conf import settings from django.test.signals import setting_changed from django.utils import importlib from django.utils.datastructures import SortedDict -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes from django.core.exceptions import ImproperlyConfigured from django.utils.crypto import ( pbkdf2, constant_time_compare, get_random_string) @@ -40,20 +43,12 @@ def check_password(password, encoded, setter=None, preferred='default'): return False preferred = get_hasher(preferred) - raw_password = password - password = smart_str(password) - encoded = smart_str(encoded) - - if len(encoded) == 32 and '$' not in encoded: - hasher = get_hasher('unsalted_md5') - else: - algorithm = encoded.split('$', 1)[0] - hasher = get_hasher(algorithm) + hasher = identify_hasher(encoded) must_update = hasher.algorithm != preferred.algorithm is_correct = hasher.verify(password, encoded) if setter and is_correct and must_update: - setter(raw_password) + setter(password) return is_correct @@ -69,11 +64,9 @@ def make_password(password, salt=None, hasher='default'): return UNUSABLE_PASSWORD hasher = get_hasher(hasher) - password = smart_str(password) if not salt: salt = hasher.salt() - salt = smart_str(salt) return hasher.encode(password, salt) @@ -125,6 +118,21 @@ def get_hasher(algorithm='default'): return HASHERS[algorithm] +def identify_hasher(encoded): + """ + Returns an instance of a loaded password hasher. + + Identifies hasher algorithm by examining encoded hash, and calls + get_hasher() to return hasher. Raises ValueError if + algorithm cannot be identified, or if hasher is not loaded. + """ + if len(encoded) == 32 and '$' not in encoded: + algorithm = 'unsalted_md5' + else: + algorithm = encoded.split('$', 1)[0] + return get_hasher(algorithm) + + def mask_hash(hash, show=6, char="*"): """ Returns the given hash, with only the first ``show`` number shown. The @@ -211,7 +219,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher): if not iterations: iterations = self.iterations hash = pbkdf2(password, salt, iterations, digest=self.digest) - hash = hash.encode('base64').strip() + hash = base64.b64encode(hash).decode('ascii').strip() return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash) def verify(self, password, encoded): @@ -291,7 +299,7 @@ class SHA1PasswordHasher(BasePasswordHasher): def encode(self, password, salt): assert password assert salt and '$' not in salt - hash = hashlib.sha1(salt + password).hexdigest() + hash = hashlib.sha1(smart_bytes(salt + password)).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) def verify(self, password, encoded): @@ -319,7 +327,7 @@ class MD5PasswordHasher(BasePasswordHasher): def encode(self, password, salt): assert password assert salt and '$' not in salt - hash = hashlib.md5(salt + password).hexdigest() + hash = hashlib.md5(smart_bytes(salt + password)).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) def verify(self, password, encoded): @@ -353,7 +361,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher): return '' def encode(self, password, salt): - return hashlib.md5(password).hexdigest() + return hashlib.md5(smart_bytes(password)).hexdigest() def verify(self, password, encoded): encoded_2 = self.encode(password, '') diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py index 2ae4084416..277c43fd1e 100644 --- a/django/contrib/auth/management/__init__.py +++ b/django/contrib/auth/management/__init__.py @@ -1,6 +1,8 @@ """ Creates permissions for all installed apps that need permissions. """ +from __future__ import unicode_literals + import getpass import locale import unicodedata @@ -8,17 +10,18 @@ import unicodedata from django.contrib.auth import models as auth_app, get_user_model from django.core import exceptions from django.db.models import get_models, signals +from django.utils.six.moves import input def _get_permission_codename(action, opts): - return u'%s_%s' % (action, opts.object_name.lower()) + return '%s_%s' % (action, opts.object_name.lower()) def _get_all_permissions(opts): "Returns (codename, name) for all permissions in the given opts." perms = [] for action in ('add', 'change', 'delete'): - perms.append((_get_permission_codename(action, opts), u'Can %s %s' % (action, opts.verbose_name_raw))) + perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name_raw))) return perms + list(opts.permissions) @@ -67,10 +70,10 @@ def create_superuser(app, created_models, verbosity, db, **kwargs): msg = ("\nYou just installed Django's auth system, which means you " "don't have any superusers defined.\nWould you like to create one " "now? (yes/no): ") - confirm = raw_input(msg) + confirm = input(msg) while 1: if confirm not in ('yes', 'no'): - confirm = raw_input('Please enter either "yes" or "no": ') + confirm = input('Please enter either "yes" or "no": ') continue if confirm == 'yes': call_command("createsuperuser", interactive=True, database=db) @@ -84,14 +87,17 @@ def get_system_username(): :returns: The username as a unicode string, or an empty string if the username could not be determined. """ - try: - return getpass.getuser().decode(locale.getdefaultlocale()[1]) - except (ImportError, KeyError, UnicodeDecodeError): - # KeyError will be raised by os.getpwuid() (called by getuser()) - # if there is no corresponding entry in the /etc/passwd file - # (a very restricted chroot environment, for example). - # UnicodeDecodeError - preventive treatment for non-latin Windows. - return u'' + default_locale = locale.getdefaultlocale()[1] + if default_locale: + try: + return getpass.getuser().decode(default_locale) + except (ImportError, KeyError, UnicodeDecodeError): + # KeyError will be raised by os.getpwuid() (called by getuser()) + # if there is no corresponding entry in the /etc/passwd file + # (a very restricted chroot environment, for example). + # UnicodeDecodeError - preventive treatment for non-latin Windows. + pass + return '' def get_default_username(check_db=True): @@ -111,7 +117,7 @@ def get_default_username(check_db=True): default_username = get_system_username() try: default_username = unicodedata.normalize('NFKD', default_username)\ - .encode('ascii', 'ignore').replace(' ', '').lower() + .encode('ascii', 'ignore').decode('ascii').replace(' ', '').lower() except UnicodeDecodeError: return '' diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index 3db1b877e1..9821b00c4d 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -11,6 +11,7 @@ from django.contrib.auth.management import get_default_username from django.core import exceptions from django.core.management.base import BaseCommand, CommandError from django.db import DEFAULT_DB_ALIAS +from django.utils.six.moves import input from django.utils.text import capfirst @@ -78,7 +79,7 @@ class Command(BaseCommand): input_msg = capfirst(username_field.verbose_name) if default_username: input_msg += ' (leave blank to use %r)' % default_username - raw_value = raw_input(input_msg + ': ') + raw_value = input(input_msg + ': ') if default_username and raw_value == '': username = default_username try: @@ -99,9 +100,9 @@ class Command(BaseCommand): for field_name in other_fields: field = UserModel._meta.get_field(field_name) - other_data[field_name] = None + other_data[field_name] = options.get(field_name) while other_data[field_name] is None: - raw_value = raw_input(capfirst(field.verbose_name + ': ')) + raw_value = input(capfirst(field.verbose_name + ': ')) try: other_data[field_name] = field.clean(raw_value, None) except exceptions.ValidationError, e: diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 6604423512..972aab239b 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import re import urllib @@ -7,7 +8,8 @@ from django.core import validators from django.db import models from django.db.models.manager import EmptyManager from django.utils.crypto import get_random_string -from django.utils.encoding import smart_str +from django.utils.http import urlquote +from django.utils import six from django.utils.translation import ugettext_lazy as _ from django.utils import timezone @@ -17,6 +19,7 @@ from django.contrib.auth.hashers import ( check_password, make_password, is_password_usable, UNUSABLE_PASSWORD) from django.contrib.auth.signals import user_logged_in from django.contrib.contenttypes.models import ContentType +from django.utils.encoding import python_2_unicode_compatible def update_last_login(sender, user, **kwargs): @@ -42,6 +45,7 @@ class PermissionManager(models.Manager): ) +@python_2_unicode_compatible class Permission(models.Model): """ The permissions system provides a way to assign permissions to specific @@ -77,11 +81,11 @@ class Permission(models.Model): ordering = ('content_type__app_label', 'content_type__model', 'codename') - def __unicode__(self): - return u"%s | %s | %s" % ( - unicode(self.content_type.app_label), - unicode(self.content_type), - unicode(self.name)) + def __str__(self): + return "%s | %s | %s" % ( + six.text_type(self.content_type.app_label), + six.text_type(self.content_type), + six.text_type(self.name)) def natural_key(self): return (self.codename,) + self.content_type.natural_key() @@ -96,6 +100,7 @@ class GroupManager(models.Manager): return self.get(name=name) +@python_2_unicode_compatible class Group(models.Model): """ Groups are a generic way of categorizing users to apply permissions, or @@ -123,7 +128,7 @@ class Group(models.Model): verbose_name = _('group') verbose_name_plural = _('groups') - def __unicode__(self): + def __str__(self): return self.name def natural_key(self): @@ -253,7 +258,7 @@ class AbstractBaseUser(models.Model): """ def setter(raw_password): self.set_password(raw_password) - self.save() + self.save(update_fields=["password"]) return check_password(raw_password, self.password, setter) def set_unusable_password(self): @@ -270,6 +275,7 @@ class AbstractBaseUser(models.Model): raise NotImplementedError() +@python_2_unicode_compatible class User(AbstractBaseUser): """ Users within the Django authentication system are represented by this @@ -311,20 +317,20 @@ class User(AbstractBaseUser): verbose_name_plural = _('users') swappable = 'AUTH_USER_MODEL' - def __unicode__(self): + def __str__(self): return self.username def natural_key(self): return (self.username,) def get_absolute_url(self): - return "/users/%s/" % urllib.quote(smart_str(self.username)) + return "/users/%s/" % urlquote(self.username) def get_full_name(self): """ Returns the first_name plus the last_name, with a space in between. """ - full_name = u'%s %s' % (self.first_name, self.last_name) + full_name = '%s %s' % (self.first_name, self.last_name) return full_name.strip() def get_short_name(self): @@ -425,6 +431,7 @@ class User(AbstractBaseUser): return self._profile_cache +@python_2_unicode_compatible class AnonymousUser(object): id = None pk = None @@ -438,11 +445,8 @@ class AnonymousUser(object): def __init__(self): pass - def __unicode__(self): - return 'AnonymousUser' - def __str__(self): - return unicode(self).encode('utf-8') + return 'AnonymousUser' def __eq__(self, other): return isinstance(other, self.__class__) diff --git a/django/contrib/auth/tests/auth_backends.py b/django/contrib/auth/tests/auth_backends.py index 7b38acfa50..9a4d8f9b3a 100644 --- a/django/contrib/auth/tests/auth_backends.py +++ b/django/contrib/auth/tests/auth_backends.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf import settings from django.contrib.auth.models import User, Group, Permission, AnonymousUser from django.contrib.contenttypes.models import ContentType @@ -51,7 +53,7 @@ class BackendTest(TestCase): # reloading user to purge the _perm_cache user = User.objects.get(username='test') - self.assertEqual(user.get_all_permissions() == set([u'auth.test']), True) + self.assertEqual(user.get_all_permissions() == set(['auth.test']), True) self.assertEqual(user.get_group_permissions(), set([])) self.assertEqual(user.has_module_perms('Group'), False) self.assertEqual(user.has_module_perms('auth'), True) @@ -62,7 +64,7 @@ class BackendTest(TestCase): user.user_permissions.add(perm) user.save() user = User.objects.get(username='test') - self.assertEqual(user.get_all_permissions(), set([u'auth.test2', u'auth.test', u'auth.test3'])) + self.assertEqual(user.get_all_permissions(), set(['auth.test2', 'auth.test', 'auth.test3'])) self.assertEqual(user.has_perm('test'), False) self.assertEqual(user.has_perm('auth.test'), True) self.assertEqual(user.has_perms(['auth.test2', 'auth.test3']), True) @@ -72,9 +74,9 @@ class BackendTest(TestCase): group.save() user.groups.add(group) user = User.objects.get(username='test') - exp = set([u'auth.test2', u'auth.test', u'auth.test3', u'auth.test_group']) + exp = set(['auth.test2', 'auth.test', 'auth.test3', 'auth.test_group']) self.assertEqual(user.get_all_permissions(), exp) - self.assertEqual(user.get_group_permissions(), set([u'auth.test_group'])) + self.assertEqual(user.get_group_permissions(), set(['auth.test_group'])) self.assertEqual(user.has_perms(['auth.test3', 'auth.test_group']), True) user = AnonymousUser() diff --git a/django/contrib/auth/tests/basic.py b/django/contrib/auth/tests/basic.py index 3b3cd103f3..10adfc6a10 100644 --- a/django/contrib/auth/tests/basic.py +++ b/django/contrib/auth/tests/basic.py @@ -1,7 +1,10 @@ -from django.test import TestCase +import locale + +from django.contrib.auth.management.commands import createsuperuser from django.contrib.auth.models import User, AnonymousUser from django.core.management import call_command -from StringIO import StringIO +from django.test import TestCase +from django.utils.six import StringIO class BasicTestCase(TestCase): @@ -60,3 +63,87 @@ class BasicTestCase(TestCase): self.assertTrue(super.is_superuser) self.assertTrue(super.is_active) self.assertTrue(super.is_staff) + + def test_createsuperuser_management_command(self): + "Check the operation of the createsuperuser management command" + # We can use the management command to create a superuser + new_io = StringIO() + call_command("createsuperuser", + interactive=False, + username="joe", + email="joe@somewhere.org", + stdout=new_io + ) + command_output = new_io.getvalue().strip() + self.assertEqual(command_output, 'Superuser created successfully.') + u = User.objects.get(username="joe") + self.assertEqual(u.email, 'joe@somewhere.org') + + # created password should be unusable + self.assertFalse(u.has_usable_password()) + + # We can supress output on the management command + new_io = StringIO() + call_command("createsuperuser", + interactive=False, + username="joe2", + email="joe2@somewhere.org", + verbosity=0, + stdout=new_io + ) + command_output = new_io.getvalue().strip() + self.assertEqual(command_output, '') + u = User.objects.get(username="joe2") + self.assertEqual(u.email, 'joe2@somewhere.org') + self.assertFalse(u.has_usable_password()) + + new_io = StringIO() + call_command("createsuperuser", + interactive=False, + username="joe+admin@somewhere.org", + email="joe@somewhere.org", + stdout=new_io + ) + u = User.objects.get(username="joe+admin@somewhere.org") + self.assertEqual(u.email, 'joe@somewhere.org') + self.assertFalse(u.has_usable_password()) + + def test_createsuperuser_nolocale(self): + """ + Check that createsuperuser does not break when no locale is set. See + ticket #16017. + """ + + old_getdefaultlocale = locale.getdefaultlocale + old_getpass = createsuperuser.getpass + try: + # Temporarily remove locale information + locale.getdefaultlocale = lambda: (None, None) + + # Temporarily replace getpass to allow interactive code to be used + # non-interactively + class mock_getpass: + pass + mock_getpass.getpass = staticmethod(lambda p=None: "nopasswd") + createsuperuser.getpass = mock_getpass + + # Call the command in this new environment + new_io = StringIO() + call_command("createsuperuser", + interactive=True, + username="nolocale@somewhere.org", + email="nolocale@somewhere.org", + stdout=new_io + ) + + except TypeError: + self.fail("createsuperuser fails if the OS provides no information about the current locale") + + finally: + # Re-apply locale and getpass information + createsuperuser.getpass = old_getpass + locale.getdefaultlocale = old_getdefaultlocale + + # If we were successful, a user should have been created + u = User.objects.get(username="nolocale@somewhere.org") + self.assertEqual(u.email, 'nolocale@somewhere.org') diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py index fd2b526f17..594b55c633 100644 --- a/django/contrib/auth/tests/forms.py +++ b/django/contrib/auth/tests/forms.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import os from django.contrib.auth.models import User from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm, @@ -6,7 +8,8 @@ from django.core import mail from django.forms.fields import Field, EmailField from django.test import TestCase from django.test.utils import override_settings -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text +from django.utils import six from django.utils import translation from django.utils.translation import ugettext as _ @@ -25,7 +28,7 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form["username"].errors, - [force_unicode(form.error_messages['duplicate_username'])]) + [force_text(form.error_messages['duplicate_username'])]) def test_invalid_data(self): data = { @@ -36,7 +39,7 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form["username"].errors, - [force_unicode(form.fields['username'].error_messages['invalid'])]) + [force_text(form.fields['username'].error_messages['invalid'])]) def test_password_verification(self): # The verification password is incorrect. @@ -48,13 +51,13 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form["password2"].errors, - [force_unicode(form.error_messages['password_mismatch'])]) + [force_text(form.error_messages['password_mismatch'])]) def test_both_passwords(self): # One (or both) passwords weren't given data = {'username': 'jsmith'} form = UserCreationForm(data) - required_error = [force_unicode(Field.default_error_messages['required'])] + required_error = [force_text(Field.default_error_messages['required'])] self.assertFalse(form.is_valid()) self.assertEqual(form['password1'].errors, required_error) self.assertEqual(form['password2'].errors, required_error) @@ -63,6 +66,7 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form['password1'].errors, required_error) + self.assertEqual(form['password2'].errors, []) def test_success(self): # The success case. @@ -92,7 +96,7 @@ class AuthenticationFormTest(TestCase): form = AuthenticationForm(None, data) self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), - [force_unicode(form.error_messages['invalid_login'])]) + [force_text(form.error_messages['invalid_login'])]) def test_inactive_user(self): # The user is inactive. @@ -103,7 +107,7 @@ class AuthenticationFormTest(TestCase): form = AuthenticationForm(None, data) self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), - [force_unicode(form.error_messages['inactive'])]) + [force_text(form.error_messages['inactive'])]) def test_inactive_user_i18n(self): with self.settings(USE_I18N=True): @@ -116,7 +120,7 @@ class AuthenticationFormTest(TestCase): form = AuthenticationForm(None, data) self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), - [force_unicode(form.error_messages['inactive'])]) + [force_text(form.error_messages['inactive'])]) def test_success(self): # The success case @@ -144,7 +148,7 @@ class SetPasswordFormTest(TestCase): form = SetPasswordForm(user, data) self.assertFalse(form.is_valid()) self.assertEqual(form["new_password2"].errors, - [force_unicode(form.error_messages['password_mismatch'])]) + [force_text(form.error_messages['password_mismatch'])]) def test_success(self): user = User.objects.get(username='testclient') @@ -171,7 +175,7 @@ class PasswordChangeFormTest(TestCase): form = PasswordChangeForm(user, data) self.assertFalse(form.is_valid()) self.assertEqual(form["old_password"].errors, - [force_unicode(form.error_messages['password_incorrect'])]) + [force_text(form.error_messages['password_incorrect'])]) def test_password_verification(self): # The two new passwords do not match. @@ -184,7 +188,7 @@ class PasswordChangeFormTest(TestCase): form = PasswordChangeForm(user, data) self.assertFalse(form.is_valid()) self.assertEqual(form["new_password2"].errors, - [force_unicode(form.error_messages['password_mismatch'])]) + [force_text(form.error_messages['password_mismatch'])]) def test_success(self): # The success case. @@ -200,7 +204,7 @@ class PasswordChangeFormTest(TestCase): def test_field_order(self): # Regression test - check the order of fields: user = User.objects.get(username='testclient') - self.assertEqual(PasswordChangeForm(user, {}).fields.keys(), + self.assertEqual(list(PasswordChangeForm(user, {}).fields), ['old_password', 'new_password1', 'new_password2']) @@ -215,7 +219,7 @@ class UserChangeFormTest(TestCase): form = UserChangeForm(data, instance=user) self.assertFalse(form.is_valid()) self.assertEqual(form['username'].errors, - [force_unicode(form.fields['username'].error_messages['invalid'])]) + [force_text(form.fields['username'].error_messages['invalid'])]) def test_bug_14242(self): # A regression test, introduce by adding an optimization for the @@ -270,7 +274,7 @@ class PasswordResetFormTest(TestCase): form = PasswordResetForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form['email'].errors, - [force_unicode(EmailField.default_error_messages['invalid'])]) + [force_text(EmailField.default_error_messages['invalid'])]) def test_nonexistant_email(self): # Test nonexistant email address @@ -278,7 +282,7 @@ class PasswordResetFormTest(TestCase): form = PasswordResetForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, - {'email': [force_unicode(form.error_messages['unknown'])]}) + {'email': [force_text(form.error_messages['unknown'])]}) def test_cleaned_data(self): # Regression test @@ -299,7 +303,7 @@ class PasswordResetFormTest(TestCase): # potential case where contrib.sites is not installed. Refs #16412. form.save(domain_override='example.com') self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].subject, u'Custom password reset on example.com') + self.assertEqual(mail.outbox[0].subject, 'Custom password reset on example.com') def test_bug_5605(self): # bug #5605, preserve the case of the user name (before the @ in the @@ -328,4 +332,4 @@ class PasswordResetFormTest(TestCase): form = PasswordResetForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form["email"].errors, - [_(u"The user account associated with this e-mail address cannot reset the password.")]) + [_("The user account associated with this e-mail address cannot reset the password.")]) diff --git a/django/contrib/auth/tests/hashers.py b/django/contrib/auth/tests/hashers.py index 8a11511688..673263b566 100644 --- a/django/contrib/auth/tests/hashers.py +++ b/django/contrib/auth/tests/hashers.py @@ -1,7 +1,9 @@ +from __future__ import unicode_literals + from django.conf.global_settings import PASSWORD_HASHERS as default_hashers from django.contrib.auth.hashers import (is_password_usable, check_password, make_password, PBKDF2PasswordHasher, load_hashers, - PBKDF2SHA1PasswordHasher, get_hasher, UNUSABLE_PASSWORD) + PBKDF2SHA1PasswordHasher, get_hasher, identify_hasher, UNUSABLE_PASSWORD) from django.utils import unittest from django.utils.unittest import skipUnless @@ -26,7 +28,7 @@ class TestUtilsHashPass(unittest.TestCase): encoded = make_password('letmein') self.assertTrue(encoded.startswith('pbkdf2_sha256$')) self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) def test_pkbdf2(self): @@ -34,47 +36,53 @@ class TestUtilsHashPass(unittest.TestCase): self.assertEqual(encoded, 'pbkdf2_sha256$10000$seasalt$FQCNpiZpTb0zub+HBsH6TOwyRxJ19FwvjbweatNmK/Y=') self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") def test_sha1(self): encoded = make_password('letmein', 'seasalt', 'sha1') self.assertEqual(encoded, 'sha1$seasalt$fec3530984afba6bade3347b7140d1a7da7da8c7') self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "sha1") def test_md5(self): encoded = make_password('letmein', 'seasalt', 'md5') self.assertEqual(encoded, 'md5$seasalt$f5531bef9f3687d0ccf0f617f0e25573') self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "md5") def test_unsalted_md5(self): encoded = make_password('letmein', 'seasalt', 'unsalted_md5') self.assertEqual(encoded, '0d107d09f5bbe40cade3de5c71e9e9b7') self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5") @skipUnless(crypt, "no crypt module to generate password.") def test_crypt(self): encoded = make_password('letmein', 'ab', 'crypt') self.assertEqual(encoded, 'crypt$$abN/qM.L/H8EQ') self.assertTrue(is_password_usable(encoded)) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "crypt") @skipUnless(bcrypt, "py-bcrypt not installed") def test_bcrypt(self): encoded = make_password('letmein', hasher='bcrypt') self.assertTrue(is_password_usable(encoded)) self.assertTrue(encoded.startswith('bcrypt$')) - self.assertTrue(check_password(u'letmein', encoded)) + self.assertTrue(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt") def test_unusable(self): encoded = make_password(None) @@ -82,13 +90,15 @@ class TestUtilsHashPass(unittest.TestCase): self.assertFalse(check_password(None, encoded)) self.assertFalse(check_password(UNUSABLE_PASSWORD, encoded)) self.assertFalse(check_password('', encoded)) - self.assertFalse(check_password(u'letmein', encoded)) + self.assertFalse(check_password('letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + self.assertRaises(ValueError, identify_hasher, encoded) def test_bad_algorithm(self): def doit(): make_password('letmein', hasher='lolcat') self.assertRaises(ValueError, doit) + self.assertRaises(ValueError, identify_hasher, "lolcat$salt$hash") def test_low_level_pkbdf2(self): hasher = PBKDF2PasswordHasher() diff --git a/django/contrib/auth/tests/management.py b/django/contrib/auth/tests/management.py index 20f4cde8f9..3dcde9d218 100644 --- a/django/contrib/auth/tests/management.py +++ b/django/contrib/auth/tests/management.py @@ -1,5 +1,5 @@ +from __future__ import unicode_literals from datetime import date -from StringIO import StringIO from django.contrib.auth import models, management from django.contrib.auth.management.commands import changepassword @@ -9,6 +9,7 @@ from django.core.management import call_command from django.core.management.base import CommandError from django.test import TestCase from django.test.utils import override_settings +from django.utils.six import StringIO class GetDefaultUsernameTestCase(TestCase): @@ -20,19 +21,19 @@ class GetDefaultUsernameTestCase(TestCase): management.get_system_username = self._getpass_getuser def test_simple(self): - management.get_system_username = lambda: u'joe' + management.get_system_username = lambda: 'joe' self.assertEqual(management.get_default_username(), 'joe') def test_existing(self): models.User.objects.create(username='joe') - management.get_system_username = lambda: u'joe' + management.get_system_username = lambda: 'joe' self.assertEqual(management.get_default_username(), '') self.assertEqual( management.get_default_username(check_db=False), 'joe') def test_i18n(self): # 'Julia' with accented 'u': - management.get_system_username = lambda: u'J\xfalia' + management.get_system_username = lambda: 'J\xfalia' self.assertEqual(management.get_default_username(), 'julia') diff --git a/django/contrib/auth/tests/models.py b/django/contrib/auth/tests/models.py index b9565922a2..b40157bfe2 100644 --- a/django/contrib/auth/tests/models.py +++ b/django/contrib/auth/tests/models.py @@ -5,39 +5,29 @@ from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable, UserManager) -@override_settings(USE_TZ=False) +@override_settings(USE_TZ=False, AUTH_PROFILE_MODULE='') class ProfileTestCase(TestCase): - fixtures = ['authtestdata.json'] - - def setUp(self): - """Backs up the AUTH_PROFILE_MODULE""" - self.old_AUTH_PROFILE_MODULE = getattr(settings, - 'AUTH_PROFILE_MODULE', None) - - def tearDown(self): - """Restores the AUTH_PROFILE_MODULE -- if it was not set it is deleted, - otherwise the old value is restored""" - if self.old_AUTH_PROFILE_MODULE is None and \ - hasattr(settings, 'AUTH_PROFILE_MODULE'): - del settings.AUTH_PROFILE_MODULE - - if self.old_AUTH_PROFILE_MODULE is not None: - settings.AUTH_PROFILE_MODULE = self.old_AUTH_PROFILE_MODULE def test_site_profile_not_available(self): + user = User.objects.create(username='testclient') + # calling get_profile without AUTH_PROFILE_MODULE set - if hasattr(settings, 'AUTH_PROFILE_MODULE'): - del settings.AUTH_PROFILE_MODULE - user = User.objects.get(username='testclient') - self.assertRaises(SiteProfileNotAvailable, user.get_profile) + del settings.AUTH_PROFILE_MODULE + with self.assertRaisesRegexp(SiteProfileNotAvailable, + "You need to set AUTH_PROFILE_MODULE in your project"): + user.get_profile() # Bad syntax in AUTH_PROFILE_MODULE: settings.AUTH_PROFILE_MODULE = 'foobar' - self.assertRaises(SiteProfileNotAvailable, user.get_profile) + with self.assertRaisesRegexp(SiteProfileNotAvailable, + "app_label and model_name should be separated by a dot"): + user.get_profile() # module that doesn't exist settings.AUTH_PROFILE_MODULE = 'foo.bar' - self.assertRaises(SiteProfileNotAvailable, user.get_profile) + with self.assertRaisesRegexp(SiteProfileNotAvailable, + "Unable to load the profile model"): + user.get_profile() @override_settings(USE_TZ=False) diff --git a/django/contrib/auth/tests/tokens.py b/django/contrib/auth/tests/tokens.py index beccfc5d07..44117a4f84 100644 --- a/django/contrib/auth/tests/tokens.py +++ b/django/contrib/auth/tests/tokens.py @@ -1,9 +1,11 @@ +import sys from datetime import date, timedelta from django.conf import settings from django.contrib.auth.models import User from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.test import TestCase +from django.utils import unittest class TokenGeneratorTest(TestCase): @@ -51,6 +53,7 @@ class TokenGeneratorTest(TestCase): p2 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS + 1)) self.assertFalse(p2.check_token(user, tk1)) + @unittest.skipIf(sys.version_info[:2] >= (3, 0), "Unnecessary test with Python 3") def test_date_length(self): """ Make sure we don't allow overly long dates, causing a potential DoS. diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py index a9754c5bad..3c847f456a 100644 --- a/django/contrib/auth/tests/views.py +++ b/django/contrib/auth/tests/views.py @@ -1,6 +1,5 @@ import os import re -import urllib from django.conf import settings from django.contrib.sites.models import Site, RequestSite @@ -8,8 +7,9 @@ from django.contrib.auth.models import User from django.core import mail from django.core.urlresolvers import reverse, NoReverseMatch from django.http import QueryDict -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.html import escape +from django.utils.http import urlquote from django.test import TestCase from django.test.utils import override_settings @@ -46,7 +46,7 @@ class AuthViewsTestCase(TestCase): self.assertTrue(SESSION_KEY in self.client.session) def assertContainsEscaped(self, response, text, **kwargs): - return self.assertContains(response, escape(force_unicode(text)), **kwargs) + return self.assertContains(response, escape(force_text(text)), **kwargs) class AuthViewNamedURLTests(AuthViewsTestCase): @@ -256,7 +256,7 @@ class LoginTest(AuthViewsTestCase): nasty_url = '%(url)s?%(next)s=%(bad_url)s' % { 'url': login_url, 'next': REDIRECT_FIELD_NAME, - 'bad_url': urllib.quote(bad_url), + 'bad_url': urlquote(bad_url), } response = self.client.post(nasty_url, { 'username': 'testclient', @@ -277,7 +277,7 @@ class LoginTest(AuthViewsTestCase): safe_url = '%(url)s?%(next)s=%(good_url)s' % { 'url': login_url, 'next': REDIRECT_FIELD_NAME, - 'good_url': urllib.quote(good_url), + 'good_url': urlquote(good_url), } response = self.client.post(safe_url, { 'username': 'testclient', @@ -412,7 +412,7 @@ class LogoutTest(AuthViewsTestCase): nasty_url = '%(url)s?%(next)s=%(bad_url)s' % { 'url': logout_url, 'next': REDIRECT_FIELD_NAME, - 'bad_url': urllib.quote(bad_url), + 'bad_url': urlquote(bad_url), } self.login() response = self.client.get(nasty_url) @@ -432,7 +432,7 @@ class LogoutTest(AuthViewsTestCase): safe_url = '%(url)s?%(next)s=%(good_url)s' % { 'url': logout_url, 'next': REDIRECT_FIELD_NAME, - 'good_url': urllib.quote(good_url), + 'good_url': urlquote(good_url), } self.login() response = self.client.get(safe_url) diff --git a/django/contrib/auth/tokens.py b/django/contrib/auth/tokens.py index 9c3e9aefee..930c70012b 100644 --- a/django/contrib/auth/tokens.py +++ b/django/contrib/auth/tokens.py @@ -2,6 +2,7 @@ from datetime import date from django.conf import settings from django.utils.http import int_to_base36, base36_to_int from django.utils.crypto import constant_time_compare, salted_hmac +from django.utils import six class PasswordResetTokenGenerator(object): @@ -57,8 +58,8 @@ class PasswordResetTokenGenerator(object): # Ensure results are consistent across DB backends login_timestamp = user.last_login.replace(microsecond=0, tzinfo=None) - value = (unicode(user.id) + user.password + - unicode(login_timestamp) + unicode(timestamp)) + value = (six.text_type(user.id) + user.password + + six.text_type(login_timestamp) + six.text_type(timestamp)) hash = salted_hmac(key_salt, value).hexdigest()[::2] return "%s-%s" % (ts_b36, hash) diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index d32d2180b9..0e79213934 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -1,9 +1,13 @@ -import urlparse +try: + from urllib.parse import urlparse, urlunparse +except ImportError: # Python 2 + from urlparse import urlparse, urlunparse from django.conf import settings from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect, QueryDict from django.template.response import TemplateResponse +from django.utils.encoding import force_str from django.utils.http import base36_to_int from django.utils.translation import ugettext as _ from django.views.decorators.debug import sensitive_post_parameters @@ -34,7 +38,7 @@ def login(request, template_name='registration/login.html', if request.method == "POST": form = authentication_form(data=request.POST) if form.is_valid(): - netloc = urlparse.urlparse(redirect_to)[1] + netloc = urlparse(redirect_to)[1] # Use default setting if redirect_to is empty if not redirect_to: @@ -81,7 +85,7 @@ def logout(request, next_page=None, auth_logout(request) redirect_to = request.REQUEST.get(redirect_field_name, '') if redirect_to: - netloc = urlparse.urlparse(redirect_to)[1] + netloc = urlparse(redirect_to)[1] # Security check -- don't allow redirection to a different host. if not (netloc and netloc != request.get_host()): return HttpResponseRedirect(redirect_to) @@ -116,16 +120,16 @@ def redirect_to_login(next, login_url=None, """ Redirects the user to the login page, passing the given 'next' page """ - if not login_url: - login_url = settings.LOGIN_URL + # urlparse chokes on lazy objects in Python 3 + login_url_as_str = force_str(login_url or settings.LOGIN_URL) - login_url_parts = list(urlparse.urlparse(login_url)) + login_url_parts = list(urlparse(login_url_as_str)) if redirect_field_name: querystring = QueryDict(login_url_parts[4], mutable=True) querystring[redirect_field_name] = next login_url_parts[4] = querystring.urlencode(safe='/') - return HttpResponseRedirect(urlparse.urlunparse(login_url_parts)) + return HttpResponseRedirect(urlunparse(login_url_parts)) # 4 views for password reset: @@ -203,7 +207,7 @@ def password_reset_confirm(request, uidb36=None, token=None, try: uid_int = base36_to_int(uidb36) user = User.objects.get(id=uid_int) - except (ValueError, User.DoesNotExist): + except (ValueError, OverflowError, User.DoesNotExist): user = None if user is not None and token_generator.check_token(user, token): diff --git a/django/contrib/comments/admin.py b/django/contrib/comments/admin.py index 4cb90663a0..0024a1d1b5 100644 --- a/django/contrib/comments/admin.py +++ b/django/contrib/comments/admin.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.contrib import admin from django.contrib.comments.models import Comment from django.utils.translation import ugettext_lazy as _, ungettext @@ -62,8 +64,8 @@ class CommentsAdmin(admin.ModelAdmin): action(request, comment) n_comments += 1 - msg = ungettext(u'1 comment was successfully %(action)s.', - u'%(count)s comments were successfully %(action)s.', + msg = ungettext('1 comment was successfully %(action)s.', + '%(count)s comments were successfully %(action)s.', n_comments) self.message_user(request, msg % {'count': n_comments, 'action': done_message(n_comments)}) diff --git a/django/contrib/comments/forms.py b/django/contrib/comments/forms.py index 830e24bca9..bd254d2733 100644 --- a/django/contrib/comments/forms.py +++ b/django/contrib/comments/forms.py @@ -5,7 +5,7 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib.comments.models import Comment from django.utils.crypto import salted_hmac, constant_time_compare -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.text import get_text_list from django.utils import timezone from django.utils.translation import ungettext, ugettext, ugettext_lazy as _ @@ -133,7 +133,7 @@ class CommentDetailsForm(CommentSecurityForm): """ return dict( content_type = ContentType.objects.get_for_model(self.target_object), - object_pk = force_unicode(self.target_object._get_pk_val()), + object_pk = force_text(self.target_object._get_pk_val()), user_name = self.cleaned_data["name"], user_email = self.cleaned_data["email"], user_url = self.cleaned_data["url"], diff --git a/django/contrib/comments/managers.py b/django/contrib/comments/managers.py index 499feee6c3..bc0fc5f332 100644 --- a/django/contrib/comments/managers.py +++ b/django/contrib/comments/managers.py @@ -1,6 +1,6 @@ from django.db import models from django.contrib.contenttypes.models import ContentType -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text class CommentManager(models.Manager): @@ -18,5 +18,5 @@ class CommentManager(models.Manager): ct = ContentType.objects.get_for_model(model) qs = self.get_query_set().filter(content_type=ct) if isinstance(model, models.Model): - qs = qs.filter(object_pk=force_unicode(model._get_pk_val())) + qs = qs.filter(object_pk=force_text(model._get_pk_val())) return qs diff --git a/django/contrib/comments/models.py b/django/contrib/comments/models.py index bece36e318..a39c2622dd 100644 --- a/django/contrib/comments/models.py +++ b/django/contrib/comments/models.py @@ -7,6 +7,7 @@ from django.core import urlresolvers from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils import timezone +from django.utils.encoding import python_2_unicode_compatible COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000) @@ -40,6 +41,7 @@ class BaseCommentAbstractModel(models.Model): ) +@python_2_unicode_compatible class Comment(BaseCommentAbstractModel): """ A user comment about some object. @@ -77,7 +79,7 @@ class Comment(BaseCommentAbstractModel): verbose_name = _('comment') verbose_name_plural = _('comments') - def __unicode__(self): + def __str__(self): return "%s: %s..." % (self.name, self.comment[:50]) def save(self, *args, **kwargs): @@ -158,6 +160,7 @@ class Comment(BaseCommentAbstractModel): return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d +@python_2_unicode_compatible class CommentFlag(models.Model): """ Records a flag on a comment. This is intentionally flexible; right now, a @@ -187,7 +190,7 @@ class CommentFlag(models.Model): verbose_name = _('comment flag') verbose_name_plural = _('comment flags') - def __unicode__(self): + def __str__(self): return "%s flag of comment ID %s by %s" % \ (self.flag, self.comment_id, self.user.username) diff --git a/django/contrib/comments/templatetags/comments.py b/django/contrib/comments/templatetags/comments.py index ce1825e529..4d4eb2322f 100644 --- a/django/contrib/comments/templatetags/comments.py +++ b/django/contrib/comments/templatetags/comments.py @@ -3,7 +3,7 @@ from django.template.loader import render_to_string from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib import comments -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text register = template.Library() @@ -75,7 +75,7 @@ class BaseCommentNode(template.Node): qs = self.comment_model.objects.filter( content_type = ctype, - object_pk = smart_unicode(object_pk), + object_pk = smart_text(object_pk), site__pk = settings.SITE_ID, ) diff --git a/django/contrib/comments/views/moderation.py b/django/contrib/comments/views/moderation.py index fb9e91ef97..39933e75c8 100644 --- a/django/contrib/comments/views/moderation.py +++ b/django/contrib/comments/views/moderation.py @@ -17,7 +17,7 @@ def flag(request, comment_id, next=None): """ Flags a comment. Confirmation on GET, action on POST. - Templates: `comments/flag.html`, + Templates: :template:`comments/flag.html`, Context: comment the flagged `comments.comment` object @@ -43,7 +43,7 @@ def delete(request, comment_id, next=None): Deletes a comment. Confirmation on GET, action on POST. Requires the "can moderate comments" permission. - Templates: `comments/delete.html`, + Templates: :template:`comments/delete.html`, Context: comment the flagged `comments.comment` object @@ -70,7 +70,7 @@ def approve(request, comment_id, next=None): Approve a comment (that is, mark it as public and non-removed). Confirmation on GET, action on POST. Requires the "can moderate comments" permission. - Templates: `comments/approve.html`, + Templates: :template:`comments/approve.html`, Context: comment the `comments.comment` object for approval diff --git a/django/contrib/comments/views/utils.py b/django/contrib/comments/views/utils.py index cc985e52d2..abaed68560 100644 --- a/django/contrib/comments/views/utils.py +++ b/django/contrib/comments/views/utils.py @@ -2,8 +2,12 @@ A few bits of helper functions for comment views. """ -import urllib import textwrap +try: + from urllib.parse import urlencode +except ImportError: # Python 2 + from urllib import urlencode + from django.http import HttpResponseRedirect from django.core import urlresolvers from django.shortcuts import render_to_response @@ -33,7 +37,7 @@ def next_redirect(data, default, default_view, **get_kwargs): anchor = '' joiner = ('?' in next) and '&' or '?' - next += joiner + urllib.urlencode(get_kwargs) + anchor + next += joiner + urlencode(get_kwargs) + anchor return HttpResponseRedirect(next) def confirmation_view(template, doc="Display a confirmation view."): @@ -56,7 +60,7 @@ def confirmation_view(template, doc="Display a confirmation view."): confirmed.__doc__ = textwrap.dedent("""\ %s - Templates: `%s`` + Templates: :template:`%s`` Context: comment The posted comment diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index a6732d7a2e..29e93eefe7 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -1,6 +1,7 @@ """ Classes allowing "generic" relations through ContentType and object-id fields. """ +from __future__ import unicode_literals from collections import defaultdict from functools import partial @@ -16,7 +17,7 @@ from django.forms import ModelForm from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets from django.contrib.contenttypes.models import ContentType -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text class GenericForeignKey(object): """ @@ -131,7 +132,7 @@ class GenericForeignKey(object): def __set__(self, instance, value): if instance is None: - raise AttributeError(u"%s must be accessed via instance" % self.related.opts.object_name) + raise AttributeError("%s must be accessed via instance" % self.related.opts.object_name) ct = None fk = None @@ -168,7 +169,7 @@ class GenericRelation(RelatedField, Field): def value_to_string(self, obj): qs = getattr(obj, self.name).all() - return smart_unicode([instance._get_pk_val() for instance in qs]) + return smart_text([instance._get_pk_val() for instance in qs]) def m2m_db_table(self): return self.rel.to._meta.db_table diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py index 6a23ef5287..9f287d494b 100644 --- a/django/contrib/contenttypes/management.py +++ b/django/contrib/contenttypes/management.py @@ -1,6 +1,8 @@ from django.contrib.contenttypes.models import ContentType from django.db.models import get_apps, get_models, signals -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text +from django.utils import six +from django.utils.six.moves import input def update_contenttypes(app, created_models, verbosity=2, **kwargs): """ @@ -24,17 +26,17 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs): ) to_remove = [ ct - for (model_name, ct) in content_types.iteritems() + for (model_name, ct) in six.iteritems(content_types) if model_name not in app_models ] cts = ContentType.objects.bulk_create([ ContentType( - name=smart_unicode(model._meta.verbose_name_raw), + name=smart_text(model._meta.verbose_name_raw), app_label=app_label, model=model_name, ) - for (model_name, model) in app_models.iteritems() + for (model_name, model) in six.iteritems(app_models) if model_name not in content_types ]) if verbosity >= 2: @@ -48,7 +50,7 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs): ' %s | %s' % (ct.app_label, ct.model) for ct in to_remove ]) - ok_to_delete = raw_input("""The following content types are stale and need to be deleted: + ok_to_delete = input("""The following content types are stale and need to be deleted: %s diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py index d588ff4d23..b658655bbb 100644 --- a/django/contrib/contenttypes/models.py +++ b/django/contrib/contenttypes/models.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode, force_unicode +from django.utils.encoding import smart_text, force_text +from django.utils.encoding import python_2_unicode_compatible class ContentTypeManager(models.Manager): @@ -16,39 +17,44 @@ class ContentTypeManager(models.Manager): self._add_to_cache(self.db, ct) return ct - def _get_opts(self, model): - return model._meta.concrete_model._meta + def _get_opts(self, model, for_concrete_model): + if for_concrete_model: + model = model._meta.concrete_model + elif model._deferred: + model = model._meta.proxy_for_model + return model._meta def _get_from_cache(self, opts): key = (opts.app_label, opts.object_name.lower()) return self.__class__._cache[self.db][key] - def get_for_model(self, model): + def get_for_model(self, model, for_concrete_model=True): """ Returns the ContentType object for a given model, creating the ContentType if necessary. Lookups are cached so that subsequent lookups for the same model don't hit the database. """ - opts = self._get_opts(model) + opts = self._get_opts(model, for_concrete_model) try: ct = self._get_from_cache(opts) except KeyError: - # Load or create the ContentType entry. The smart_unicode() is + # Load or create the ContentType entry. The smart_text() is # needed around opts.verbose_name_raw because name_raw might be a # django.utils.functional.__proxy__ object. ct, created = self.get_or_create( app_label = opts.app_label, model = opts.object_name.lower(), - defaults = {'name': smart_unicode(opts.verbose_name_raw)}, + defaults = {'name': smart_text(opts.verbose_name_raw)}, ) self._add_to_cache(self.db, ct) return ct - def get_for_models(self, *models): + def get_for_models(self, *models, **kwargs): """ Given *models, returns a dictionary mapping {model: content_type}. """ + for_concrete_models = kwargs.pop('for_concrete_models', True) # Final results results = {} # models that aren't already in the cache @@ -56,7 +62,7 @@ class ContentTypeManager(models.Manager): needed_models = set() needed_opts = set() for model in models: - opts = self._get_opts(model) + opts = self._get_opts(model, for_concrete_models) try: ct = self._get_from_cache(opts) except KeyError: @@ -81,7 +87,7 @@ class ContentTypeManager(models.Manager): ct = self.create( app_label=opts.app_label, model=opts.object_name.lower(), - name=smart_unicode(opts.verbose_name_raw), + name=smart_text(opts.verbose_name_raw), ) self._add_to_cache(self.db, ct) results[ct.model_class()] = ct @@ -117,6 +123,7 @@ class ContentTypeManager(models.Manager): self.__class__._cache.setdefault(using, {})[key] = ct self.__class__._cache.setdefault(using, {})[ct.id] = ct +@python_2_unicode_compatible class ContentType(models.Model): name = models.CharField(max_length=100) app_label = models.CharField(max_length=100) @@ -130,7 +137,7 @@ class ContentType(models.Model): ordering = ('name',) unique_together = (('app_label', 'model'),) - def __unicode__(self): + def __str__(self): # self.name is deprecated in favor of using model's verbose_name, which # can be translated. Formal deprecation is delayed until we have DB # migration to be able to remove the field from the database along with @@ -142,7 +149,7 @@ class ContentType(models.Model): if not model or self.name != model._meta.verbose_name_raw: return self.name else: - return force_unicode(model._meta.verbose_name) + return force_text(model._meta.verbose_name) def model_class(self): "Returns the Python model class for this type of content." diff --git a/django/contrib/contenttypes/tests.py b/django/contrib/contenttypes/tests.py index 0057dd535d..2f92a34581 100644 --- a/django/contrib/contenttypes/tests.py +++ b/django/contrib/contenttypes/tests.py @@ -1,4 +1,4 @@ -import urllib +from __future__ import unicode_literals from django.db import models from django.contrib.contenttypes.models import ContentType @@ -6,16 +6,26 @@ from django.contrib.contenttypes.views import shortcut from django.contrib.sites.models import Site from django.http import HttpRequest, Http404 from django.test import TestCase -from django.utils.encoding import smart_str +from django.utils.http import urlquote +from django.utils import six +from django.utils.encoding import python_2_unicode_compatible +class ConcreteModel(models.Model): + name = models.CharField(max_length=10) + +class ProxyModel(ConcreteModel): + class Meta: + proxy = True + +@python_2_unicode_compatible class FooWithoutUrl(models.Model): """ Fake model not defining ``get_absolute_url`` for :meth:`ContentTypesTests.test_shortcut_view_without_get_absolute_url`""" name = models.CharField(max_length=30, unique=True) - def __unicode__(self): + def __str__(self): return self.name @@ -26,7 +36,7 @@ class FooWithUrl(FooWithoutUrl): """ def get_absolute_url(self): - return "/users/%s/" % urllib.quote(smart_str(self.name)) + return "/users/%s/" % urlquote(self.name) class FooWithBrokenAbsoluteUrl(FooWithoutUrl): """ @@ -112,6 +122,87 @@ class ContentTypesTests(TestCase): FooWithUrl: ContentType.objects.get_for_model(FooWithUrl), }) + def test_get_for_concrete_model(self): + """ + Make sure the `for_concrete_model` kwarg correctly works + with concrete, proxy and deferred models + """ + concrete_model_ct = ContentType.objects.get_for_model(ConcreteModel) + + self.assertEqual(concrete_model_ct, + ContentType.objects.get_for_model(ProxyModel)) + + self.assertEqual(concrete_model_ct, + ContentType.objects.get_for_model(ConcreteModel, + for_concrete_model=False)) + + proxy_model_ct = ContentType.objects.get_for_model(ProxyModel, + for_concrete_model=False) + + self.assertNotEqual(concrete_model_ct, proxy_model_ct) + + # Make sure deferred model are correctly handled + ConcreteModel.objects.create(name="Concrete") + DeferredConcreteModel = ConcreteModel.objects.only('pk').get().__class__ + DeferredProxyModel = ProxyModel.objects.only('pk').get().__class__ + + self.assertEqual(concrete_model_ct, + ContentType.objects.get_for_model(DeferredConcreteModel)) + + self.assertEqual(concrete_model_ct, + ContentType.objects.get_for_model(DeferredConcreteModel, + for_concrete_model=False)) + + self.assertEqual(concrete_model_ct, + ContentType.objects.get_for_model(DeferredProxyModel)) + + self.assertEqual(proxy_model_ct, + ContentType.objects.get_for_model(DeferredProxyModel, + for_concrete_model=False)) + + def test_get_for_concrete_models(self): + """ + Make sure the `for_concrete_models` kwarg correctly works + with concrete, proxy and deferred models. + """ + concrete_model_ct = ContentType.objects.get_for_model(ConcreteModel) + + cts = ContentType.objects.get_for_models(ConcreteModel, ProxyModel) + self.assertEqual(cts, { + ConcreteModel: concrete_model_ct, + ProxyModel: concrete_model_ct, + }) + + proxy_model_ct = ContentType.objects.get_for_model(ProxyModel, + for_concrete_model=False) + cts = ContentType.objects.get_for_models(ConcreteModel, ProxyModel, + for_concrete_models=False) + self.assertEqual(cts, { + ConcreteModel: concrete_model_ct, + ProxyModel: proxy_model_ct, + }) + + # Make sure deferred model are correctly handled + ConcreteModel.objects.create(name="Concrete") + DeferredConcreteModel = ConcreteModel.objects.only('pk').get().__class__ + DeferredProxyModel = ProxyModel.objects.only('pk').get().__class__ + + cts = ContentType.objects.get_for_models(DeferredConcreteModel, + DeferredProxyModel) + self.assertEqual(cts, { + DeferredConcreteModel: concrete_model_ct, + DeferredProxyModel: concrete_model_ct, + }) + + cts = ContentType.objects.get_for_models(DeferredConcreteModel, + DeferredProxyModel, + for_concrete_models=False) + self.assertEqual(cts, { + DeferredConcreteModel: concrete_model_ct, + DeferredProxyModel: proxy_model_ct, + }) + + def test_shortcut_view(self): """ Check that the shortcut view (used for the admin "view on site" @@ -181,4 +272,4 @@ class ContentTypesTests(TestCase): app_label = 'contenttypes', model = 'OldModel', ) - self.assertEqual(unicode(ct), u'Old model') + self.assertEqual(six.text_type(ct), 'Old model') diff --git a/django/contrib/contenttypes/views.py b/django/contrib/contenttypes/views.py index 8d8e1c7a3e..5c22cb6672 100644 --- a/django/contrib/contenttypes/views.py +++ b/django/contrib/contenttypes/views.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import http from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site, get_current_site @@ -12,11 +14,11 @@ def shortcut(request, content_type_id, object_id): try: content_type = ContentType.objects.get(pk=content_type_id) if not content_type.model_class(): - raise http.Http404(_(u"Content type %(ct_id)s object has no associated model") % + raise http.Http404(_("Content type %(ct_id)s object has no associated model") % {'ct_id': content_type_id}) obj = content_type.get_object_for_this_type(pk=object_id) except (ObjectDoesNotExist, ValueError): - raise http.Http404(_(u"Content type %(ct_id)s object %(obj_id)s doesn't exist") % + raise http.Http404(_("Content type %(ct_id)s object %(obj_id)s doesn't exist") % {'ct_id': content_type_id, 'obj_id': object_id}) try: diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py index 9a4ba17e19..9d6ac34f3f 100644 --- a/django/contrib/databrowse/datastructures.py +++ b/django/contrib/databrowse/datastructures.py @@ -2,13 +2,14 @@ These classes are light wrappers around Django's database API that provide convenience functionality and permalink functions for the databrowse app. """ +from __future__ import unicode_literals from django.db import models from django.utils import formats from django.utils.text import capfirst -from django.utils.encoding import smart_unicode, smart_str, iri_to_uri -from django.utils.safestring import mark_safe +from django.utils.encoding import smart_text, smart_str, iri_to_uri from django.db.models.query import QuerySet +from django.utils.encoding import python_2_unicode_compatible EMPTY_VALUE = '(None)' DISPLAY_SIZE = 100 @@ -17,19 +18,19 @@ class EasyModel(object): def __init__(self, site, model): self.site = site self.model = model - self.model_list = site.registry.keys() + self.model_list = list(site.registry.keys()) self.verbose_name = model._meta.verbose_name self.verbose_name_plural = model._meta.verbose_name_plural def __repr__(self): - return '' % smart_str(self.model._meta.object_name) + return smart_str('' % self.model._meta.object_name) def model_databrowse(self): "Returns the ModelDatabrowse class for this model." return self.site.registry[self.model] def url(self): - return mark_safe('%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name)) + return '%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name) def objects(self, **kwargs): return self.get_query_set().filter(**kwargs) @@ -61,7 +62,7 @@ class EasyField(object): self.model, self.field = easy_model, field def __repr__(self): - return smart_str(u'' % (self.model.model._meta.object_name, self.field.name)) + return smart_str('' % (self.model.model._meta.object_name, self.field.name)) def choices(self): for value, label in self.field.choices: @@ -69,9 +70,9 @@ class EasyField(object): def url(self): if self.field.choices: - return mark_safe('%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name)) + return '%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name) elif self.field.rel: - return mark_safe('%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name)) + return '%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name) class EasyChoice(object): def __init__(self, easy_model, field, value, label): @@ -79,32 +80,30 @@ class EasyChoice(object): self.value, self.label = value, label def __repr__(self): - return smart_str(u'' % (self.model.model._meta.object_name, self.field.name)) + return smart_str('' % (self.model.model._meta.object_name, self.field.name)) def url(self): - return mark_safe('%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value))) + return '%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value)) +@python_2_unicode_compatible class EasyInstance(object): def __init__(self, easy_model, instance): self.model, self.instance = easy_model, instance def __repr__(self): - return smart_str(u'' % (self.model.model._meta.object_name, self.instance._get_pk_val())) - - def __unicode__(self): - val = smart_unicode(self.instance) - if len(val) > DISPLAY_SIZE: - return val[:DISPLAY_SIZE] + u'...' - return val + return smart_str('' % (self.model.model._meta.object_name, self.instance._get_pk_val())) def __str__(self): - return self.__unicode__().encode('utf-8') + val = smart_text(self.instance) + if len(val) > DISPLAY_SIZE: + return val[:DISPLAY_SIZE] + '...' + return val def pk(self): return self.instance._get_pk_val() def url(self): - return mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, iri_to_uri(self.pk()))) + return '%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, iri_to_uri(self.pk())) def fields(self): """ @@ -136,7 +135,7 @@ class EasyInstanceField(object): self.raw_value = getattr(instance.instance, field.name) def __repr__(self): - return smart_str(u'' % (self.model.model._meta.object_name, self.field.name)) + return smart_str('' % (self.model.model._meta.object_name, self.field.name)) def values(self): """ @@ -176,8 +175,6 @@ class EasyInstanceField(object): for plugin_name, plugin in self.model.model_databrowse().plugins.items(): urls = plugin.urls(plugin_name, self) if urls is not None: - #plugin_urls.append(urls) - values = self.values() return zip(self.values(), urls) if self.field.rel: m = EasyModel(self.model.site, self.field.rel.to) @@ -186,20 +183,20 @@ class EasyInstanceField(object): for value in self.values(): if value is None: continue - url = mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val()))) - lst.append((smart_unicode(value), url)) + url = '%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val())) + lst.append((smart_text(value), url)) else: lst = [(value, None) for value in self.values()] elif self.field.choices: lst = [] for value in self.values(): - url = mark_safe('%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value))) + url = '%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value)) lst.append((value, url)) elif isinstance(self.field, models.URLField): - val = self.values()[0] + val = list(self.values())[0] lst = [(val, iri_to_uri(val))] else: - lst = [(self.values()[0], None)] + lst = [(list(self.values())[0], None)] return lst class EasyQuerySet(QuerySet): diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py index 3416f88ca7..a548c33c8f 100644 --- a/django/contrib/databrowse/plugins/calendars.py +++ b/django/contrib/databrowse/plugins/calendars.py @@ -1,11 +1,13 @@ +from __future__ import unicode_literals + from django import http from django.db import models from django.contrib.databrowse.datastructures import EasyModel from django.contrib.databrowse.sites import DatabrowsePlugin from django.shortcuts import render_to_response +from django.utils.html import format_html, format_html_join from django.utils.text import capfirst -from django.utils.encoding import force_unicode -from django.utils.safestring import mark_safe +from django.utils.encoding import force_text from django.views.generic import dates from django.utils import datetime_safe @@ -61,19 +63,20 @@ class CalendarPlugin(DatabrowsePlugin): def model_index_html(self, request, model, site): fields = self.field_dict(model) if not fields: - return u'' - return mark_safe(u'

    View calendar by: %s

    ' % \ - u', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) + return '' + return format_html('

    View calendar by: {0}

    ', + format_html_join(', ', '{1}', + ((f.name, force_text(capfirst(f.verbose_name))) for f in fields.values()))) def urls(self, plugin_name, easy_instance_field): if isinstance(easy_instance_field.field, models.DateField): d = easy_instance_field.raw_value - return [mark_safe(u'%s%s/%s/%s/%s/%s/' % ( + return ['%s%s/%s/%s/%s/%s/' % ( easy_instance_field.model.url(), plugin_name, easy_instance_field.field.name, str(d.year), datetime_safe.new_date(d).strftime('%b').lower(), - d.day))] + d.day)] def model_view(self, request, model_databrowse, url): self.model, self.site = model_databrowse.model, model_databrowse.site @@ -93,7 +96,7 @@ class CalendarPlugin(DatabrowsePlugin): def homepage_view(self, request): easy_model = EasyModel(self.site, self.model) - field_list = self.fields.values() + field_list = list(self.fields.values()) field_list.sort(key=lambda k:k.verbose_name) return render_to_response('databrowse/calendar_homepage.html', { 'root_url': self.site.root_url, diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py index b3210681e9..dc5e9aef14 100644 --- a/django/contrib/databrowse/plugins/fieldchoices.py +++ b/django/contrib/databrowse/plugins/fieldchoices.py @@ -1,12 +1,15 @@ +from __future__ import unicode_literals + from django import http from django.db import models from django.contrib.databrowse.datastructures import EasyModel from django.contrib.databrowse.sites import DatabrowsePlugin from django.shortcuts import render_to_response +from django.utils.html import format_html, format_html_join +from django.utils.http import urlquote from django.utils.text import capfirst -from django.utils.encoding import smart_str, force_unicode -from django.utils.safestring import mark_safe -import urllib +from django.utils.encoding import force_text + class FieldChoicePlugin(DatabrowsePlugin): def __init__(self, field_filter=None): @@ -29,17 +32,17 @@ class FieldChoicePlugin(DatabrowsePlugin): def model_index_html(self, request, model, site): fields = self.field_dict(model) if not fields: - return u'' - return mark_safe(u'

    View by: %s

    ' % \ - u', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) + return '' + return format_html('

    View by: {0}

    ', + format_html_join(', ', '{1}', + ((f.name, force_text(capfirst(f.verbose_name))) for f in fields.values()))) def urls(self, plugin_name, easy_instance_field): if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values(): - field_value = smart_str(easy_instance_field.raw_value) - return [mark_safe(u'%s%s/%s/%s/' % ( + return ['%s%s/%s/%s/' % ( easy_instance_field.model.url(), plugin_name, easy_instance_field.field.name, - urllib.quote(field_value, safe='')))] + urlquote(easy_instance_field.raw_value, safe=''))] def model_view(self, request, model_databrowse, url): self.model, self.site = model_databrowse.model, model_databrowse.site @@ -60,7 +63,7 @@ class FieldChoicePlugin(DatabrowsePlugin): def homepage_view(self, request): easy_model = EasyModel(self.site, self.model) - field_list = self.fields.values() + field_list = list(self.fields.values()) field_list.sort(key=lambda k: k.verbose_name) return render_to_response('databrowse/fieldchoice_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list}) diff --git a/django/contrib/databrowse/plugins/objects.py b/django/contrib/databrowse/plugins/objects.py index 7326566655..e956f4ea67 100644 --- a/django/contrib/databrowse/plugins/objects.py +++ b/django/contrib/databrowse/plugins/objects.py @@ -1,14 +1,18 @@ +try: + from urllib.parse import urljoin +except ImportError: # Python 2 + from urlparse import urljoin + from django import http from django.contrib.databrowse.datastructures import EasyModel from django.contrib.databrowse.sites import DatabrowsePlugin from django.shortcuts import render_to_response -import urlparse class ObjectDetailPlugin(DatabrowsePlugin): def model_view(self, request, model_databrowse, url): # If the object ID wasn't provided, redirect to the model page, which is one level up. if url is None: - return http.HttpResponseRedirect(urlparse.urljoin(request.path, '../')) + return http.HttpResponseRedirect(urljoin(request.path, '../')) easy_model = EasyModel(model_databrowse.site, model_databrowse.model) obj = easy_model.object_by_pk(url) return render_to_response('databrowse/object_detail.html', {'object': obj, 'root_url': model_databrowse.site.root_url}) diff --git a/django/contrib/databrowse/sites.py b/django/contrib/databrowse/sites.py index d90bb562b2..b5cb2639d6 100644 --- a/django/contrib/databrowse/sites.py +++ b/django/contrib/databrowse/sites.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import http from django.db import models from django.contrib.databrowse.datastructures import EasyModel @@ -61,7 +63,7 @@ class ModelDatabrowse(object): def main_view(self, request): easy_model = EasyModel(self.site, self.model) - html_snippets = mark_safe(u'\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()])) + html_snippets = mark_safe('\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()])) return render_to_response('databrowse/model_detail.html', { 'model': easy_model, 'root_url': self.site.root_url, diff --git a/django/contrib/databrowse/tests.py b/django/contrib/databrowse/tests.py index 149383cf72..d649b4af67 100644 --- a/django/contrib/databrowse/tests.py +++ b/django/contrib/databrowse/tests.py @@ -1,26 +1,30 @@ from django.contrib import databrowse from django.db import models from django.test import TestCase +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class SomeModel(models.Model): some_field = models.CharField(max_length=50) - def __unicode__(self): + def __str__(self): return self.some_field +@python_2_unicode_compatible class SomeOtherModel(models.Model): some_other_field = models.CharField(max_length=50) - def __unicode__(self): + def __str__(self): return self.some_other_field +@python_2_unicode_compatible class YetAnotherModel(models.Model): yet_another_field = models.CharField(max_length=50) - def __unicode__(self): + def __str__(self): return self.yet_another_field diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py index 85873ac7b1..3a5b4d6135 100644 --- a/django/contrib/flatpages/models.py +++ b/django/contrib/flatpages/models.py @@ -1,8 +1,12 @@ +from __future__ import unicode_literals + from django.db import models from django.contrib.sites.models import Site from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class FlatPage(models.Model): url = models.CharField(_('URL'), max_length=100, db_index=True) title = models.CharField(_('title'), max_length=200) @@ -19,8 +23,8 @@ class FlatPage(models.Model): verbose_name_plural = _('flat pages') ordering = ('url',) - def __unicode__(self): - return u"%s -- %s" % (self.url, self.title) + def __str__(self): + return "%s -- %s" % (self.url, self.title) def get_absolute_url(self): return self.url diff --git a/django/contrib/flatpages/tests/forms.py b/django/contrib/flatpages/tests/forms.py index 0e54176aa2..3bdfa15dfe 100644 --- a/django/contrib/flatpages/tests/forms.py +++ b/django/contrib/flatpages/tests/forms.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf import settings from django.contrib.flatpages.forms import FlatpageForm from django.contrib.flatpages.models import FlatPage @@ -60,7 +62,7 @@ class FlatpageAdminFormTests(TestCase): self.assertEqual( f.errors, - {'__all__': [u'Flatpage with url /myflatpage1/ already exists for site example.com']}) + {'__all__': ['Flatpage with url /myflatpage1/ already exists for site example.com']}) def test_flatpage_admin_form_edit(self): """ @@ -92,5 +94,5 @@ class FlatpageAdminFormTests(TestCase): self.assertEqual( f.errors, - {'sites': [translation.ugettext(u'This field is required.')]}) + {'sites': [translation.ugettext('This field is required.')]}) diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py index ef7fda5d5c..0b462ac5a4 100644 --- a/django/contrib/flatpages/views.py +++ b/django/contrib/flatpages/views.py @@ -23,7 +23,7 @@ def flatpage(request, url): Models: `flatpages.flatpages` Templates: Uses the template defined by the ``template_name`` field, - or `flatpages/default.html` if template_name is not defined. + or :template:`flatpages/default.html` if template_name is not defined. Context: flatpage `flatpages.flatpages` object diff --git a/django/contrib/formtools/tests/__init__.py b/django/contrib/formtools/tests/__init__.py index b5d905071f..15941332ed 100644 --- a/django/contrib/formtools/tests/__init__.py +++ b/django/contrib/formtools/tests/__init__.py @@ -1,4 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import datetime import os +import pickle import re import warnings @@ -14,6 +19,7 @@ from django.contrib.formtools.tests.wizard import * from django.contrib.formtools.tests.forms import * success_string = "Done was called!" +success_string_encoded = success_string.encode() class TestFormPreview(preview.FormPreview): def get_context(self, request, form): @@ -41,7 +47,7 @@ class PreviewTests(TestCase): self.preview = preview.FormPreview(TestForm) input_template = '' self.input = input_template % (self.preview.unused_name('stage'), "%d") - self.test_data = {'field1':u'foo', 'field1_':u'asdf'} + self.test_data = {'field1': 'foo', 'field1_': 'asdf'} def test_unused_name(self): """ @@ -76,7 +82,7 @@ class PreviewTests(TestCase): """ # Pass strings for form submittal and add stage variable to # show we previously saw first stage of the form. - self.test_data.update({'stage': 1}) + self.test_data.update({'stage': 1, 'date1': datetime.date(2006, 10, 25)}) response = self.client.post('/preview/', self.test_data) # Check to confirm stage is set to 2 in output form. stage = self.input % 2 @@ -94,13 +100,13 @@ class PreviewTests(TestCase): """ # Pass strings for form submittal and add stage variable to # show we previously saw first stage of the form. - self.test_data.update({'stage':2}) + self.test_data.update({'stage': 2, 'date1': datetime.date(2006, 10, 25)}) response = self.client.post('/preview/', self.test_data) - self.assertNotEqual(response.content, success_string) + self.assertNotEqual(response.content, success_string_encoded) hash = self.preview.security_hash(None, TestForm(self.test_data)) self.test_data.update({'hash': hash}) response = self.client.post('/preview/', self.test_data) - self.assertEqual(response.content, success_string) + self.assertEqual(response.content, success_string_encoded) def test_bool_submit(self): """ @@ -117,10 +123,10 @@ class PreviewTests(TestCase): """ self.test_data.update({'stage':2}) hash = self.preview.security_hash(None, TestForm(self.test_data)) - self.test_data.update({'hash':hash, 'bool1':u'False'}) + self.test_data.update({'hash': hash, 'bool1': 'False'}) with warnings.catch_warnings(record=True): response = self.client.post('/preview/', self.test_data) - self.assertEqual(response.content, success_string) + self.assertEqual(response.content, success_string_encoded) def test_form_submit_good_hash(self): """ @@ -131,11 +137,11 @@ class PreviewTests(TestCase): # show we previously saw first stage of the form. self.test_data.update({'stage':2}) response = self.client.post('/preview/', self.test_data) - self.assertNotEqual(response.content, success_string) + self.assertNotEqual(response.content, success_string_encoded) hash = utils.form_hmac(TestForm(self.test_data)) self.test_data.update({'hash': hash}) response = self.client.post('/preview/', self.test_data) - self.assertEqual(response.content, success_string) + self.assertEqual(response.content, success_string_encoded) def test_form_submit_bad_hash(self): @@ -148,11 +154,11 @@ class PreviewTests(TestCase): self.test_data.update({'stage':2}) response = self.client.post('/preview/', self.test_data) self.assertEqual(response.status_code, 200) - self.assertNotEqual(response.content, success_string) + self.assertNotEqual(response.content, success_string_encoded) hash = utils.form_hmac(TestForm(self.test_data)) + "bad" self.test_data.update({'hash': hash}) response = self.client.post('/previewpreview/', self.test_data) - self.assertNotEqual(response.content, success_string) + self.assertNotEqual(response.content, success_string_encoded) class FormHmacTests(unittest.TestCase): @@ -163,8 +169,8 @@ class FormHmacTests(unittest.TestCase): leading/trailing whitespace so as to be friendly to broken browsers that submit it (usually in textareas). """ - f1 = HashTestForm({'name': u'joe', 'bio': u'Nothing notable.'}) - f2 = HashTestForm({'name': u' joe', 'bio': u'Nothing notable. '}) + f1 = HashTestForm({'name': 'joe', 'bio': 'Speaking español.'}) + f2 = HashTestForm({'name': ' joe', 'bio': 'Speaking español. '}) hash1 = utils.form_hmac(f1) hash2 = utils.form_hmac(f2) self.assertEqual(hash1, hash2) @@ -266,10 +272,13 @@ class WizardTests(TestCase): Form should advance if the hash is present and good, as calculated using current method. """ - data = {"0-field": u"test", - "1-field": u"test2", - "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "wizard_step": u"1"} + data = {"0-field": "test", + "1-field": "test2", + "hash_0": { + 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", + 3: "9355d5dff22d49dbad58e46189982cec649f9f5b", + }[pickle.HIGHEST_PROTOCOL], + "wizard_step": "1"} response = self.client.post('/wizard1/', data) self.assertEqual(2, response.context['step0']) @@ -291,18 +300,27 @@ class WizardTests(TestCase): reached[0] = True wizard = WizardWithProcessStep([WizardPageOneForm]) - data = {"0-field": u"test", - "1-field": u"test2", - "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "wizard_step": u"1"} + data = {"0-field": "test", + "1-field": "test2", + "hash_0": { + 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", + 3: "9355d5dff22d49dbad58e46189982cec649f9f5b", + }[pickle.HIGHEST_PROTOCOL], + "wizard_step": "1"} wizard(DummyRequest(POST=data)) self.assertTrue(reached[0]) - data = {"0-field": u"test", - "1-field": u"test2", - "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "hash_1": u"1e6f6315da42e62f33a30640ec7e007ad3fbf1a1", - "wizard_step": u"2"} + data = {"0-field": "test", + "1-field": "test2", + "hash_0": { + 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", + 3: "9355d5dff22d49dbad58e46189982cec649f9f5b", + }[pickle.HIGHEST_PROTOCOL], + "hash_1": { + 2: "1e6f6315da42e62f33a30640ec7e007ad3fbf1a1", + 3: "c33142ef9d01b1beae238adf22c3c6c57328f51a", + }[pickle.HIGHEST_PROTOCOL], + "wizard_step": "2"} self.assertRaises(http.Http404, wizard, DummyRequest(POST=data)) def test_14498(self): @@ -315,16 +333,19 @@ class WizardTests(TestCase): class WizardWithProcessStep(TestWizardClass): def process_step(self, request, form, step): - that.assertTrue(hasattr(form, 'cleaned_data')) + that.assertTrue(form.is_valid()) reached[0] = True wizard = WizardWithProcessStep([WizardPageOneForm, WizardPageTwoForm, WizardPageThreeForm]) - data = {"0-field": u"test", - "1-field": u"test2", - "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "wizard_step": u"1"} + data = {"0-field": "test", + "1-field": "test2", + "hash_0": { + 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", + 3: "9355d5dff22d49dbad58e46189982cec649f9f5b", + }[pickle.HIGHEST_PROTOCOL], + "wizard_step": "1"} wizard(DummyRequest(POST=data)) self.assertTrue(reached[0]) @@ -345,10 +366,13 @@ class WizardTests(TestCase): wizard = Wizard([WizardPageOneForm, WizardPageTwoForm]) - data = {"0-field": u"test", - "1-field": u"test2", - "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "wizard_step": u"1"} + data = {"0-field": "test", + "1-field": "test2", + "hash_0": { + 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", + 3: "9355d5dff22d49dbad58e46189982cec649f9f5b", + }[pickle.HIGHEST_PROTOCOL], + "wizard_step": "1"} wizard(DummyRequest(POST=data)) self.assertTrue(reached[0]) @@ -371,10 +395,13 @@ class WizardTests(TestCase): wizard = WizardWithProcessStep([WizardPageOneForm, WizardPageTwoForm, WizardPageThreeForm]) - data = {"0-field": u"test", - "1-field": u"test2", - "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", - "wizard_step": u"1"} + data = {"0-field": "test", + "1-field": "test2", + "hash_0": { + 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca", + 3: "9355d5dff22d49dbad58e46189982cec649f9f5b", + }[pickle.HIGHEST_PROTOCOL], + "wizard_step": "1"} wizard(DummyRequest(POST=data)) self.assertTrue(reached[0]) diff --git a/django/contrib/formtools/tests/forms.py b/django/contrib/formtools/tests/forms.py index 9f6f596d3c..1ed2ab48bb 100644 --- a/django/contrib/formtools/tests/forms.py +++ b/django/contrib/formtools/tests/forms.py @@ -21,6 +21,7 @@ class TestForm(forms.Form): field1 = forms.CharField() field1_ = forms.CharField() bool1 = forms.BooleanField(required=False) + date1 = forms.DateField(required=False) class HashTestForm(forms.Form): name = forms.CharField() diff --git a/django/contrib/formtools/tests/wizard/forms.py b/django/contrib/formtools/tests/wizard/forms.py index 8afbd30721..51cfaa661b 100644 --- a/django/contrib/formtools/tests/wizard/forms.py +++ b/django/contrib/formtools/tests/wizard/forms.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import forms, http from django.conf import settings from django.test import TestCase @@ -64,22 +66,22 @@ class TestWizard(WizardView): class FormTests(TestCase): def test_form_init(self): testform = TestWizard.get_initkwargs([Step1, Step2]) - self.assertEqual(testform['form_list'], {u'0': Step1, u'1': Step2}) + self.assertEqual(testform['form_list'], {'0': Step1, '1': Step2}) testform = TestWizard.get_initkwargs([('start', Step1), ('step2', Step2)]) self.assertEqual( - testform['form_list'], {u'start': Step1, u'step2': Step2}) + testform['form_list'], {'start': Step1, 'step2': Step2}) testform = TestWizard.get_initkwargs([Step1, Step2, ('finish', Step3)]) self.assertEqual( - testform['form_list'], {u'0': Step1, u'1': Step2, u'finish': Step3}) + testform['form_list'], {'0': Step1, '1': Step2, 'finish': Step3}) def test_first_step(self): request = get_request() testform = TestWizard.as_view([Step1, Step2]) response, instance = testform(request) - self.assertEqual(instance.steps.current, u'0') + self.assertEqual(instance.steps.current, '0') testform = TestWizard.as_view([('start', Step1), ('step2', Step2)]) response, instance = testform(request) diff --git a/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py b/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py index 37913fa078..7529d89a2c 100644 --- a/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py +++ b/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.core.urlresolvers import reverse from django.http import QueryDict from django.test import TestCase @@ -51,8 +53,8 @@ class NamedWizardTests(object): self.assertEqual(response.status_code, 200) self.assertEqual(response.context['wizard']['steps'].current, 'form1') self.assertEqual(response.context['wizard']['form'].errors, - {'name': [u'This field is required.'], - 'user': [u'This field is required.']}) + {'name': ['This field is required.'], + 'user': ['This field is required.']}) def test_form_post_success(self): response = self.client.post( @@ -120,6 +122,7 @@ class NamedWizardTests(object): self.assertEqual(response.context['wizard']['steps'].current, 'form2') post_data = self.wizard_step_data[1] + post_data['form2-file1'].close() post_data['form2-file1'] = open(__file__, 'rb') response = self.client.post( reverse(self.wizard_urlname, @@ -147,13 +150,15 @@ class NamedWizardTests(object): self.assertEqual(response.status_code, 200) all_data = response.context['form_list'] - self.assertEqual(all_data[1]['file1'].read(), open(__file__, 'rb').read()) + with open(__file__, 'rb') as f: + self.assertEqual(all_data[1]['file1'].read(), f.read()) + all_data[1]['file1'].close() del all_data[1]['file1'] self.assertEqual(all_data, [ - {'name': u'Pony', 'thirsty': True, 'user': self.testuser}, - {'address1': u'123 Main St', 'address2': u'Djangoland'}, - {'random_crap': u'blah blah'}, - [{'random_crap': u'blah blah'}, {'random_crap': u'blah blah'}]]) + {'name': 'Pony', 'thirsty': True, 'user': self.testuser}, + {'address1': '123 Main St', 'address2': 'Djangoland'}, + {'random_crap': 'blah blah'}, + [{'random_crap': 'blah blah'}, {'random_crap': 'blah blah'}]]) def test_cleaned_data(self): response = self.client.get( @@ -180,9 +185,10 @@ class NamedWizardTests(object): response = self.client.get(step2_url) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['wizard']['steps'].current, 'form2') - self.assertEqual( - response.context['wizard']['form'].files['form2-file1'].read(), - open(__file__, 'rb').read()) + with open(__file__, 'rb') as f: + self.assertEqual( + response.context['wizard']['form'].files['form2-file1'].read(), + f.read()) response = self.client.post( reverse(self.wizard_urlname, @@ -199,15 +205,17 @@ class NamedWizardTests(object): self.assertEqual(response.status_code, 200) all_data = response.context['all_cleaned_data'] - self.assertEqual(all_data['file1'].read(), open(__file__, 'rb').read()) + with open(__file__, 'rb') as f: + self.assertEqual(all_data['file1'].read(), f.read()) + all_data['file1'].close() del all_data['file1'] self.assertEqual( all_data, - {'name': u'Pony', 'thirsty': True, 'user': self.testuser, - 'address1': u'123 Main St', 'address2': u'Djangoland', - 'random_crap': u'blah blah', 'formset-form4': [ - {'random_crap': u'blah blah'}, - {'random_crap': u'blah blah'} + {'name': 'Pony', 'thirsty': True, 'user': self.testuser, + 'address1': '123 Main St', 'address2': 'Djangoland', + 'random_crap': 'blah blah', 'formset-form4': [ + {'random_crap': 'blah blah'}, + {'random_crap': 'blah blah'} ]}) def test_manipulated_data(self): @@ -223,6 +231,7 @@ class NamedWizardTests(object): self.assertEqual(response.status_code, 200) post_data = self.wizard_step_data[1] + post_data['form2-file1'].close() post_data['form2-file1'] = open(__file__, 'rb') response = self.client.post( reverse(self.wizard_urlname, diff --git a/django/contrib/formtools/tests/wizard/wizardtests/tests.py b/django/contrib/formtools/tests/wizard/wizardtests/tests.py index a9a81ba70f..586bd59341 100644 --- a/django/contrib/formtools/tests/wizard/wizardtests/tests.py +++ b/django/contrib/formtools/tests/wizard/wizardtests/tests.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import os from django import forms @@ -33,8 +35,8 @@ class WizardTests(object): self.assertEqual(response.status_code, 200) self.assertEqual(response.context['wizard']['steps'].current, 'form1') self.assertEqual(response.context['wizard']['form'].errors, - {'name': [u'This field is required.'], - 'user': [u'This field is required.']}) + {'name': ['This field is required.'], + 'user': ['This field is required.']}) def test_form_post_success(self): response = self.client.post(self.wizard_url, self.wizard_step_data[0]) @@ -93,14 +95,16 @@ class WizardTests(object): self.assertEqual(response.status_code, 200) all_data = response.context['form_list'] - self.assertEqual(all_data[1]['file1'].read(), open(__file__, 'rb').read()) + with open(__file__, 'rb') as f: + self.assertEqual(all_data[1]['file1'].read(), f.read()) + all_data[1]['file1'].close() del all_data[1]['file1'] self.assertEqual(all_data, [ - {'name': u'Pony', 'thirsty': True, 'user': self.testuser}, - {'address1': u'123 Main St', 'address2': u'Djangoland'}, - {'random_crap': u'blah blah'}, - [{'random_crap': u'blah blah'}, - {'random_crap': u'blah blah'}]]) + {'name': 'Pony', 'thirsty': True, 'user': self.testuser}, + {'address1': '123 Main St', 'address2': 'Djangoland'}, + {'random_crap': 'blah blah'}, + [{'random_crap': 'blah blah'}, + {'random_crap': 'blah blah'}]]) def test_cleaned_data(self): response = self.client.get(self.wizard_url) @@ -110,8 +114,9 @@ class WizardTests(object): self.assertEqual(response.status_code, 200) post_data = self.wizard_step_data[1] - post_data['form2-file1'] = open(__file__, 'rb') - response = self.client.post(self.wizard_url, post_data) + with open(__file__, 'rb') as post_file: + post_data['form2-file1'] = post_file + response = self.client.post(self.wizard_url, post_data) self.assertEqual(response.status_code, 200) response = self.client.post(self.wizard_url, self.wizard_step_data[2]) @@ -121,14 +126,16 @@ class WizardTests(object): self.assertEqual(response.status_code, 200) all_data = response.context['all_cleaned_data'] - self.assertEqual(all_data['file1'].read(), open(__file__, 'rb').read()) + with open(__file__, 'rb') as f: + self.assertEqual(all_data['file1'].read(), f.read()) + all_data['file1'].close() del all_data['file1'] self.assertEqual(all_data, { - 'name': u'Pony', 'thirsty': True, 'user': self.testuser, - 'address1': u'123 Main St', 'address2': u'Djangoland', - 'random_crap': u'blah blah', 'formset-form4': [ - {'random_crap': u'blah blah'}, - {'random_crap': u'blah blah'}]}) + 'name': 'Pony', 'thirsty': True, 'user': self.testuser, + 'address1': '123 Main St', 'address2': 'Djangoland', + 'random_crap': 'blah blah', 'formset-form4': [ + {'random_crap': 'blah blah'}, + {'random_crap': 'blah blah'}]}) def test_manipulated_data(self): response = self.client.get(self.wizard_url) @@ -138,6 +145,7 @@ class WizardTests(object): self.assertEqual(response.status_code, 200) post_data = self.wizard_step_data[1] + post_data['form2-file1'].close() post_data['form2-file1'] = open(__file__, 'rb') response = self.client.post(self.wizard_url, post_data) self.assertEqual(response.status_code, 200) @@ -165,6 +173,7 @@ class WizardTests(object): self.assertEqual(response.context['wizard']['steps'].current, 'form2') post_data = self.wizard_step_data[1] + post_data['form2-file1'].close() post_data['form2-file1'] = open(__file__, 'rb') response = self.client.post(self.wizard_url, post_data) self.assertEqual(response.status_code, 200) diff --git a/django/contrib/formtools/utils.py b/django/contrib/formtools/utils.py index 96a0092a35..76277c6b49 100644 --- a/django/contrib/formtools/utils.py +++ b/django/contrib/formtools/utils.py @@ -1,7 +1,10 @@ +from __future__ import unicode_literals + # Do not try cPickle here (see #18340) import pickle from django.utils.crypto import salted_hmac +from django.utils import six def form_hmac(form): @@ -16,7 +19,7 @@ def form_hmac(form): value = bf.data or '' else: value = bf.field.clean(bf.data) or '' - if isinstance(value, basestring): + if isinstance(value, six.string_types): value = value.strip() data.append((bf.name, value)) diff --git a/django/contrib/formtools/wizard/storage/base.py b/django/contrib/formtools/wizard/storage/base.py index 274e07ffbe..aafc833484 100644 --- a/django/contrib/formtools/wizard/storage/base.py +++ b/django/contrib/formtools/wizard/storage/base.py @@ -1,7 +1,7 @@ from django.core.files.uploadedfile import UploadedFile from django.utils.datastructures import MultiValueDict -from django.utils.encoding import smart_str from django.utils.functional import lazy_property +from django.utils import six from django.contrib.formtools.wizard.storage.exceptions import NoFileStorageConfigured @@ -72,9 +72,8 @@ class BaseStorage(object): raise NoFileStorageConfigured files = {} - for field, field_dict in wizard_files.iteritems(): - field_dict = dict((smart_str(k), v) - for k, v in field_dict.iteritems()) + for field, field_dict in six.iteritems(wizard_files): + field_dict = field_dict.copy() tmp_name = field_dict.pop('tmp_name') files[field] = UploadedFile( file=self.file_storage.open(tmp_name), **field_dict) @@ -87,7 +86,7 @@ class BaseStorage(object): if step not in self.data[self.step_files_key]: self.data[self.step_files_key][step] = {} - for field, field_file in (files or {}).iteritems(): + for field, field_file in six.iteritems(files or {}): tmp_filename = self.file_storage.save(field_file.name, field_file) file_dict = { 'tmp_name': tmp_filename, diff --git a/django/contrib/formtools/wizard/views.py b/django/contrib/formtools/wizard/views.py index 4372c8aa6a..ea41e86852 100644 --- a/django/contrib/formtools/wizard/views.py +++ b/django/contrib/formtools/wizard/views.py @@ -7,6 +7,7 @@ from django.forms import formsets, ValidationError from django.views.generic import TemplateView from django.utils.datastructures import SortedDict from django.utils.decorators import classonlymethod +from django.utils import six from django.contrib.formtools.wizard.storage import get_storage from django.contrib.formtools.wizard.storage.exceptions import NoFileStorageConfigured @@ -43,7 +44,7 @@ class StepsHelper(object): @property def all(self): "Returns the names of all steps/forms." - return self._wizard.get_form_list().keys() + return list(self._wizard.get_form_list()) @property def count(self): @@ -133,8 +134,9 @@ class WizardView(TemplateView): The key should be equal to the `step_name` in the `form_list` (or the str of the zero based counter - if no step_names added in the `form_list`) - * `instance_dict` - contains a dictionary of instance objects. This - is only used when `ModelForm`s are used. The key should be equal to + * `instance_dict` - contains a dictionary whose values are model + instances if the step is based on a ``ModelForm`` and querysets if + the step is based on a ``ModelFormSet``. The key should be equal to the `step_name` in the `form_list`. Same rules as for `initial_dict` apply. * `condition_dict` - contains a dictionary of boolean values or @@ -156,20 +158,20 @@ class WizardView(TemplateView): if isinstance(form, (list, tuple)): # if the element is a tuple, add the tuple to the new created # sorted dictionary. - init_form_list[unicode(form[0])] = form[1] + init_form_list[six.text_type(form[0])] = form[1] else: # if not, add the form with a zero based counter as unicode - init_form_list[unicode(i)] = form + init_form_list[six.text_type(i)] = form # walk through the new created list of forms - for form in init_form_list.itervalues(): + for form in six.itervalues(init_form_list): if issubclass(form, formsets.BaseFormSet): # if the element is based on BaseFormSet (FormSet/ModelFormSet) # we need to override the form variable. form = form.form # check if any form contains a FileField, if yes, we need a # file_storage added to the wizardview (by subclassing). - for field in form.base_fields.itervalues(): + for field in six.itervalues(form.base_fields): if (isinstance(field, forms.FileField) and not hasattr(cls, 'file_storage')): raise NoFileStorageConfigured @@ -194,7 +196,7 @@ class WizardView(TemplateView): could use data from other (maybe previous forms). """ form_list = SortedDict() - for form_key, form_class in self.form_list.iteritems(): + for form_key, form_class in six.iteritems(self.form_list): # try to fetch the value from condition list, by default, the form # gets passed to the new list. condition = self.condition_dict.get(form_key, True) diff --git a/django/contrib/gis/admin/widgets.py b/django/contrib/gis/admin/widgets.py index aaffae8f5f..47570d3f9d 100644 --- a/django/contrib/gis/admin/widgets.py +++ b/django/contrib/gis/admin/widgets.py @@ -1,10 +1,11 @@ from django.forms.widgets import Textarea from django.template import loader, Context from django.templatetags.static import static +from django.utils import six from django.utils import translation from django.contrib.gis.gdal import OGRException -from django.contrib.gis.geos import GEOSGeometry, GEOSException +from django.contrib.gis.geos import GEOSGeometry, GEOSException, fromstr # Creating a template context that contains Django settings # values needed by admin map templates. @@ -25,7 +26,7 @@ class OpenLayersWidget(Textarea): # If a string reaches here (via a validation error on another # field) then just reconstruct the Geometry. - if isinstance(value, basestring): + if isinstance(value, six.string_types): try: value = GEOSGeometry(value) except (GEOSException, ValueError): @@ -104,3 +105,25 @@ class OpenLayersWidget(Textarea): raise TypeError map_options[js_name] = value return map_options + + def _has_changed(self, initial, data): + """ Compare geographic value of data with its initial value. """ + + # Ensure we are dealing with a geographic object + if isinstance(initial, six.string_types): + try: + initial = GEOSGeometry(initial) + except (GEOSException, ValueError): + initial = None + + # Only do a geographic comparison if both values are available + if initial and data: + data = fromstr(data) + data.transform(initial.srid) + # If the initial value was not added by the browser, the geometry + # provided may be slightly different, the first time it is saved. + # The comparison is done with a very low tolerance. + return not initial.equals_exact(data, tolerance=0.000001) + else: + # Check for change of state of existence + return bool(initial) != bool(data) diff --git a/django/contrib/gis/db/backends/base.py b/django/contrib/gis/db/backends/base.py index 26e97622a8..f7af420a8d 100644 --- a/django/contrib/gis/db/backends/base.py +++ b/django/contrib/gis/db/backends/base.py @@ -4,6 +4,8 @@ Base/mixin classes for the spatial backend database operations and the """ import re from django.contrib.gis import gdal +from django.utils import six +from django.utils.encoding import python_2_unicode_compatible class BaseSpatialOperations(object): """ @@ -88,7 +90,7 @@ class BaseSpatialOperations(object): # For quoting column values, rather than columns. def geo_quote_name(self, name): - if isinstance(name, unicode): + if isinstance(name, six.text_type): name = name.encode('ascii') return "'%s'" % name @@ -130,6 +132,7 @@ class BaseSpatialOperations(object): def spatial_ref_sys(self): raise NotImplementedError +@python_2_unicode_compatible class SpatialRefSysMixin(object): """ The SpatialRefSysMixin is a class used by the database-dependent @@ -324,12 +327,12 @@ class SpatialRefSysMixin(object): radius, flattening = sphere_params return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening) - def __unicode__(self): + def __str__(self): """ Returns the string representation. If GDAL is installed, it will be 'pretty' OGC WKT. """ try: - return unicode(self.srs) + return six.text_type(self.srs) except: - return unicode(self.wkt) + return six.text_type(self.wkt) diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py index c0e5aa6691..7152f4682d 100644 --- a/django/contrib/gis/db/backends/mysql/operations.py +++ b/django/contrib/gis/db/backends/mysql/operations.py @@ -3,6 +3,8 @@ from django.db.backends.mysql.base import DatabaseOperations from django.contrib.gis.db.backends.adapter import WKTAdapter from django.contrib.gis.db.backends.base import BaseSpatialOperations +from django.utils import six + class MySQLOperations(DatabaseOperations, BaseSpatialOperations): compiler_module = 'django.contrib.gis.db.backends.mysql.compiler' @@ -30,7 +32,7 @@ class MySQLOperations(DatabaseOperations, BaseSpatialOperations): 'within' : 'MBRWithin', } - gis_terms = dict([(term, None) for term in geometry_functions.keys() + ['isnull']]) + gis_terms = dict([(term, None) for term in list(geometry_functions) + ['isnull']]) def geo_db_type(self, f): return f.geom_type diff --git a/django/contrib/gis/db/backends/oracle/models.py b/django/contrib/gis/db/backends/oracle/models.py index ed29f7bb38..b7deb3a946 100644 --- a/django/contrib/gis/db/backends/oracle/models.py +++ b/django/contrib/gis/db/backends/oracle/models.py @@ -9,7 +9,9 @@ """ from django.contrib.gis.db import models from django.contrib.gis.db.backends.base import SpatialRefSysMixin +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class GeometryColumns(models.Model): "Maps to the Oracle USER_SDO_GEOM_METADATA table." table_name = models.CharField(max_length=32) @@ -36,7 +38,7 @@ class GeometryColumns(models.Model): """ return 'column_name' - def __unicode__(self): + def __str__(self): return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid) class SpatialRefSys(models.Model, SpatialRefSysMixin): diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index a2374bb808..392feb129b 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -16,6 +16,7 @@ from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter from django.contrib.gis.db.backends.util import SpatialFunction from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Distance +from django.utils import six class SDOOperation(SpatialFunction): "Base class for SDO* Oracle operations." @@ -65,7 +66,7 @@ class SDORelate(SpatialFunction): super(SDORelate, self).__init__(self.relate_func, mask=mask) # Valid distance types and substitutions -dtypes = (Decimal, Distance, float, int, long) +dtypes = (Decimal, Distance, float) + six.integer_types class OracleOperations(DatabaseOperations, BaseSpatialOperations): compiler_module = "django.contrib.gis.db.backends.oracle.compiler" @@ -120,14 +121,14 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations): 'exact' : SDOOperation('SDO_EQUAL'), 'overlaps' : SDOOperation('SDO_OVERLAPS'), 'same_as' : SDOOperation('SDO_EQUAL'), - 'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch' + 'relate' : (SDORelate, six.string_types), # Oracle uses a different syntax, e.g., 'mask=inside+touch' 'touches' : SDOOperation('SDO_TOUCH'), 'within' : SDOOperation('SDO_INSIDE'), } geometry_functions.update(distance_functions) gis_terms = ['isnull'] - gis_terms += geometry_functions.keys() + gis_terms += list(geometry_functions) gis_terms = dict([(term, None) for term in gis_terms]) truncate_params = {'relate' : None} diff --git a/django/contrib/gis/db/backends/postgis/models.py b/django/contrib/gis/db/backends/postgis/models.py index a38598343c..e8052594c6 100644 --- a/django/contrib/gis/db/backends/postgis/models.py +++ b/django/contrib/gis/db/backends/postgis/models.py @@ -3,7 +3,9 @@ """ from django.db import models from django.contrib.gis.db.backends.base import SpatialRefSysMixin +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class GeometryColumns(models.Model): """ The 'geometry_columns' table from the PostGIS. See the PostGIS @@ -37,7 +39,7 @@ class GeometryColumns(models.Model): """ return 'f_geometry_column' - def __unicode__(self): + def __str__(self): return "%s.%s - %dD %s field (SRID: %d)" % \ (self.f_table_name, self.f_geometry_column, self.coord_dimension, self.type, self.srid) diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 964be8de0e..434d8719cc 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -10,6 +10,7 @@ from django.contrib.gis.measure import Distance from django.core.exceptions import ImproperlyConfigured from django.db.backends.postgresql_psycopg2.base import DatabaseOperations from django.db.utils import DatabaseError +from django.utils import six #### Classes used in constructing PostGIS spatial SQL #### class PostGISOperator(SpatialOperation): @@ -161,11 +162,13 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): 'overlaps' : PostGISFunction(prefix, 'Overlaps'), 'contains' : PostGISFunction(prefix, 'Contains'), 'intersects' : PostGISFunction(prefix, 'Intersects'), - 'relate' : (PostGISRelate, basestring), - } + 'relate' : (PostGISRelate, six.string_types), + 'coveredby' : PostGISFunction(prefix, 'CoveredBy'), + 'covers' : PostGISFunction(prefix, 'Covers'), + } # Valid distance types and substitutions - dtypes = (Decimal, Distance, float, int, long) + dtypes = (Decimal, Distance, float) + six.integer_types def get_dist_ops(operator): "Returns operations for both regular and spherical distances." return {'cartesian' : PostGISDistance(prefix, operator), @@ -177,33 +180,12 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): 'distance_gte' : (get_dist_ops('>='), dtypes), 'distance_lt' : (get_dist_ops('<'), dtypes), 'distance_lte' : (get_dist_ops('<='), dtypes), - } - - # Versions 1.2.2+ have KML serialization support. - if version < (1, 2, 2): - ASKML = False - else: - ASKML = 'ST_AsKML' - self.geometry_functions.update( - {'coveredby' : PostGISFunction(prefix, 'CoveredBy'), - 'covers' : PostGISFunction(prefix, 'Covers'), - }) - self.distance_functions['dwithin'] = (PostGISFunctionParam(prefix, 'DWithin'), dtypes) + 'dwithin' : (PostGISFunctionParam(prefix, 'DWithin'), dtypes) + } # Adding the distance functions to the geometries lookup. self.geometry_functions.update(self.distance_functions) - # The union aggregate and topology operation use the same signature - # in versions 1.3+. - if version < (1, 3, 0): - UNIONAGG = 'GeomUnion' - UNION = 'Union' - MAKELINE = False - else: - UNIONAGG = 'ST_Union' - UNION = 'ST_Union' - MAKELINE = 'ST_MakeLine' - # Only PostGIS versions 1.3.4+ have GeoJSON serialization support. if version < (1, 3, 4): GEOJSON = False @@ -235,8 +217,8 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): # Creating a dictionary lookup of all GIS terms for PostGIS. gis_terms = ['isnull'] - gis_terms += self.geometry_operators.keys() - gis_terms += self.geometry_functions.keys() + gis_terms += list(self.geometry_operators) + gis_terms += list(self.geometry_functions) self.gis_terms = dict([(term, None) for term in gis_terms]) self.area = prefix + 'Area' @@ -255,11 +237,11 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): self.geojson = GEOJSON self.gml = prefix + 'AsGML' self.intersection = prefix + 'Intersection' - self.kml = ASKML + self.kml = prefix + 'AsKML' self.length = prefix + 'Length' self.length3d = prefix + 'Length3D' self.length_spheroid = prefix + 'length_spheroid' - self.makeline = MAKELINE + self.makeline = prefix + 'MakeLine' self.mem_size = prefix + 'mem_size' self.num_geom = prefix + 'NumGeometries' self.num_points =prefix + 'npoints' @@ -274,8 +256,8 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): self.sym_difference = prefix + 'SymDifference' self.transform = prefix + 'Transform' self.translate = prefix + 'Translate' - self.union = UNION - self.unionagg = UNIONAGG + self.union = prefix + 'Union' + self.unionagg = prefix + 'Union' def check_aggregate_support(self, aggregate): """ diff --git a/django/contrib/gis/db/backends/spatialite/creation.py b/django/contrib/gis/db/backends/spatialite/creation.py index 7efab3e073..27332b9b57 100644 --- a/django/contrib/gis/db/backends/spatialite/creation.py +++ b/django/contrib/gis/db/backends/spatialite/creation.py @@ -30,9 +30,7 @@ class SpatiaLiteCreation(DatabaseCreation): self.connection.close() self.connection.settings_dict["NAME"] = test_database_name - - # Confirm the feature set of the test database - self.connection.features.confirm() + self.connection.ops.confirm_spatial_components_versions() # Need to load the SpatiaLite initialization SQL before running `syncdb`. self.load_spatialite_sql() @@ -102,14 +100,14 @@ class SpatiaLiteCreation(DatabaseCreation): """ This routine loads up the SpatiaLite SQL file. """ - if self.connection.ops.spatial_version[:2] >= (3, 0): - # Spatialite >= 3.0.x -- No need to load any SQL file, calling + if self.connection.ops.spatial_version[:2] >= (2, 4): + # Spatialite >= 2.4 -- No need to load any SQL file, calling # InitSpatialMetaData() transparently creates the spatial metadata # tables cur = self.connection._cursor() cur.execute("SELECT InitSpatialMetaData()") else: - # Spatialite < 3.0.x -- Load the initial SQL + # Spatialite < 2.4 -- Load the initial SQL # Getting the location of the SpatiaLite SQL file, and confirming # it exists. diff --git a/django/contrib/gis/db/backends/spatialite/introspection.py b/django/contrib/gis/db/backends/spatialite/introspection.py index 1b5952ceac..4f12ade114 100644 --- a/django/contrib/gis/db/backends/spatialite/introspection.py +++ b/django/contrib/gis/db/backends/spatialite/introspection.py @@ -1,5 +1,6 @@ from django.contrib.gis.gdal import OGRGeomType from django.db.backends.sqlite3.introspection import DatabaseIntrospection, FlexibleFieldLookupDict +from django.utils import six class GeoFlexibleFieldLookupDict(FlexibleFieldLookupDict): """ @@ -43,7 +44,7 @@ class SpatiaLiteIntrospection(DatabaseIntrospection): field_params = {} if srid != 4326: field_params['srid'] = srid - if isinstance(dim, basestring) and 'Z' in dim: + if isinstance(dim, six.string_types) and 'Z' in dim: field_params['dim'] = 3 finally: cursor.close() diff --git a/django/contrib/gis/db/backends/spatialite/models.py b/django/contrib/gis/db/backends/spatialite/models.py index 684c5d8fc7..b281f0bc62 100644 --- a/django/contrib/gis/db/backends/spatialite/models.py +++ b/django/contrib/gis/db/backends/spatialite/models.py @@ -3,7 +3,9 @@ """ from django.db import models from django.contrib.gis.db.backends.base import SpatialRefSysMixin +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class GeometryColumns(models.Model): """ The 'geometry_columns' table from SpatiaLite. @@ -35,7 +37,7 @@ class GeometryColumns(models.Model): """ return 'f_geometry_column' - def __unicode__(self): + def __str__(self): return "%s.%s - %dD %s field (SRID: %d)" % \ (self.f_table_name, self.f_geometry_column, self.coord_dimension, self.type, self.srid) diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 6adcdc5275..80f05ef076 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -9,6 +9,7 @@ from django.contrib.gis.measure import Distance from django.core.exceptions import ImproperlyConfigured from django.db.backends.sqlite3.base import DatabaseOperations from django.db.utils import DatabaseError +from django.utils import six class SpatiaLiteOperator(SpatialOperation): "For SpatiaLite operators (e.g. `&&`, `~`)." @@ -42,7 +43,7 @@ class SpatiaLiteRelate(SpatiaLiteFunctionParam): super(SpatiaLiteRelate, self).__init__('Relate') # Valid distance types and substitutions -dtypes = (Decimal, Distance, float, int, long) +dtypes = (Decimal, Distance, float) + six.integer_types def get_dist_ops(operator): "Returns operations for regular distances; spherical distances are not currently supported." return (SpatiaLiteDistance(operator),) @@ -89,7 +90,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): 'overlaps' : SpatiaLiteFunction('Overlaps'), 'contains' : SpatiaLiteFunction('Contains'), 'intersects' : SpatiaLiteFunction('Intersects'), - 'relate' : (SpatiaLiteRelate, basestring), + 'relate' : (SpatiaLiteRelate, six.string_types), # Returns true if B's bounding box completely contains A's bounding box. 'contained' : SpatiaLiteFunction('MbrWithin'), # Returns true if A's bounding box completely contains B's bounding box. @@ -112,6 +113,12 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): def __init__(self, connection): super(DatabaseOperations, self).__init__(connection) + # Creating the GIS terms dictionary. + gis_terms = ['isnull'] + gis_terms += self.geometry_functions.keys() + self.gis_terms = dict([(term, None) for term in gis_terms]) + + def confirm_spatial_components_versions(self): # Determine the version of the SpatiaLite library. try: vtup = self.spatialite_version_tuple() @@ -128,11 +135,6 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): 'SQL loaded on this database?' % (self.connection.settings_dict['NAME'], msg)) - # Creating the GIS terms dictionary. - gis_terms = ['isnull'] - gis_terms += self.geometry_functions.keys() - self.gis_terms = dict([(term, None) for term in gis_terms]) - if version >= (2, 4, 0): # Spatialite 2.4.0-RC4 added AsGML and AsKML, however both # RC2 (shipped in popular Debian/Ubuntu packages) and RC4 diff --git a/django/contrib/gis/db/backends/util.py b/django/contrib/gis/db/backends/util.py index b50c8e222e..648fcfe963 100644 --- a/django/contrib/gis/db/backends/util.py +++ b/django/contrib/gis/db/backends/util.py @@ -3,14 +3,16 @@ A collection of utility routines and classes used by the spatial backends. """ +from django.utils import six + def gqn(val): """ The geographic quote name function; used for quoting tables and geometries (they use single rather than the double quotes of the backend quotename function). """ - if isinstance(val, basestring): - if isinstance(val, unicode): val = val.encode('ascii') + if isinstance(val, six.string_types): + if isinstance(val, six.text_type): val = val.encode('ascii') return "'%s'" % val else: return str(val) diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py index 2b1660763a..17630d0899 100644 --- a/django/contrib/gis/db/models/fields.py +++ b/django/contrib/gis/db/models/fields.py @@ -4,6 +4,7 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.gis import forms from django.contrib.gis.db.models.proxy import GeometryProxy from django.contrib.gis.geometry.backend import Geometry, GeometryException +from django.utils import six # Local cache of the spatial_ref_sys table, which holds SRID data for each # spatial database alias. This cache exists so that the database isn't queried @@ -159,7 +160,7 @@ class GeometryField(Field): # from the given string input. if isinstance(geom, Geometry): pass - elif isinstance(geom, basestring) or hasattr(geom, '__geo_interface__'): + elif isinstance(geom, six.string_types) or hasattr(geom, '__geo_interface__'): try: geom = Geometry(geom) except GeometryException: diff --git a/django/contrib/gis/db/models/proxy.py b/django/contrib/gis/db/models/proxy.py index e569dd5c4f..413610fc5c 100644 --- a/django/contrib/gis/db/models/proxy.py +++ b/django/contrib/gis/db/models/proxy.py @@ -5,6 +5,7 @@ corresponding to geographic model fields. Thanks to Robert Coup for providing this functionality (see #4322). """ +from django.utils import six class GeometryProxy(object): def __init__(self, klass, field): @@ -53,7 +54,7 @@ class GeometryProxy(object): if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'): # Assigning the SRID to the geometry. if value.srid is None: value.srid = self._field.srid - elif value is None or isinstance(value, (basestring, buffer)): + elif value is None or isinstance(value, six.string_types + (buffer,)): # Set with None, WKT, HEX, or WKB pass else: diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index c1e360de27..cc61dfa4d2 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -1,11 +1,15 @@ from django.db import connections from django.db.models.query import QuerySet, ValuesQuerySet, ValuesListQuerySet +from django.utils import six from django.contrib.gis.db.models import aggregates from django.contrib.gis.db.models.fields import get_srid_info, PointField, LineStringField from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Area, Distance +from django.utils import six + +from django.utils import six class GeoQuerySet(QuerySet): "The Geographic QuerySet." @@ -22,7 +26,7 @@ class GeoQuerySet(QuerySet): flat = kwargs.pop('flat', False) if kwargs: raise TypeError('Unexpected keyword arguments to values_list: %s' - % (kwargs.keys(),)) + % (list(kwargs),)) if flat and len(fields) > 1: raise TypeError("'flat' is not valid when values_list is called with more than one field.") return self._clone(klass=GeoValuesListQuerySet, setup=True, flat=flat, @@ -144,7 +148,7 @@ class GeoQuerySet(QuerySet): if not backend.geojson: raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.') - if not isinstance(precision, (int, long)): + if not isinstance(precision, six.integer_types): raise TypeError('Precision keyword must be set with an integer.') # Setting the options flag -- which depends on which version of @@ -173,7 +177,7 @@ class GeoQuerySet(QuerySet): The `precision` keyword may be used to custom the number of _characters_ used in the output GeoHash, the default is 20. """ - s = {'desc' : 'GeoHash', + s = {'desc' : 'GeoHash', 'procedure_args': {'precision': precision}, 'procedure_fmt': '%(geo_col)s,%(precision)s', } @@ -309,7 +313,7 @@ class GeoQuerySet(QuerySet): - 2 arguments: X and Y sizes to snap the grid to. - 4 arguments: X, Y sizes and the X, Y origins. """ - if False in [isinstance(arg, (float, int, long)) for arg in args]: + if False in [isinstance(arg, (float,) + six.integer_types) for arg in args]: raise TypeError('Size argument(s) for the grid must be a float or integer values.') nargs = len(args) @@ -349,7 +353,7 @@ class GeoQuerySet(QuerySet): digits used in output (defaults to 8). """ relative = int(bool(relative)) - if not isinstance(precision, (int, long)): + if not isinstance(precision, six.integer_types): raise TypeError('SVG precision keyword argument must be an integer.') s = {'desc' : 'SVG', 'procedure_fmt' : '%(geo_col)s,%(rel)s,%(precision)s', @@ -390,7 +394,7 @@ class GeoQuerySet(QuerySet): Transforms the given geometry field to the given SRID. If no SRID is provided, the transformation will default to using 4326 (WGS84). """ - if not isinstance(srid, (int, long)): + if not isinstance(srid, six.integer_types): raise TypeError('An integer SRID must be provided.') field_name = kwargs.get('field_name', None) tmp, geo_field = self._spatial_setup('transform', field_name=field_name) @@ -528,12 +532,12 @@ class GeoQuerySet(QuerySet): if settings.get('setup', True): default_args, geo_field = self._spatial_setup(att, desc=settings['desc'], field_name=field_name, geo_field_type=settings.get('geo_field_type', None)) - for k, v in default_args.iteritems(): settings['procedure_args'].setdefault(k, v) + for k, v in six.iteritems(default_args): settings['procedure_args'].setdefault(k, v) else: geo_field = settings['geo_field'] # The attribute to attach to the model. - if not isinstance(model_att, basestring): model_att = att + if not isinstance(model_att, six.string_types): model_att = att # Special handling for any argument that is a geometry. for name in settings['geom_args']: diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index ebaee60bd0..5c8d2647f7 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -1,8 +1,9 @@ -from future_builtins import zip +from django.utils.six.moves import zip from django.db.backends.util import truncate_name, typecast_timestamp from django.db.models.sql import compiler from django.db.models.sql.constants import MULTI +from django.utils import six SQLCompiler = compiler.SQLCompiler @@ -24,7 +25,7 @@ class GeoSQLCompiler(compiler.SQLCompiler): qn = self.quote_name_unless_alias qn2 = self.connection.ops.quote_name result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias)) - for alias, col in self.query.extra_select.iteritems()] + for alias, col in six.iteritems(self.query.extra_select)] aliases = set(self.query.extra_select.keys()) if with_aliases: col_aliases = aliases.copy() @@ -170,7 +171,7 @@ class GeoSQLCompiler(compiler.SQLCompiler): objects. """ values = [] - aliases = self.query.extra_select.keys() + aliases = list(self.query.extra_select) # Have to set a starting row number offset that is used for # determining the correct starting row index -- needed for diff --git a/django/contrib/gis/feeds.py b/django/contrib/gis/feeds.py index c14fb858a5..d7c52bf019 100644 --- a/django/contrib/gis/feeds.py +++ b/django/contrib/gis/feeds.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.contrib.syndication.views import Feed as BaseFeed from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed @@ -13,7 +15,7 @@ class GeoFeedMixin(object): a single white space. Given a tuple of coordinates, this will return a unicode GeoRSS representation. """ - return u' '.join([u'%f %f' % (coord[1], coord[0]) for coord in coords]) + return ' '.join(['%f %f' % (coord[1], coord[0]) for coord in coords]) def add_georss_point(self, handler, coords, w3c_geo=False): """ @@ -23,10 +25,10 @@ class GeoFeedMixin(object): """ if w3c_geo: lon, lat = coords[:2] - handler.addQuickElement(u'geo:lat', u'%f' % lat) - handler.addQuickElement(u'geo:lon', u'%f' % lon) + handler.addQuickElement('geo:lat', '%f' % lat) + handler.addQuickElement('geo:lon', '%f' % lon) else: - handler.addQuickElement(u'georss:point', self.georss_coords((coords,))) + handler.addQuickElement('georss:point', self.georss_coords((coords,))) def add_georss_element(self, handler, item, w3c_geo=False): """ @@ -57,7 +59,7 @@ class GeoFeedMixin(object): # If a GeoRSS box was given via tuple. if not box_coords is None: if w3c_geo: raise ValueError('Cannot use simple GeoRSS box in W3C Geo feeds.') - handler.addQuickElement(u'georss:box', self.georss_coords(box_coords)) + handler.addQuickElement('georss:box', self.georss_coords(box_coords)) else: # Getting the lower-case geometry type. gtype = str(geom.geom_type).lower() @@ -68,10 +70,10 @@ class GeoFeedMixin(object): # For formatting consistent w/the GeoRSS simple standard: # http://georss.org/1.0#simple if gtype in ('linestring', 'linearring'): - handler.addQuickElement(u'georss:line', self.georss_coords(geom.coords)) + handler.addQuickElement('georss:line', self.georss_coords(geom.coords)) elif gtype in ('polygon',): # Only support the exterior ring. - handler.addQuickElement(u'georss:polygon', self.georss_coords(geom[0].coords)) + handler.addQuickElement('georss:polygon', self.georss_coords(geom[0].coords)) else: raise ValueError('Geometry type "%s" not supported.' % geom.geom_type) @@ -79,7 +81,7 @@ class GeoFeedMixin(object): class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin): def rss_attributes(self): attrs = super(GeoRSSFeed, self).rss_attributes() - attrs[u'xmlns:georss'] = u'http://www.georss.org/georss' + attrs['xmlns:georss'] = 'http://www.georss.org/georss' return attrs def add_item_elements(self, handler, item): @@ -93,7 +95,7 @@ class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin): class GeoAtom1Feed(Atom1Feed, GeoFeedMixin): def root_attributes(self): attrs = super(GeoAtom1Feed, self).root_attributes() - attrs[u'xmlns:georss'] = u'http://www.georss.org/georss' + attrs['xmlns:georss'] = 'http://www.georss.org/georss' return attrs def add_item_elements(self, handler, item): @@ -107,7 +109,7 @@ class GeoAtom1Feed(Atom1Feed, GeoFeedMixin): class W3CGeoFeed(Rss201rev2Feed, GeoFeedMixin): def rss_attributes(self): attrs = super(W3CGeoFeed, self).rss_attributes() - attrs[u'xmlns:geo'] = u'http://www.w3.org/2003/01/geo/wgs84_pos#' + attrs['xmlns:geo'] = 'http://www.w3.org/2003/01/geo/wgs84_pos#' return attrs def add_item_elements(self, handler, item): diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py index f806dcb3c6..cefb6830ba 100644 --- a/django/contrib/gis/forms/fields.py +++ b/django/contrib/gis/forms/fields.py @@ -1,9 +1,11 @@ +from __future__ import unicode_literals + from django import forms from django.utils.translation import ugettext_lazy as _ # While this couples the geographic forms to the GEOS library, # it decouples from database (by not importing SpatialBackend). -from django.contrib.gis.geos import GEOSGeometry +from django.contrib.gis.geos import GEOSException, GEOSGeometry class GeometryField(forms.Field): """ @@ -14,10 +16,10 @@ class GeometryField(forms.Field): widget = forms.Textarea default_error_messages = { - 'no_geom' : _(u'No geometry value provided.'), - 'invalid_geom' : _(u'Invalid geometry value.'), - 'invalid_geom_type' : _(u'Invalid geometry type.'), - 'transform_error' : _(u'An error occurred when transforming the geometry ' + 'no_geom' : _('No geometry value provided.'), + 'invalid_geom' : _('Invalid geometry value.'), + 'invalid_geom_type' : _('Invalid geometry type.'), + 'transform_error' : _('An error occurred when transforming the geometry ' 'to the SRID of the geometry form field.'), } @@ -29,6 +31,15 @@ class GeometryField(forms.Field): self.null = kwargs.pop('null', True) super(GeometryField, self).__init__(**kwargs) + def to_python(self, value): + """ + Transforms the value to a Geometry object. + """ + try: + return GEOSGeometry(value) + except (GEOSException, ValueError, TypeError): + raise forms.ValidationError(self.error_messages['invalid_geom']) + def clean(self, value): """ Validates that the input value can be converted to a Geometry @@ -42,11 +53,8 @@ class GeometryField(forms.Field): else: raise forms.ValidationError(self.error_messages['no_geom']) - # Trying to create a Geometry object from the form value. - try: - geom = GEOSGeometry(value) - except: - raise forms.ValidationError(self.error_messages['invalid_geom']) + # Transform the value to a python object first + geom = self.to_python(value) # Ensuring that the geometry is of the correct type (indicated # using the OGC string label). diff --git a/django/contrib/gis/gdal/__init__.py b/django/contrib/gis/gdal/__init__.py index cc47ae9bc2..adff96b47a 100644 --- a/django/contrib/gis/gdal/__init__.py +++ b/django/contrib/gis/gdal/__init__.py @@ -6,7 +6,7 @@ reference system to another. Driver: Wraps an OGR data source driver. - + DataSource: Wrapper for the OGR data source object, supports OGR-supported data sources. @@ -20,15 +20,15 @@ SpatialReference: Represents OSR Spatial Reference objects. - The GDAL library will be imported from the system path using the default + The GDAL library will be imported from the system path using the default library name for the current OS. The default library path may be overridden - by setting `GDAL_LIBRARY_PATH` in your settings with the path to the GDAL C - library on your system. + by setting `GDAL_LIBRARY_PATH` in your settings with the path to the GDAL C + library on your system. - GDAL links to a large number of external libraries that consume RAM when + GDAL links to a large number of external libraries that consume RAM when loaded. Thus, it may desirable to disable GDAL on systems with limited RAM resources -- this may be accomplished by setting `GDAL_LIBRARY_PATH` - to a non-existant file location (e.g., `GDAL_LIBRARY_PATH='/null/path'`; + to a non-existant file location (e.g., `GDAL_LIBRARY_PATH='/null/path'`; setting to None/False/'' will not work as a string must be given). """ # Attempting to import objects that depend on the GDAL library. The @@ -37,12 +37,12 @@ try: from django.contrib.gis.gdal.driver import Driver from django.contrib.gis.gdal.datasource import DataSource - from django.contrib.gis.gdal.libgdal import gdal_version, gdal_full_version, gdal_release_date, GEOJSON, GDAL_VERSION + from django.contrib.gis.gdal.libgdal import gdal_version, gdal_full_version, gdal_release_date, GDAL_VERSION from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform from django.contrib.gis.gdal.geometries import OGRGeometry HAS_GDAL = True except: - HAS_GDAL, GEOJSON = False, False + HAS_GDAL = False try: from django.contrib.gis.gdal.envelope import Envelope diff --git a/django/contrib/gis/gdal/base.py b/django/contrib/gis/gdal/base.py index 36c03eb51e..e86277e95e 100644 --- a/django/contrib/gis/gdal/base.py +++ b/django/contrib/gis/gdal/base.py @@ -1,6 +1,7 @@ from ctypes import c_void_p from django.contrib.gis.gdal.error import GDALException +from django.utils import six class GDALBase(object): """ @@ -24,7 +25,7 @@ class GDALBase(object): def _set_ptr(self, ptr): # Only allow the pointer to be set with pointers of the # compatible type or None (NULL). - if isinstance(ptr, (int, long)): + if isinstance(ptr, six.integer_types): self._ptr = self.ptr_type(ptr) elif ptr is None or isinstance(ptr, self.ptr_type): self._ptr = ptr diff --git a/django/contrib/gis/gdal/datasource.py b/django/contrib/gis/gdal/datasource.py index e5f3602ddb..4ceddc6c72 100644 --- a/django/contrib/gis/gdal/datasource.py +++ b/django/contrib/gis/gdal/datasource.py @@ -45,6 +45,9 @@ from django.contrib.gis.gdal.layer import Layer # Getting the ctypes prototypes for the DataSource. from django.contrib.gis.gdal.prototypes import ds as capi +from django.utils import six +from django.utils.six.moves import xrange + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # @@ -65,7 +68,7 @@ class DataSource(GDALBase): if not capi.get_driver_count(): capi.register_all() - if isinstance(ds_input, basestring): + if isinstance(ds_input, six.string_types): # The data source driver is a void pointer. ds_driver = Driver.ptr_type() try: @@ -84,7 +87,7 @@ class DataSource(GDALBase): self.ptr = ds self.driver = Driver(ds_driver) else: - # Raise an exception if the returned pointer is NULL + # Raise an exception if the returned pointer is NULL raise OGRException('Invalid data source file "%s"' % ds_input) def __del__(self): @@ -98,7 +101,7 @@ class DataSource(GDALBase): def __getitem__(self, index): "Allows use of the index [] operator to get a layer at the index." - if isinstance(index, basestring): + if isinstance(index, six.string_types): l = capi.get_layer_by_name(self.ptr, index) if not l: raise OGRIndexError('invalid OGR Layer name given: "%s"' % index) elif isinstance(index, int): @@ -108,7 +111,7 @@ class DataSource(GDALBase): else: raise TypeError('Invalid index type: %s' % type(index)) return Layer(l, self) - + def __len__(self): "Returns the number of layers within the data source." return self.layer_count diff --git a/django/contrib/gis/gdal/driver.py b/django/contrib/gis/gdal/driver.py index 1753db2b2b..de4dc61c63 100644 --- a/django/contrib/gis/gdal/driver.py +++ b/django/contrib/gis/gdal/driver.py @@ -1,9 +1,11 @@ -# prerequisites imports +# prerequisites imports from ctypes import c_void_p from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.error import OGRException from django.contrib.gis.gdal.prototypes import ds as capi +from django.utils import six + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # @@ -18,11 +20,11 @@ class Driver(GDALBase): 'tiger' : 'TIGER', 'tiger/line' : 'TIGER', } - + def __init__(self, dr_input): "Initializes an OGR driver on either a string or integer input." - if isinstance(dr_input, basestring): + if isinstance(dr_input, six.string_types): # If a string name of the driver was passed in self._register() @@ -57,7 +59,7 @@ class Driver(GDALBase): # Only register all if the driver count is 0 (or else all drivers # will be registered over and over again) if not self.driver_count: capi.register_all() - + # Driver properties @property def driver_count(self): diff --git a/django/contrib/gis/gdal/feature.py b/django/contrib/gis/gdal/feature.py index 47fd9e522e..292004873d 100644 --- a/django/contrib/gis/gdal/feature.py +++ b/django/contrib/gis/gdal/feature.py @@ -7,6 +7,9 @@ from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType # ctypes function prototypes from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api +from django.utils import six +from django.utils.six.moves import xrange + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # @@ -30,17 +33,17 @@ class Feature(GDALBase): """ Gets the Field object at the specified index, which may be either an integer or the Field's string label. Note that the Field object - is not the field's _value_ -- use the `get` method instead to + is not the field's _value_ -- use the `get` method instead to retrieve the value (e.g. an integer) instead of a Field instance. """ - if isinstance(index, basestring): + if isinstance(index, six.string_types): i = self.index(index) else: if index < 0 or index > self.num_fields: raise OGRIndexError('index out of range') i = index return Field(self.ptr, i) - + def __iter__(self): "Iterates over each field in the Feature." for i in xrange(self.num_fields): @@ -49,7 +52,7 @@ class Feature(GDALBase): def __len__(self): "Returns the count of fields in this feature." return self.num_fields - + def __str__(self): "The string name of the feature." return 'Feature FID %d in Layer<%s>' % (self.fid, self.layer_name) @@ -63,7 +66,7 @@ class Feature(GDALBase): def fid(self): "Returns the feature identifier." return capi.get_fid(self.ptr) - + @property def layer_name(self): "Returns the name of the layer for the feature." @@ -77,7 +80,7 @@ class Feature(GDALBase): @property def fields(self): "Returns a list of fields in the Feature." - return [capi.get_field_name(capi.get_field_defn(self._fdefn, i)) + return [capi.get_field_name(capi.get_field_defn(self._fdefn, i)) for i in xrange(self.num_fields)] @property @@ -91,7 +94,7 @@ class Feature(GDALBase): def geom_type(self): "Returns the OGR Geometry Type for this Feture." return OGRGeomType(capi.get_fd_geom_type(self._fdefn)) - + #### Feature Methods #### def get(self, field): """ diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index b4d4ad1646..373ece777d 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -40,7 +40,7 @@ """ # Python library requisites. import sys -from binascii import a2b_hex +from binascii import a2b_hex, b2a_hex from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p # Getting GDAL prerequisites @@ -48,7 +48,7 @@ from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException from django.contrib.gis.gdal.geomtype import OGRGeomType -from django.contrib.gis.gdal.libgdal import GEOJSON, GDAL_VERSION +from django.contrib.gis.gdal.libgdal import GDAL_VERSION from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform # Getting the ctypes prototype functions that interface w/the GDAL C library. @@ -57,6 +57,9 @@ from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api # For recognizing geometry input. from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex +from django.utils import six +from django.utils.six.moves import xrange + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # @@ -69,7 +72,7 @@ class OGRGeometry(GDALBase): def __init__(self, geom_input, srs=None): "Initializes Geometry on either WKT or an OGR pointer as input." - str_instance = isinstance(geom_input, basestring) + str_instance = isinstance(geom_input, six.string_types) # If HEX, unpack input to to a binary buffer. if str_instance and hex_regex.match(geom_input): @@ -79,7 +82,7 @@ class OGRGeometry(GDALBase): # Constructing the geometry, if str_instance: # Checking if unicode - if isinstance(geom_input, unicode): + if isinstance(geom_input, six.text_type): # Encoding to ASCII, WKT or HEX doesn't need any more. geom_input = geom_input.encode('ascii') @@ -97,10 +100,7 @@ class OGRGeometry(GDALBase): else: g = capi.from_wkt(byref(c_char_p(wkt_m.group('wkt'))), None, byref(c_void_p())) elif json_m: - if GEOJSON: - g = capi.from_json(geom_input) - else: - raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.') + g = capi.from_json(geom_input) else: # Seeing if the input is a valid short-hand string # (e.g., 'Point', 'POLYGON'). @@ -284,7 +284,7 @@ class OGRGeometry(GDALBase): # (decremented) when this geometry's destructor is called. if isinstance(srs, SpatialReference): srs_ptr = srs.ptr - elif isinstance(srs, (int, long, basestring)): + elif isinstance(srs, six.integer_types + six.string_types): sr = SpatialReference(srs) srs_ptr = sr.ptr else: @@ -300,7 +300,7 @@ class OGRGeometry(GDALBase): return None def _set_srid(self, srid): - if isinstance(srid, (int, long)): + if isinstance(srid, six.integer_types): self.srs = srid else: raise TypeError('SRID must be set with an integer.') @@ -322,28 +322,20 @@ class OGRGeometry(GDALBase): @property def hex(self): "Returns the hexadecimal representation of the WKB (a string)." - return str(self.wkb).encode('hex').upper() - #return b2a_hex(self.wkb).upper() + return b2a_hex(self.wkb).upper() @property def json(self): """ - Returns the GeoJSON representation of this Geometry (requires - GDAL 1.5+). + Returns the GeoJSON representation of this Geometry. """ - if GEOJSON: - return capi.to_json(self.ptr) - else: - raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.') + return capi.to_json(self.ptr) geojson = json @property def kml(self): "Returns the KML representation of the Geometry." - if GEOJSON: - return capi.to_kml(self.ptr, None) - else: - raise NotImplementedError('KML output only supported on GDAL 1.5+.') + return capi.to_kml(self.ptr, None) @property def wkb_size(self): @@ -420,7 +412,7 @@ class OGRGeometry(GDALBase): capi.geom_transform(self.ptr, coord_trans.ptr) elif isinstance(coord_trans, SpatialReference): capi.geom_transform_to(self.ptr, coord_trans.ptr) - elif isinstance(coord_trans, (int, long, basestring)): + elif isinstance(coord_trans, six.integer_types + six.string_types): sr = SpatialReference(coord_trans) capi.geom_transform_to(self.ptr, sr.ptr) else: @@ -695,7 +687,7 @@ class GeometryCollection(OGRGeometry): for g in geom: capi.add_geom(self.ptr, g.ptr) else: capi.add_geom(self.ptr, geom.ptr) - elif isinstance(geom, basestring): + elif isinstance(geom, six.string_types): tmp = OGRGeometry(geom) capi.add_geom(self.ptr, tmp.ptr) else: diff --git a/django/contrib/gis/gdal/geomtype.py b/django/contrib/gis/gdal/geomtype.py index 3bf94d4815..fe4b89adeb 100644 --- a/django/contrib/gis/gdal/geomtype.py +++ b/django/contrib/gis/gdal/geomtype.py @@ -1,5 +1,7 @@ from django.contrib.gis.gdal.error import OGRException +from django.utils import six + #### OGRGeomType #### class OGRGeomType(object): "Encapulates OGR Geometry Types." @@ -32,7 +34,7 @@ class OGRGeomType(object): "Figures out the correct OGR Type based upon the input." if isinstance(type_input, OGRGeomType): num = type_input.num - elif isinstance(type_input, basestring): + elif isinstance(type_input, six.string_types): type_input = type_input.lower() if type_input == 'geometry': type_input='unknown' num = self._str_types.get(type_input, None) @@ -44,7 +46,7 @@ class OGRGeomType(object): num = type_input else: raise TypeError('Invalid OGR input type given.') - + # Setting the OGR geometry type number. self.num = num @@ -59,7 +61,7 @@ class OGRGeomType(object): """ if isinstance(other, OGRGeomType): return self.num == other.num - elif isinstance(other, basestring): + elif isinstance(other, six.string_types): return self.name.lower() == other.lower() elif isinstance(other, int): return self.num == other diff --git a/django/contrib/gis/gdal/layer.py b/django/contrib/gis/gdal/layer.py index a2163bc3c8..2357fbb88a 100644 --- a/django/contrib/gis/gdal/layer.py +++ b/django/contrib/gis/gdal/layer.py @@ -14,6 +14,9 @@ from django.contrib.gis.gdal.srs import SpatialReference # GDAL ctypes function prototypes. from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api, srs as srs_api +from django.utils import six +from django.utils.six.moves import xrange + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # @@ -25,8 +28,8 @@ class Layer(GDALBase): def __init__(self, layer_ptr, ds): """ Initializes on an OGR C pointer to the Layer and the `DataSource` object - that owns this layer. The `DataSource` object is required so that a - reference to it is kept with this Layer. This prevents garbage + that owns this layer. The `DataSource` object is required so that a + reference to it is kept with this Layer. This prevents garbage collection of the `DataSource` while this Layer is still active. """ if not layer_ptr: @@ -39,7 +42,7 @@ class Layer(GDALBase): def __getitem__(self, index): "Gets the Feature at the specified index." - if isinstance(index, (int, long)): + if isinstance(index, six.integer_types): # An integer index was given -- we cannot do a check based on the # number of features because the beginning and ending feature IDs # are not guaranteed to be 0 and len(layer)-1, respectively. @@ -85,7 +88,7 @@ class Layer(GDALBase): # each feature until the given feature ID is encountered. for feat in self: if feat.fid == feat_id: return feat - # Should have returned a Feature, raise an OGRIndexError. + # Should have returned a Feature, raise an OGRIndexError. raise OGRIndexError('Invalid feature id: %s.' % feat_id) #### Layer properties #### @@ -131,9 +134,9 @@ class Layer(GDALBase): Returns a list of string names corresponding to each of the Fields available in this Layer. """ - return [capi.get_field_name(capi.get_field_defn(self._ldefn, i)) + return [capi.get_field_name(capi.get_field_defn(self._ldefn, i)) for i in xrange(self.num_fields) ] - + @property def field_types(self): """ @@ -145,13 +148,13 @@ class Layer(GDALBase): return [OGRFieldTypes[capi.get_field_type(capi.get_field_defn(self._ldefn, i))] for i in xrange(self.num_fields)] - @property + @property def field_widths(self): "Returns a list of the maximum field widths for the features." return [capi.get_field_width(capi.get_field_defn(self._ldefn, i)) for i in xrange(self.num_fields)] - @property + @property def field_precisions(self): "Returns the field precisions for the features." return [capi.get_field_precision(capi.get_field_defn(self._ldefn, i)) diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index f991388835..27a5b8172e 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -15,31 +15,32 @@ if lib_path: lib_names = None elif os.name == 'nt': # Windows NT shared libraries - lib_names = ['gdal18', 'gdal17', 'gdal16', 'gdal15'] + lib_names = ['gdal19', 'gdal18', 'gdal17', 'gdal16', 'gdal15'] elif os.name == 'posix': # *NIX library names. - lib_names = ['gdal', 'GDAL', 'gdal1.8.0', 'gdal1.7.0', 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0'] + lib_names = ['gdal', 'GDAL', 'gdal1.9.0', 'gdal1.8.0', 'gdal1.7.0', + 'gdal1.6.0', 'gdal1.5.0'] else: raise OGRException('Unsupported OS "%s"' % os.name) -# Using the ctypes `find_library` utility to find the +# Using the ctypes `find_library` utility to find the # path to the GDAL library from the list of library names. if lib_names: for lib_name in lib_names: lib_path = find_library(lib_name) if not lib_path is None: break - + if lib_path is None: raise OGRException('Could not find the GDAL library (tried "%s"). ' - 'Try setting GDAL_LIBRARY_PATH in your settings.' % + 'Try setting GDAL_LIBRARY_PATH in your settings.' % '", "'.join(lib_names)) # This loads the GDAL/OGR C library lgdal = CDLL(lib_path) -# On Windows, the GDAL binaries have some OSR routines exported with -# STDCALL, while others are not. Thus, the library will also need to -# be loaded up as WinDLL for said OSR functions that require the +# On Windows, the GDAL binaries have some OSR routines exported with +# STDCALL, while others are not. Thus, the library will also need to +# be loaded up as WinDLL for said OSR functions that require the # different calling convention. if os.name == 'nt': from ctypes import WinDLL @@ -66,11 +67,11 @@ def gdal_version(): "Returns only the GDAL version number information." return _version_info('RELEASE_NAME') -def gdal_full_version(): +def gdal_full_version(): "Returns the full GDAL version information." return _version_info('') -def gdal_release_date(date=False): +def gdal_release_date(date=False): """ Returns the release date in a string format, e.g, "2007/06/27". If the date keyword argument is set to True, a Python datetime object @@ -96,10 +97,3 @@ GDAL_MINOR_VERSION = int(_verinfo['minor']) GDAL_SUBMINOR_VERSION = _verinfo['subminor'] and int(_verinfo['subminor']) GDAL_VERSION = (GDAL_MAJOR_VERSION, GDAL_MINOR_VERSION, GDAL_SUBMINOR_VERSION) del _verinfo - -# GeoJSON support is available only in GDAL 1.5+. -if GDAL_VERSION >= (1, 5): - GEOJSON = True -else: - GEOJSON = False - diff --git a/django/contrib/gis/gdal/prototypes/errcheck.py b/django/contrib/gis/gdal/prototypes/errcheck.py index 91858ea572..d8ff1c7dcf 100644 --- a/django/contrib/gis/gdal/prototypes/errcheck.py +++ b/django/contrib/gis/gdal/prototypes/errcheck.py @@ -5,9 +5,10 @@ from ctypes import c_void_p, string_at from django.contrib.gis.gdal.error import check_err, OGRException, SRSException from django.contrib.gis.gdal.libgdal import lgdal +from django.utils import six -# Helper routines for retrieving pointers and/or values from -# arguments passed in by reference. +# Helper routines for retrieving pointers and/or values from +# arguments passed in by reference. def arg_byref(args, offset=-1): "Returns the pointer argument's by-refernece value." return args[offset]._obj.value @@ -53,7 +54,7 @@ def check_string(result, func, cargs, offset=-1, str_result=False): ptr = ptr_byref(cargs, offset) # Getting the string value s = ptr.value - # Correctly freeing the allocated memory beind GDAL pointer + # Correctly freeing the allocated memory beind GDAL pointer # w/the VSIFree routine. if ptr: lgdal.VSIFree(ptr) return s @@ -71,9 +72,9 @@ def check_geom(result, func, cargs): "Checks a function that returns a geometry." # OGR_G_Clone may return an integer, even though the # restype is set to c_void_p - if isinstance(result, (int, long)): + if isinstance(result, six.integer_types): result = c_void_p(result) - if not result: + if not result: raise OGRException('Invalid geometry pointer returned from "%s".' % func.__name__) return result @@ -85,7 +86,7 @@ def check_geom_offset(result, func, cargs, offset=-1): ### Spatial Reference error-checking routines ### def check_srs(result, func, cargs): - if isinstance(result, (int, long)): + if isinstance(result, six.integer_types): result = c_void_p(result) if not result: raise SRSException('Invalid spatial reference pointer returned from "%s".' % func.__name__) @@ -109,11 +110,11 @@ def check_errcode(result, func, cargs): def check_pointer(result, func, cargs): "Makes sure the result pointer is valid." - if isinstance(result, (int, long)): + if isinstance(result, six.integer_types): result = c_void_p(result) - if bool(result): + if bool(result): return result - else: + else: raise OGRException('Invalid pointer returned from "%s"' % func.__name__) def check_str_arg(result, func, cargs): diff --git a/django/contrib/gis/gdal/prototypes/geom.py b/django/contrib/gis/gdal/prototypes/geom.py index 7fa83910c7..f2c833d576 100644 --- a/django/contrib/gis/gdal/prototypes/geom.py +++ b/django/contrib/gis/gdal/prototypes/geom.py @@ -1,6 +1,6 @@ from ctypes import c_char_p, c_double, c_int, c_void_p, POINTER from django.contrib.gis.gdal.envelope import OGREnvelope -from django.contrib.gis.gdal.libgdal import lgdal, GEOJSON +from django.contrib.gis.gdal.libgdal import lgdal from django.contrib.gis.gdal.prototypes.errcheck import check_bool, check_envelope from django.contrib.gis.gdal.prototypes.generation import (const_string_output, double_output, geom_output, int_output, srs_output, string_output, void_output) @@ -25,15 +25,10 @@ def topology_func(f): ### OGR_G ctypes function prototypes ### -# GeoJSON routines, if supported. -if GEOJSON: - from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p]) - to_json = string_output(lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True) - to_kml = string_output(lgdal.OGR_G_ExportToKML, [c_void_p, c_char_p], str_result=True) -else: - from_json = False - to_json = False - to_kml = False +# GeoJSON routines. +from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p]) +to_json = string_output(lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True) +to_kml = string_output(lgdal.OGR_G_ExportToKML, [c_void_p, c_char_p], str_result=True) # GetX, GetY, GetZ all return doubles. getx = pnt_func(lgdal.OGR_G_GetX) diff --git a/django/contrib/gis/gdal/srs.py b/django/contrib/gis/gdal/srs.py index 67049731de..cdeaaca690 100644 --- a/django/contrib/gis/gdal/srs.py +++ b/django/contrib/gis/gdal/srs.py @@ -33,11 +33,13 @@ from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.error import SRSException from django.contrib.gis.gdal.prototypes import srs as capi +from django.utils import six + #### Spatial Reference class. #### class SpatialReference(GDALBase): """ A wrapper for the OGRSpatialReference object. According to the GDAL Web site, - the SpatialReference object "provide[s] services to represent coordinate + the SpatialReference object "provide[s] services to represent coordinate systems (projections and datums) and to transform between them." """ @@ -45,16 +47,16 @@ class SpatialReference(GDALBase): def __init__(self, srs_input=''): """ Creates a GDAL OSR Spatial Reference object from the given input. - The input may be string of OGC Well Known Text (WKT), an integer - EPSG code, a PROJ.4 string, and/or a projection "well known" shorthand + The input may be string of OGC Well Known Text (WKT), an integer + EPSG code, a PROJ.4 string, and/or a projection "well known" shorthand string (one of 'WGS84', 'WGS72', 'NAD27', 'NAD83'). """ buf = c_char_p('') srs_type = 'user' - if isinstance(srs_input, basestring): + if isinstance(srs_input, six.string_types): # Encoding to ASCII if unicode passed in. - if isinstance(srs_input, unicode): + if isinstance(srs_input, six.text_type): srs_input = srs_input.encode('ascii') try: # If SRID is a string, e.g., '4326', then make acceptable @@ -63,7 +65,7 @@ class SpatialReference(GDALBase): srs_input = 'EPSG:%d' % srid except ValueError: pass - elif isinstance(srs_input, (int, long)): + elif isinstance(srs_input, six.integer_types): # EPSG integer code was input. srs_type = 'epsg' elif isinstance(srs_input, self.ptr_type): @@ -97,8 +99,8 @@ class SpatialReference(GDALBase): def __getitem__(self, target): """ - Returns the value of the given string attribute node, None if the node - doesn't exist. Can also take a tuple as a parameter, (target, child), + Returns the value of the given string attribute node, None if the node + doesn't exist. Can also take a tuple as a parameter, (target, child), where child is the index of the attribute in the WKT. For example: >>> wkt = 'GEOGCS["WGS 84", DATUM["WGS_1984, ... AUTHORITY["EPSG","4326"]]') @@ -133,14 +135,14 @@ class SpatialReference(GDALBase): The attribute value for the given target node (e.g. 'PROJCS'). The index keyword specifies an index of the child node to return. """ - if not isinstance(target, basestring) or not isinstance(index, int): + if not isinstance(target, six.string_types) or not isinstance(index, int): raise TypeError return capi.get_attr_value(self.ptr, target, index) def auth_name(self, target): "Returns the authority name for the given string target node." return capi.get_auth_name(self.ptr, target) - + def auth_code(self, target): "Returns the authority code for the given string target node." return capi.get_auth_code(self.ptr, target) @@ -167,7 +169,7 @@ class SpatialReference(GDALBase): def validate(self): "Checks to see if the given spatial reference is valid." capi.srs_validate(self.ptr) - + #### Name & SRID properties #### @property def name(self): @@ -184,7 +186,7 @@ class SpatialReference(GDALBase): return int(self.attr_value('AUTHORITY', 1)) except (TypeError, ValueError): return None - + #### Unit Properties #### @property def linear_name(self): @@ -213,7 +215,7 @@ class SpatialReference(GDALBase): @property def units(self): """ - Returns a 2-tuple of the units value and the units name, + Returns a 2-tuple of the units value and the units name, and will automatically determines whether to return the linear or angular units. """ @@ -252,7 +254,7 @@ class SpatialReference(GDALBase): @property def geographic(self): """ - Returns True if this SpatialReference is geographic + Returns True if this SpatialReference is geographic (root node is GEOGCS). """ return bool(capi.isgeographic(self.ptr)) @@ -265,7 +267,7 @@ class SpatialReference(GDALBase): @property def projected(self): """ - Returns True if this SpatialReference is a projected coordinate system + Returns True if this SpatialReference is a projected coordinate system (root node is PROJCS). """ return bool(capi.isprojected(self.ptr)) diff --git a/django/contrib/gis/gdal/tests/__init__.py b/django/contrib/gis/gdal/tests/__init__.py index eb72a38775..262d294a43 100644 --- a/django/contrib/gis/gdal/tests/__init__.py +++ b/django/contrib/gis/gdal/tests/__init__.py @@ -19,7 +19,8 @@ test_suites = [test_driver.suite(), def suite(): "Builds a test suite for the GDAL tests." s = TestSuite() - map(s.addTest, test_suites) + for test_suite in test_suites: + s.addTest(test_suite) return s def run(verbosity=1): diff --git a/django/contrib/gis/gdal/tests/test_envelope.py b/django/contrib/gis/gdal/tests/test_envelope.py index 7574284bba..07e12c0ca7 100644 --- a/django/contrib/gis/gdal/tests/test_envelope.py +++ b/django/contrib/gis/gdal/tests/test_envelope.py @@ -23,7 +23,7 @@ class EnvelopeTest(unittest.TestCase): self.assertRaises(OGRException, Envelope, (0, 0, 5, 5, 3)) self.assertRaises(OGRException, Envelope, ()) self.assertRaises(ValueError, Envelope, 0, 'a', 5, 5) - self.assertRaises(TypeError, Envelope, u'foo') + self.assertRaises(TypeError, Envelope, 'foo') self.assertRaises(OGRException, Envelope, (1, 1, 0, 0)) try: Envelope(0, 0, 0, 0) diff --git a/django/contrib/gis/gdal/tests/test_geom.py b/django/contrib/gis/gdal/tests/test_geom.py index b68aa41b0a..a0b2593605 100644 --- a/django/contrib/gis/gdal/tests/test_geom.py +++ b/django/contrib/gis/gdal/tests/test_geom.py @@ -1,13 +1,13 @@ from binascii import b2a_hex try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle from django.contrib.gis.gdal import (OGRGeometry, OGRGeomType, OGRException, OGRIndexError, SpatialReference, CoordTransform, GDAL_VERSION) -from django.contrib.gis.gdal.prototypes.geom import GEOJSON from django.contrib.gis.geometry.test_data import TestDataMixin +from django.utils.six.moves import xrange from django.utils import unittest class OGRGeomTest(unittest.TestCase, TestDataMixin): @@ -108,7 +108,6 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin): def test01e_json(self): "Testing GeoJSON input/output." - if not GEOJSON: return for g in self.geometries.json_geoms: geom = OGRGeometry(g.wkt) if not hasattr(g, 'not_equal'): @@ -244,9 +243,6 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin): self.fail('Should have raised an OGRException!') print("\nEND - expecting IllegalArgumentException; safe to ignore.\n") - # Closing the rings -- doesn't work on GDAL versions 1.4.1 and below: - # http://trac.osgeo.org/gdal/ticket/1673 - if GDAL_VERSION <= (1, 4, 1): return poly.close_rings() self.assertEqual(10, poly.point_count) # Two closing points should've been added self.assertEqual(OGRGeometry('POINT(2.5 2.5)'), poly.centroid) diff --git a/django/contrib/gis/geoip/base.py b/django/contrib/gis/geoip/base.py index e00e0a4d93..944240c811 100644 --- a/django/contrib/gis/geoip/base.py +++ b/django/contrib/gis/geoip/base.py @@ -10,6 +10,8 @@ from django.contrib.gis.geoip.prototypes import ( GeoIP_country_code_by_addr, GeoIP_country_code_by_name, GeoIP_country_name_by_addr, GeoIP_country_name_by_name) +from django.utils import six + # Regular expressions for recognizing the GeoIP free database editions. free_regex = re.compile(r'^GEO-\d{3}FREE') lite_regex = re.compile(r'^GEO-\d{3}LITE') @@ -86,7 +88,7 @@ class GeoIP(object): if not path: path = GEOIP_SETTINGS.get('GEOIP_PATH', None) if not path: raise GeoIPException('GeoIP path must be provided via parameter or the GEOIP_PATH setting.') - if not isinstance(path, basestring): + if not isinstance(path, six.string_types): raise TypeError('Invalid path type: %s' % type(path).__name__) if os.path.isdir(path): @@ -129,7 +131,7 @@ class GeoIP(object): def _check_query(self, query, country=False, city=False, city_or_country=False): "Helper routine for checking the query and database availability." # Making sure a string was passed in for the query. - if not isinstance(query, basestring): + if not isinstance(query, six.string_types): raise TypeError('GeoIP query must be a string, not type %s' % type(query).__name__) # GeoIP only takes ASCII-encoded strings. diff --git a/django/contrib/gis/geoip/tests.py b/django/contrib/gis/geoip/tests.py index a629d86bbf..e53230d9ad 100644 --- a/django/contrib/gis/geoip/tests.py +++ b/django/contrib/gis/geoip/tests.py @@ -1,9 +1,13 @@ +from __future__ import unicode_literals + import os from django.conf import settings from django.contrib.gis.geos import GEOSGeometry from django.contrib.gis.geoip import GeoIP, GeoIPException from django.utils import unittest +from django.utils import six + # Note: Requires use of both the GeoIP country and city datasets. # The GEOIP_DATA path should be the only setting set (the directory # should contain links or the actual database files 'GeoIP.dat' and @@ -33,7 +37,7 @@ class GeoIPTest(unittest.TestCase): bad_params = (23, 'foo', 15.23) for bad in bad_params: self.assertRaises(GeoIPException, GeoIP, cache=bad) - if isinstance(bad, basestring): + if isinstance(bad, six.string_types): e = GeoIPException else: e = TypeError @@ -99,13 +103,13 @@ class GeoIPTest(unittest.TestCase): "Testing that GeoIP strings are properly encoded, see #16553." g = GeoIP() d = g.city('62.224.93.23') - self.assertEqual(u'Sch\xf6mberg', d['city']) + self.assertEqual('Schümberg', d['city']) def test06_unicode_query(self): "Testing that GeoIP accepts unicode string queries, see #17059." g = GeoIP() - d = g.country(u'whitehouse.gov') - self.assertEqual(u'US', d['country_code']) + d = g.country('whitehouse.gov') + self.assertEqual('US', d['country_code']) def suite(): diff --git a/django/contrib/gis/geometry/test_data.py b/django/contrib/gis/geometry/test_data.py index f92740248e..505f0e4f4b 100644 --- a/django/contrib/gis/geometry/test_data.py +++ b/django/contrib/gis/geometry/test_data.py @@ -7,6 +7,7 @@ import json import os from django.contrib import gis +from django.utils import six # This global used to store reference geometry data. @@ -25,7 +26,7 @@ def tuplize(seq): def strconvert(d): "Converts all keys in dictionary to str type." - return dict([(str(k), v) for k, v in d.iteritems()]) + return dict([(str(k), v) for k, v in six.iteritems(d)]) def get_ds_file(name, ext): diff --git a/django/contrib/gis/geos/base.py b/django/contrib/gis/geos/base.py index 754bcce7ad..fd2693ed59 100644 --- a/django/contrib/gis/geos/base.py +++ b/django/contrib/gis/geos/base.py @@ -10,7 +10,6 @@ except ImportError: # A 'dummy' gdal module. class GDALInfo(object): HAS_GDAL = False - GEOJSON = False gdal = GDALInfo() # NumPy supported? diff --git a/django/contrib/gis/geos/collections.py b/django/contrib/gis/geos/collections.py index 8b0edf5985..2b62bce22c 100644 --- a/django/contrib/gis/geos/collections.py +++ b/django/contrib/gis/geos/collections.py @@ -10,6 +10,7 @@ from django.contrib.gis.geos.linestring import LineString, LinearRing from django.contrib.gis.geos.point import Point from django.contrib.gis.geos.polygon import Polygon from django.contrib.gis.geos import prototypes as capi +from django.utils.six.moves import xrange class GeometryCollection(GEOSGeometry): _typeid = 7 @@ -100,11 +101,11 @@ class MultiLineString(GeometryCollection): @property def merged(self): - """ - Returns a LineString representing the line merge of this + """ + Returns a LineString representing the line merge of this MultiLineString. - """ - return self._topology(capi.geos_linemerge(self.ptr)) + """ + return self._topology(capi.geos_linemerge(self.ptr)) class MultiPolygon(GeometryCollection): _allowed = Polygon diff --git a/django/contrib/gis/geos/coordseq.py b/django/contrib/gis/geos/coordseq.py index 027d34e740..acf34f7262 100644 --- a/django/contrib/gis/geos/coordseq.py +++ b/django/contrib/gis/geos/coordseq.py @@ -8,6 +8,7 @@ from django.contrib.gis.geos.base import GEOSBase, numpy from django.contrib.gis.geos.error import GEOSException, GEOSIndexError from django.contrib.gis.geos.libgeos import CS_PTR from django.contrib.gis.geos import prototypes as capi +from django.utils.six.moves import xrange class GEOSCoordSeq(GEOSBase): "The internal representation of a list of coordinates inside a Geometry." diff --git a/django/contrib/gis/geos/factory.py b/django/contrib/gis/geos/factory.py index ee33f14d19..fbd7d5a3e9 100644 --- a/django/contrib/gis/geos/factory.py +++ b/django/contrib/gis/geos/factory.py @@ -1,12 +1,14 @@ from django.contrib.gis.geos.geometry import GEOSGeometry, wkt_regex, hex_regex +from django.utils import six + def fromfile(file_h): """ Given a string file name, returns a GEOSGeometry. The file may contain WKB, WKT, or HEX. """ # If given a file name, get a real handle. - if isinstance(file_h, basestring): + if isinstance(file_h, six.string_types): with open(file_h, 'rb') as file_h: buf = file_h.read() else: diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index f411d5a353..4e5409de1d 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -27,6 +27,8 @@ from django.contrib.gis.geos.prototypes.io import wkt_r, wkt_w, wkb_r, wkb_w, ew # For recognizing geometry input. from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex +from django.utils import six + class GEOSGeometry(GEOSBase, ListMixin): "A class that, generally, encapsulates a GEOS geometry." @@ -52,8 +54,8 @@ class GEOSGeometry(GEOSBase, ListMixin): The `srid` keyword is used to specify the Source Reference Identifier (SRID) number for this Geometry. If not set, the SRID will be None. """ - if isinstance(geo_input, basestring): - if isinstance(geo_input, unicode): + if isinstance(geo_input, six.string_types): + if isinstance(geo_input, six.text_type): # Encoding to ASCII, WKT or HEXEWKB doesn't need any more. geo_input = geo_input.encode('ascii') @@ -65,7 +67,7 @@ class GEOSGeometry(GEOSBase, ListMixin): elif hex_regex.match(geo_input): # Handling HEXEWKB input. g = wkb_r().read(geo_input) - elif gdal.GEOJSON and json_regex.match(geo_input): + elif gdal.HAS_GDAL and json_regex.match(geo_input): # Handling GeoJSON input. g = wkb_r().read(gdal.OGRGeometry(geo_input).wkb) else: @@ -153,7 +155,7 @@ class GEOSGeometry(GEOSBase, ListMixin): Equivalence testing, a Geometry may be compared with another Geometry or a WKT representation. """ - if isinstance(other, basestring): + if isinstance(other, six.string_types): return self.wkt == other elif isinstance(other, GEOSGeometry): return self.equals_exact(other) @@ -333,7 +335,7 @@ class GEOSGeometry(GEOSBase, ListMixin): Returns true if the elements in the DE-9IM intersection matrix for the two Geometries match the elements in pattern. """ - if not isinstance(pattern, basestring) or len(pattern) > 9: + if not isinstance(pattern, six.string_types) or len(pattern) > 9: raise GEOSException('invalid intersection matrix pattern') return capi.geos_relatepattern(self.ptr, other.ptr, pattern) @@ -409,13 +411,12 @@ class GEOSGeometry(GEOSBase, ListMixin): @property def json(self): """ - Returns GeoJSON representation of this Geometry if GDAL 1.5+ - is installed. + Returns GeoJSON representation of this Geometry if GDAL is installed. """ - if gdal.GEOJSON: + if gdal.HAS_GDAL: return self.ogr.json else: - raise GEOSException('GeoJSON output only supported on GDAL 1.5+.') + raise GEOSException('GeoJSON output only supported when GDAL is installed.') geojson = json @property diff --git a/django/contrib/gis/geos/linestring.py b/django/contrib/gis/geos/linestring.py index ecf774145e..4784ea7c2d 100644 --- a/django/contrib/gis/geos/linestring.py +++ b/django/contrib/gis/geos/linestring.py @@ -4,6 +4,7 @@ from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.geometry import GEOSGeometry from django.contrib.gis.geos.point import Point from django.contrib.gis.geos import prototypes as capi +from django.utils.six.moves import xrange class LineString(GEOSGeometry): _init_func = capi.create_linestring @@ -128,7 +129,7 @@ class LineString(GEOSGeometry): @property def merged(self): "Returns the line merge of this LineString." - return self._topology(capi.geos_linemerge(self.ptr)) + return self._topology(capi.geos_linemerge(self.ptr)) @property def x(self): diff --git a/django/contrib/gis/geos/mutable_list.py b/django/contrib/gis/geos/mutable_list.py index 1a9dcf0b5b..69e50e6b3f 100644 --- a/django/contrib/gis/geos/mutable_list.py +++ b/django/contrib/gis/geos/mutable_list.py @@ -9,6 +9,8 @@ See also http://www.aryehleib.com/MutableLists.html Author: Aryeh Leib Taurog. """ from django.utils.functional import total_ordering +from django.utils import six +from django.utils.six.moves import xrange @total_ordering class ListMixin(object): @@ -82,12 +84,12 @@ class ListMixin(object): def __delitem__(self, index): "Delete the item(s) at the specified index/slice." - if not isinstance(index, (int, long, slice)): + if not isinstance(index, six.integer_types + (slice,)): raise TypeError("%s is not a legal index" % index) # calculate new length and dimensions origLen = len(self) - if isinstance(index, (int, long)): + if isinstance(index, six.integer_types): index = self._checkindex(index) indexRange = [index] else: @@ -195,7 +197,7 @@ class ListMixin(object): def insert(self, index, val): "Standard list insert method" - if not isinstance(index, (int, long)): + if not isinstance(index, six.integer_types): raise TypeError("%s is not a legal index" % index) self[index:index] = [val] diff --git a/django/contrib/gis/geos/point.py b/django/contrib/gis/geos/point.py index b126856ba3..907347dbf8 100644 --- a/django/contrib/gis/geos/point.py +++ b/django/contrib/gis/geos/point.py @@ -2,6 +2,8 @@ from ctypes import c_uint from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.geometry import GEOSGeometry from django.contrib.gis.geos import prototypes as capi +from django.utils import six +from django.utils.six.moves import xrange class Point(GEOSGeometry): _minlength = 2 @@ -20,9 +22,9 @@ class Point(GEOSGeometry): # Here a tuple or list was passed in under the `x` parameter. ndim = len(x) coords = x - elif isinstance(x, (int, float, long)) and isinstance(y, (int, float, long)): + elif isinstance(x, six.integer_types + (float,)) and isinstance(y, six.integer_types + (float,)): # Here X, Y, and (optionally) Z were passed in individually, as parameters. - if isinstance(z, (int, float, long)): + if isinstance(z, six.integer_types + (float,)): ndim = 3 coords = [x, y, z] else: diff --git a/django/contrib/gis/geos/polygon.py b/django/contrib/gis/geos/polygon.py index 77ce60cf16..c50f549e7a 100644 --- a/django/contrib/gis/geos/polygon.py +++ b/django/contrib/gis/geos/polygon.py @@ -3,6 +3,8 @@ from django.contrib.gis.geos.geometry import GEOSGeometry from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR from django.contrib.gis.geos.linestring import LinearRing from django.contrib.gis.geos import prototypes as capi +from django.utils import six +from django.utils.six.moves import xrange class Polygon(GEOSGeometry): _minlength = 1 @@ -55,8 +57,11 @@ class Polygon(GEOSGeometry): def from_bbox(cls, bbox): "Constructs a Polygon from a bounding box (4-tuple)." x0, y0, x1, y1 = bbox - return GEOSGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % ( - x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) ) + for z in bbox: + if not isinstance(z, six.integer_types + (float,)): + return GEOSGeometry('POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % + (x0, y0, x0, y1, x1, y1, x1, y0, x0, y0)) + return Polygon(((x0, y0), (x0, y1), (x1, y1), (x1, y0), (x0, y0))) ### These routines are needed for list-like operation w/ListMixin ### def _create_polygon(self, length, items): diff --git a/django/contrib/gis/geos/prototypes/io.py b/django/contrib/gis/geos/prototypes/io.py index 56f702243d..053b9948a2 100644 --- a/django/contrib/gis/geos/prototypes/io.py +++ b/django/contrib/gis/geos/prototypes/io.py @@ -6,6 +6,8 @@ from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string from django.contrib.gis.geos.prototypes.geom import c_uchar_p, geos_char_p from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc +from django.utils import six + ### The WKB/WKT Reader/Writer structures and pointers ### class WKTReader_st(Structure): pass class WKTWriter_st(Structure): pass @@ -118,7 +120,7 @@ class _WKTReader(IOBase): ptr_type = WKT_READ_PTR def read(self, wkt): - if not isinstance(wkt, basestring): raise TypeError + if not isinstance(wkt, six.string_types): raise TypeError return wkt_reader_read(self.ptr, wkt) class _WKBReader(IOBase): @@ -131,7 +133,7 @@ class _WKBReader(IOBase): if isinstance(wkb, buffer): wkb_s = str(wkb) return wkb_reader_read(self.ptr, wkb_s, len(wkb_s)) - elif isinstance(wkb, basestring): + elif isinstance(wkb, six.string_types): return wkb_reader_read_hex(self.ptr, wkb, len(wkb)) else: raise TypeError @@ -195,7 +197,7 @@ class WKBWriter(IOBase): # `ThreadLocalIO` object holds instances of the WKT and WKB reader/writer # objects that are local to the thread. The `GEOSGeometry` internals # access these instances by calling the module-level functions, defined -# below. +# below. class ThreadLocalIO(threading.local): wkt_r = None wkt_w = None diff --git a/django/contrib/gis/geos/prototypes/misc.py b/django/contrib/gis/geos/prototypes/misc.py index fd4f78a011..5a9b5555ad 100644 --- a/django/contrib/gis/geos/prototypes/misc.py +++ b/django/contrib/gis/geos/prototypes/misc.py @@ -7,6 +7,7 @@ from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE from django.contrib.gis.geos.prototypes.errcheck import check_dbl, check_string from django.contrib.gis.geos.prototypes.geom import geos_char_p from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc +from django.utils.six.moves import xrange __all__ = ['geos_area', 'geos_distance', 'geos_length'] diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 9946495f54..d621c6b4d4 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -1,6 +1,5 @@ import ctypes import random -import unittest from django.contrib.gis.geos import (GEOSException, GEOSIndexError, GEOSGeometry, GeometryCollection, Point, MultiPoint, Polygon, MultiPolygon, LinearRing, @@ -9,6 +8,10 @@ from django.contrib.gis.geos.base import gdal, numpy, GEOSBase from django.contrib.gis.geos.libgeos import GEOS_PREPARE from django.contrib.gis.geometry.test_data import TestDataMixin +from django.utils import six +from django.utils.six.moves import xrange +from django.utils import unittest + class GEOSTest(unittest.TestCase, TestDataMixin): @@ -195,9 +198,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(srid, poly.shell.srid) self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export + @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") def test_json(self): "Testing GeoJSON input/output (via GDAL)." - if not gdal or not gdal.GEOJSON: return for g in self.geometries.json_geoms: geom = GEOSGeometry(g.wkt) if not hasattr(g, 'not_equal'): @@ -383,6 +386,13 @@ class GEOSTest(unittest.TestCase, TestDataMixin): p = Polygon.from_bbox(bbox) self.assertEqual(bbox, p.extent) + # Testing numerical precision + x = 3.14159265358979323 + bbox = (0, 0, 1, x) + p = Polygon.from_bbox(bbox) + y = p.extent[-1] + self.assertEqual(format(x, '.13f'), format(y, '.13f')) + def test_polygons(self): "Testing Polygon objects." @@ -813,9 +823,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): # And, they should be equal. self.assertEqual(gc1, gc2) + @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") def test_gdal(self): "Testing `ogr` and `srs` properties." - if not gdal.HAS_GDAL: return g1 = fromstr('POINT(5 23)') self.assertEqual(True, isinstance(g1.ogr, gdal.OGRGeometry)) self.assertEqual(g1.srs, None) @@ -835,9 +845,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertNotEqual(poly._ptr, cpy1._ptr) self.assertNotEqual(poly._ptr, cpy2._ptr) + @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") def test_transform(self): "Testing `transform` method." - if not gdal.HAS_GDAL: return orig = GEOSGeometry('POINT (-104.609 38.255)', 4326) trans = GEOSGeometry('POINT (992385.4472045 481455.4944650)', 2774) @@ -943,7 +953,8 @@ class GEOSTest(unittest.TestCase, TestDataMixin): def test_pickle(self): "Testing pickling and unpickling support." # Using both pickle and cPickle -- just 'cause. - import pickle, cPickle + from django.utils.six.moves import cPickle + import pickle # Creating a list of test geometries for pickling, # and setting the SRID on some of them. @@ -963,9 +974,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertEqual(geom, tmpg) if not no_srid: self.assertEqual(geom.srid, tmpg.srid) + @unittest.skipUnless(GEOS_PREPARE, "geos >= 3.1.0 is required") def test_prepared(self): "Testing PreparedGeometry support." - if not GEOS_PREPARE: return # Creating a simple multipolygon and getting a prepared version. mpoly = GEOSGeometry('MULTIPOLYGON(((0 0,0 5,5 5,5 0,0 0)),((5 5,5 10,10 10,10 5,5 5)))') prep = mpoly.prepared @@ -990,14 +1001,13 @@ class GEOSTest(unittest.TestCase, TestDataMixin): for geom, merged in zip(ref_geoms, ref_merged): self.assertEqual(merged, geom.merged) + @unittest.skipUnless(GEOS_PREPARE, "geos >= 3.1.0 is required") def test_valid_reason(self): "Testing IsValidReason support" - # Skipping tests if GEOS < v3.1. - if not GEOS_PREPARE: return g = GEOSGeometry("POINT(0 0)") self.assertTrue(g.valid) - self.assertTrue(isinstance(g.valid_reason, basestring)) + self.assertTrue(isinstance(g.valid_reason, six.string_types)) self.assertEqual(g.valid_reason, "Valid Geometry") print("\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n") @@ -1005,7 +1015,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): g = GEOSGeometry("LINESTRING(0 0, 0 0)") self.assertTrue(not g.valid) - self.assertTrue(isinstance(g.valid_reason, basestring)) + self.assertTrue(isinstance(g.valid_reason, six.string_types)) self.assertTrue(g.valid_reason.startswith("Too few points in geometry component")) print("\nEND - expecting GEOS_NOTICE; safe to ignore.\n") diff --git a/django/contrib/gis/geos/tests/test_io.py b/django/contrib/gis/geos/tests/test_io.py index 50c36684a0..ebf178a807 100644 --- a/django/contrib/gis/geos/tests/test_io.py +++ b/django/contrib/gis/geos/tests/test_io.py @@ -1,6 +1,7 @@ import binascii import unittest from django.contrib.gis.geos import GEOSGeometry, WKTReader, WKTWriter, WKBReader, WKBWriter, geos_version_info +from django.utils import six class GEOSIOTest(unittest.TestCase): @@ -12,12 +13,12 @@ class GEOSIOTest(unittest.TestCase): # read() should return a GEOSGeometry ref = GEOSGeometry(wkt) g1 = wkt_r.read(wkt) - g2 = wkt_r.read(unicode(wkt)) + g2 = wkt_r.read(six.text_type(wkt)) for geom in (g1, g2): self.assertEqual(ref, geom) - # Should only accept basestring objects. + # Should only accept six.string_types objects. self.assertRaises(TypeError, wkt_r.read, 1) self.assertRaises(TypeError, wkt_r.read, buffer('foo')) @@ -48,7 +49,7 @@ class GEOSIOTest(unittest.TestCase): bad_input = (1, 5.23, None, False) for bad_wkb in bad_input: self.assertRaises(TypeError, wkb_r.read, bad_wkb) - + def test04_wkbwriter(self): wkb_w = WKBWriter() @@ -67,7 +68,7 @@ class GEOSIOTest(unittest.TestCase): for bad_byteorder in (-1, 2, 523, 'foo', None): # Equivalent of `wkb_w.byteorder = bad_byteorder` self.assertRaises(ValueError, wkb_w._set_byteorder, bad_byteorder) - + # Setting the byteorder to 0 (for Big Endian) wkb_w.byteorder = 0 self.assertEqual(hex2, wkb_w.write_hex(g)) @@ -79,7 +80,7 @@ class GEOSIOTest(unittest.TestCase): # Now, trying out the 3D and SRID flags. g = GEOSGeometry('POINT (5 23 17)') g.srid = 4326 - + hex3d = '0101000080000000000000144000000000000037400000000000003140' wkb3d = buffer(binascii.a2b_hex(hex3d)) hex3d_srid = '01010000A0E6100000000000000000144000000000000037400000000000003140' diff --git a/django/contrib/gis/geos/tests/test_mutable_list.py b/django/contrib/gis/geos/tests/test_mutable_list.py index 3e63a25e95..cd174d7cfa 100644 --- a/django/contrib/gis/geos/tests/test_mutable_list.py +++ b/django/contrib/gis/geos/tests/test_mutable_list.py @@ -4,6 +4,7 @@ # Modified from original contribution by Aryeh Leib Taurog, which was # released under the New BSD license. from django.contrib.gis.geos.mutable_list import ListMixin +from django.utils import six from django.utils import unittest @@ -267,7 +268,7 @@ class ListMixinTest(unittest.TestCase): def test07_allowed_types(self): 'Type-restricted list' pl, ul = self.lists_of_len() - ul._allowed = (int, long) + ul._allowed = six.integer_types ul[1] = 50 ul[:2] = [60, 70, 80] def setfcn(x, i, v): x[i] = v diff --git a/django/contrib/gis/maps/google/gmap.py b/django/contrib/gis/maps/google/gmap.py index f0e8d73399..75b285ca76 100644 --- a/django/contrib/gis/maps/google/gmap.py +++ b/django/contrib/gis/maps/google/gmap.py @@ -1,6 +1,8 @@ from django.conf import settings from django.template.loader import render_to_string +from django.utils.html import format_html from django.utils.safestring import mark_safe +from django.utils.six.moves import xrange from django.contrib.gis.maps.google.overlays import GPolygon, GPolyline, GMarker @@ -10,7 +12,7 @@ class GoogleMapException(Exception): # The default Google Maps URL (for the API javascript) # TODO: Internationalize for Japan, UK, etc. -GOOGLE_MAPS_URL='http://maps.google.com/maps?file=api&v=%s&key=' +GOOGLE_MAPS_URL='http://maps.google.com/maps?file=api&v=%s&key=' class GoogleMap(object): @@ -48,7 +50,7 @@ class GoogleMap(object): # Can specify the API URL in the `api_url` keyword. if not api_url: - self.api_url = mark_safe(getattr(settings, 'GOOGLE_MAPS_URL', GOOGLE_MAPS_URL) % self.version) + self.api_url = getattr(settings, 'GOOGLE_MAPS_URL', GOOGLE_MAPS_URL) % self.version else: self.api_url = api_url @@ -111,17 +113,18 @@ class GoogleMap(object): @property def body(self): "Returns HTML body tag for loading and unloading Google Maps javascript." - return mark_safe('' % (self.onload, self.onunload)) + return format_html('', self.onload, self.onunload) @property def onload(self): "Returns the `onload` HTML attribute." - return mark_safe('onload="%s.%s_load()"' % (self.js_module, self.dom_id)) + return format_html('onload="{0}.{1}_load()"', self.js_module, self.dom_id) @property def api_script(self): "Returns the ' % (self.api_url, self.key)) + return format_html('', + self.api_url, self.key) @property def js(self): @@ -131,17 +134,17 @@ class GoogleMap(object): @property def scripts(self): "Returns all tags required with Google Maps JavaScript." - return mark_safe('%s\n ' % (self.api_script, self.js)) + return format_html('%s\n ', self.api_script, mark_safe(self.js)) @property def style(self): "Returns additional CSS styling needed for Google Maps on IE." - return mark_safe('' % self.vml_css) + return format_html('', self.vml_css) @property def xhtml(self): "Returns XHTML information needed for IE VML overlays." - return mark_safe('' % self.xmlns) + return format_html('', self.xmlns) @property def icons(self): diff --git a/django/contrib/gis/maps/google/overlays.py b/django/contrib/gis/maps/google/overlays.py index eaf12dad6d..b82d967da6 100644 --- a/django/contrib/gis/maps/google/overlays.py +++ b/django/contrib/gis/maps/google/overlays.py @@ -1,8 +1,11 @@ from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Polygon from django.utils.functional import total_ordering from django.utils.safestring import mark_safe +from django.utils import six +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class GEvent(object): """ A Python wrapper for the Google GEvent object. @@ -47,10 +50,11 @@ class GEvent(object): self.event = event self.action = action - def __unicode__(self): + def __str__(self): "Returns the parameter part of a GEvent." return mark_safe('"%s", %s' %(self.event, self.action)) +@python_2_unicode_compatible class GOverlayBase(object): def __init__(self): self.events = [] @@ -63,7 +67,7 @@ class GOverlayBase(object): "Attaches a GEvent to the overlay object." self.events.append(event) - def __unicode__(self): + def __str__(self): "The string representation is the JavaScript API call." return mark_safe('%s(%s)' % (self.__class__.__name__, self.js_params)) @@ -98,7 +102,7 @@ class GPolygon(GOverlayBase): fill_opacity: The opacity of the polygon fill. Defaults to 0.4. """ - if isinstance(poly, basestring): poly = fromstr(poly) + if isinstance(poly, six.string_types): poly = fromstr(poly) if isinstance(poly, (tuple, list)): poly = Polygon(poly) if not isinstance(poly, Polygon): raise TypeError('GPolygon may only initialize on GEOS Polygons.') @@ -148,7 +152,7 @@ class GPolyline(GOverlayBase): The opacity of the polyline, between 0 and 1. Defaults to 1. """ # If a GEOS geometry isn't passed in, try to contsruct one. - if isinstance(geom, basestring): geom = fromstr(geom) + if isinstance(geom, six.string_types): geom = fromstr(geom) if isinstance(geom, (tuple, list)): geom = Polygon(geom) # Generating the lat/lng coordinate pairs. if isinstance(geom, (LineString, LinearRing)): @@ -239,9 +243,9 @@ class GIcon(object): def __lt__(self, other): return self.varname < other.varname - + def __hash__(self): - # XOR with hash of GIcon type so that hash('varname') won't + # XOR with hash of GIcon type so that hash('varname') won't # equal hash(GIcon('varname')). return hash(self.__class__) ^ hash(self.varname) @@ -278,7 +282,7 @@ class GMarker(GOverlayBase): Draggable option for GMarker, disabled by default. """ # If a GEOS geometry isn't passed in, try to construct one. - if isinstance(geom, basestring): geom = fromstr(geom) + if isinstance(geom, six.string_types): geom = fromstr(geom) if isinstance(geom, (tuple, list)): geom = Point(geom) if isinstance(geom, Point): self.latlng = self.latlng_from_coords(geom.coords) diff --git a/django/contrib/gis/maps/google/zoom.py b/django/contrib/gis/maps/google/zoom.py index fc9ff1db96..c93cf4ef48 100644 --- a/django/contrib/gis/maps/google/zoom.py +++ b/django/contrib/gis/maps/google/zoom.py @@ -1,5 +1,6 @@ from django.contrib.gis.geos import GEOSGeometry, LinearRing, Polygon, Point from django.contrib.gis.maps.google.gmap import GoogleMapException +from django.utils.six.moves import xrange from math import pi, sin, log, exp, atan # Constants used for degree to radian conversion, and vice-versa. @@ -20,21 +21,21 @@ class GoogleZoom(object): "Google Maps Hacks" may be found at http://safari.oreilly.com/0596101619 """ - + def __init__(self, num_zoom=19, tilesize=256): "Initializes the Google Zoom object." # Google's tilesize is 256x256, square tiles are assumed. self._tilesize = tilesize - + # The number of zoom levels self._nzoom = num_zoom - # Initializing arrays to hold the parameters for each one of the + # Initializing arrays to hold the parameters for each one of the # zoom levels. self._degpp = [] # Degrees per pixel self._radpp = [] # Radians per pixel self._npix = [] # 1/2 the number of pixels for a tile at the given zoom level - + # Incrementing through the zoom levels and populating the parameter arrays. z = tilesize # The number of pixels per zoom level. for i in xrange(num_zoom): @@ -70,9 +71,9 @@ class GoogleZoom(object): # with with the number of degrees/pixel at the given zoom level. px_x = round(npix + (lon * self._degpp[zoom])) - # Creating the factor, and ensuring that 1 or -1 is not passed in as the + # Creating the factor, and ensuring that 1 or -1 is not passed in as the # base to the logarithm. Here's why: - # if fac = -1, we'll get log(0) which is undefined; + # if fac = -1, we'll get log(0) which is undefined; # if fac = 1, our logarithm base will be divided by 0, also undefined. fac = min(max(sin(DTOR * lat), -0.9999), 0.9999) @@ -98,7 +99,7 @@ class GoogleZoom(object): # Returning the longitude, latitude coordinate pair. return (lon, lat) - + def tile(self, lonlat, zoom): """ Returns a Polygon corresponding to the region represented by a fictional @@ -119,7 +120,7 @@ class GoogleZoom(object): # Constructing the Polygon, representing the tile and returning. return Polygon(LinearRing(ll, (ll[0], ur[1]), ur, (ur[0], ll[1]), ll), srid=4326) - + def get_zoom(self, geom): "Returns the optimal Zoom level for the given geometry." # Checking the input type. @@ -139,10 +140,10 @@ class GoogleZoom(object): # When we span more than one tile, this is an approximately good # zoom level. if (env_w > tile_w) or (env_h > tile_h): - if z == 0: + if z == 0: raise GoogleMapException('Geometry width and height should not exceed that of the Earth.') return z-1 - + # Otherwise, we've zoomed in to the max. return self._nzoom-1 diff --git a/django/contrib/gis/measure.py b/django/contrib/gis/measure.py index 46e198c410..6e074be355 100644 --- a/django/contrib/gis/measure.py +++ b/django/contrib/gis/measure.py @@ -30,7 +30,7 @@ Distance and Area objects to allow for sensible and convienient calculation and conversions. -Authors: Robert Coup, Justin Bronn +Authors: Robert Coup, Justin Bronn, Riccardo Di Virgilio Inspired by GeoPy (http://exogen.case.edu/projects/geopy/) and Geoff Biggs' PhD work on dimensioned units for robotics. @@ -39,15 +39,140 @@ __all__ = ['A', 'Area', 'D', 'Distance'] from decimal import Decimal from django.utils.functional import total_ordering +from django.utils import six +NUMERIC_TYPES = six.integer_types + (float, Decimal) +AREA_PREFIX = "sq_" + +def pretty_name(obj): + return obj.__name__ if obj.__class__ == type else obj.__class__.__name__ + + +@total_ordering class MeasureBase(object): + STANDARD_UNIT = None + ALIAS = {} + UNITS = {} + LALIAS = {} + + def __init__(self, default_unit=None, **kwargs): + value, self._default_unit = self.default_units(kwargs) + setattr(self, self.STANDARD_UNIT, value) + if default_unit and isinstance(default_unit, six.string_types): + self._default_unit = default_unit + + def _get_standard(self): + return getattr(self, self.STANDARD_UNIT) + + def _set_standard(self, value): + setattr(self, self.STANDARD_UNIT, value) + + standard = property(_get_standard, _set_standard) + + def __getattr__(self, name): + if name in self.UNITS: + return self.standard / self.UNITS[name] + else: + raise AttributeError('Unknown unit type: %s' % name) + + def __repr__(self): + return '%s(%s=%s)' % (pretty_name(self), self._default_unit, + getattr(self, self._default_unit)) + + def __str__(self): + return '%s %s' % (getattr(self, self._default_unit), self._default_unit) + + # **** Comparison methods **** + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.standard == other.standard + else: + return NotImplemented + + def __lt__(self, other): + if isinstance(other, self.__class__): + return self.standard < other.standard + else: + return NotImplemented + + # **** Operators methods **** + + def __add__(self, other): + if isinstance(other, self.__class__): + return self.__class__(default_unit=self._default_unit, + **{self.STANDARD_UNIT: (self.standard + other.standard)}) + else: + raise TypeError('%(class)s must be added with %(class)s' % {"class":pretty_name(self)}) + + def __iadd__(self, other): + if isinstance(other, self.__class__): + self.standard += other.standard + return self + else: + raise TypeError('%(class)s must be added with %(class)s' % {"class":pretty_name(self)}) + + def __sub__(self, other): + if isinstance(other, self.__class__): + return self.__class__(default_unit=self._default_unit, + **{self.STANDARD_UNIT: (self.standard - other.standard)}) + else: + raise TypeError('%(class)s must be subtracted from %(class)s' % {"class":pretty_name(self)}) + + def __isub__(self, other): + if isinstance(other, self.__class__): + self.standard -= other.standard + return self + else: + raise TypeError('%(class)s must be subtracted from %(class)s' % {"class":pretty_name(self)}) + + def __mul__(self, other): + if isinstance(other, NUMERIC_TYPES): + return self.__class__(default_unit=self._default_unit, + **{self.STANDARD_UNIT: (self.standard * other)}) + else: + raise TypeError('%(class)s must be multiplied with number' % {"class":pretty_name(self)}) + + def __imul__(self, other): + if isinstance(other, NUMERIC_TYPES): + self.standard *= float(other) + return self + else: + raise TypeError('%(class)s must be multiplied with number' % {"class":pretty_name(self)}) + + def __rmul__(self, other): + return self * other + + def __truediv__(self, other): + if isinstance(other, self.__class__): + return self.standard / other.standard + if isinstance(other, NUMERIC_TYPES): + return self.__class__(default_unit=self._default_unit, + **{self.STANDARD_UNIT: (self.standard / other)}) + else: + raise TypeError('%(class)s must be divided with number or %(class)s' % {"class":pretty_name(self)}) + __div__ = __truediv__ # Python 2 compatibility + + def __itruediv__(self, other): + if isinstance(other, NUMERIC_TYPES): + self.standard /= float(other) + return self + else: + raise TypeError('%(class)s must be divided with number' % {"class":pretty_name(self)}) + __idiv__ = __itruediv__ # Python 2 compatibility + + def __bool__(self): + return bool(self.standard) + __nonzero__ = __bool__ # Python 2 compatibility + def default_units(self, kwargs): """ Return the unit value and the default units specified from the given keyword arguments dictionary. """ val = 0.0 - for unit, value in kwargs.iteritems(): + default_unit = self.STANDARD_UNIT + for unit, value in six.iteritems(kwargs): if not isinstance(value, float): value = float(value) if unit in self.UNITS: val += self.UNITS[unit] * value @@ -86,8 +211,8 @@ class MeasureBase(object): else: raise Exception('Could not find a unit keyword associated with "%s"' % unit_str) -@total_ordering class Distance(MeasureBase): + STANDARD_UNIT = "m" UNITS = { 'chain' : 20.1168, 'chain_benoit' : 20.116782, @@ -163,189 +288,34 @@ class Distance(MeasureBase): } LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()]) - def __init__(self, default_unit=None, **kwargs): - # The base unit is in meters. - self.m, self._default_unit = self.default_units(kwargs) - if default_unit and isinstance(default_unit, str): - self._default_unit = default_unit - - def __getattr__(self, name): - if name in self.UNITS: - return self.m / self.UNITS[name] - else: - raise AttributeError('Unknown unit type: %s' % name) - - def __repr__(self): - return 'Distance(%s=%s)' % (self._default_unit, getattr(self, self._default_unit)) - - def __str__(self): - return '%s %s' % (getattr(self, self._default_unit), self._default_unit) - - def __eq__(self, other): - if isinstance(other, Distance): - return self.m == other.m - else: - return NotImplemented - - def __lt__(self, other): - if isinstance(other, Distance): - return self.m < other.m - else: - return NotImplemented - - def __add__(self, other): - if isinstance(other, Distance): - return Distance(default_unit=self._default_unit, m=(self.m + other.m)) - else: - raise TypeError('Distance must be added with Distance') - - def __iadd__(self, other): - if isinstance(other, Distance): - self.m += other.m - return self - else: - raise TypeError('Distance must be added with Distance') - - def __sub__(self, other): - if isinstance(other, Distance): - return Distance(default_unit=self._default_unit, m=(self.m - other.m)) - else: - raise TypeError('Distance must be subtracted from Distance') - - def __isub__(self, other): - if isinstance(other, Distance): - self.m -= other.m - return self - else: - raise TypeError('Distance must be subtracted from Distance') - def __mul__(self, other): - if isinstance(other, (int, float, long, Decimal)): - return Distance(default_unit=self._default_unit, m=(self.m * float(other))) - elif isinstance(other, Distance): - return Area(default_unit='sq_' + self._default_unit, sq_m=(self.m * other.m)) + if isinstance(other, self.__class__): + return Area(default_unit=AREA_PREFIX + self._default_unit, + **{AREA_PREFIX + self.STANDARD_UNIT: (self.standard * other.standard)}) + elif isinstance(other, NUMERIC_TYPES): + return self.__class__(default_unit=self._default_unit, + **{self.STANDARD_UNIT: (self.standard * other)}) else: - raise TypeError('Distance must be multiplied with number or Distance') + raise TypeError('%(distance)s must be multiplied with number or %(distance)s' % { + "distance" : pretty_name(self.__class__), + }) - def __imul__(self, other): - if isinstance(other, (int, float, long, Decimal)): - self.m *= float(other) - return self - else: - raise TypeError('Distance must be multiplied with number') - def __rmul__(self, other): - return self * other - - def __div__(self, other): - if isinstance(other, (int, float, long, Decimal)): - return Distance(default_unit=self._default_unit, m=(self.m / float(other))) - else: - raise TypeError('Distance must be divided with number') - - def __idiv__(self, other): - if isinstance(other, (int, float, long, Decimal)): - self.m /= float(other) - return self - else: - raise TypeError('Distance must be divided with number') - - def __nonzero__(self): - return bool(self.m) - -@total_ordering class Area(MeasureBase): + STANDARD_UNIT = AREA_PREFIX + Distance.STANDARD_UNIT # Getting the square units values and the alias dictionary. - UNITS = dict([('sq_%s' % k, v ** 2) for k, v in Distance.UNITS.items()]) - ALIAS = dict([(k, 'sq_%s' % v) for k, v in Distance.ALIAS.items()]) + UNITS = dict([('%s%s' % (AREA_PREFIX, k), v ** 2) for k, v in Distance.UNITS.items()]) + ALIAS = dict([(k, '%s%s' % (AREA_PREFIX, v)) for k, v in Distance.ALIAS.items()]) LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()]) - def __init__(self, default_unit=None, **kwargs): - self.sq_m, self._default_unit = self.default_units(kwargs) - if default_unit and isinstance(default_unit, str): - self._default_unit = default_unit - - def __getattr__(self, name): - if name in self.UNITS: - return self.sq_m / self.UNITS[name] + def __truediv__(self, other): + if isinstance(other, NUMERIC_TYPES): + return self.__class__(default_unit=self._default_unit, + **{self.STANDARD_UNIT: (self.standard / other)}) else: - raise AttributeError('Unknown unit type: ' + name) + raise TypeError('%(class)s must be divided by a number' % {"class":pretty_name(self)}) + __div__ = __truediv__ # Python 2 compatibility - def __repr__(self): - return 'Area(%s=%s)' % (self._default_unit, getattr(self, self._default_unit)) - - def __str__(self): - return '%s %s' % (getattr(self, self._default_unit), self._default_unit) - - def __eq__(self, other): - if isinstance(other, Area): - return self.sq_m == other.sq_m - else: - return NotImplemented - - def __lt__(self, other): - if isinstance(other, Area): - return self.sq_m < other.sq_m - else: - return NotImplemented - - def __add__(self, other): - if isinstance(other, Area): - return Area(default_unit=self._default_unit, sq_m=(self.sq_m + other.sq_m)) - else: - raise TypeError('Area must be added with Area') - - def __iadd__(self, other): - if isinstance(other, Area): - self.sq_m += other.sq_m - return self - else: - raise TypeError('Area must be added with Area') - - def __sub__(self, other): - if isinstance(other, Area): - return Area(default_unit=self._default_unit, sq_m=(self.sq_m - other.sq_m)) - else: - raise TypeError('Area must be subtracted from Area') - - def __isub__(self, other): - if isinstance(other, Area): - self.sq_m -= other.sq_m - return self - else: - raise TypeError('Area must be subtracted from Area') - - def __mul__(self, other): - if isinstance(other, (int, float, long, Decimal)): - return Area(default_unit=self._default_unit, sq_m=(self.sq_m * float(other))) - else: - raise TypeError('Area must be multiplied with number') - - def __imul__(self, other): - if isinstance(other, (int, float, long, Decimal)): - self.sq_m *= float(other) - return self - else: - raise TypeError('Area must be multiplied with number') - - def __rmul__(self, other): - return self * other - - def __div__(self, other): - if isinstance(other, (int, float, long, Decimal)): - return Area(default_unit=self._default_unit, sq_m=(self.sq_m / float(other))) - else: - raise TypeError('Area must be divided with number') - - def __idiv__(self, other): - if isinstance(other, (int, float, long, Decimal)): - self.sq_m /= float(other) - return self - else: - raise TypeError('Area must be divided with number') - - def __nonzero__(self): - return bool(self.sq_m) # Shortcuts D = Distance diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index 3a9acad7b0..8bcdba1b44 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.http import HttpResponse, Http404 from django.template import loader from django.contrib.sites.models import get_current_site @@ -6,7 +8,8 @@ from django.core.paginator import EmptyPage, PageNotAnInteger from django.contrib.gis.db.models.fields import GeometryField from django.db import connections, DEFAULT_DB_ALIAS from django.db.models import get_model -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes +from django.utils import six from django.utils.translation import ugettext as _ from django.contrib.gis.shortcuts import render_to_kml, render_to_kmz @@ -41,10 +44,10 @@ def sitemap(request, sitemaps, section=None): maps, urls = [], [] if section is not None: if section not in sitemaps: - raise Http404(_(u"No sitemap available for section: %r") % section) + raise Http404(_("No sitemap available for section: %r") % section) maps.append(sitemaps[section]) else: - maps = sitemaps.values() + maps = list(six.itervalues(sitemaps)) page = request.GET.get("p", 1) current_site = get_current_site(request) @@ -55,10 +58,10 @@ def sitemap(request, sitemaps, section=None): else: urls.extend(site.get_urls(page=page, site=current_site)) except EmptyPage: - raise Http404(_(u"Page %s empty") % page) + raise Http404(_("Page %s empty") % page) except PageNotAnInteger: - raise Http404(_(u"No page '%s'") % page) - xml = smart_str(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls})) + raise Http404(_("No page '%s'") % page) + xml = smart_bytes(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls})) return HttpResponse(xml, content_type='application/xml') def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB_ALIAS): diff --git a/django/contrib/gis/tests/__init__.py b/django/contrib/gis/tests/__init__.py index 3433e195a8..765c03018b 100644 --- a/django/contrib/gis/tests/__init__.py +++ b/django/contrib/gis/tests/__init__.py @@ -2,6 +2,10 @@ from django.conf import settings from django.test.simple import build_suite, DjangoTestSuiteRunner from django.utils import unittest +from .test_geoforms import GeometryFieldTest +from .test_measure import DistanceTest, AreaTest +from .test_spatialrefsys import SpatialRefSysTest + def geo_apps(namespace=True, runtests=False): """ @@ -54,20 +58,12 @@ def geodjango_suite(apps=True): from django.contrib.gis.geos import tests as geos_tests suite.addTest(geos_tests.suite()) - # Adding the measurment tests. - from django.contrib.gis.tests import test_measure - suite.addTest(test_measure.suite()) - # Adding GDAL tests, and any test suite that depends on GDAL, to the # suite if GDAL is available. from django.contrib.gis.gdal import HAS_GDAL if HAS_GDAL: from django.contrib.gis.gdal import tests as gdal_tests suite.addTest(gdal_tests.suite()) - - from django.contrib.gis.tests import test_spatialrefsys, test_geoforms - suite.addTest(test_spatialrefsys.suite()) - suite.addTest(test_geoforms.suite()) else: sys.stderr.write('GDAL not available - no tests requiring GDAL will be run.\n') diff --git a/django/contrib/gis/tests/distapp/models.py b/django/contrib/gis/tests/distapp/models.py index 76e7d3a03f..bf08829eae 100644 --- a/django/contrib/gis/tests/distapp/models.py +++ b/django/contrib/gis/tests/distapp/models.py @@ -1,50 +1,58 @@ from django.contrib.gis.db import models +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class SouthTexasCity(models.Model): "City model on projected coordinate system for South Texas." name = models.CharField(max_length=30) point = models.PointField(srid=32140) objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name +@python_2_unicode_compatible class SouthTexasCityFt(models.Model): "Same City model as above, but U.S. survey feet are the units." name = models.CharField(max_length=30) point = models.PointField(srid=2278) objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name +@python_2_unicode_compatible class AustraliaCity(models.Model): "City model for Australia, using WGS84." name = models.CharField(max_length=30) point = models.PointField() objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name +@python_2_unicode_compatible class CensusZipcode(models.Model): "Model for a few South Texas ZIP codes (in original Census NAD83)." name = models.CharField(max_length=5) poly = models.PolygonField(srid=4269) objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name +@python_2_unicode_compatible class SouthTexasZipcode(models.Model): "Model for a few South Texas ZIP codes." name = models.CharField(max_length=5) poly = models.PolygonField(srid=32140, null=True) objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name +@python_2_unicode_compatible class Interstate(models.Model): "Geodetic model for U.S. Interstates." name = models.CharField(max_length=10) path = models.LineStringField() objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name +@python_2_unicode_compatible class SouthTexasInterstate(models.Model): "Projected model for South Texas Interstates." name = models.CharField(max_length=10) path = models.LineStringField(srid=32140) objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name diff --git a/django/contrib/gis/tests/geo3d/models.py b/django/contrib/gis/tests/geo3d/models.py index 3c4f77ee05..81e5f55f78 100644 --- a/django/contrib/gis/tests/geo3d/models.py +++ b/django/contrib/gis/tests/geo3d/models.py @@ -1,59 +1,67 @@ from django.contrib.gis.db import models +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class City3D(models.Model): name = models.CharField(max_length=30) point = models.PointField(dim=3) objects = models.GeoManager() - def __unicode__(self): + def __str__(self): return self.name +@python_2_unicode_compatible class Interstate2D(models.Model): name = models.CharField(max_length=30) line = models.LineStringField(srid=4269) objects = models.GeoManager() - def __unicode__(self): + def __str__(self): return self.name +@python_2_unicode_compatible class Interstate3D(models.Model): name = models.CharField(max_length=30) line = models.LineStringField(dim=3, srid=4269) objects = models.GeoManager() - def __unicode__(self): + def __str__(self): return self.name +@python_2_unicode_compatible class InterstateProj2D(models.Model): name = models.CharField(max_length=30) line = models.LineStringField(srid=32140) objects = models.GeoManager() - def __unicode__(self): + def __str__(self): return self.name +@python_2_unicode_compatible class InterstateProj3D(models.Model): name = models.CharField(max_length=30) line = models.LineStringField(dim=3, srid=32140) objects = models.GeoManager() - def __unicode__(self): + def __str__(self): return self.name +@python_2_unicode_compatible class Polygon2D(models.Model): name = models.CharField(max_length=30) poly = models.PolygonField(srid=32140) objects = models.GeoManager() - - def __unicode__(self): + + def __str__(self): return self.name +@python_2_unicode_compatible class Polygon3D(models.Model): name = models.CharField(max_length=30) poly = models.PolygonField(dim=3, srid=32140) objects = models.GeoManager() - - def __unicode__(self): + + def __str__(self): return self.name class Point2D(models.Model): diff --git a/django/contrib/gis/tests/geoadmin/models.py b/django/contrib/gis/tests/geoadmin/models.py index 51a76d1a0e..af0898823d 100644 --- a/django/contrib/gis/tests/geoadmin/models.py +++ b/django/contrib/gis/tests/geoadmin/models.py @@ -1,10 +1,12 @@ from django.contrib.gis.db import models from django.contrib.gis import admin +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class City(models.Model): name = models.CharField(max_length=30) point = models.PointField() objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name admin.site.register(City, admin.OSMGeoAdmin) diff --git a/django/contrib/gis/tests/geoadmin/tests.py b/django/contrib/gis/tests/geoadmin/tests.py index 53c00c6588..6fadebdb9a 100644 --- a/django/contrib/gis/tests/geoadmin/tests.py +++ b/django/contrib/gis/tests/geoadmin/tests.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from django.test import TestCase from django.contrib.gis import admin -from django.contrib.gis.geos import Point +from django.contrib.gis.geos import GEOSGeometry, Point from .models import City @@ -10,7 +10,7 @@ from .models import City class GeoAdminTest(TestCase): urls = 'django.contrib.gis.tests.geoadmin.urls' - def test01_ensure_geographic_media(self): + def test_ensure_geographic_media(self): geoadmin = admin.site._registry[City] admin_js = geoadmin.media.render_js() self.assertTrue(any([geoadmin.openlayers_url in js for js in admin_js])) @@ -33,3 +33,21 @@ class GeoAdminTest(TestCase): self.assertIn( """geodjango_point.layers.base = new OpenLayers.Layer.WMS("OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: \'basic\', format: 'image/jpeg'});""", result) + + def test_olwidget_has_changed(self): + """ Check that changes are accurately noticed by OpenLayersWidget. """ + geoadmin = admin.site._registry[City] + form = geoadmin.get_changelist_form(None)() + has_changed = form.fields['point'].widget._has_changed + + initial = Point(13.4197458572965953, 52.5194108501149799, srid=4326) + data_same = "SRID=3857;POINT(1493879.2754093995 6894592.019687599)" + data_almost_same = "SRID=3857;POINT(1493879.2754093990 6894592.019687590)" + data_changed = "SRID=3857;POINT(1493884.0527237 6894593.8111804)" + + self.assertTrue(has_changed(None, data_changed)) + self.assertTrue(has_changed(initial, "")) + self.assertFalse(has_changed(None, "")) + self.assertFalse(has_changed(initial, data_same)) + self.assertFalse(has_changed(initial, data_almost_same)) + self.assertTrue(has_changed(initial, data_changed)) diff --git a/django/contrib/gis/tests/geoapp/models.py b/django/contrib/gis/tests/geoapp/models.py index 79061e1cfc..abde509c8b 100644 --- a/django/contrib/gis/tests/geoapp/models.py +++ b/django/contrib/gis/tests/geoapp/models.py @@ -1,20 +1,23 @@ from django.contrib.gis.db import models from django.contrib.gis.tests.utils import mysql, spatialite +from django.utils.encoding import python_2_unicode_compatible # MySQL spatial indices can't handle NULL geometries. null_flag = not mysql +@python_2_unicode_compatible class Country(models.Model): name = models.CharField(max_length=30) mpoly = models.MultiPolygonField() # SRID, by default, is 4326 objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name +@python_2_unicode_compatible class City(models.Model): name = models.CharField(max_length=30) point = models.PointField() objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name # This is an inherited model from City class PennsylvaniaCity(City): @@ -22,28 +25,31 @@ class PennsylvaniaCity(City): founded = models.DateTimeField(null=True) objects = models.GeoManager() # TODO: This should be implicitly inherited. +@python_2_unicode_compatible class State(models.Model): name = models.CharField(max_length=30) poly = models.PolygonField(null=null_flag) # Allowing NULL geometries here. objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name +@python_2_unicode_compatible class Track(models.Model): name = models.CharField(max_length=30) line = models.LineStringField() objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name class Truth(models.Model): val = models.BooleanField() objects = models.GeoManager() if not spatialite: + @python_2_unicode_compatible class Feature(models.Model): name = models.CharField(max_length=20) geom = models.GeometryField() objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name class MinusOneSRID(models.Model): geom = models.PointField(srid=-1) # Minus one SRID. diff --git a/django/contrib/gis/tests/geoapp/test_feeds.py b/django/contrib/gis/tests/geoapp/test_feeds.py index 936c1ded46..85e777ae78 100644 --- a/django/contrib/gis/tests/geoapp/test_feeds.py +++ b/django/contrib/gis/tests/geoapp/test_feeds.py @@ -44,7 +44,7 @@ class GeoFeedTest(TestCase): # Incrementing through the feeds. for feed in [feed1, feed2]: # Ensuring the georss namespace was added to the element. - self.assertEqual(feed.getAttribute(u'xmlns:georss'), u'http://www.georss.org/georss') + self.assertEqual(feed.getAttribute('xmlns:georss'), 'http://www.georss.org/georss') chan = feed.getElementsByTagName('channel')[0] items = chan.getElementsByTagName('item') self.assertEqual(len(items), City.objects.count()) @@ -64,7 +64,7 @@ class GeoFeedTest(TestCase): for feed in [feed1, feed2]: # Ensuring the georsss namespace was added to the element. - self.assertEqual(feed.getAttribute(u'xmlns:georss'), u'http://www.georss.org/georss') + self.assertEqual(feed.getAttribute('xmlns:georss'), 'http://www.georss.org/georss') entries = feed.getElementsByTagName('entry') self.assertEqual(len(entries), City.objects.count()) @@ -77,7 +77,7 @@ class GeoFeedTest(TestCase): doc = minidom.parseString(self.client.get('/feeds/w3cgeo1/').content) feed = doc.firstChild # Ensuring the geo namespace was added to the element. - self.assertEqual(feed.getAttribute(u'xmlns:geo'), u'http://www.w3.org/2003/01/geo/wgs84_pos#') + self.assertEqual(feed.getAttribute('xmlns:geo'), 'http://www.w3.org/2003/01/geo/wgs84_pos#') chan = feed.getElementsByTagName('channel')[0] items = chan.getElementsByTagName('item') self.assertEqual(len(items), City.objects.count()) diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py index a9d802d8f1..fffd7d3cab 100644 --- a/django/contrib/gis/tests/geoapp/test_regress.py +++ b/django/contrib/gis/tests/geoapp/test_regress.py @@ -12,7 +12,7 @@ from .models import City, PennsylvaniaCity, State, Truth class GeoRegressionTests(TestCase): - def test01_update(self): + def test_update(self): "Testing GeoQuerySet.update(). See #10411." pnt = City.objects.get(name='Pueblo').point bak = pnt.clone() @@ -24,7 +24,7 @@ class GeoRegressionTests(TestCase): City.objects.filter(name='Pueblo').update(point=bak) self.assertEqual(bak, City.objects.get(name='Pueblo').point) - def test02_kmz(self): + def test_kmz(self): "Testing `render_to_kmz` with non-ASCII data. See #11624." name = '\xc3\x85land Islands'.decode('iso-8859-1') places = [{'name' : name, @@ -35,7 +35,7 @@ class GeoRegressionTests(TestCase): @no_spatialite @no_mysql - def test03_extent(self): + def test_extent(self): "Testing `extent` on a table with a single point. See #11827." pnt = City.objects.get(name='Pueblo').point ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y) @@ -43,14 +43,14 @@ class GeoRegressionTests(TestCase): for ref_val, val in zip(ref_ext, extent): self.assertAlmostEqual(ref_val, val, 4) - def test04_unicode_date(self): + def test_unicode_date(self): "Testing dates are converted properly, even on SpatiaLite. See #16408." founded = datetime(1857, 5, 23) mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)', founded=founded) self.assertEqual(founded, PennsylvaniaCity.objects.dates('founded', 'day')[0]) - def test05_empty_count(self): + def test_empty_count(self): "Testing that PostGISAdapter.__eq__ does check empty strings. See #13670." # contrived example, but need a geo lookup paired with an id__in lookup pueblo = City.objects.get(name='Pueblo') @@ -60,12 +60,12 @@ class GeoRegressionTests(TestCase): # .count() should not throw TypeError in __eq__ self.assertEqual(cities_within_state.count(), 1) - def test06_defer_or_only_with_annotate(self): + def test_defer_or_only_with_annotate(self): "Regression for #16409. Make sure defer() and only() work with annotate()" self.assertIsInstance(list(City.objects.annotate(Count('point')).defer('name')), list) self.assertIsInstance(list(City.objects.annotate(Count('point')).only('name')), list) - def test07_boolean_conversion(self): + def test_boolean_conversion(self): "Testing Boolean value conversion with the spatial backend, see #15169." t1 = Truth.objects.create(val=True) t2 = Truth.objects.create(val=False) diff --git a/django/contrib/gis/tests/geoapp/test_sitemaps.py b/django/contrib/gis/tests/geoapp/test_sitemaps.py index a5c8f41ba4..5f063dfba3 100644 --- a/django/contrib/gis/tests/geoapp/test_sitemaps.py +++ b/django/contrib/gis/tests/geoapp/test_sitemaps.py @@ -34,7 +34,7 @@ class GeoSitemapTest(TestCase): # Getting the geo index. doc = minidom.parseString(self.client.get('/sitemap.xml').content) index = doc.firstChild - self.assertEqual(index.getAttribute(u'xmlns'), u'http://www.sitemaps.org/schemas/sitemap/0.9') + self.assertEqual(index.getAttribute('xmlns'), 'http://www.sitemaps.org/schemas/sitemap/0.9') self.assertEqual(3, len(index.getElementsByTagName('sitemap'))) def test_geositemap_kml(self): @@ -44,8 +44,8 @@ class GeoSitemapTest(TestCase): # Ensuring the right sitemaps namespaces are present. urlset = doc.firstChild - self.assertEqual(urlset.getAttribute(u'xmlns'), u'http://www.sitemaps.org/schemas/sitemap/0.9') - self.assertEqual(urlset.getAttribute(u'xmlns:geo'), u'http://www.google.com/geo/schemas/sitemap/1.0') + self.assertEqual(urlset.getAttribute('xmlns'), 'http://www.sitemaps.org/schemas/sitemap/0.9') + self.assertEqual(urlset.getAttribute('xmlns:geo'), 'http://www.google.com/geo/schemas/sitemap/1.0') urls = urlset.getElementsByTagName('url') self.assertEqual(2, len(urls)) # Should only be 2 sitemaps. @@ -84,8 +84,8 @@ class GeoSitemapTest(TestCase): # Ensuring the right sitemaps namespaces are present. urlset = doc.firstChild - self.assertEqual(urlset.getAttribute(u'xmlns'), u'http://www.sitemaps.org/schemas/sitemap/0.9') - self.assertEqual(urlset.getAttribute(u'xmlns:geo'), u'http://www.google.com/geo/schemas/sitemap/1.0') + self.assertEqual(urlset.getAttribute('xmlns'), 'http://www.sitemaps.org/schemas/sitemap/0.9') + self.assertEqual(urlset.getAttribute('xmlns:geo'), 'http://www.google.com/geo/schemas/sitemap/1.0') # Making sure the correct number of feed URLs were included. urls = urlset.getElementsByTagName('url') diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index 4136a65d5c..b06d6b5e1b 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -11,22 +11,28 @@ from django.contrib.gis.tests.utils import ( no_mysql, no_oracle, no_spatialite, mysql, oracle, postgis, spatialite) from django.test import TestCase +from django.utils import six from .models import Country, City, PennsylvaniaCity, State, Track +from .test_feeds import GeoFeedTest +from .test_regress import GeoRegressionTests +from .test_sitemaps import GeoSitemapTest + + if not spatialite: from .models import Feature, MinusOneSRID class GeoModelTest(TestCase): - def test01_fixtures(self): + def test_fixtures(self): "Testing geographic model initialization from fixtures." # Ensuring that data was loaded from initial data fixtures. self.assertEqual(2, Country.objects.count()) self.assertEqual(8, City.objects.count()) self.assertEqual(2, State.objects.count()) - def test02_proxy(self): + def test_proxy(self): "Testing Lazy-Geometry support (using the GeometryProxy)." ## Testing on a Point pnt = Point(0, 0) @@ -94,165 +100,97 @@ class GeoModelTest(TestCase): self.assertEqual(ply, State.objects.get(name='NullState').poly) ns.delete() - def test03a_kml(self): - "Testing KML output from the database using GeoQuerySet.kml()." - # Only PostGIS and Spatialite (>=2.4.0-RC4) support KML serialization - if not (postgis or (spatialite and connection.ops.kml)): - self.assertRaises(NotImplementedError, State.objects.all().kml, field_name='poly') - return - - # Should throw a TypeError when trying to obtain KML from a - # non-geometry field. - qs = City.objects.all() - self.assertRaises(TypeError, qs.kml, 'name') - - # The reference KML depends on the version of PostGIS used - # (the output stopped including altitude in 1.3.3). - if connection.ops.spatial_version >= (1, 3, 3): - ref_kml = '-104.609252,38.255001' - else: - ref_kml = '-104.609252,38.255001,0' - - # Ensuring the KML is as expected. - ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo') - ptown2 = City.objects.kml(precision=9).get(name='Pueblo') - for ptown in [ptown1, ptown2]: - self.assertEqual(ref_kml, ptown.kml) - - def test03b_gml(self): - "Testing GML output from the database using GeoQuerySet.gml()." - if mysql or (spatialite and not connection.ops.gml) : - self.assertRaises(NotImplementedError, Country.objects.all().gml, field_name='mpoly') - return - - # Should throw a TypeError when tyring to obtain GML from a - # non-geometry field. - qs = City.objects.all() - self.assertRaises(TypeError, qs.gml, field_name='name') - ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo') - ptown2 = City.objects.gml(precision=9).get(name='Pueblo') + @no_mysql + def test_lookup_insert_transform(self): + "Testing automatic transform for lookups and inserts." + # San Antonio in 'WGS84' (SRID 4326) + sa_4326 = 'POINT (-98.493183 29.424170)' + wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84 + # Oracle doesn't have SRID 3084, using 41157. if oracle: - # No precision parameter for Oracle :-/ - gml_regex = re.compile(r'^-104.60925\d+,38.25500\d+ ') - elif spatialite: - # Spatialite has extra colon in SrsName - gml_regex = re.compile(r'^-104.609251\d+,38.255001') + # San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157) + # Used the following Oracle SQL to get this value: + # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM(SDO_GEOMETRY('POINT (-98.493183 29.424170)', 4326), 41157)) FROM DUAL; + nad_wkt = 'POINT (300662.034646583 5416427.45974934)' + nad_srid = 41157 else: - gml_regex = re.compile(r'^-104\.60925\d+,38\.255001') + # San Antonio in 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084) + nad_wkt = 'POINT (1645978.362408288754523 6276356.025927528738976)' # Used ogr.py in gdal 1.4.1 for this transform + nad_srid = 3084 - for ptown in [ptown1, ptown2]: - self.assertTrue(gml_regex.match(ptown.gml)) - - - def test03c_geojson(self): - "Testing GeoJSON output from the database using GeoQuerySet.geojson()." - # Only PostGIS 1.3.4+ supports GeoJSON. - if not connection.ops.geojson: - self.assertRaises(NotImplementedError, Country.objects.all().geojson, field_name='mpoly') - return - - if connection.ops.spatial_version >= (1, 4, 0): - pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}' - houston_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}' - victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.305196,48.462611]}' - chicago_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + # Constructing & querying with a point from a different SRID. Oracle + # `SDO_OVERLAPBDYINTERSECT` operates differently from + # `ST_Intersects`, so contains is used instead. + nad_pnt = fromstr(nad_wkt, srid=nad_srid) + if oracle: + tx = Country.objects.get(mpoly__contains=nad_pnt) else: - pueblo_json = '{"type":"Point","coordinates":[-104.60925200,38.25500100]}' - houston_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"coordinates":[-95.36315100,29.76337400]}' - victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.30519600,48.46261100]}' - chicago_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + tx = Country.objects.get(mpoly__intersects=nad_pnt) + self.assertEqual('Texas', tx.name) - # Precision argument should only be an integer - self.assertRaises(TypeError, City.objects.geojson, precision='foo') + # Creating San Antonio. Remember the Alamo. + sa = City.objects.create(name='San Antonio', point=nad_pnt) - # Reference queries and values. - # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo'; - self.assertEqual(pueblo_json, City.objects.geojson().get(name='Pueblo').geojson) + # Now verifying that San Antonio was transformed correctly + sa = City.objects.get(name='San Antonio') + self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6) + self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6) - # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; - # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; - # This time we want to include the CRS by using the `crs` keyword. - self.assertEqual(houston_json, City.objects.geojson(crs=True, model_att='json').get(name='Houston').json) + # If the GeometryField SRID is -1, then we shouldn't perform any + # transformation if the SRID of the input geometry is different. + # SpatiaLite does not support missing SRID values. + if not spatialite: + m1 = MinusOneSRID(geom=Point(17, 23, srid=4326)) + m1.save() + self.assertEqual(-1, m1.geom.srid) - # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Victoria'; - # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; - # This time we include the bounding box by using the `bbox` keyword. - self.assertEqual(victoria_json, City.objects.geojson(bbox=True).get(name='Victoria').geojson) + def test_createnull(self): + "Testing creating a model instance and the geometry being None" + c = City() + self.assertEqual(c.point, None) - # 1.(3|4).x: SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago'; - # Finally, we set every available keyword. - self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson) + @no_spatialite # SpatiaLite does not support abstract geometry columns + def test_geometryfield(self): + "Testing the general GeometryField." + Feature(name='Point', geom=Point(1, 1)).save() + Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).save() + Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))).save() + Feature(name='GeometryCollection', + geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)), + Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))).save() - def test03d_svg(self): - "Testing SVG output using GeoQuerySet.svg()." - if mysql or oracle: - self.assertRaises(NotImplementedError, City.objects.svg) - return + f_1 = Feature.objects.get(name='Point') + self.assertEqual(True, isinstance(f_1.geom, Point)) + self.assertEqual((1.0, 1.0), f_1.geom.tuple) + f_2 = Feature.objects.get(name='LineString') + self.assertEqual(True, isinstance(f_2.geom, LineString)) + self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple) - self.assertRaises(TypeError, City.objects.svg, precision='foo') - # SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo'; - svg1 = 'cx="-104.609252" cy="-38.255001"' - # Even though relative, only one point so it's practically the same except for - # the 'c' letter prefix on the x,y values. - svg2 = svg1.replace('c', '') - self.assertEqual(svg1, City.objects.svg().get(name='Pueblo').svg) - self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg) + f_3 = Feature.objects.get(name='Polygon') + self.assertEqual(True, isinstance(f_3.geom, Polygon)) + f_4 = Feature.objects.get(name='GeometryCollection') + self.assertEqual(True, isinstance(f_4.geom, GeometryCollection)) + self.assertEqual(f_3.geom, f_4.geom[2]) @no_mysql - def test04_transform(self): - "Testing the transform() GeoManager method." - # Pre-transformed points for Houston and Pueblo. - htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084) - ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774) - prec = 3 # Precision is low due to version variations in PROJ and GDAL. + def test_inherited_geofields(self): + "Test GeoQuerySet methods on inherited Geometry fields." + # Creating a Pennsylvanian city. + mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)') - # Asserting the result of the transform operation with the values in - # the pre-transformed points. Oracle does not have the 3084 SRID. - if not oracle: - h = City.objects.transform(htown.srid).get(name='Houston') - self.assertEqual(3084, h.point.srid) - self.assertAlmostEqual(htown.x, h.point.x, prec) - self.assertAlmostEqual(htown.y, h.point.y, prec) + # All transformation SQL will need to be performed on the + # _parent_ table. + qs = PennsylvaniaCity.objects.transform(32128) - p1 = City.objects.transform(ptown.srid, field_name='point').get(name='Pueblo') - p2 = City.objects.transform(srid=ptown.srid).get(name='Pueblo') - for p in [p1, p2]: - self.assertEqual(2774, p.point.srid) - self.assertAlmostEqual(ptown.x, p.point.x, prec) - self.assertAlmostEqual(ptown.y, p.point.y, prec) + self.assertEqual(1, qs.count()) + for pc in qs: self.assertEqual(32128, pc.point.srid) + + +class GeoLookupTest(TestCase): @no_mysql - @no_spatialite # SpatiaLite does not have an Extent function - def test05_extent(self): - "Testing the `extent` GeoQuerySet method." - # Reference query: - # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');` - # => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203) - expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) - - qs = City.objects.filter(name__in=('Houston', 'Dallas')) - extent = qs.extent() - - for val, exp in zip(extent, expected): - self.assertAlmostEqual(exp, val, 4) - - # Only PostGIS has support for the MakeLine aggregate. - @no_mysql - @no_oracle - @no_spatialite - def test06_make_line(self): - "Testing the `make_line` GeoQuerySet method." - # Ensuring that a `TypeError` is raised on models without PointFields. - self.assertRaises(TypeError, State.objects.make_line) - self.assertRaises(TypeError, Country.objects.make_line) - # Reference query: - # SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city; - ref_line = GEOSGeometry('LINESTRING(-95.363151 29.763374,-96.801611 32.782057,-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)', srid=4326) - self.assertEqual(ref_line, City.objects.make_line()) - - @no_mysql - def test09_disjoint(self): + def test_disjoint_lookup(self): "Testing the `disjoint` lookup type." ptown = City.objects.get(name='Pueblo') qs1 = City.objects.filter(point__disjoint=ptown.point) @@ -262,7 +200,7 @@ class GeoModelTest(TestCase): self.assertEqual(1, qs2.count()) self.assertEqual('Kansas', qs2[0].name) - def test10_contains_contained(self): + def test_contains_contained_lookups(self): "Testing the 'contained', 'contains', and 'bbcontains' lookup types." # Getting Texas, yes we were a country -- once ;) texas = Country.objects.get(name='Texas') @@ -307,86 +245,11 @@ class GeoModelTest(TestCase): self.assertEqual(1, len(qs)) self.assertEqual('Texas', qs[0].name) - @no_mysql - def test11_lookup_insert_transform(self): - "Testing automatic transform for lookups and inserts." - # San Antonio in 'WGS84' (SRID 4326) - sa_4326 = 'POINT (-98.493183 29.424170)' - wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84 - - # Oracle doesn't have SRID 3084, using 41157. - if oracle: - # San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157) - # Used the following Oracle SQL to get this value: - # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM(SDO_GEOMETRY('POINT (-98.493183 29.424170)', 4326), 41157)) FROM DUAL; - nad_wkt = 'POINT (300662.034646583 5416427.45974934)' - nad_srid = 41157 - else: - # San Antonio in 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084) - nad_wkt = 'POINT (1645978.362408288754523 6276356.025927528738976)' # Used ogr.py in gdal 1.4.1 for this transform - nad_srid = 3084 - - # Constructing & querying with a point from a different SRID. Oracle - # `SDO_OVERLAPBDYINTERSECT` operates differently from - # `ST_Intersects`, so contains is used instead. - nad_pnt = fromstr(nad_wkt, srid=nad_srid) - if oracle: - tx = Country.objects.get(mpoly__contains=nad_pnt) - else: - tx = Country.objects.get(mpoly__intersects=nad_pnt) - self.assertEqual('Texas', tx.name) - - # Creating San Antonio. Remember the Alamo. - sa = City.objects.create(name='San Antonio', point=nad_pnt) - - # Now verifying that San Antonio was transformed correctly - sa = City.objects.get(name='San Antonio') - self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6) - self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6) - - # If the GeometryField SRID is -1, then we shouldn't perform any - # transformation if the SRID of the input geometry is different. - # SpatiaLite does not support missing SRID values. - if not spatialite: - m1 = MinusOneSRID(geom=Point(17, 23, srid=4326)) - m1.save() - self.assertEqual(-1, m1.geom.srid) - - @no_mysql - def test12_null_geometries(self): - "Testing NULL geometry support, and the `isnull` lookup type." - # Creating a state with a NULL boundary. - State.objects.create(name='Puerto Rico') - - # Querying for both NULL and Non-NULL values. - nullqs = State.objects.filter(poly__isnull=True) - validqs = State.objects.filter(poly__isnull=False) - - # Puerto Rico should be NULL (it's a commonwealth unincorporated territory) - self.assertEqual(1, len(nullqs)) - self.assertEqual('Puerto Rico', nullqs[0].name) - - # The valid states should be Colorado & Kansas - self.assertEqual(2, len(validqs)) - state_names = [s.name for s in validqs] - self.assertEqual(True, 'Colorado' in state_names) - self.assertEqual(True, 'Kansas' in state_names) - - # Saving another commonwealth w/a NULL geometry. - nmi = State.objects.create(name='Northern Mariana Islands', poly=None) - self.assertEqual(nmi.poly, None) - - # Assigning a geomery and saving -- then UPDATE back to NULL. - nmi.poly = 'POLYGON((0 0,1 0,1 1,1 0,0 0))' - nmi.save() - State.objects.filter(name='Northern Mariana Islands').update(poly=None) - self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly) - # Only PostGIS has `left` and `right` lookup types. @no_mysql @no_oracle @no_spatialite - def test13_left_right(self): + def test_left_right_lookups(self): "Testing the 'left' and 'right' lookup types." # Left: A << B => true if xmax(A) < xmin(B) # Right: A >> B => true if xmin(A) > xmax(B) @@ -422,7 +285,7 @@ class GeoModelTest(TestCase): self.assertEqual(2, len(qs)) for c in qs: self.assertEqual(True, c.name in cities) - def test14_equals(self): + def test_equals_lookups(self): "Testing the 'same_as' and 'equals' lookup types." pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326) c1 = City.objects.get(point=pnt) @@ -431,7 +294,37 @@ class GeoModelTest(TestCase): for c in [c1, c2, c3]: self.assertEqual('Houston', c.name) @no_mysql - def test15_relate(self): + def test_null_geometries(self): + "Testing NULL geometry support, and the `isnull` lookup type." + # Creating a state with a NULL boundary. + State.objects.create(name='Puerto Rico') + + # Querying for both NULL and Non-NULL values. + nullqs = State.objects.filter(poly__isnull=True) + validqs = State.objects.filter(poly__isnull=False) + + # Puerto Rico should be NULL (it's a commonwealth unincorporated territory) + self.assertEqual(1, len(nullqs)) + self.assertEqual('Puerto Rico', nullqs[0].name) + + # The valid states should be Colorado & Kansas + self.assertEqual(2, len(validqs)) + state_names = [s.name for s in validqs] + self.assertEqual(True, 'Colorado' in state_names) + self.assertEqual(True, 'Kansas' in state_names) + + # Saving another commonwealth w/a NULL geometry. + nmi = State.objects.create(name='Northern Mariana Islands', poly=None) + self.assertEqual(nmi.poly, None) + + # Assigning a geomery and saving -- then UPDATE back to NULL. + nmi.poly = 'POLYGON((0 0,1 0,1 1,1 0,0 0))' + nmi.save() + State.objects.filter(name='Northern Mariana Islands').update(poly=None) + self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly) + + @no_mysql + def test_relate_lookup(self): "Testing the 'relate' lookup type." # To make things more interesting, we will have our Texas reference point in # different SRIDs. @@ -473,60 +366,12 @@ class GeoModelTest(TestCase): self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, intersects_mask)).name) self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name) - def test16_createnull(self): - "Testing creating a model instance and the geometry being None" - c = City() - self.assertEqual(c.point, None) + +class GeoQuerySetTest(TestCase): + # Please keep the tests in GeoQuerySet method's alphabetic order @no_mysql - def test17_unionagg(self): - "Testing the `unionagg` (aggregate union) GeoManager method." - tx = Country.objects.get(name='Texas').mpoly - # Houston, Dallas -- Oracle has different order. - union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') - union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') - qs = City.objects.filter(point__within=tx) - self.assertRaises(TypeError, qs.unionagg, 'name') - # Using `field_name` keyword argument in one query and specifying an - # order in the other (which should not be used because this is - # an aggregate method on a spatial column) - u1 = qs.unionagg(field_name='point') - u2 = qs.order_by('name').unionagg() - tol = 0.00001 - if oracle: - union = union2 - else: - union = union1 - self.assertEqual(True, union.equals_exact(u1, tol)) - self.assertEqual(True, union.equals_exact(u2, tol)) - qs = City.objects.filter(name='NotACity') - self.assertEqual(None, qs.unionagg(field_name='point')) - - @no_spatialite # SpatiaLite does not support abstract geometry columns - def test18_geometryfield(self): - "Testing the general GeometryField." - Feature(name='Point', geom=Point(1, 1)).save() - Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).save() - Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))).save() - Feature(name='GeometryCollection', - geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)), - Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))).save() - - f_1 = Feature.objects.get(name='Point') - self.assertEqual(True, isinstance(f_1.geom, Point)) - self.assertEqual((1.0, 1.0), f_1.geom.tuple) - f_2 = Feature.objects.get(name='LineString') - self.assertEqual(True, isinstance(f_2.geom, LineString)) - self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple) - - f_3 = Feature.objects.get(name='Polygon') - self.assertEqual(True, isinstance(f_3.geom, Polygon)) - f_4 = Feature.objects.get(name='GeometryCollection') - self.assertEqual(True, isinstance(f_4.geom, GeometryCollection)) - self.assertEqual(f_3.geom, f_4.geom[2]) - - @no_mysql - def test19_centroid(self): + def test_centroid(self): "Testing the `centroid` GeoQuerySet method." qs = State.objects.exclude(poly__isnull=True).centroid() if oracle: @@ -539,84 +384,7 @@ class GeoModelTest(TestCase): self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol)) @no_mysql - def test20_pointonsurface(self): - "Testing the `point_on_surface` GeoQuerySet method." - # Reference values. - if oracle: - # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY; - ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326), - 'Texas' : fromstr('POINT (-103.002434 36.500397)', srid=4326), - } - - elif postgis or spatialite: - # Using GEOSGeometry to compute the reference point on surface values - # -- since PostGIS also uses GEOS these should be the same. - ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface, - 'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface - } - - for c in Country.objects.point_on_surface(): - if spatialite: - # XXX This seems to be a WKT-translation-related precision issue? - tol = 0.00001 - else: - tol = 0.000000001 - self.assertEqual(True, ref[c.name].equals_exact(c.point_on_surface, tol)) - - @no_mysql - @no_oracle - def test21_scale(self): - "Testing the `scale` GeoQuerySet method." - xfac, yfac = 2, 3 - tol = 5 # XXX The low precision tolerance is for SpatiaLite - qs = Country.objects.scale(xfac, yfac, model_att='scaled') - for c in qs: - for p1, p2 in zip(c.mpoly, c.scaled): - for r1, r2 in zip(p1, p2): - for c1, c2 in zip(r1.coords, r2.coords): - self.assertAlmostEqual(c1[0] * xfac, c2[0], tol) - self.assertAlmostEqual(c1[1] * yfac, c2[1], tol) - - @no_mysql - @no_oracle - def test22_translate(self): - "Testing the `translate` GeoQuerySet method." - xfac, yfac = 5, -23 - qs = Country.objects.translate(xfac, yfac, model_att='translated') - for c in qs: - for p1, p2 in zip(c.mpoly, c.translated): - for r1, r2 in zip(p1, p2): - for c1, c2 in zip(r1.coords, r2.coords): - # XXX The low precision is for SpatiaLite - self.assertAlmostEqual(c1[0] + xfac, c2[0], 5) - self.assertAlmostEqual(c1[1] + yfac, c2[1], 5) - - @no_mysql - def test23_numgeom(self): - "Testing the `num_geom` GeoQuerySet method." - # Both 'countries' only have two geometries. - for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom) - for c in City.objects.filter(point__isnull=False).num_geom(): - # Oracle will return 1 for the number of geometries on non-collections, - # whereas PostGIS will return None. - if postgis: - self.assertEqual(None, c.num_geom) - else: - self.assertEqual(1, c.num_geom) - - @no_mysql - @no_spatialite # SpatiaLite can only count vertices in LineStrings - def test24_numpoints(self): - "Testing the `num_points` GeoQuerySet method." - for c in Country.objects.num_points(): - self.assertEqual(c.mpoly.num_points, c.num_points) - - if not oracle: - # Oracle cannot count vertices in Point geometries. - for c in City.objects.num_points(): self.assertEqual(1, c.num_points) - - @no_mysql - def test25_geoset(self): + def test_diff_intersection_union(self): "Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods." geom = Point(5, 23) tol = 1 @@ -643,27 +411,237 @@ class GeoModelTest(TestCase): self.assertEqual(c.mpoly.union(geom), c.union) @no_mysql - def test26_inherited_geofields(self): - "Test GeoQuerySet methods on inherited Geometry fields." - # Creating a Pennsylvanian city. - mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)') + @no_spatialite # SpatiaLite does not have an Extent function + def test_extent(self): + "Testing the `extent` GeoQuerySet method." + # Reference query: + # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');` + # => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203) + expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) - # All transformation SQL will need to be performed on the - # _parent_ table. - qs = PennsylvaniaCity.objects.transform(32128) + qs = City.objects.filter(name__in=('Houston', 'Dallas')) + extent = qs.extent() - self.assertEqual(1, qs.count()) - for pc in qs: self.assertEqual(32128, pc.point.srid) + for val, exp in zip(extent, expected): + self.assertAlmostEqual(exp, val, 4) @no_mysql @no_oracle @no_spatialite - def test27_snap_to_grid(self): + def test_force_rhr(self): + "Testing GeoQuerySet.force_rhr()." + rings = ( ( (0, 0), (5, 0), (0, 5), (0, 0) ), + ( (1, 1), (1, 3), (3, 1), (1, 1) ), + ) + rhr_rings = ( ( (0, 0), (0, 5), (5, 0), (0, 0) ), + ( (1, 1), (3, 1), (1, 3), (1, 1) ), + ) + State.objects.create(name='Foo', poly=Polygon(*rings)) + s = State.objects.force_rhr().get(name='Foo') + self.assertEqual(rhr_rings, s.force_rhr.coords) + + @no_mysql + @no_oracle + @no_spatialite + def test_geohash(self): + "Testing GeoQuerySet.geohash()." + if not connection.ops.geohash: return + # Reference query: + # SELECT ST_GeoHash(point) FROM geoapp_city WHERE name='Houston'; + # SELECT ST_GeoHash(point, 5) FROM geoapp_city WHERE name='Houston'; + ref_hash = '9vk1mfq8jx0c8e0386z6' + h1 = City.objects.geohash().get(name='Houston') + h2 = City.objects.geohash(precision=5).get(name='Houston') + self.assertEqual(ref_hash, h1.geohash) + self.assertEqual(ref_hash[:5], h2.geohash) + + def test_geojson(self): + "Testing GeoJSON output from the database using GeoQuerySet.geojson()." + # Only PostGIS 1.3.4+ supports GeoJSON. + if not connection.ops.geojson: + self.assertRaises(NotImplementedError, Country.objects.all().geojson, field_name='mpoly') + return + + if connection.ops.spatial_version >= (1, 4, 0): + pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}' + houston_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}' + victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.305196,48.462611]}' + chicago_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + else: + pueblo_json = '{"type":"Point","coordinates":[-104.60925200,38.25500100]}' + houston_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"coordinates":[-95.36315100,29.76337400]}' + victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.30519600,48.46261100]}' + chicago_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + + # Precision argument should only be an integer + self.assertRaises(TypeError, City.objects.geojson, precision='foo') + + # Reference queries and values. + # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo'; + self.assertEqual(pueblo_json, City.objects.geojson().get(name='Pueblo').geojson) + + # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; + # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; + # This time we want to include the CRS by using the `crs` keyword. + self.assertEqual(houston_json, City.objects.geojson(crs=True, model_att='json').get(name='Houston').json) + + # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Victoria'; + # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; + # This time we include the bounding box by using the `bbox` keyword. + self.assertEqual(victoria_json, City.objects.geojson(bbox=True).get(name='Victoria').geojson) + + # 1.(3|4).x: SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago'; + # Finally, we set every available keyword. + self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson) + + def test_gml(self): + "Testing GML output from the database using GeoQuerySet.gml()." + if mysql or (spatialite and not connection.ops.gml) : + self.assertRaises(NotImplementedError, Country.objects.all().gml, field_name='mpoly') + return + + # Should throw a TypeError when tyring to obtain GML from a + # non-geometry field. + qs = City.objects.all() + self.assertRaises(TypeError, qs.gml, field_name='name') + ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo') + ptown2 = City.objects.gml(precision=9).get(name='Pueblo') + + if oracle: + # No precision parameter for Oracle :-/ + gml_regex = re.compile(r'^-104.60925\d+,38.25500\d+ ') + elif spatialite: + # Spatialite has extra colon in SrsName + gml_regex = re.compile(r'^-104.609251\d+,38.255001') + else: + gml_regex = re.compile(r'^-104\.60925\d+,38\.255001') + + for ptown in [ptown1, ptown2]: + self.assertTrue(gml_regex.match(ptown.gml)) + + def test_kml(self): + "Testing KML output from the database using GeoQuerySet.kml()." + # Only PostGIS and Spatialite (>=2.4.0-RC4) support KML serialization + if not (postgis or (spatialite and connection.ops.kml)): + self.assertRaises(NotImplementedError, State.objects.all().kml, field_name='poly') + return + + # Should throw a TypeError when trying to obtain KML from a + # non-geometry field. + qs = City.objects.all() + self.assertRaises(TypeError, qs.kml, 'name') + + # The reference KML depends on the version of PostGIS used + # (the output stopped including altitude in 1.3.3). + if connection.ops.spatial_version >= (1, 3, 3): + ref_kml = '-104.609252,38.255001' + else: + ref_kml = '-104.609252,38.255001,0' + + # Ensuring the KML is as expected. + ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo') + ptown2 = City.objects.kml(precision=9).get(name='Pueblo') + for ptown in [ptown1, ptown2]: + self.assertEqual(ref_kml, ptown.kml) + + # Only PostGIS has support for the MakeLine aggregate. + @no_mysql + @no_oracle + @no_spatialite + def test_make_line(self): + "Testing the `make_line` GeoQuerySet method." + # Ensuring that a `TypeError` is raised on models without PointFields. + self.assertRaises(TypeError, State.objects.make_line) + self.assertRaises(TypeError, Country.objects.make_line) + # Reference query: + # SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city; + ref_line = GEOSGeometry('LINESTRING(-95.363151 29.763374,-96.801611 32.782057,-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)', srid=4326) + self.assertEqual(ref_line, City.objects.make_line()) + + @no_mysql + def test_num_geom(self): + "Testing the `num_geom` GeoQuerySet method." + # Both 'countries' only have two geometries. + for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom) + for c in City.objects.filter(point__isnull=False).num_geom(): + # Oracle will return 1 for the number of geometries on non-collections, + # whereas PostGIS will return None. + if postgis: + self.assertEqual(None, c.num_geom) + else: + self.assertEqual(1, c.num_geom) + + @no_mysql + @no_spatialite # SpatiaLite can only count vertices in LineStrings + def test_num_points(self): + "Testing the `num_points` GeoQuerySet method." + for c in Country.objects.num_points(): + self.assertEqual(c.mpoly.num_points, c.num_points) + + if not oracle: + # Oracle cannot count vertices in Point geometries. + for c in City.objects.num_points(): self.assertEqual(1, c.num_points) + + @no_mysql + def test_point_on_surface(self): + "Testing the `point_on_surface` GeoQuerySet method." + # Reference values. + if oracle: + # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY; + ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326), + 'Texas' : fromstr('POINT (-103.002434 36.500397)', srid=4326), + } + + elif postgis or spatialite: + # Using GEOSGeometry to compute the reference point on surface values + # -- since PostGIS also uses GEOS these should be the same. + ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface, + 'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface + } + + for c in Country.objects.point_on_surface(): + if spatialite: + # XXX This seems to be a WKT-translation-related precision issue? + tol = 0.00001 + else: + tol = 0.000000001 + self.assertEqual(True, ref[c.name].equals_exact(c.point_on_surface, tol)) + + @no_mysql + @no_spatialite + def test_reverse_geom(self): + "Testing GeoQuerySet.reverse_geom()." + coords = [ (-95.363151, 29.763374), (-95.448601, 29.713803) ] + Track.objects.create(name='Foo', line=LineString(coords)) + t = Track.objects.reverse_geom().get(name='Foo') + coords.reverse() + self.assertEqual(tuple(coords), t.reverse_geom.coords) + if oracle: + self.assertRaises(TypeError, State.objects.reverse_geom) + + @no_mysql + @no_oracle + def test_scale(self): + "Testing the `scale` GeoQuerySet method." + xfac, yfac = 2, 3 + tol = 5 # XXX The low precision tolerance is for SpatiaLite + qs = Country.objects.scale(xfac, yfac, model_att='scaled') + for c in qs: + for p1, p2 in zip(c.mpoly, c.scaled): + for r1, r2 in zip(p1, p2): + for c1, c2 in zip(r1.coords, r2.coords): + self.assertAlmostEqual(c1[0] * xfac, c2[0], tol) + self.assertAlmostEqual(c1[1] * yfac, c2[1], tol) + + @no_mysql + @no_oracle + @no_spatialite + def test_snap_to_grid(self): "Testing GeoQuerySet.snap_to_grid()." # Let's try and break snap_to_grid() with bad combinations of arguments. for bad_args in ((), range(3), range(5)): self.assertRaises(ValueError, Country.objects.snap_to_grid, *bad_args) - for bad_args in (('1.0',), (1.0, None), tuple(map(unicode, range(4)))): + for bad_args in (('1.0',), (1.0, None), tuple(map(six.text_type, range(4)))): self.assertRaises(TypeError, Country.objects.snap_to_grid, *bad_args) # Boundary for San Marino, courtesy of Bjorn Sandvik of thematicmapping.org @@ -694,48 +672,78 @@ class GeoModelTest(TestCase): ref = fromstr('MULTIPOLYGON(((12.4 43.87,12.45 43.87,12.45 44.1,12.5 44.1,12.5 43.87,12.45 43.87,12.4 43.87)))') self.assertTrue(ref.equals_exact(Country.objects.snap_to_grid(0.05, 0.23, 0.5, 0.17).get(name='San Marino').snap_to_grid, tol)) + def test_svg(self): + "Testing SVG output using GeoQuerySet.svg()." + if mysql or oracle: + self.assertRaises(NotImplementedError, City.objects.svg) + return + + self.assertRaises(TypeError, City.objects.svg, precision='foo') + # SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo'; + svg1 = 'cx="-104.609252" cy="-38.255001"' + # Even though relative, only one point so it's practically the same except for + # the 'c' letter prefix on the x,y values. + svg2 = svg1.replace('c', '') + self.assertEqual(svg1, City.objects.svg().get(name='Pueblo').svg) + self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg) + @no_mysql - @no_spatialite - def test28_reverse(self): - "Testing GeoQuerySet.reverse_geom()." - coords = [ (-95.363151, 29.763374), (-95.448601, 29.713803) ] - Track.objects.create(name='Foo', line=LineString(coords)) - t = Track.objects.reverse_geom().get(name='Foo') - coords.reverse() - self.assertEqual(tuple(coords), t.reverse_geom.coords) + def test_transform(self): + "Testing the transform() GeoQuerySet method." + # Pre-transformed points for Houston and Pueblo. + htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084) + ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774) + prec = 3 # Precision is low due to version variations in PROJ and GDAL. + + # Asserting the result of the transform operation with the values in + # the pre-transformed points. Oracle does not have the 3084 SRID. + if not oracle: + h = City.objects.transform(htown.srid).get(name='Houston') + self.assertEqual(3084, h.point.srid) + self.assertAlmostEqual(htown.x, h.point.x, prec) + self.assertAlmostEqual(htown.y, h.point.y, prec) + + p1 = City.objects.transform(ptown.srid, field_name='point').get(name='Pueblo') + p2 = City.objects.transform(srid=ptown.srid).get(name='Pueblo') + for p in [p1, p2]: + self.assertEqual(2774, p.point.srid) + self.assertAlmostEqual(ptown.x, p.point.x, prec) + self.assertAlmostEqual(ptown.y, p.point.y, prec) + + @no_mysql + @no_oracle + def test_translate(self): + "Testing the `translate` GeoQuerySet method." + xfac, yfac = 5, -23 + qs = Country.objects.translate(xfac, yfac, model_att='translated') + for c in qs: + for p1, p2 in zip(c.mpoly, c.translated): + for r1, r2 in zip(p1, p2): + for c1, c2 in zip(r1.coords, r2.coords): + # XXX The low precision is for SpatiaLite + self.assertAlmostEqual(c1[0] + xfac, c2[0], 5) + self.assertAlmostEqual(c1[1] + yfac, c2[1], 5) + + @no_mysql + def test_unionagg(self): + "Testing the `unionagg` (aggregate union) GeoQuerySet method." + tx = Country.objects.get(name='Texas').mpoly + # Houston, Dallas -- Oracle has different order. + union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') + union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') + qs = City.objects.filter(point__within=tx) + self.assertRaises(TypeError, qs.unionagg, 'name') + # Using `field_name` keyword argument in one query and specifying an + # order in the other (which should not be used because this is + # an aggregate method on a spatial column) + u1 = qs.unionagg(field_name='point') + u2 = qs.order_by('name').unionagg() + tol = 0.00001 if oracle: - self.assertRaises(TypeError, State.objects.reverse_geom) - - @no_mysql - @no_oracle - @no_spatialite - def test29_force_rhr(self): - "Testing GeoQuerySet.force_rhr()." - rings = ( ( (0, 0), (5, 0), (0, 5), (0, 0) ), - ( (1, 1), (1, 3), (3, 1), (1, 1) ), - ) - rhr_rings = ( ( (0, 0), (0, 5), (5, 0), (0, 0) ), - ( (1, 1), (3, 1), (1, 3), (1, 1) ), - ) - State.objects.create(name='Foo', poly=Polygon(*rings)) - s = State.objects.force_rhr().get(name='Foo') - self.assertEqual(rhr_rings, s.force_rhr.coords) - - @no_mysql - @no_oracle - @no_spatialite - def test30_geohash(self): - "Testing GeoQuerySet.geohash()." - if not connection.ops.geohash: return - # Reference query: - # SELECT ST_GeoHash(point) FROM geoapp_city WHERE name='Houston'; - # SELECT ST_GeoHash(point, 5) FROM geoapp_city WHERE name='Houston'; - ref_hash = '9vk1mfq8jx0c8e0386z6' - h1 = City.objects.geohash().get(name='Houston') - h2 = City.objects.geohash(precision=5).get(name='Houston') - self.assertEqual(ref_hash, h1.geohash) - self.assertEqual(ref_hash[:5], h2.geohash) - -from .test_feeds import GeoFeedTest -from .test_regress import GeoRegressionTests -from .test_sitemaps import GeoSitemapTest + union = union2 + else: + union = union1 + self.assertEqual(True, union.equals_exact(u1, tol)) + self.assertEqual(True, union.equals_exact(u2, tol)) + qs = City.objects.filter(name='NotACity') + self.assertEqual(None, qs.unionagg(field_name='point')) diff --git a/django/contrib/gis/tests/geogapp/models.py b/django/contrib/gis/tests/geogapp/models.py index 3696ba2ff4..7e802f9321 100644 --- a/django/contrib/gis/tests/geogapp/models.py +++ b/django/contrib/gis/tests/geogapp/models.py @@ -1,20 +1,24 @@ from django.contrib.gis.db import models +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class City(models.Model): name = models.CharField(max_length=30) point = models.PointField(geography=True) objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name +@python_2_unicode_compatible class Zipcode(models.Model): code = models.CharField(max_length=10) poly = models.PolygonField(geography=True) objects = models.GeoManager() - def __unicode__(self): return self.code + def __str__(self): return self.code +@python_2_unicode_compatible class County(models.Model): name = models.CharField(max_length=25) state = models.CharField(max_length=20) mpoly = models.MultiPolygonField(geography=True) objects = models.GeoManager() - def __unicode__(self): return ' County, '.join([self.name, self.state]) + def __str__(self): return ' County, '.join([self.name, self.state]) diff --git a/django/contrib/gis/tests/relatedapp/models.py b/django/contrib/gis/tests/relatedapp/models.py index aec4e15749..659fef7a93 100644 --- a/django/contrib/gis/tests/relatedapp/models.py +++ b/django/contrib/gis/tests/relatedapp/models.py @@ -1,37 +1,41 @@ from django.contrib.gis.db import models from django.contrib.localflavor.us.models import USStateField +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class Location(models.Model): point = models.PointField() objects = models.GeoManager() - def __unicode__(self): return self.point.wkt + def __str__(self): return self.point.wkt +@python_2_unicode_compatible class City(models.Model): name = models.CharField(max_length=50) state = USStateField() location = models.ForeignKey(Location) objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name class AugmentedLocation(Location): extra_text = models.TextField(blank=True) objects = models.GeoManager() - + class DirectoryEntry(models.Model): listing_text = models.CharField(max_length=50) location = models.ForeignKey(AugmentedLocation) objects = models.GeoManager() +@python_2_unicode_compatible class Parcel(models.Model): name = models.CharField(max_length=30) city = models.ForeignKey(City) center1 = models.PointField() # Throwing a curveball w/`db_column` here. - center2 = models.PointField(srid=2276, db_column='mycenter') + center2 = models.PointField(srid=2276, db_column='mycenter') border1 = models.PolygonField() border2 = models.PolygonField(srid=2276) objects = models.GeoManager() - def __unicode__(self): return self.name + def __str__(self): return self.name # These use the GeoManager but do not have any geographic fields. class Author(models.Model): diff --git a/django/contrib/gis/tests/test_geoforms.py b/django/contrib/gis/tests/test_geoforms.py index 59fab0166e..ed851df0d2 100644 --- a/django/contrib/gis/tests/test_geoforms.py +++ b/django/contrib/gis/tests/test_geoforms.py @@ -1,9 +1,14 @@ from django.forms import ValidationError -from django.contrib.gis import forms -from django.contrib.gis.geos import GEOSGeometry +from django.contrib.gis.gdal import HAS_GDAL +from django.contrib.gis.tests.utils import HAS_SPATIALREFSYS from django.utils import unittest +if HAS_SPATIALREFSYS: + from django.contrib.gis import forms + from django.contrib.gis.geos import GEOSGeometry + +@unittest.skipUnless(HAS_GDAL and HAS_SPATIALREFSYS, "GeometryFieldTest needs gdal support and a spatial database") class GeometryFieldTest(unittest.TestCase): def test00_init(self): @@ -51,8 +56,25 @@ class GeometryFieldTest(unittest.TestCase): pnt_fld = forms.GeometryField(geom_type='POINT') self.assertEqual(GEOSGeometry('POINT(5 23)'), pnt_fld.clean('POINT(5 23)')) + # a WKT for any other geom_type will be properly transformed by `to_python` + self.assertEqual(GEOSGeometry('LINESTRING(0 0, 1 1)'), pnt_fld.to_python('LINESTRING(0 0, 1 1)')) + # but rejected by `clean` self.assertRaises(forms.ValidationError, pnt_fld.clean, 'LINESTRING(0 0, 1 1)') + def test04_to_python(self): + """ + Testing to_python returns a correct GEOSGeometry object or + a ValidationError + """ + fld = forms.GeometryField() + # to_python returns the same GEOSGeometry for a WKT + for wkt in ('POINT(5 23)', 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'LINESTRING(0 0, 1 1)'): + self.assertEqual(GEOSGeometry(wkt), fld.to_python(wkt)) + # but raises a ValidationError for any other string + for wkt in ('POINT(5)', 'MULTI POLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'BLAH(0 0, 1 1)'): + self.assertRaises(forms.ValidationError, fld.to_python, wkt) + + def suite(): s = unittest.TestSuite() s.addTest(unittest.makeSuite(GeometryFieldTest)) diff --git a/django/contrib/gis/tests/test_measure.py b/django/contrib/gis/tests/test_measure.py index f3325628b6..0428704d6c 100644 --- a/django/contrib/gis/tests/test_measure.py +++ b/django/contrib/gis/tests/test_measure.py @@ -93,6 +93,8 @@ class DistanceTest(unittest.TestCase): self.assertEqual(d4.m, 50) d4 /= 5 self.assertEqual(d4.m, 10) + d5 = d1 / D(m=2) + self.assertEqual(d5, 50) a5 = d1 * D(m=10) self.assertTrue(isinstance(a5, Area)) @@ -102,10 +104,6 @@ class DistanceTest(unittest.TestCase): d1 *= D(m=1) self.fail('Distance *= Distance should raise TypeError') - with self.assertRaises(TypeError): - d5 = d1 / D(m=1) - self.fail('Distance / Distance should raise TypeError') - with self.assertRaises(TypeError): d1 /= D(m=1) self.fail('Distance /= Distance should raise TypeError') diff --git a/django/contrib/gis/tests/test_spatialrefsys.py b/django/contrib/gis/tests/test_spatialrefsys.py index 608f6453c2..5cdc68a74d 100644 --- a/django/contrib/gis/tests/test_spatialrefsys.py +++ b/django/contrib/gis/tests/test_spatialrefsys.py @@ -1,6 +1,7 @@ from django.db import connection -from django.contrib.gis.gdal import GDAL_VERSION -from django.contrib.gis.tests.utils import no_mysql, oracle, postgis, spatialite +from django.contrib.gis.gdal import HAS_GDAL +from django.contrib.gis.tests.utils import (no_mysql, oracle, postgis, + spatialite, HAS_SPATIALREFSYS, SpatialRefSys) from django.utils import unittest @@ -28,13 +29,8 @@ test_srs = ({'srid' : 4326, }, ) -if oracle: - from django.contrib.gis.db.backends.oracle.models import SpatialRefSys -elif postgis: - from django.contrib.gis.db.backends.postgis.models import SpatialRefSys -elif spatialite: - from django.contrib.gis.db.backends.spatialite.models import SpatialRefSys - +@unittest.skipUnless(HAS_GDAL and HAS_SPATIALREFSYS, + "SpatialRefSysTest needs gdal support and a spatial database") class SpatialRefSysTest(unittest.TestCase): @no_mysql @@ -65,6 +61,7 @@ class SpatialRefSysTest(unittest.TestCase): @no_mysql def test02_osr(self): "Testing getting OSR objects from SpatialRefSys model objects." + from django.contrib.gis.gdal import GDAL_VERSION for sd in test_srs: sr = SpatialRefSys.objects.get(srid=sd['srid']) self.assertEqual(True, sr.spheroid.startswith(sd['spheroid'])) diff --git a/django/contrib/gis/tests/utils.py b/django/contrib/gis/tests/utils.py index b758fd0fee..a83ba8a93f 100644 --- a/django/contrib/gis/tests/utils.py +++ b/django/contrib/gis/tests/utils.py @@ -24,3 +24,14 @@ oracle = _default_db == 'oracle' postgis = _default_db == 'postgis' mysql = _default_db == 'mysql' spatialite = _default_db == 'spatialite' + +HAS_SPATIALREFSYS = True +if oracle: + from django.contrib.gis.db.backends.oracle.models import SpatialRefSys +elif postgis: + from django.contrib.gis.db.backends.postgis.models import SpatialRefSys +elif spatialite: + from django.contrib.gis.db.backends.spatialite.models import SpatialRefSys +else: + HAS_SPATIALREFSYS = False + SpatialRefSys = None diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py index 48d6c1b70e..e898f6de2e 100644 --- a/django/contrib/gis/utils/layermapping.py +++ b/django/contrib/gis/utils/layermapping.py @@ -17,6 +17,7 @@ from django.contrib.gis.gdal.field import ( OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime) from django.db import models, transaction from django.contrib.localflavor.us.models import USStateField +from django.utils import six # LayerMapping exceptions. class LayerMapError(Exception): pass @@ -74,7 +75,7 @@ class LayerMapping(object): argument usage. """ # Getting the DataSource and the associated Layer. - if isinstance(data, basestring): + if isinstance(data, six.string_types): self.ds = DataSource(data) else: self.ds = data @@ -249,7 +250,7 @@ class LayerMapping(object): sr = source_srs elif isinstance(source_srs, self.spatial_backend.spatial_ref_sys()): sr = source_srs.srs - elif isinstance(source_srs, (int, basestring)): + elif isinstance(source_srs, (int, six.string_types)): sr = SpatialReference(source_srs) else: # Otherwise just pulling the SpatialReference from the layer @@ -266,7 +267,7 @@ class LayerMapping(object): # List of fields to determine uniqueness with for attr in unique: if not attr in self.mapping: raise ValueError - elif isinstance(unique, basestring): + elif isinstance(unique, six.string_types): # Only a single field passed in. if unique not in self.mapping: raise ValueError else: @@ -312,7 +313,7 @@ class LayerMapping(object): will construct and return the uniqueness keyword arguments -- a subset of the feature kwargs. """ - if isinstance(self.unique, basestring): + if isinstance(self.unique, six.string_types): return {self.unique : kwargs[self.unique]} else: return dict((fld, kwargs[fld]) for fld in self.unique) @@ -329,7 +330,7 @@ class LayerMapping(object): if self.encoding: # The encoding for OGR data sources may be specified here # (e.g., 'cp437' for Census Bureau boundary files). - val = unicode(ogr_field.value, self.encoding) + val = six.text_type(ogr_field.value, self.encoding) else: val = ogr_field.value if model_field.max_length and len(val) > model_field.max_length: diff --git a/django/contrib/gis/utils/ogrinspect.py b/django/contrib/gis/utils/ogrinspect.py index a9a0362eef..4266ee4b4c 100644 --- a/django/contrib/gis/utils/ogrinspect.py +++ b/django/contrib/gis/utils/ogrinspect.py @@ -5,10 +5,11 @@ models for GeoDjango and/or mapping dictionaries for use with the Author: Travis Pinney, Dane Springmeyer, & Justin Bronn """ -from future_builtins import zip +from django.utils.six.moves import zip # Requires GDAL to use. from django.contrib.gis.gdal import DataSource from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime +from django.utils import six def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False): """ @@ -24,7 +25,7 @@ def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False): `multi_geom` => Boolean (default: False) - specify as multigeometry. """ - if isinstance(data_source, basestring): + if isinstance(data_source, six.string_types): # Instantiating the DataSource from the string. data_source = DataSource(data_source) elif isinstance(data_source, DataSource): @@ -222,4 +223,4 @@ def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=Non if name_field: yield '' - yield ' def __unicode__(self): return self.%s' % name_field + yield ' def __str__(self): return self.%s' % name_field diff --git a/django/contrib/gis/utils/wkt.py b/django/contrib/gis/utils/wkt.py index 4aecc6247d..d60eed31bd 100644 --- a/django/contrib/gis/utils/wkt.py +++ b/django/contrib/gis/utils/wkt.py @@ -2,9 +2,11 @@ Utilities for manipulating Geometry WKT. """ +from django.utils import six + def precision_wkt(geom, prec): """ - Returns WKT text of the geometry according to the given precision (an + Returns WKT text of the geometry according to the given precision (an integer or a string). If the precision is an integer, then the decimal places of coordinates WKT will be truncated to that number: @@ -14,12 +16,12 @@ def precision_wkt(geom, prec): >>> precision(geom, 1) 'POINT (5.0 23.0)' - If the precision is a string, it must be valid Python format string + If the precision is a string, it must be valid Python format string (e.g., '%20.7f') -- thus, you should know what you're doing. """ if isinstance(prec, int): num_fmt = '%%.%df' % prec - elif isinstance(prec, basestring): + elif isinstance(prec, six.string_types): num_fmt = prec else: raise TypeError diff --git a/django/contrib/gis/views.py b/django/contrib/gis/views.py index fc51a0bf3a..3fa8f044de 100644 --- a/django/contrib/gis/views.py +++ b/django/contrib/gis/views.py @@ -1,10 +1,12 @@ +from __future__ import unicode_literals + from django.http import Http404 from django.utils.translation import ugettext as _ def feed(request, url, feed_dict=None): """Provided for backwards compatibility.""" if not feed_dict: - raise Http404(_(u"No feeds are registered.")) + raise Http404(_("No feeds are registered.")) try: slug, param = url.split('/', 1) @@ -14,7 +16,7 @@ def feed(request, url, feed_dict=None): try: f = feed_dict[slug] except KeyError: - raise Http404(_(u"Slug %r isn't registered.") % slug) + raise Http404(_("Slug %r isn't registered.") % slug) instance = f() instance.feed_url = getattr(f, 'feed_url', None) or request.path diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index b075ff05c7..7e8f163174 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -1,10 +1,11 @@ +from __future__ import unicode_literals import re -from datetime import date, datetime, timedelta +from datetime import date, datetime from django import template from django.conf import settings from django.template import defaultfilters -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.formats import number_format from django.utils.translation import pgettext, ungettext, ugettext as _ from django.utils.timezone import is_aware, utc @@ -23,8 +24,8 @@ def ordinal(value): return value suffixes = (_('th'), _('st'), _('nd'), _('rd'), _('th'), _('th'), _('th'), _('th'), _('th'), _('th')) if value % 100 in (11, 12, 13): # special case - return u"%d%s" % (value, suffixes[0]) - return u"%d%s" % (value, suffixes[value % 10]) + return "%d%s" % (value, suffixes[0]) + return "%d%s" % (value, suffixes[value % 10]) @register.filter(is_safe=True) def intcomma(value, use_l10n=True): @@ -40,7 +41,7 @@ def intcomma(value, use_l10n=True): return intcomma(value, False) else: return number_format(value, force_grouping=True) - orig = force_unicode(value) + orig = force_text(value) new = re.sub("^(-?\d+)(\d{3})", '\g<1>,\g<2>', orig) if orig == new: return new @@ -142,7 +143,9 @@ def apnumber(value): return value return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1] -@register.filter +# Perform the comparison in the default time zone when USE_TZ = True +# (unless a specific time zone has been applied with the |timezone filter). +@register.filter(expects_localtime=True) def naturalday(value, arg=None): """ For date values that are tomorrow, today or yesterday compared to @@ -161,13 +164,15 @@ def naturalday(value, arg=None): today = datetime.now(tzinfo).date() delta = value - today if delta.days == 0: - return _(u'today') + return _('today') elif delta.days == 1: - return _(u'tomorrow') + return _('tomorrow') elif delta.days == -1: - return _(u'yesterday') + return _('yesterday') return defaultfilters.date(value, arg) +# This filter doesn't require expects_localtime=True because it deals properly +# with both naive and aware datetimes. Therefore avoid the cost of conversion. @register.filter def naturaltime(value): """ @@ -183,42 +188,42 @@ def naturaltime(value): if delta.days != 0: return pgettext( 'naturaltime', '%(delta)s ago' - ) % {'delta': defaultfilters.timesince(value)} + ) % {'delta': defaultfilters.timesince(value, now)} elif delta.seconds == 0: - return _(u'now') + return _('now') elif delta.seconds < 60: return ungettext( - u'a second ago', u'%(count)s seconds ago', delta.seconds + 'a second ago', '%(count)s seconds ago', delta.seconds ) % {'count': delta.seconds} elif delta.seconds // 60 < 60: count = delta.seconds // 60 return ungettext( - u'a minute ago', u'%(count)s minutes ago', count + 'a minute ago', '%(count)s minutes ago', count ) % {'count': count} else: count = delta.seconds // 60 // 60 return ungettext( - u'an hour ago', u'%(count)s hours ago', count + 'an hour ago', '%(count)s hours ago', count ) % {'count': count} else: delta = value - now if delta.days != 0: return pgettext( 'naturaltime', '%(delta)s from now' - ) % {'delta': defaultfilters.timeuntil(value)} + ) % {'delta': defaultfilters.timeuntil(value, now)} elif delta.seconds == 0: - return _(u'now') + return _('now') elif delta.seconds < 60: return ungettext( - u'a second from now', u'%(count)s seconds from now', delta.seconds + 'a second from now', '%(count)s seconds from now', delta.seconds ) % {'count': delta.seconds} elif delta.seconds // 60 < 60: count = delta.seconds // 60 return ungettext( - u'a minute from now', u'%(count)s minutes from now', count + 'a minute from now', '%(count)s minutes from now', count ) % {'count': count} else: count = delta.seconds // 60 // 60 return ungettext( - u'an hour from now', u'%(count)s hours from now', count + 'an hour from now', '%(count)s hours from now', count ) % {'count': count} diff --git a/django/contrib/humanize/tests.py b/django/contrib/humanize/tests.py index bf6684766b..a0f13d3ee9 100644 --- a/django/contrib/humanize/tests.py +++ b/django/contrib/humanize/tests.py @@ -1,12 +1,31 @@ +from __future__ import unicode_literals import datetime -import new +from django.contrib.humanize.templatetags import humanize from django.template import Template, Context, defaultfilters from django.test import TestCase -from django.utils import translation, tzinfo -from django.utils.translation import ugettext as _ +from django.test.utils import override_settings from django.utils.html import escape from django.utils.timezone import utc +from django.utils import translation +from django.utils.translation import ugettext as _ +from django.utils import tzinfo + + +# Mock out datetime in some tests so they don't fail occasionally when they +# run too slow. Use a fixed datetime for datetime.now(). DST change in +# America/Chicago (the default time zone) happened on March 11th in 2012. + +now = datetime.datetime(2012, 3, 9, 22, 30) + +class MockDateTime(datetime.datetime): + @classmethod + def now(self, tz=None): + if tz is None or tz.utcoffset(now) is None: + return now + else: + # equals now.replace(tzinfo=utc) + return now.replace(tzinfo=tz) + tz.utcoffset(now) class HumanizeTests(TestCase): @@ -89,8 +108,8 @@ class HumanizeTests(TestCase): def test_apnumber(self): test_list = [str(x) for x in range(1, 11)] test_list.append(None) - result_list = (u'one', u'two', u'three', u'four', u'five', u'six', - u'seven', u'eight', u'nine', u'10', None) + result_list = ('one', 'two', 'three', 'four', 'five', 'six', + 'seven', 'eight', 'nine', '10', None) self.humanize_tester(test_list, result_list, 'apnumber') @@ -99,37 +118,45 @@ class HumanizeTests(TestCase): yesterday = today - datetime.timedelta(days=1) tomorrow = today + datetime.timedelta(days=1) someday = today - datetime.timedelta(days=10) - notdate = u"I'm not a date value" + notdate = "I'm not a date value" test_list = (today, yesterday, tomorrow, someday, notdate, None) someday_result = defaultfilters.date(someday) - result_list = (_(u'today'), _(u'yesterday'), _(u'tomorrow'), - someday_result, u"I'm not a date value", None) + result_list = (_('today'), _('yesterday'), _('tomorrow'), + someday_result, "I'm not a date value", None) self.humanize_tester(test_list, result_list, 'naturalday') def test_naturalday_tz(self): - from django.contrib.humanize.templatetags.humanize import naturalday - today = datetime.date.today() tz_one = tzinfo.FixedOffset(datetime.timedelta(hours=-12)) tz_two = tzinfo.FixedOffset(datetime.timedelta(hours=12)) # Can be today or yesterday date_one = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_one) - naturalday_one = naturalday(date_one) + naturalday_one = humanize.naturalday(date_one) # Can be today or tomorrow date_two = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_two) - naturalday_two = naturalday(date_two) + naturalday_two = humanize.naturalday(date_two) # As 24h of difference they will never be the same self.assertNotEqual(naturalday_one, naturalday_two) + def test_naturalday_uses_localtime(self): + # Regression for #18504 + # This is 2012-03-08HT19:30:00-06:00 in Ameria/Chicago + dt = datetime.datetime(2012, 3, 9, 1, 30, tzinfo=utc) + + orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime + try: + with override_settings(USE_TZ=True): + self.humanize_tester([dt], ['yesterday'], 'naturalday') + finally: + humanize.datetime = orig_humanize_datetime + def test_naturaltime(self): class naive(datetime.tzinfo): def utcoffset(self, dt): return None - # we're going to mock datetime.datetime, so use a fixed datetime - now = datetime.datetime(2011, 8, 15) test_list = [ now, now - datetime.timedelta(seconds=1), @@ -147,6 +174,7 @@ class HumanizeTests(TestCase): now + datetime.timedelta(hours=1, minutes=30, seconds=30), now + datetime.timedelta(hours=23, minutes=50, seconds=50), now + datetime.timedelta(days=1), + now + datetime.timedelta(days=2, hours=6), now + datetime.timedelta(days=500), now.replace(tzinfo=naive()), now.replace(tzinfo=utc), @@ -168,33 +196,22 @@ class HumanizeTests(TestCase): 'an hour from now', '23 hours from now', '1 day from now', + '2 days, 6 hours from now', '1 year, 4 months from now', 'now', 'now', ] + # Because of the DST change, 2 days and 6 hours after the chosen + # date in naive arithmetic is only 2 days and 5 hours after in + # aware arithmetic. + result_list_with_tz_support = result_list[:] + assert result_list_with_tz_support[-4] == '2 days, 6 hours from now' + result_list_with_tz_support[-4] == '2 days, 5 hours from now' - # mock out datetime so these tests don't fail occasionally when the - # test runs too slow - class MockDateTime(datetime.datetime): - @classmethod - def now(self, tz=None): - if tz is None or tz.utcoffset(now) is None: - return now - else: - # equals now.replace(tzinfo=utc) - return now.replace(tzinfo=tz) + tz.utcoffset(now) - - # naturaltime also calls timesince/timeuntil - from django.contrib.humanize.templatetags import humanize - from django.utils import timesince - orig_humanize_datetime = humanize.datetime - orig_timesince_datetime = timesince.datetime - humanize.datetime = MockDateTime - timesince.datetime = new.module(b"mock_datetime") - timesince.datetime.datetime = MockDateTime - + orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime try: self.humanize_tester(test_list, result_list, 'naturaltime') + with override_settings(USE_TZ=True): + self.humanize_tester(test_list, result_list_with_tz_support, 'naturaltime') finally: humanize.datetime = orig_humanize_datetime - timesince.datetime = orig_timesince_datetime diff --git a/django/contrib/localflavor/ar/ar_provinces.py b/django/contrib/localflavor/ar/ar_provinces.py index a0efd4ba33..600ef1eb16 100644 --- a/django/contrib/localflavor/ar/ar_provinces.py +++ b/django/contrib/localflavor/ar/ar_provinces.py @@ -7,30 +7,31 @@ http://www.argentina.gov.ar/argentina/portal/paginas.dhtml?pagina=425 This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals PROVINCE_CHOICES = ( - ('B', u'Buenos Aires'), - ('K', u'Catamarca'), - ('H', u'Chaco'), - ('U', u'Chubut'), - ('C', u'Ciudad Autónoma de Buenos Aires'), - ('X', u'Córdoba'), - ('W', u'Corrientes'), - ('E', u'Entre Ríos'), - ('P', u'Formosa'), - ('Y', u'Jujuy'), - ('L', u'La Pampa'), - ('F', u'La Rioja'), - ('M', u'Mendoza'), - ('N', u'Misiones'), - ('Q', u'Neuquén'), - ('R', u'Río Negro'), - ('A', u'Salta'), - ('J', u'San Juan'), - ('D', u'San Luis'), - ('Z', u'Santa Cruz'), - ('S', u'Santa Fe'), - ('G', u'Santiago del Estero'), - ('V', u'Tierra del Fuego, Antártida e Islas del Atlántico Sur'), - ('T', u'Tucumán'), + ('B', 'Buenos Aires'), + ('K', 'Catamarca'), + ('H', 'Chaco'), + ('U', 'Chubut'), + ('C', 'Ciudad Autónoma de Buenos Aires'), + ('X', 'Córdoba'), + ('W', 'Corrientes'), + ('E', 'Entre Ríos'), + ('P', 'Formosa'), + ('Y', 'Jujuy'), + ('L', 'La Pampa'), + ('F', 'La Rioja'), + ('M', 'Mendoza'), + ('N', 'Misiones'), + ('Q', 'Neuquén'), + ('R', 'Río Negro'), + ('A', 'Salta'), + ('J', 'San Juan'), + ('D', 'San Luis'), + ('Z', 'Santa Cruz'), + ('S', 'Santa Fe'), + ('G', 'Santiago del Estero'), + ('V', 'Tierra del Fuego, Antártida e Islas del Atlántico Sur'), + ('T', 'Tucumán'), ) diff --git a/django/contrib/localflavor/ar/forms.py b/django/contrib/localflavor/ar/forms.py index 1805839ce4..dc4235f9dd 100644 --- a/django/contrib/localflavor/ar/forms.py +++ b/django/contrib/localflavor/ar/forms.py @@ -3,7 +3,7 @@ AR-specific Form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.localflavor.ar.ar_provinces import PROVINCE_CHOICES from django.core.validators import EMPTY_VALUES @@ -37,11 +37,11 @@ class ARPostalCodeField(RegexField): def clean(self, value): value = super(ARPostalCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if len(value) not in (4, 8): raise ValidationError(self.error_messages['invalid']) if len(value) == 8: - return u'%s%s%s' % (value[0].upper(), value[1:5], value[5:].upper()) + return '%s%s%s' % (value[0].upper(), value[1:5], value[5:].upper()) return value class ARDNIField(CharField): @@ -63,7 +63,7 @@ class ARDNIField(CharField): """ value = super(ARDNIField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not value.isdigit(): value = value.replace('.', '') if not value.isdigit(): @@ -81,6 +81,7 @@ class ARCUITField(RegexField): default_error_messages = { 'invalid': _('Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'), 'checksum': _("Invalid CUIT."), + 'legal_type': _('Invalid legal type. Type must be 27, 20, 23 or 30.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -94,8 +95,10 @@ class ARCUITField(RegexField): """ value = super(ARCUITField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value, cd = self._canon(value) + if not value[:2] in ['27', '20', '23', '30']: + raise ValidationError(self.error_messages['legal_type']) if self._calc_cd(value) != cd: raise ValidationError(self.error_messages['checksum']) return self._format(value, cd) @@ -105,13 +108,20 @@ class ARCUITField(RegexField): return cuit[:-1], cuit[-1] def _calc_cd(self, cuit): + # Calculation code based on: + # http://es.wikipedia.org/wiki/C%C3%B3digo_%C3%9Anico_de_Identificaci%C3%B3n_Tributaria mults = (5, 4, 3, 2, 7, 6, 5, 4, 3, 2) tmp = sum([m * int(cuit[idx]) for idx, m in enumerate(mults)]) - return str(11 - tmp % 11) + result = 11 - (tmp % 11) + if result == 11: + result = 0 + elif result == 10: + result = 9 + return str(result) def _format(self, cuit, check_digit=None): if check_digit == None: check_digit = cuit[-1] cuit = cuit[:-1] - return u'%s-%s-%s' % (cuit[:2], cuit[2:], check_digit) + return '%s-%s-%s' % (cuit[:2], cuit[2:], check_digit) diff --git a/django/contrib/localflavor/at/forms.py b/django/contrib/localflavor/at/forms.py index 262641bf1f..c531bec2e9 100644 --- a/django/contrib/localflavor/at/forms.py +++ b/django/contrib/localflavor/at/forms.py @@ -1,7 +1,7 @@ """ AT-specific Form helpers """ - +from __future__ import unicode_literals import re from django.core.validators import EMPTY_VALUES @@ -47,13 +47,13 @@ class ATSocialSecurityNumberField(Field): """ default_error_messages = { - 'invalid': _(u'Enter a valid Austrian Social Security Number in XXXX XXXXXX format.'), + 'invalid': _('Enter a valid Austrian Social Security Number in XXXX XXXXXX format.'), } def clean(self, value): value = super(ATSocialSecurityNumberField, self).clean(value) if value in EMPTY_VALUES: - return u"" + return "" if not re_ssn.search(value): raise ValidationError(self.error_messages['invalid']) sqnr, date = value.split(" ") @@ -66,4 +66,4 @@ class ATSocialSecurityNumberField(Field): res = res % 11 if res != int(check): raise ValidationError(self.error_messages['invalid']) - return u'%s%s %s'%(sqnr, check, date,) + return '%s%s %s'%(sqnr, check, date,) diff --git a/django/contrib/localflavor/au/forms.py b/django/contrib/localflavor/au/forms.py index 19df98dc33..d3a00e200c 100644 --- a/django/contrib/localflavor/au/forms.py +++ b/django/contrib/localflavor/au/forms.py @@ -2,7 +2,7 @@ Australian-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -10,7 +10,7 @@ from django.contrib.localflavor.au.au_states import STATE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -34,7 +34,7 @@ class AUPostCodeField(RegexField): class AUPhoneNumberField(Field): """Australian phone number field.""" default_error_messages = { - 'invalid': u'Phone numbers must contain 10 digits.', + 'invalid': 'Phone numbers must contain 10 digits.', } def clean(self, value): @@ -43,11 +43,11 @@ class AUPhoneNumberField(Field): """ super(AUPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' - value = re.sub('(\(|\)|\s+|-)', '', smart_unicode(value)) + return '' + value = re.sub('(\(|\)|\s+|-)', '', smart_text(value)) phone_match = PHONE_DIGITS_RE.search(value) if phone_match: - return u'%s' % phone_match.group(1) + return '%s' % phone_match.group(1) raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/br/br_states.py b/django/contrib/localflavor/br/br_states.py index 98e54bca2c..ab37b1d223 100644 --- a/django/contrib/localflavor/br/br_states.py +++ b/django/contrib/localflavor/br/br_states.py @@ -5,33 +5,34 @@ An alphabetical list of Brazilian states for use as `choices` in a formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals STATE_CHOICES = ( ('AC', 'Acre'), ('AL', 'Alagoas'), - ('AP', u'Amapá'), + ('AP', 'Amapá'), ('AM', 'Amazonas'), ('BA', 'Bahia'), - ('CE', u'Ceará'), + ('CE', 'Ceará'), ('DF', 'Distrito Federal'), - ('ES', u'Espírito Santo'), - ('GO', u'Goiás'), - ('MA', u'Maranhão'), + ('ES', 'Espírito Santo'), + ('GO', 'Goiás'), + ('MA', 'Maranhão'), ('MT', 'Mato Grosso'), ('MS', 'Mato Grosso do Sul'), ('MG', 'Minas Gerais'), - ('PA', u'Pará'), - ('PB', u'Paraíba'), - ('PR', u'Paraná'), + ('PA', 'Pará'), + ('PB', 'Paraíba'), + ('PR', 'Paraná'), ('PE', 'Pernambuco'), - ('PI', u'Piauí'), + ('PI', 'Piauí'), ('RJ', 'Rio de Janeiro'), ('RN', 'Rio Grande do Norte'), ('RS', 'Rio Grande do Sul'), - ('RO', u'Rondônia'), + ('RO', 'Rondônia'), ('RR', 'Roraima'), ('SC', 'Santa Catarina'), - ('SP', u'São Paulo'), + ('SP', 'São Paulo'), ('SE', 'Sergipe'), ('TO', 'Tocantins'), ) diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py index 88c7f2efcc..0f957be37f 100644 --- a/django/contrib/localflavor/br/forms.py +++ b/django/contrib/localflavor/br/forms.py @@ -3,7 +3,7 @@ BR-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -11,7 +11,7 @@ from django.contrib.localflavor.br.br_states import STATE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, CharField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -34,11 +34,11 @@ class BRPhoneNumberField(Field): def clean(self, value): super(BRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + return '' + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: - return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) + return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) raise ValidationError(self.error_messages['invalid']) class BRStateSelect(Select): @@ -55,7 +55,7 @@ class BRStateChoiceField(Field): """ widget = Select default_error_messages = { - 'invalid': _(u'Select a valid brazilian state. That state is not one of the available states.'), + 'invalid': _('Select a valid brazilian state. That state is not one of the available states.'), } def __init__(self, required=True, widget=None, label=None, @@ -67,11 +67,11 @@ class BRStateChoiceField(Field): def clean(self, value): value = super(BRStateChoiceField, self).clean(value) if value in EMPTY_VALUES: - value = u'' - value = smart_unicode(value) - if value == u'': + value = '' + value = smart_text(value) + if value == '': return value - valid_values = set([smart_unicode(k) for k, v in self.widget.choices]) + valid_values = set([smart_text(k) for k, v in self.widget.choices]) if value not in valid_values: raise ValidationError(self.error_messages['invalid']) return value @@ -105,7 +105,7 @@ class BRCPFField(CharField): """ value = super(BRCPFField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' orig_value = value[:] if not value.isdigit(): value = re.sub("[-\.]", "", value) @@ -142,7 +142,7 @@ class BRCNPJField(Field): """ value = super(BRCNPJField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' orig_value = value[:] if not value.isdigit(): value = re.sub("[-/\.]", "", value) @@ -154,10 +154,10 @@ class BRCNPJField(Field): raise ValidationError(self.error_messages['max_digits']) orig_dv = value[-2:] - new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(5, 1, -1) + range(9, 1, -1))]) + new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(list(range(5, 1, -1)) + list(range(9, 1, -1)))]) new_1dv = DV_maker(new_1dv % 11) value = value[:-2] + str(new_1dv) + value[-1] - new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(range(6, 1, -1) + range(9, 1, -1))]) + new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(list(range(6, 1, -1)) + list(range(9, 1, -1)))]) new_2dv = DV_maker(new_2dv % 11) value = value[:-1] + str(new_2dv) if value[-2:] != orig_dv: diff --git a/django/contrib/localflavor/ca/forms.py b/django/contrib/localflavor/ca/forms.py index c3be79968f..4ebfb06c2b 100644 --- a/django/contrib/localflavor/ca/forms.py +++ b/django/contrib/localflavor/ca/forms.py @@ -2,14 +2,14 @@ Canada-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, CharField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -26,7 +26,7 @@ class CAPostalCodeField(CharField): http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp#1402170 """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXX XXX.'), + 'invalid': _('Enter a postal code in the format XXX XXX.'), } postcode_regex = re.compile(r'^([ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ]) *(\d[ABCEGHJKLMNPRSTVWXYZ]\d)$') @@ -34,7 +34,7 @@ class CAPostalCodeField(CharField): def clean(self, value): value = super(CAPostalCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' postcode = value.upper().strip() m = self.postcode_regex.match(postcode) if not m: @@ -44,7 +44,7 @@ class CAPostalCodeField(CharField): class CAPhoneNumberField(Field): """Canadian phone number field.""" default_error_messages = { - 'invalid': _(u'Phone numbers must be in XXX-XXX-XXXX format.'), + 'invalid': _('Phone numbers must be in XXX-XXX-XXXX format.'), } def clean(self, value): @@ -52,11 +52,11 @@ class CAPhoneNumberField(Field): """ super(CAPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + return '' + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: - return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) + return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) raise ValidationError(self.error_messages['invalid']) class CAProvinceField(Field): @@ -66,22 +66,22 @@ class CAProvinceField(Field): abbreviation for the given province. """ default_error_messages = { - 'invalid': _(u'Enter a Canadian province or territory.'), + 'invalid': _('Enter a Canadian province or territory.'), } def clean(self, value): super(CAProvinceField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' try: value = value.strip().lower() except AttributeError: pass else: # Load data in memory only when it is required, see also #17275 - from django.contrib.localflavor.ca.ca_provinces import PROVINCES_NORMALIZED + from .ca_provinces import PROVINCES_NORMALIZED try: - return PROVINCES_NORMALIZED[value.strip().lower()].decode('ascii') + return PROVINCES_NORMALIZED[value.strip().lower()] except KeyError: pass raise ValidationError(self.error_messages['invalid']) @@ -93,7 +93,7 @@ class CAProvinceSelect(Select): """ def __init__(self, attrs=None): # Load data in memory only when it is required, see also #17275 - from django.contrib.localflavor.ca.ca_provinces import PROVINCE_CHOICES + from .ca_provinces import PROVINCE_CHOICES super(CAProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES) class CASocialInsuranceNumberField(Field): @@ -113,14 +113,14 @@ class CASocialInsuranceNumberField(Field): def clean(self, value): super(CASocialInsuranceNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(sin_re, value) if not match: raise ValidationError(self.error_messages['invalid']) - number = u'%s-%s-%s' % (match.group(1), match.group(2), match.group(3)) - check_number = u'%s%s%s' % (match.group(1), match.group(2), match.group(3)) + number = '%s-%s-%s' % (match.group(1), match.group(2), match.group(3)) + check_number = '%s%s%s' % (match.group(1), match.group(2), match.group(3)) if not self.luhn_checksum_is_valid(check_number): raise ValidationError(self.error_messages['invalid']) return number diff --git a/django/contrib/localflavor/ch/forms.py b/django/contrib/localflavor/ch/forms.py index 649a98ab71..bf71eeea32 100644 --- a/django/contrib/localflavor/ch/forms.py +++ b/django/contrib/localflavor/ch/forms.py @@ -2,7 +2,7 @@ Swiss-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -10,7 +10,7 @@ from django.contrib.localflavor.ch.ch_states import STATE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -40,11 +40,11 @@ class CHPhoneNumberField(Field): def clean(self, value): super(CHPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' - value = re.sub('(\.|\s|/|-)', '', smart_unicode(value)) + return '' + value = re.sub('(\.|\s|/|-)', '', smart_text(value)) m = phone_digits_re.search(value) if m: - return u'%s %s %s %s' % (value[0:3], value[3:6], value[6:8], value[8:10]) + return '%s %s %s %s' % (value[0:3], value[3:6], value[6:8], value[8:10]) raise ValidationError(self.error_messages['invalid']) class CHStateSelect(Select): @@ -102,7 +102,7 @@ class CHIdentityCardNumberField(Field): def clean(self, value): super(CHIdentityCardNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(id_re, value) if not match: @@ -118,5 +118,5 @@ class CHIdentityCardNumberField(Field): if not self.has_valid_checksum(all_digits): raise ValidationError(self.error_messages['invalid']) - return u'%s%s%s' % (idnumber, pos9, checksum) + return '%s%s%s' % (idnumber, pos9, checksum) diff --git a/django/contrib/localflavor/cl/cl_regions.py b/django/contrib/localflavor/cl/cl_regions.py index 47db6d3912..d76f6ad834 100644 --- a/django/contrib/localflavor/cl/cl_regions.py +++ b/django/contrib/localflavor/cl/cl_regions.py @@ -5,21 +5,22 @@ A list of Chilean regions as `choices` in a formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals REGION_CHOICES = ( - ('RM', u'Región Metropolitana de Santiago'), - ('I', u'Región de Tarapacá'), - ('II', u'Región de Antofagasta'), - ('III', u'Región de Atacama'), - ('IV', u'Región de Coquimbo'), - ('V', u'Región de Valparaíso'), - ('VI', u'Región del Libertador Bernardo O\'Higgins'), - ('VII', u'Región del Maule'), - ('VIII',u'Región del Bío Bío'), - ('IX', u'Región de la Araucanía'), - ('X', u'Región de los Lagos'), - ('XI', u'Región de Aysén del General Carlos Ibáñez del Campo'), - ('XII', u'Región de Magallanes y la Antártica Chilena'), - ('XIV', u'Región de Los Ríos'), - ('XV', u'Región de Arica-Parinacota'), + ('RM', 'Región Metropolitana de Santiago'), + ('I', 'Región de Tarapacá'), + ('II', 'Región de Antofagasta'), + ('III', 'Región de Atacama'), + ('IV', 'Región de Coquimbo'), + ('V', 'Región de Valparaíso'), + ('VI', 'Región del Libertador Bernardo O\'Higgins'), + ('VII', 'Región del Maule'), + ('VIII','Región del Bío Bío'), + ('IX', 'Región de la Araucanía'), + ('X', 'Región de los Lagos'), + ('XI', 'Región de Aysén del General Carlos Ibáñez del Campo'), + ('XII', 'Región de Magallanes y la Antártica Chilena'), + ('XIV', 'Región de Los Ríos'), + ('XV', 'Región de Arica-Parinacota'), ) diff --git a/django/contrib/localflavor/cl/forms.py b/django/contrib/localflavor/cl/forms.py index 7a9aa2da8c..a5340141ce 100644 --- a/django/contrib/localflavor/cl/forms.py +++ b/django/contrib/localflavor/cl/forms.py @@ -2,15 +2,15 @@ Chile specific form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals -from django.contrib.localflavor.cl.cl_regions import REGION_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import RegexField, Select from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text +from .cl_regions import REGION_CHOICES class CLRegionSelect(Select): """ @@ -50,7 +50,7 @@ class CLRutField(RegexField): """ super(CLRutField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' rut, verificador = self._canonify(value) if self._algorithm(rut) == verificador: return self._format(rut, verificador) @@ -68,14 +68,14 @@ class CLRutField(RegexField): multi += 1 if multi == 8: multi = 2 - return u'0123456789K0'[11 - suma % 11] + return '0123456789K0'[11 - suma % 11] def _canonify(self, rut): """ Turns the RUT into one normalized format. Returns a (rut, verifier) tuple. """ - rut = smart_unicode(rut).replace(' ', '').replace('.', '').replace('-', '') + rut = smart_text(rut).replace(' ', '').replace('.', '').replace('-', '') return rut[:-1], rut[-1].upper() def _format(self, code, verifier=None): @@ -93,5 +93,5 @@ class CLRutField(RegexField): else: new_dot = pos - 3 code = code[:new_dot] + '.' + code[new_dot:] - return u'%s-%s' % (code, verifier) + return '%s-%s' % (code, verifier) diff --git a/django/contrib/localflavor/cn/cn_provinces.py b/django/contrib/localflavor/cn/cn_provinces.py index fe0aa37cb6..c27cba6423 100644 --- a/django/contrib/localflavor/cn/cn_provinces.py +++ b/django/contrib/localflavor/cn/cn_provinces.py @@ -9,41 +9,41 @@ http://en.wikipedia.org/wiki/Province_%28China%29 http://en.wikipedia.org/wiki/Direct-controlled_municipality http://en.wikipedia.org/wiki/Autonomous_regions_of_China """ - +from __future__ import unicode_literals CN_PROVINCE_CHOICES = ( - ("anhui", u"安徽"), - ("beijing", u"北京"), - ("chongqing", u"重庆"), - ("fujian", u"福建"), - ("gansu", u"甘肃"), - ("guangdong", u"广东"), - ("guangxi", u"广西壮族自治区"), - ("guizhou", u"贵州"), - ("hainan", u"海南"), - ("hebei", u"河北"), - ("heilongjiang", u"黑龙江"), - ("henan", u"河南"), - ("hongkong", u"香港"), - ("hubei", u"湖北"), - ("hunan", u"湖南"), - ("jiangsu", u"江苏"), - ("jiangxi", u"江西"), - ("jilin", u"吉林"), - ("liaoning", u"辽宁"), - ("macao", u"澳门"), - ("neimongol", u"内蒙古自治区"), - ("ningxia", u"宁夏回族自治区"), - ("qinghai", u"青海"), - ("shaanxi", u"陕西"), - ("shandong", u"山东"), - ("shanghai", u"上海"), - ("shanxi", u"山西"), - ("sichuan", u"四川"), - ("taiwan", u"台湾"), - ("tianjin", u"天津"), - ("xinjiang", u"新疆维吾尔自治区"), - ("xizang", u"西藏自治区"), - ("yunnan", u"云南"), - ("zhejiang", u"浙江"), + ("anhui", "安徽"), + ("beijing", "北京"), + ("chongqing", "重庆"), + ("fujian", "福建"), + ("gansu", "甘肃"), + ("guangdong", "广东"), + ("guangxi", "广西壮族自治区"), + ("guizhou", "贵州"), + ("hainan", "海南"), + ("hebei", "河北"), + ("heilongjiang", "黑龙江"), + ("henan", "河南"), + ("hongkong", "香港"), + ("hubei", "湖北"), + ("hunan", "湖南"), + ("jiangsu", "江苏"), + ("jiangxi", "江西"), + ("jilin", "吉林"), + ("liaoning", "辽宁"), + ("macao", "澳门"), + ("neimongol", "内蒙古自治区"), + ("ningxia", "宁夏回族自治区"), + ("qinghai", "青海"), + ("shaanxi", "陕西"), + ("shandong", "山东"), + ("shanghai", "上海"), + ("shanxi", "山西"), + ("sichuan", "四川"), + ("taiwan", "台湾"), + ("tianjin", "天津"), + ("xinjiang", "新疆维吾尔自治区"), + ("xizang", "西藏自治区"), + ("yunnan", "云南"), + ("zhejiang", "浙江"), ) diff --git a/django/contrib/localflavor/cn/forms.py b/django/contrib/localflavor/cn/forms.py index af92ba06ec..43adcf3f01 100644 --- a/django/contrib/localflavor/cn/forms.py +++ b/django/contrib/localflavor/cn/forms.py @@ -3,7 +3,7 @@ """ Chinese-specific form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -81,7 +81,7 @@ class CNPostCodeField(RegexField): Valid code is XXXXXX where X is digit. """ default_error_messages = { - 'invalid': _(u'Enter a post code in the format XXXXXX.'), + 'invalid': _('Enter a post code in the format XXXXXX.'), } def __init__(self, *args, **kwargs): @@ -102,10 +102,10 @@ class CNIDCardField(CharField): The checksum algorithm is described in GB11643-1999. """ default_error_messages = { - 'invalid': _(u'ID Card Number consists of 15 or 18 digits.'), - 'checksum': _(u'Invalid ID Card Number: Wrong checksum'), - 'birthday': _(u'Invalid ID Card Number: Wrong birthdate'), - 'location': _(u'Invalid ID Card Number: Wrong location code'), + 'invalid': _('ID Card Number consists of 15 or 18 digits.'), + 'checksum': _('Invalid ID Card Number: Wrong checksum'), + 'birthday': _('Invalid ID Card Number: Wrong birthdate'), + 'location': _('Invalid ID Card Number: Wrong location code'), } def __init__(self, max_length=18, min_length=15, *args, **kwargs): @@ -119,7 +119,7 @@ class CNIDCardField(CharField): # Check the length of the ID card number. super(CNIDCardField, self).clean(value) if not value: - return u"" + return "" # Check whether this ID card number has valid format if not re.match(ID_CARD_RE, value): raise ValidationError(self.error_messages['invalid']) @@ -133,7 +133,7 @@ class CNIDCardField(CharField): value = value.upper() if not self.has_valid_checksum(value): raise ValidationError(self.error_messages['checksum']) - return u'%s' % value + return '%s' % value def has_valid_birthday(self, value): """ @@ -190,7 +190,7 @@ class CNPhoneNumberField(RegexField): 010-55555555-35 """ default_error_messages = { - 'invalid': _(u'Enter a valid phone number.'), + 'invalid': _('Enter a valid phone number.'), } def __init__(self, *args, **kwargs): @@ -207,7 +207,7 @@ class CNCellNumberField(RegexField): The length of the cell number should be 11. """ default_error_messages = { - 'invalid': _(u'Enter a valid cell number.'), + 'invalid': _('Enter a valid cell number.'), } def __init__(self, *args, **kwargs): diff --git a/django/contrib/localflavor/co/co_departments.py b/django/contrib/localflavor/co/co_departments.py index f0989b6ba6..7168f54cc2 100644 --- a/django/contrib/localflavor/co/co_departments.py +++ b/django/contrib/localflavor/co/co_departments.py @@ -6,39 +6,40 @@ formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals DEPARTMENT_CHOICES = ( - ('AMA', u'Amazonas'), - ('ANT', u'Antioquia'), - ('ARA', u'Arauca'), - ('ATL', u'Atlántico'), - ('DC', u'Bogotá'), - ('BOL', u'Bolívar'), - ('BOY', u'Boyacá'), - ('CAL', u'Caldas'), - ('CAQ', u'Caquetá'), - ('CAS', u'Casanare'), - ('CAU', u'Cauca'), - ('CES', u'Cesar'), - ('CHO', u'Chocó'), - ('COR', u'Córdoba'), - ('CUN', u'Cundinamarca'), - ('GUA', u'Guainía'), - ('GUV', u'Guaviare'), - ('HUI', u'Huila'), - ('LAG', u'La Guajira'), - ('MAG', u'Magdalena'), - ('MET', u'Meta'), - ('NAR', u'Nariño'), - ('NSA', u'Norte de Santander'), - ('PUT', u'Putumayo'), - ('QUI', u'Quindío'), - ('RIS', u'Risaralda'), - ('SAP', u'San Andrés and Providencia'), - ('SAN', u'Santander'), - ('SUC', u'Sucre'), - ('TOL', u'Tolima'), - ('VAC', u'Valle del Cauca'), - ('VAU', u'Vaupés'), - ('VID', u'Vichada'), + ('AMA', 'Amazonas'), + ('ANT', 'Antioquia'), + ('ARA', 'Arauca'), + ('ATL', 'Atlántico'), + ('DC', 'Bogotá'), + ('BOL', 'Bolívar'), + ('BOY', 'Boyacá'), + ('CAL', 'Caldas'), + ('CAQ', 'Caquetá'), + ('CAS', 'Casanare'), + ('CAU', 'Cauca'), + ('CES', 'Cesar'), + ('CHO', 'Chocó'), + ('COR', 'Córdoba'), + ('CUN', 'Cundinamarca'), + ('GUA', 'Guainía'), + ('GUV', 'Guaviare'), + ('HUI', 'Huila'), + ('LAG', 'La Guajira'), + ('MAG', 'Magdalena'), + ('MET', 'Meta'), + ('NAR', 'Nariño'), + ('NSA', 'Norte de Santander'), + ('PUT', 'Putumayo'), + ('QUI', 'Quindío'), + ('RIS', 'Risaralda'), + ('SAP', 'San Andrés and Providencia'), + ('SAN', 'Santander'), + ('SUC', 'Sucre'), + ('TOL', 'Tolima'), + ('VAC', 'Valle del Cauca'), + ('VAU', 'Vaupés'), + ('VID', 'Vichada'), ) diff --git a/django/contrib/localflavor/cz/forms.py b/django/contrib/localflavor/cz/forms.py index f3676b230b..c7e81e4037 100644 --- a/django/contrib/localflavor/cz/forms.py +++ b/django/contrib/localflavor/cz/forms.py @@ -2,7 +2,7 @@ Czech-specific form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -29,7 +29,7 @@ class CZPostalCodeField(RegexField): Valid form is XXXXX or XXX XX, where X represents integer. """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXXXX or XXX XX.'), + 'invalid': _('Enter a postal code in the format XXXXX or XXX XX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -49,15 +49,15 @@ class CZBirthNumberField(Field): Czech birth number field. """ default_error_messages = { - 'invalid_format': _(u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.'), - 'invalid': _(u'Enter a valid birth number.'), + 'invalid_format': _('Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.'), + 'invalid': _('Enter a valid birth number.'), } def clean(self, value, gender=None): super(CZBirthNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(birth_number, value) if not match: @@ -67,7 +67,7 @@ class CZBirthNumberField(Field): # Three digits for verification number were used until 1. january 1954 if len(id) == 3: - return u'%s' % value + return '%s' % value # Birth number is in format YYMMDD. Females have month value raised by 50. # In case that all possible number are already used (for given date), @@ -90,7 +90,7 @@ class CZBirthNumberField(Field): modulo = int(birth + id[:3]) % 11 if (modulo == int(id[-1])) or (modulo == 10 and id[-1] == '0'): - return u'%s' % value + return '%s' % value else: raise ValidationError(self.error_messages['invalid']) @@ -99,14 +99,14 @@ class CZICNumberField(Field): Czech IC number field. """ default_error_messages = { - 'invalid': _(u'Enter a valid IC number.'), + 'invalid': _('Enter a valid IC number.'), } def clean(self, value): super(CZICNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(ic_number, value) if not match: @@ -130,7 +130,7 @@ class CZICNumberField(Field): if (not remainder % 10 and check == 1) or \ (remainder == 1 and check == 0) or \ (check == (11 - remainder)): - return u'%s' % value + return '%s' % value raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/de/forms.py b/django/contrib/localflavor/de/forms.py index 80b6248ed4..a7891d117f 100644 --- a/django/contrib/localflavor/de/forms.py +++ b/django/contrib/localflavor/de/forms.py @@ -2,7 +2,7 @@ DE-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -68,7 +68,7 @@ class DEIdentityCardNumberField(Field): def clean(self, value): super(DEIdentityCardNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(id_re, value) if not match: raise ValidationError(self.error_messages['invalid']) @@ -80,9 +80,9 @@ class DEIdentityCardNumberField(Field): if residence == '0000000000' or birthday == '0000000' or validity == '0000000': raise ValidationError(self.error_messages['invalid']) - all_digits = u"%s%s%s%s" % (residence, birthday, validity, checksum) + all_digits = "%s%s%s%s" % (residence, birthday, validity, checksum) if not self.has_valid_checksum(residence) or not self.has_valid_checksum(birthday) or \ not self.has_valid_checksum(validity) or not self.has_valid_checksum(all_digits): raise ValidationError(self.error_messages['invalid']) - return u'%s%s-%s-%s-%s' % (residence, origin, birthday, validity, checksum) + return '%s%s-%s-%s-%s' % (residence, origin, birthday, validity, checksum) diff --git a/django/contrib/localflavor/de_CH/formats.py b/django/contrib/localflavor/de_CH/formats.py index d4f324a422..9d56f9f298 100644 --- a/django/contrib/localflavor/de_CH/formats.py +++ b/django/contrib/localflavor/de_CH/formats.py @@ -4,6 +4,8 @@ # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date +from __future__ import unicode_literals + DATE_FORMAT = 'j. F Y' TIME_FORMAT = 'H:i:s' DATETIME_FORMAT = 'j. F Y H:i:s' @@ -39,5 +41,5 @@ DATETIME_INPUT_FORMATS = ( # For details, please refer to http://www.bk.admin.ch/dokumentation/sprachen/04915/05016/index.html?lang=de # (in German) and the documentation DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = u'\xa0' # non-breaking space +THOUSAND_SEPARATOR = '\xa0' # non-breaking space NUMBER_GROUPING = 3 diff --git a/django/contrib/localflavor/ec/ec_provinces.py b/django/contrib/localflavor/ec/ec_provinces.py index 7e55078be0..db25d26ff8 100644 --- a/django/contrib/localflavor/ec/ec_provinces.py +++ b/django/contrib/localflavor/ec/ec_provinces.py @@ -6,30 +6,31 @@ formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals PROVINCE_CHOICES = ( - ('A', u'Azuay'), - ('B', u'Bolívar'), - ('F', u'Cañar'), - ('C', u'Carchi'), - ('H', u'Chimborazo'), - ('X', u'Cotopaxi'), - ('O', u'El Oro'), - ('E', u'Esmeraldas'), - ('W', u'Galápagos'), - ('G', u'Guayas'), - ('I', u'Imbabura'), - ('L', u'Loja'), - ('R', u'Los Ríos'), - ('M', u'Manabí'), - ('S', u'Morona Santiago'), - ('N', u'Napo'), - ('D', u'Orellana'), - ('Y', u'Pastaza'), - ('P', u'Pichincha'), - ('SE', u'Santa Elena'), - ('SD', u'Santo Domingo de los Tsáchilas'), - ('U', u'Sucumbíos'), - ('T', u'Tungurahua'), - ('Z', u'Zamora Chinchipe'), + ('A', 'Azuay'), + ('B', 'Bolívar'), + ('F', 'Cañar'), + ('C', 'Carchi'), + ('H', 'Chimborazo'), + ('X', 'Cotopaxi'), + ('O', 'El Oro'), + ('E', 'Esmeraldas'), + ('W', 'Galápagos'), + ('G', 'Guayas'), + ('I', 'Imbabura'), + ('L', 'Loja'), + ('R', 'Los Ríos'), + ('M', 'Manabí'), + ('S', 'Morona Santiago'), + ('N', 'Napo'), + ('D', 'Orellana'), + ('Y', 'Pastaza'), + ('P', 'Pichincha'), + ('SE', 'Santa Elena'), + ('SD', 'Santo Domingo de los Tsáchilas'), + ('U', 'Sucumbíos'), + ('T', 'Tungurahua'), + ('Z', 'Zamora Chinchipe'), ) diff --git a/django/contrib/localflavor/es/forms.py b/django/contrib/localflavor/es/forms.py index fe237270f5..da0769d2a0 100644 --- a/django/contrib/localflavor/es/forms.py +++ b/django/contrib/localflavor/es/forms.py @@ -3,7 +3,7 @@ Spanish-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -91,7 +91,7 @@ class ESIdentityCardNumberField(RegexField): def clean(self, value): super(ESIdentityCardNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' nif_get_checksum = lambda d: self.nif_control[int(d)%23] value = value.upper().replace(' ', '').replace('-', '') @@ -157,7 +157,7 @@ class ESCCCField(RegexField): def clean(self, value): super(ESCCCField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' control_str = [1, 2, 4, 8, 5, 10, 9, 7, 3, 6] m = re.match(r'^(\d{4})[ -]?(\d{4})[ -]?(\d{2})[ -]?(\d{10})$', value) entity, office, checksum, account = m.groups() diff --git a/django/contrib/localflavor/fi/fi_municipalities.py b/django/contrib/localflavor/fi/fi_municipalities.py index 4b8b886b8b..6f90e5e02b 100644 --- a/django/contrib/localflavor/fi/fi_municipalities.py +++ b/django/contrib/localflavor/fi/fi_municipalities.py @@ -7,347 +7,349 @@ This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals + MUNICIPALITY_CHOICES = ( - ('akaa', u"Akaa"), - ('alajarvi', u"Alajärvi"), - ('alavieska', u"Alavieska"), - ('alavus', u"Alavus"), - ('artjarvi', u"Artjärvi"), - ('asikkala', u"Asikkala"), - ('askola', u"Askola"), - ('aura', u"Aura"), - ('brando', u"Brändö"), - ('eckero', u"Eckerö"), - ('enonkoski', u"Enonkoski"), - ('enontekio', u"Enontekiö"), - ('espoo', u"Espoo"), - ('eura', u"Eura"), - ('eurajoki', u"Eurajoki"), - ('evijarvi', u"Evijärvi"), - ('finstrom', u"Finström"), - ('forssa', u"Forssa"), - ('foglo', u"Föglö"), - ('geta', u"Geta"), - ('haapajarvi', u"Haapajärvi"), - ('haapavesi', u"Haapavesi"), - ('hailuoto', u"Hailuoto"), - ('halsua', u"Halsua"), - ('hamina', u"Hamina"), - ('hammarland', u"Hammarland"), - ('hankasalmi', u"Hankasalmi"), - ('hanko', u"Hanko"), - ('harjavalta', u"Harjavalta"), - ('hartola', u"Hartola"), - ('hattula', u"Hattula"), - ('haukipudas', u"Haukipudas"), - ('hausjarvi', u"Hausjärvi"), - ('heinola', u"Heinola"), - ('heinavesi', u"Heinävesi"), - ('helsinki', u"Helsinki"), - ('hirvensalmi', u"Hirvensalmi"), - ('hollola', u"Hollola"), - ('honkajoki', u"Honkajoki"), - ('huittinen', u"Huittinen"), - ('humppila', u"Humppila"), - ('hyrynsalmi', u"Hyrynsalmi"), - ('hyvinkaa', u"Hyvinkää"), - ('hameenkoski', u"Hämeenkoski"), - ('hameenkyro', u"Hämeenkyrö"), - ('hameenlinna', u"Hämeenlinna"), - ('ii', u"Ii"), - ('iisalmi', u"Iisalmi"), - ('iitti', u"Iitti"), - ('ikaalinen', u"Ikaalinen"), - ('ilmajoki', u"Ilmajoki"), - ('ilomantsi', u"Ilomantsi"), - ('imatra', u"Imatra"), - ('inari', u"Inari"), - ('inkoo', u"Inkoo"), - ('isojoki', u"Isojoki"), - ('isokyro', u"Isokyrö"), - ('jalasjarvi', u"Jalasjärvi"), - ('janakkala', u"Janakkala"), - ('joensuu', u"Joensuu"), - ('jokioinen', u"Jokioinen"), - ('jomala', u"Jomala"), - ('joroinen', u"Joroinen"), - ('joutsa', u"Joutsa"), - ('juankoski', u"Juankoski"), - ('juuka', u"Juuka"), - ('juupajoki', u"Juupajoki"), - ('juva', u"Juva"), - ('jyvaskyla', u"Jyväskylä"), - ('jamijarvi', u"Jämijärvi"), - ('jamsa', u"Jämsä"), - ('jarvenpaa', u"Järvenpää"), - ('kaarina', u"Kaarina"), - ('kaavi', u"Kaavi"), - ('kajaani', u"Kajaani"), - ('kalajoki', u"Kalajoki"), - ('kangasala', u"Kangasala"), - ('kangasniemi', u"Kangasniemi"), - ('kankaanpaa', u"Kankaanpää"), - ('kannonkoski', u"Kannonkoski"), - ('kannus', u"Kannus"), - ('karijoki', u"Karijoki"), - ('karjalohja', u"Karjalohja"), - ('karkkila', u"Karkkila"), - ('karstula', u"Karstula"), - ('karttula', u"Karttula"), - ('karvia', u"Karvia"), - ('kaskinen', u"Kaskinen"), - ('kauhajoki', u"Kauhajoki"), - ('kauhava', u"Kauhava"), - ('kauniainen', u"Kauniainen"), - ('kaustinen', u"Kaustinen"), - ('keitele', u"Keitele"), - ('kemi', u"Kemi"), - ('kemijarvi', u"Kemijärvi"), - ('keminmaa', u"Keminmaa"), - ('kemionsaari', u"Kemiönsaari"), - ('kempele', u"Kempele"), - ('kerava', u"Kerava"), - ('kerimaki', u"Kerimäki"), - ('kesalahti', u"Kesälahti"), - ('keuruu', u"Keuruu"), - ('kihnio', u"Kihniö"), - ('kiikoinen', u"Kiikoinen"), - ('kiiminki', u"Kiiminki"), - ('kinnula', u"Kinnula"), - ('kirkkonummi', u"Kirkkonummi"), - ('kitee', u"Kitee"), - ('kittila', u"Kittilä"), - ('kiuruvesi', u"Kiuruvesi"), - ('kivijarvi', u"Kivijärvi"), - ('kokemaki', u"Kokemäki"), - ('kokkola', u"Kokkola"), - ('kolari', u"Kolari"), - ('konnevesi', u"Konnevesi"), - ('kontiolahti', u"Kontiolahti"), - ('korsnas', u"Korsnäs"), - ('koskitl', u"Koski Tl"), - ('kotka', u"Kotka"), - ('kouvola', u"Kouvola"), - ('kristiinankaupunki', u"Kristiinankaupunki"), - ('kruunupyy', u"Kruunupyy"), - ('kuhmalahti', u"Kuhmalahti"), - ('kuhmo', u"Kuhmo"), - ('kuhmoinen', u"Kuhmoinen"), - ('kumlinge', u"Kumlinge"), - ('kuopio', u"Kuopio"), - ('kuortane', u"Kuortane"), - ('kurikka', u"Kurikka"), - ('kustavi', u"Kustavi"), - ('kuusamo', u"Kuusamo"), - ('kylmakoski', u"Kylmäkoski"), - ('kyyjarvi', u"Kyyjärvi"), - ('karkola', u"Kärkölä"), - ('karsamaki', u"Kärsämäki"), - ('kokar', u"Kökar"), - ('koylio', u"Köyliö"), - ('lahti', u"Lahti"), - ('laihia', u"Laihia"), - ('laitila', u"Laitila"), - ('lapinjarvi', u"Lapinjärvi"), - ('lapinlahti', u"Lapinlahti"), - ('lappajarvi', u"Lappajärvi"), - ('lappeenranta', u"Lappeenranta"), - ('lapua', u"Lapua"), - ('laukaa', u"Laukaa"), - ('lavia', u"Lavia"), - ('lemi', u"Lemi"), - ('lemland', u"Lemland"), - ('lempaala', u"Lempäälä"), - ('leppavirta', u"Leppävirta"), - ('lestijarvi', u"Lestijärvi"), - ('lieksa', u"Lieksa"), - ('lieto', u"Lieto"), - ('liminka', u"Liminka"), - ('liperi', u"Liperi"), - ('lohja', u"Lohja"), - ('loimaa', u"Loimaa"), - ('loppi', u"Loppi"), - ('loviisa', u"Loviisa"), - ('luhanka', u"Luhanka"), - ('lumijoki', u"Lumijoki"), - ('lumparland', u"Lumparland"), - ('luoto', u"Luoto"), - ('luumaki', u"Luumäki"), - ('luvia', u"Luvia"), - ('lansi-turunmaa', u"Länsi-Turunmaa"), - ('maalahti', u"Maalahti"), - ('maaninka', u"Maaninka"), - ('maarianhamina', u"Maarianhamina"), - ('marttila', u"Marttila"), - ('masku', u"Masku"), - ('merijarvi', u"Merijärvi"), - ('merikarvia', u"Merikarvia"), - ('miehikkala', u"Miehikkälä"), - ('mikkeli', u"Mikkeli"), - ('muhos', u"Muhos"), - ('multia', u"Multia"), - ('muonio', u"Muonio"), - ('mustasaari', u"Mustasaari"), - ('muurame', u"Muurame"), - ('mynamaki', u"Mynämäki"), - ('myrskyla', u"Myrskylä"), - ('mantsala', u"Mäntsälä"), - ('mantta-vilppula', u"Mänttä-Vilppula"), - ('mantyharju', u"Mäntyharju"), - ('naantali', u"Naantali"), - ('nakkila', u"Nakkila"), - ('nastola', u"Nastola"), - ('nilsia', u"Nilsiä"), - ('nivala', u"Nivala"), - ('nokia', u"Nokia"), - ('nousiainen', u"Nousiainen"), - ('nummi-pusula', u"Nummi-Pusula"), - ('nurmes', u"Nurmes"), - ('nurmijarvi', u"Nurmijärvi"), - ('narpio', u"Närpiö"), - ('oravainen', u"Oravainen"), - ('orimattila', u"Orimattila"), - ('oripaa', u"Oripää"), - ('orivesi', u"Orivesi"), - ('oulainen', u"Oulainen"), - ('oulu', u"Oulu"), - ('oulunsalo', u"Oulunsalo"), - ('outokumpu', u"Outokumpu"), - ('padasjoki', u"Padasjoki"), - ('paimio', u"Paimio"), - ('paltamo', u"Paltamo"), - ('parikkala', u"Parikkala"), - ('parkano', u"Parkano"), - ('pedersore', u"Pedersöre"), - ('pelkosenniemi', u"Pelkosenniemi"), - ('pello', u"Pello"), - ('perho', u"Perho"), - ('pertunmaa', u"Pertunmaa"), - ('petajavesi', u"Petäjävesi"), - ('pieksamaki', u"Pieksämäki"), - ('pielavesi', u"Pielavesi"), - ('pietarsaari', u"Pietarsaari"), - ('pihtipudas', u"Pihtipudas"), - ('pirkkala', u"Pirkkala"), - ('polvijarvi', u"Polvijärvi"), - ('pomarkku', u"Pomarkku"), - ('pori', u"Pori"), - ('pornainen', u"Pornainen"), - ('porvoo', u"Porvoo"), - ('posio', u"Posio"), - ('pudasjarvi', u"Pudasjärvi"), - ('pukkila', u"Pukkila"), - ('punkaharju', u"Punkaharju"), - ('punkalaidun', u"Punkalaidun"), - ('puolanka', u"Puolanka"), - ('puumala', u"Puumala"), - ('pyhtaa', u"Pyhtää"), - ('pyhajoki', u"Pyhäjoki"), - ('pyhajarvi', u"Pyhäjärvi"), - ('pyhanta', u"Pyhäntä"), - ('pyharanta', u"Pyhäranta"), - ('palkane', u"Pälkäne"), - ('poytya', u"Pöytyä"), - ('raahe', u"Raahe"), - ('raasepori', u"Raasepori"), - ('raisio', u"Raisio"), - ('rantasalmi', u"Rantasalmi"), - ('ranua', u"Ranua"), - ('rauma', u"Rauma"), - ('rautalampi', u"Rautalampi"), - ('rautavaara', u"Rautavaara"), - ('rautjarvi', u"Rautjärvi"), - ('reisjarvi', u"Reisjärvi"), - ('riihimaki', u"Riihimäki"), - ('ristiina', u"Ristiina"), - ('ristijarvi', u"Ristijärvi"), - ('rovaniemi', u"Rovaniemi"), - ('ruokolahti', u"Ruokolahti"), - ('ruovesi', u"Ruovesi"), - ('rusko', u"Rusko"), - ('raakkyla', u"Rääkkylä"), - ('saarijarvi', u"Saarijärvi"), - ('salla', u"Salla"), - ('salo', u"Salo"), - ('saltvik', u"Saltvik"), - ('sastamala', u"Sastamala"), - ('sauvo', u"Sauvo"), - ('savitaipale', u"Savitaipale"), - ('savonlinna', u"Savonlinna"), - ('savukoski', u"Savukoski"), - ('seinajoki', u"Seinäjoki"), - ('sievi', u"Sievi"), - ('siikainen', u"Siikainen"), - ('siikajoki', u"Siikajoki"), - ('siikalatva', u"Siikalatva"), - ('siilinjarvi', u"Siilinjärvi"), - ('simo', u"Simo"), - ('sipoo', u"Sipoo"), - ('siuntio', u"Siuntio"), - ('sodankyla', u"Sodankylä"), - ('soini', u"Soini"), - ('somero', u"Somero"), - ('sonkajarvi', u"Sonkajärvi"), - ('sotkamo', u"Sotkamo"), - ('sottunga', u"Sottunga"), - ('sulkava', u"Sulkava"), - ('sund', u"Sund"), - ('suomenniemi', u"Suomenniemi"), - ('suomussalmi', u"Suomussalmi"), - ('suonenjoki', u"Suonenjoki"), - ('sysma', u"Sysmä"), - ('sakyla', u"Säkylä"), - ('taipalsaari', u"Taipalsaari"), - ('taivalkoski', u"Taivalkoski"), - ('taivassalo', u"Taivassalo"), - ('tammela', u"Tammela"), - ('tampere', u"Tampere"), - ('tarvasjoki', u"Tarvasjoki"), - ('tervo', u"Tervo"), - ('tervola', u"Tervola"), - ('teuva', u"Teuva"), - ('tohmajarvi', u"Tohmajärvi"), - ('toholampi', u"Toholampi"), - ('toivakka', u"Toivakka"), - ('tornio', u"Tornio"), - ('turku', u"Turku"), - ('tuusniemi', u"Tuusniemi"), - ('tuusula', u"Tuusula"), - ('tyrnava', u"Tyrnävä"), - ('toysa', u"Töysä"), - ('ulvila', u"Ulvila"), - ('urjala', u"Urjala"), - ('utajarvi', u"Utajärvi"), - ('utsjoki', u"Utsjoki"), - ('uurainen', u"Uurainen"), - ('uusikaarlepyy', u"Uusikaarlepyy"), - ('uusikaupunki', u"Uusikaupunki"), - ('vaala', u"Vaala"), - ('vaasa', u"Vaasa"), - ('valkeakoski', u"Valkeakoski"), - ('valtimo', u"Valtimo"), - ('vantaa', u"Vantaa"), - ('varkaus', u"Varkaus"), - ('varpaisjarvi', u"Varpaisjärvi"), - ('vehmaa', u"Vehmaa"), - ('vesanto', u"Vesanto"), - ('vesilahti', u"Vesilahti"), - ('veteli', u"Veteli"), - ('vierema', u"Vieremä"), - ('vihanti', u"Vihanti"), - ('vihti', u"Vihti"), - ('viitasaari', u"Viitasaari"), - ('vimpeli', u"Vimpeli"), - ('virolahti', u"Virolahti"), - ('virrat', u"Virrat"), - ('vardo', u"Vårdö"), - ('vahakyro', u"Vähäkyrö"), - ('voyri-maksamaa', u"Vöyri-Maksamaa"), - ('yli-ii', u"Yli-Ii"), - ('ylitornio', u"Ylitornio"), - ('ylivieska', u"Ylivieska"), - ('ylojarvi', u"Ylöjärvi"), - ('ypaja', u"Ypäjä"), - ('ahtari', u"Ähtäri"), - ('aanekoski', u"Äänekoski") -) \ No newline at end of file + ('akaa', "Akaa"), + ('alajarvi', "Alajärvi"), + ('alavieska', "Alavieska"), + ('alavus', "Alavus"), + ('artjarvi', "Artjärvi"), + ('asikkala', "Asikkala"), + ('askola', "Askola"), + ('aura', "Aura"), + ('brando', "Brändö"), + ('eckero', "Eckerö"), + ('enonkoski', "Enonkoski"), + ('enontekio', "Enontekiö"), + ('espoo', "Espoo"), + ('eura', "Eura"), + ('eurajoki', "Eurajoki"), + ('evijarvi', "Evijärvi"), + ('finstrom', "Finström"), + ('forssa', "Forssa"), + ('foglo', "Föglö"), + ('geta', "Geta"), + ('haapajarvi', "Haapajärvi"), + ('haapavesi', "Haapavesi"), + ('hailuoto', "Hailuoto"), + ('halsua', "Halsua"), + ('hamina', "Hamina"), + ('hammarland', "Hammarland"), + ('hankasalmi', "Hankasalmi"), + ('hanko', "Hanko"), + ('harjavalta', "Harjavalta"), + ('hartola', "Hartola"), + ('hattula', "Hattula"), + ('haukipudas', "Haukipudas"), + ('hausjarvi', "Hausjärvi"), + ('heinola', "Heinola"), + ('heinavesi', "Heinävesi"), + ('helsinki', "Helsinki"), + ('hirvensalmi', "Hirvensalmi"), + ('hollola', "Hollola"), + ('honkajoki', "Honkajoki"), + ('huittinen', "Huittinen"), + ('humppila', "Humppila"), + ('hyrynsalmi', "Hyrynsalmi"), + ('hyvinkaa', "Hyvinkää"), + ('hameenkoski', "Hämeenkoski"), + ('hameenkyro', "Hämeenkyrö"), + ('hameenlinna', "Hämeenlinna"), + ('ii', "Ii"), + ('iisalmi', "Iisalmi"), + ('iitti', "Iitti"), + ('ikaalinen', "Ikaalinen"), + ('ilmajoki', "Ilmajoki"), + ('ilomantsi', "Ilomantsi"), + ('imatra', "Imatra"), + ('inari', "Inari"), + ('inkoo', "Inkoo"), + ('isojoki', "Isojoki"), + ('isokyro', "Isokyrö"), + ('jalasjarvi', "Jalasjärvi"), + ('janakkala', "Janakkala"), + ('joensuu', "Joensuu"), + ('jokioinen', "Jokioinen"), + ('jomala', "Jomala"), + ('joroinen', "Joroinen"), + ('joutsa', "Joutsa"), + ('juankoski', "Juankoski"), + ('juuka', "Juuka"), + ('juupajoki', "Juupajoki"), + ('juva', "Juva"), + ('jyvaskyla', "Jyväskylä"), + ('jamijarvi', "Jämijärvi"), + ('jamsa', "Jämsä"), + ('jarvenpaa', "Järvenpää"), + ('kaarina', "Kaarina"), + ('kaavi', "Kaavi"), + ('kajaani', "Kajaani"), + ('kalajoki', "Kalajoki"), + ('kangasala', "Kangasala"), + ('kangasniemi', "Kangasniemi"), + ('kankaanpaa', "Kankaanpää"), + ('kannonkoski', "Kannonkoski"), + ('kannus', "Kannus"), + ('karijoki', "Karijoki"), + ('karjalohja', "Karjalohja"), + ('karkkila', "Karkkila"), + ('karstula', "Karstula"), + ('karttula', "Karttula"), + ('karvia', "Karvia"), + ('kaskinen', "Kaskinen"), + ('kauhajoki', "Kauhajoki"), + ('kauhava', "Kauhava"), + ('kauniainen', "Kauniainen"), + ('kaustinen', "Kaustinen"), + ('keitele', "Keitele"), + ('kemi', "Kemi"), + ('kemijarvi', "Kemijärvi"), + ('keminmaa', "Keminmaa"), + ('kemionsaari', "Kemiönsaari"), + ('kempele', "Kempele"), + ('kerava', "Kerava"), + ('kerimaki', "Kerimäki"), + ('kesalahti', "Kesälahti"), + ('keuruu', "Keuruu"), + ('kihnio', "Kihniö"), + ('kiikoinen', "Kiikoinen"), + ('kiiminki', "Kiiminki"), + ('kinnula', "Kinnula"), + ('kirkkonummi', "Kirkkonummi"), + ('kitee', "Kitee"), + ('kittila', "Kittilä"), + ('kiuruvesi', "Kiuruvesi"), + ('kivijarvi', "Kivijärvi"), + ('kokemaki', "Kokemäki"), + ('kokkola', "Kokkola"), + ('kolari', "Kolari"), + ('konnevesi', "Konnevesi"), + ('kontiolahti', "Kontiolahti"), + ('korsnas', "Korsnäs"), + ('koskitl', "Koski Tl"), + ('kotka', "Kotka"), + ('kouvola', "Kouvola"), + ('kristiinankaupunki', "Kristiinankaupunki"), + ('kruunupyy', "Kruunupyy"), + ('kuhmalahti', "Kuhmalahti"), + ('kuhmo', "Kuhmo"), + ('kuhmoinen', "Kuhmoinen"), + ('kumlinge', "Kumlinge"), + ('kuopio', "Kuopio"), + ('kuortane', "Kuortane"), + ('kurikka', "Kurikka"), + ('kustavi', "Kustavi"), + ('kuusamo', "Kuusamo"), + ('kylmakoski', "Kylmäkoski"), + ('kyyjarvi', "Kyyjärvi"), + ('karkola', "Kärkölä"), + ('karsamaki', "Kärsämäki"), + ('kokar', "Kökar"), + ('koylio', "Köyliö"), + ('lahti', "Lahti"), + ('laihia', "Laihia"), + ('laitila', "Laitila"), + ('lapinjarvi', "Lapinjärvi"), + ('lapinlahti', "Lapinlahti"), + ('lappajarvi', "Lappajärvi"), + ('lappeenranta', "Lappeenranta"), + ('lapua', "Lapua"), + ('laukaa', "Laukaa"), + ('lavia', "Lavia"), + ('lemi', "Lemi"), + ('lemland', "Lemland"), + ('lempaala', "Lempäälä"), + ('leppavirta', "Leppävirta"), + ('lestijarvi', "Lestijärvi"), + ('lieksa', "Lieksa"), + ('lieto', "Lieto"), + ('liminka', "Liminka"), + ('liperi', "Liperi"), + ('lohja', "Lohja"), + ('loimaa', "Loimaa"), + ('loppi', "Loppi"), + ('loviisa', "Loviisa"), + ('luhanka', "Luhanka"), + ('lumijoki', "Lumijoki"), + ('lumparland', "Lumparland"), + ('luoto', "Luoto"), + ('luumaki', "Luumäki"), + ('luvia', "Luvia"), + ('lansi-turunmaa', "Länsi-Turunmaa"), + ('maalahti', "Maalahti"), + ('maaninka', "Maaninka"), + ('maarianhamina', "Maarianhamina"), + ('marttila', "Marttila"), + ('masku', "Masku"), + ('merijarvi', "Merijärvi"), + ('merikarvia', "Merikarvia"), + ('miehikkala', "Miehikkälä"), + ('mikkeli', "Mikkeli"), + ('muhos', "Muhos"), + ('multia', "Multia"), + ('muonio', "Muonio"), + ('mustasaari', "Mustasaari"), + ('muurame', "Muurame"), + ('mynamaki', "Mynämäki"), + ('myrskyla', "Myrskylä"), + ('mantsala', "Mäntsälä"), + ('mantta-vilppula', "Mänttä-Vilppula"), + ('mantyharju', "Mäntyharju"), + ('naantali', "Naantali"), + ('nakkila', "Nakkila"), + ('nastola', "Nastola"), + ('nilsia', "Nilsiä"), + ('nivala', "Nivala"), + ('nokia', "Nokia"), + ('nousiainen', "Nousiainen"), + ('nummi-pusula', "Nummi-Pusula"), + ('nurmes', "Nurmes"), + ('nurmijarvi', "Nurmijärvi"), + ('narpio', "Närpiö"), + ('oravainen', "Oravainen"), + ('orimattila', "Orimattila"), + ('oripaa', "Oripää"), + ('orivesi', "Orivesi"), + ('oulainen', "Oulainen"), + ('oulu', "Oulu"), + ('oulunsalo', "Oulunsalo"), + ('outokumpu', "Outokumpu"), + ('padasjoki', "Padasjoki"), + ('paimio', "Paimio"), + ('paltamo', "Paltamo"), + ('parikkala', "Parikkala"), + ('parkano', "Parkano"), + ('pedersore', "Pedersöre"), + ('pelkosenniemi', "Pelkosenniemi"), + ('pello', "Pello"), + ('perho', "Perho"), + ('pertunmaa', "Pertunmaa"), + ('petajavesi', "Petäjävesi"), + ('pieksamaki', "Pieksämäki"), + ('pielavesi', "Pielavesi"), + ('pietarsaari', "Pietarsaari"), + ('pihtipudas', "Pihtipudas"), + ('pirkkala', "Pirkkala"), + ('polvijarvi', "Polvijärvi"), + ('pomarkku', "Pomarkku"), + ('pori', "Pori"), + ('pornainen', "Pornainen"), + ('porvoo', "Porvoo"), + ('posio', "Posio"), + ('pudasjarvi', "Pudasjärvi"), + ('pukkila', "Pukkila"), + ('punkaharju', "Punkaharju"), + ('punkalaidun', "Punkalaidun"), + ('puolanka', "Puolanka"), + ('puumala', "Puumala"), + ('pyhtaa', "Pyhtää"), + ('pyhajoki', "Pyhäjoki"), + ('pyhajarvi', "Pyhäjärvi"), + ('pyhanta', "Pyhäntä"), + ('pyharanta', "Pyhäranta"), + ('palkane', "Pälkäne"), + ('poytya', "Pöytyä"), + ('raahe', "Raahe"), + ('raasepori', "Raasepori"), + ('raisio', "Raisio"), + ('rantasalmi', "Rantasalmi"), + ('ranua', "Ranua"), + ('rauma', "Rauma"), + ('rautalampi', "Rautalampi"), + ('rautavaara', "Rautavaara"), + ('rautjarvi', "Rautjärvi"), + ('reisjarvi', "Reisjärvi"), + ('riihimaki', "Riihimäki"), + ('ristiina', "Ristiina"), + ('ristijarvi', "Ristijärvi"), + ('rovaniemi', "Rovaniemi"), + ('ruokolahti', "Ruokolahti"), + ('ruovesi', "Ruovesi"), + ('rusko', "Rusko"), + ('raakkyla', "Rääkkylä"), + ('saarijarvi', "Saarijärvi"), + ('salla', "Salla"), + ('salo', "Salo"), + ('saltvik', "Saltvik"), + ('sastamala', "Sastamala"), + ('sauvo', "Sauvo"), + ('savitaipale', "Savitaipale"), + ('savonlinna', "Savonlinna"), + ('savukoski', "Savukoski"), + ('seinajoki', "Seinäjoki"), + ('sievi', "Sievi"), + ('siikainen', "Siikainen"), + ('siikajoki', "Siikajoki"), + ('siikalatva', "Siikalatva"), + ('siilinjarvi', "Siilinjärvi"), + ('simo', "Simo"), + ('sipoo', "Sipoo"), + ('siuntio', "Siuntio"), + ('sodankyla', "Sodankylä"), + ('soini', "Soini"), + ('somero', "Somero"), + ('sonkajarvi', "Sonkajärvi"), + ('sotkamo', "Sotkamo"), + ('sottunga', "Sottunga"), + ('sulkava', "Sulkava"), + ('sund', "Sund"), + ('suomenniemi', "Suomenniemi"), + ('suomussalmi', "Suomussalmi"), + ('suonenjoki', "Suonenjoki"), + ('sysma', "Sysmä"), + ('sakyla', "Säkylä"), + ('taipalsaari', "Taipalsaari"), + ('taivalkoski', "Taivalkoski"), + ('taivassalo', "Taivassalo"), + ('tammela', "Tammela"), + ('tampere', "Tampere"), + ('tarvasjoki', "Tarvasjoki"), + ('tervo', "Tervo"), + ('tervola', "Tervola"), + ('teuva', "Teuva"), + ('tohmajarvi', "Tohmajärvi"), + ('toholampi', "Toholampi"), + ('toivakka', "Toivakka"), + ('tornio', "Tornio"), + ('turku', "Turku"), + ('tuusniemi', "Tuusniemi"), + ('tuusula', "Tuusula"), + ('tyrnava', "Tyrnävä"), + ('toysa', "Töysä"), + ('ulvila', "Ulvila"), + ('urjala', "Urjala"), + ('utajarvi', "Utajärvi"), + ('utsjoki', "Utsjoki"), + ('uurainen', "Uurainen"), + ('uusikaarlepyy', "Uusikaarlepyy"), + ('uusikaupunki', "Uusikaupunki"), + ('vaala', "Vaala"), + ('vaasa', "Vaasa"), + ('valkeakoski', "Valkeakoski"), + ('valtimo', "Valtimo"), + ('vantaa', "Vantaa"), + ('varkaus', "Varkaus"), + ('varpaisjarvi', "Varpaisjärvi"), + ('vehmaa', "Vehmaa"), + ('vesanto', "Vesanto"), + ('vesilahti', "Vesilahti"), + ('veteli', "Veteli"), + ('vierema', "Vieremä"), + ('vihanti', "Vihanti"), + ('vihti', "Vihti"), + ('viitasaari', "Viitasaari"), + ('vimpeli', "Vimpeli"), + ('virolahti', "Virolahti"), + ('virrat', "Virrat"), + ('vardo', "Vårdö"), + ('vahakyro', "Vähäkyrö"), + ('voyri-maksamaa', "Vöyri-Maksamaa"), + ('yli-ii', "Yli-Ii"), + ('ylitornio', "Ylitornio"), + ('ylivieska', "Ylivieska"), + ('ylojarvi', "Ylöjärvi"), + ('ypaja', "Ypäjä"), + ('ahtari', "Ähtäri"), + ('aanekoski', "Äänekoski") +) diff --git a/django/contrib/localflavor/fi/forms.py b/django/contrib/localflavor/fi/forms.py index ddc3b48c54..633f3e5f1b 100644 --- a/django/contrib/localflavor/fi/forms.py +++ b/django/contrib/localflavor/fi/forms.py @@ -2,7 +2,7 @@ FI-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -36,7 +36,7 @@ class FISocialSecurityNumber(Field): def clean(self, value): super(FISocialSecurityNumber, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' checkmarks = "0123456789ABCDEFHJKLMNPRSTUVWXY" result = re.match(r"""^ @@ -51,5 +51,5 @@ class FISocialSecurityNumber(Field): gd = result.groupdict() checksum = int(gd['date'] + gd['serial']) if checkmarks[checksum % len(checkmarks)] == gd['checksum'].upper(): - return u'%s' % value.upper() + return '%s' % value.upper() raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/fr/forms.py b/django/contrib/localflavor/fr/forms.py index 34e4a96bf4..8b841fff5f 100644 --- a/django/contrib/localflavor/fr/forms.py +++ b/django/contrib/localflavor/fr/forms.py @@ -1,15 +1,15 @@ """ FR-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re from django.contrib.localflavor.fr.fr_department import DEPARTMENT_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError -from django.forms.fields import Field, RegexField, Select -from django.utils.encoding import smart_unicode +from django.forms.fields import CharField, RegexField, Select +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -20,11 +20,11 @@ class FRZipCodeField(RegexField): 'invalid': _('Enter a zip code in the format XXXXX.'), } - def __init__(self, max_length=None, min_length=None, *args, **kwargs): + def __init__(self, max_length=5, min_length=5, *args, **kwargs): super(FRZipCodeField, self).__init__(r'^\d{5}$', max_length, min_length, *args, **kwargs) -class FRPhoneNumberField(Field): +class FRPhoneNumberField(CharField): """ Validate local French phone number (not international ones) The correct format is '0X XX XX XX XX'. @@ -35,14 +35,18 @@ class FRPhoneNumberField(Field): 'invalid': _('Phone numbers must be in 0X XX XX XX XX format.'), } + def __init__(self, max_length=14, min_length=10, *args, **kwargs): + super(FRPhoneNumberField, self).__init__( + max_length, min_length, *args, **kwargs) + def clean(self, value): super(FRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' - value = re.sub('(\.|\s)', '', smart_unicode(value)) + return '' + value = re.sub('(\.|\s)', '', smart_text(value)) m = phone_digits_re.search(value) if m: - return u'%s %s %s %s %s' % (value[0:2], value[2:4], value[4:6], value[6:8], value[8:10]) + return '%s %s %s %s %s' % (value[0:2], value[2:4], value[4:6], value[6:8], value[8:10]) raise ValidationError(self.error_messages['invalid']) class FRDepartmentSelect(Select): @@ -51,4 +55,3 @@ class FRDepartmentSelect(Select): """ def __init__(self, attrs=None): super(FRDepartmentSelect, self).__init__(attrs, choices=DEPARTMENT_CHOICES) - diff --git a/django/contrib/localflavor/fr/fr_department.py b/django/contrib/localflavor/fr/fr_department.py index 9f146ac8f9..a2cca957c2 100644 --- a/django/contrib/localflavor/fr/fr_department.py +++ b/django/contrib/localflavor/fr/fr_department.py @@ -1,117 +1,118 @@ # -*- coding: utf-8 -*- # See the "Code officiel géographique" on the INSEE website . +from __future__ import unicode_literals DEPARTMENT_CHOICES = ( # Metropolitan departments - ('01', u'01 - Ain'), - ('02', u'02 - Aisne'), - ('03', u'03 - Allier'), - ('04', u'04 - Alpes-de-Haute-Provence'), - ('05', u'05 - Hautes-Alpes'), - ('06', u'06 - Alpes-Maritimes'), - ('07', u'07 - Ardèche'), - ('08', u'08 - Ardennes'), - ('09', u'09 - Ariège'), - ('10', u'10 - Aube'), - ('11', u'11 - Aude'), - ('12', u'12 - Aveyron'), - ('13', u'13 - Bouches-du-Rhône'), - ('14', u'14 - Calvados'), - ('15', u'15 - Cantal'), - ('16', u'16 - Charente'), - ('17', u'17 - Charente-Maritime'), - ('18', u'18 - Cher'), - ('19', u'19 - Corrèze'), - ('2A', u'2A - Corse-du-Sud'), - ('2B', u'2B - Haute-Corse'), - ('21', u'21 - Côte-d\'Or'), - ('22', u'22 - Côtes-d\'Armor'), - ('23', u'23 - Creuse'), - ('24', u'24 - Dordogne'), - ('25', u'25 - Doubs'), - ('26', u'26 - Drôme'), - ('27', u'27 - Eure'), - ('28', u'28 - Eure-et-Loir'), - ('29', u'29 - Finistère'), - ('30', u'30 - Gard'), - ('31', u'31 - Haute-Garonne'), - ('32', u'32 - Gers'), - ('33', u'33 - Gironde'), - ('34', u'34 - Hérault'), - ('35', u'35 - Ille-et-Vilaine'), - ('36', u'36 - Indre'), - ('37', u'37 - Indre-et-Loire'), - ('38', u'38 - Isère'), - ('39', u'39 - Jura'), - ('40', u'40 - Landes'), - ('41', u'41 - Loir-et-Cher'), - ('42', u'42 - Loire'), - ('43', u'43 - Haute-Loire'), - ('44', u'44 - Loire-Atlantique'), - ('45', u'45 - Loiret'), - ('46', u'46 - Lot'), - ('47', u'47 - Lot-et-Garonne'), - ('48', u'48 - Lozère'), - ('49', u'49 - Maine-et-Loire'), - ('50', u'50 - Manche'), - ('51', u'51 - Marne'), - ('52', u'52 - Haute-Marne'), - ('53', u'53 - Mayenne'), - ('54', u'54 - Meurthe-et-Moselle'), - ('55', u'55 - Meuse'), - ('56', u'56 - Morbihan'), - ('57', u'57 - Moselle'), - ('58', u'58 - Nièvre'), - ('59', u'59 - Nord'), - ('60', u'60 - Oise'), - ('61', u'61 - Orne'), - ('62', u'62 - Pas-de-Calais'), - ('63', u'63 - Puy-de-Dôme'), - ('64', u'64 - Pyrénées-Atlantiques'), - ('65', u'65 - Hautes-Pyrénées'), - ('66', u'66 - Pyrénées-Orientales'), - ('67', u'67 - Bas-Rhin'), - ('68', u'68 - Haut-Rhin'), - ('69', u'69 - Rhône'), - ('70', u'70 - Haute-Saône'), - ('71', u'71 - Saône-et-Loire'), - ('72', u'72 - Sarthe'), - ('73', u'73 - Savoie'), - ('74', u'74 - Haute-Savoie'), - ('75', u'75 - Paris'), - ('76', u'76 - Seine-Maritime'), - ('77', u'77 - Seine-et-Marne'), - ('78', u'78 - Yvelines'), - ('79', u'79 - Deux-Sèvres'), - ('80', u'80 - Somme'), - ('81', u'81 - Tarn'), - ('82', u'82 - Tarn-et-Garonne'), - ('83', u'83 - Var'), - ('84', u'84 - Vaucluse'), - ('85', u'85 - Vendée'), - ('86', u'86 - Vienne'), - ('87', u'87 - Haute-Vienne'), - ('88', u'88 - Vosges'), - ('89', u'89 - Yonne'), - ('90', u'90 - Territoire de Belfort'), - ('91', u'91 - Essonne'), - ('92', u'92 - Hauts-de-Seine'), - ('93', u'93 - Seine-Saint-Denis'), - ('94', u'94 - Val-de-Marne'), - ('95', u'95 - Val-d\'Oise'), + ('01', '01 - Ain'), + ('02', '02 - Aisne'), + ('03', '03 - Allier'), + ('04', '04 - Alpes-de-Haute-Provence'), + ('05', '05 - Hautes-Alpes'), + ('06', '06 - Alpes-Maritimes'), + ('07', '07 - Ardèche'), + ('08', '08 - Ardennes'), + ('09', '09 - Ariège'), + ('10', '10 - Aube'), + ('11', '11 - Aude'), + ('12', '12 - Aveyron'), + ('13', '13 - Bouches-du-Rhône'), + ('14', '14 - Calvados'), + ('15', '15 - Cantal'), + ('16', '16 - Charente'), + ('17', '17 - Charente-Maritime'), + ('18', '18 - Cher'), + ('19', '19 - Corrèze'), + ('2A', '2A - Corse-du-Sud'), + ('2B', '2B - Haute-Corse'), + ('21', '21 - Côte-d\'Or'), + ('22', '22 - Côtes-d\'Armor'), + ('23', '23 - Creuse'), + ('24', '24 - Dordogne'), + ('25', '25 - Doubs'), + ('26', '26 - Drôme'), + ('27', '27 - Eure'), + ('28', '28 - Eure-et-Loir'), + ('29', '29 - Finistère'), + ('30', '30 - Gard'), + ('31', '31 - Haute-Garonne'), + ('32', '32 - Gers'), + ('33', '33 - Gironde'), + ('34', '34 - Hérault'), + ('35', '35 - Ille-et-Vilaine'), + ('36', '36 - Indre'), + ('37', '37 - Indre-et-Loire'), + ('38', '38 - Isère'), + ('39', '39 - Jura'), + ('40', '40 - Landes'), + ('41', '41 - Loir-et-Cher'), + ('42', '42 - Loire'), + ('43', '43 - Haute-Loire'), + ('44', '44 - Loire-Atlantique'), + ('45', '45 - Loiret'), + ('46', '46 - Lot'), + ('47', '47 - Lot-et-Garonne'), + ('48', '48 - Lozère'), + ('49', '49 - Maine-et-Loire'), + ('50', '50 - Manche'), + ('51', '51 - Marne'), + ('52', '52 - Haute-Marne'), + ('53', '53 - Mayenne'), + ('54', '54 - Meurthe-et-Moselle'), + ('55', '55 - Meuse'), + ('56', '56 - Morbihan'), + ('57', '57 - Moselle'), + ('58', '58 - Nièvre'), + ('59', '59 - Nord'), + ('60', '60 - Oise'), + ('61', '61 - Orne'), + ('62', '62 - Pas-de-Calais'), + ('63', '63 - Puy-de-Dôme'), + ('64', '64 - Pyrénées-Atlantiques'), + ('65', '65 - Hautes-Pyrénées'), + ('66', '66 - Pyrénées-Orientales'), + ('67', '67 - Bas-Rhin'), + ('68', '68 - Haut-Rhin'), + ('69', '69 - Rhône'), + ('70', '70 - Haute-Saône'), + ('71', '71 - Saône-et-Loire'), + ('72', '72 - Sarthe'), + ('73', '73 - Savoie'), + ('74', '74 - Haute-Savoie'), + ('75', '75 - Paris'), + ('76', '76 - Seine-Maritime'), + ('77', '77 - Seine-et-Marne'), + ('78', '78 - Yvelines'), + ('79', '79 - Deux-Sèvres'), + ('80', '80 - Somme'), + ('81', '81 - Tarn'), + ('82', '82 - Tarn-et-Garonne'), + ('83', '83 - Var'), + ('84', '84 - Vaucluse'), + ('85', '85 - Vendée'), + ('86', '86 - Vienne'), + ('87', '87 - Haute-Vienne'), + ('88', '88 - Vosges'), + ('89', '89 - Yonne'), + ('90', '90 - Territoire de Belfort'), + ('91', '91 - Essonne'), + ('92', '92 - Hauts-de-Seine'), + ('93', '93 - Seine-Saint-Denis'), + ('94', '94 - Val-de-Marne'), + ('95', '95 - Val-d\'Oise'), # Overseas departments, communities, and other territories - ('971', u'971 - Guadeloupe'), - ('972', u'972 - Martinique'), - ('973', u'973 - Guyane'), - ('974', u'974 - La Réunion'), - ('975', u'975 - Saint-Pierre-et-Miquelon'), - ('976', u'976 - Mayotte'), - ('977', u'977 - Saint-Barthélemy'), - ('978', u'978 - Saint-Martin'), - ('984', u'984 - Terres australes et antarctiques françaises'), - ('986', u'986 - Wallis et Futuna'), - ('987', u'987 - Polynésie française'), - ('988', u'988 - Nouvelle-Calédonie'), - ('989', u'989 - Île de Clipperton'), + ('971', '971 - Guadeloupe'), + ('972', '972 - Martinique'), + ('973', '973 - Guyane'), + ('974', '974 - La Réunion'), + ('975', '975 - Saint-Pierre-et-Miquelon'), + ('976', '976 - Mayotte'), + ('977', '977 - Saint-Barthélemy'), + ('978', '978 - Saint-Martin'), + ('984', '984 - Terres australes et antarctiques françaises'), + ('986', '986 - Wallis et Futuna'), + ('987', '987 - Polynésie française'), + ('988', '988 - Nouvelle-Calédonie'), + ('989', '989 - Île de Clipperton'), ) diff --git a/django/contrib/localflavor/gb/forms.py b/django/contrib/localflavor/gb/forms.py index a6658578b1..bf90f80281 100644 --- a/django/contrib/localflavor/gb/forms.py +++ b/django/contrib/localflavor/gb/forms.py @@ -2,7 +2,7 @@ GB-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -22,7 +22,7 @@ class GBPostcodeField(CharField): The value is uppercased and a space added in the correct place, if required. """ default_error_messages = { - 'invalid': _(u'Enter a valid postcode.'), + 'invalid': _('Enter a valid postcode.'), } outcode_pattern = '[A-PR-UWYZ]([0-9]{1,2}|([A-HIK-Y][0-9](|[0-9]|[ABEHMNPRVWXY]))|[0-9][A-HJKSTUW])' incode_pattern = '[0-9][ABD-HJLNP-UW-Z]{2}' @@ -31,7 +31,7 @@ class GBPostcodeField(CharField): def clean(self, value): value = super(GBPostcodeField, self).clean(value) - if value == u'': + if value == '': return value postcode = value.upper().strip() # Put a single space before the incode (second part). diff --git a/django/contrib/localflavor/hk/forms.py b/django/contrib/localflavor/hk/forms.py index 852ef7d4b2..ab4f70f193 100644 --- a/django/contrib/localflavor/hk/forms.py +++ b/django/contrib/localflavor/hk/forms.py @@ -1,14 +1,14 @@ """ Hong Kong specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re from django.core.validators import EMPTY_VALUES from django.forms import CharField from django.forms import ValidationError -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -51,14 +51,14 @@ class HKPhoneNumberField(CharField): super(HKPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' - value = re.sub('(\(|\)|\s+|\+)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+|\+)', '', smart_text(value)) m = hk_phone_digits_re.search(value) if not m: raise ValidationError(self.error_messages['invalid']) - value = u'%s-%s' % (m.group(1), m.group(2)) + value = '%s-%s' % (m.group(1), m.group(2)) for special in hk_special_numbers: if value.startswith(special): raise ValidationError(self.error_messages['disguise']) diff --git a/django/contrib/localflavor/hr/forms.py b/django/contrib/localflavor/hr/forms.py index 0ff283d6c8..b935fd8a3a 100644 --- a/django/contrib/localflavor/hr/forms.py +++ b/django/contrib/localflavor/hr/forms.py @@ -2,7 +2,7 @@ """ HR-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -12,15 +12,15 @@ from django.contrib.localflavor.hr.hr_choices import ( from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, Select, RegexField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ jmbg_re = re.compile(r'^(?P
    \d{2})(?P\d{2})(?P\d{3})' + \ r'(?P\d{2})(?P\d{3})(?P\d{1})$') oib_re = re.compile(r'^\d{11}$') -plate_re = re.compile(ur'^(?P[A-ZČŠŽ]{2})' + \ - ur'(?P\d{3,4})(?P[ABCDEFGHIJKLMNOPRSTUVZ]{1,2})$') +plate_re = re.compile(r'^(?P[A-ZČŠŽ]{2})' + \ + r'(?P\d{3,4})(?P[ABCDEFGHIJKLMNOPRSTUVZ]{1,2})$') postal_code_re = re.compile(r'^\d{5}$') phone_re = re.compile(r'^(\+385|00385|0)(?P\d{2})(?P\d{6,7})$') jmbag_re = re.compile(r'^601983(?P\d{1})1(?P\d{10})(?P\d{1})$') @@ -79,7 +79,7 @@ class HRJMBGField(Field): def clean(self, value): super(HRJMBGField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip() @@ -110,7 +110,7 @@ class HRJMBGField(Field): if not str(m) == k: raise ValidationError(self.error_messages['invalid']) - return u'%s' % (value, ) + return '%s' % (value, ) class HROIBField(RegexField): @@ -130,7 +130,7 @@ class HROIBField(RegexField): def clean(self, value): super(HROIBField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' return '%s' % (value, ) @@ -157,9 +157,9 @@ class HRLicensePlateField(Field): def clean(self, value): super(HRLicensePlateField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' - value = re.sub(r'[\s\-]+', '', smart_unicode(value.strip())).upper() + value = re.sub(r'[\s\-]+', '', smart_text(value.strip())).upper() matches = plate_re.search(value) if matches is None: @@ -175,7 +175,7 @@ class HRLicensePlateField(Field): if int(number) == 0: raise ValidationError(self.error_messages['number']) - return u'%s %s-%s' % (prefix,number,matches.group('suffix'), ) + return '%s %s-%s' % (prefix,number,matches.group('suffix'), ) class HRPostalCodeField(Field): @@ -193,7 +193,7 @@ class HRPostalCodeField(Field): def clean(self, value): super(HRPostalCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip() if not postal_code_re.search(value): @@ -223,9 +223,9 @@ class HRPhoneNumberField(Field): def clean(self, value): super(HRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' - value = re.sub(r'[\-\s\(\)]', '', smart_unicode(value)) + value = re.sub(r'[\-\s\(\)]', '', smart_text(value)) matches = phone_re.search(value) if matches is None: @@ -262,7 +262,7 @@ class HRJMBAGField(Field): def clean(self, value): super(HRJMBAGField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub(r'[\-\s]', '', value.strip()) diff --git a/django/contrib/localflavor/hr/hr_choices.py b/django/contrib/localflavor/hr/hr_choices.py index 24d3d42c5b..beb2969405 100644 --- a/django/contrib/localflavor/hr/hr_choices.py +++ b/django/contrib/localflavor/hr/hr_choices.py @@ -6,30 +6,32 @@ Sources: Croatia doesn't have official abbreviations for counties. The ones provided are in common use. """ +from __future__ import unicode_literals + from django.utils.translation import ugettext_lazy as _ HR_COUNTY_CHOICES = ( ('GZG', _('Grad Zagreb')), - (u'BBŽ', _(u'Bjelovarsko-bilogorska županija')), - (u'BPŽ', _(u'Brodsko-posavska županija')), - (u'DNŽ', _(u'Dubrovačko-neretvanska županija')), - (u'IŽ', _(u'Istarska županija')), - (u'KŽ', _(u'Karlovačka županija')), - (u'KKŽ', _(u'Koprivničko-križevačka županija')), - (u'KZŽ', _(u'Krapinsko-zagorska županija')), - (u'LSŽ', _(u'Ličko-senjska županija')), - (u'MŽ', _(u'Međimurska županija')), - (u'OBŽ', _(u'Osječko-baranjska županija')), - (u'PSŽ', _(u'Požeško-slavonska županija')), - (u'PGŽ', _(u'Primorsko-goranska županija')), - (u'SMŽ', _(u'Sisačko-moslavačka županija')), - (u'SDŽ', _(u'Splitsko-dalmatinska županija')), - (u'ŠKŽ', _(u'Šibensko-kninska županija')), - (u'VŽ', _(u'Varaždinska županija')), - (u'VPŽ', _(u'Virovitičko-podravska županija')), - (u'VSŽ', _(u'Vukovarsko-srijemska županija')), - (u'ZDŽ', _(u'Zadarska županija')), - (u'ZGŽ', _(u'Zagrebačka županija')), + ('BBŽ', _('Bjelovarsko-bilogorska županija')), + ('BPŽ', _('Brodsko-posavska županija')), + ('DNŽ', _('Dubrovačko-neretvanska županija')), + ('IŽ', _('Istarska županija')), + ('KŽ', _('Karlovačka županija')), + ('KKŽ', _('Koprivničko-križevačka županija')), + ('KZŽ', _('Krapinsko-zagorska županija')), + ('LSŽ', _('Ličko-senjska županija')), + ('MŽ', _('Međimurska županija')), + ('OBŽ', _('Osječko-baranjska županija')), + ('PSŽ', _('Požeško-slavonska županija')), + ('PGŽ', _('Primorsko-goranska županija')), + ('SMŽ', _('Sisačko-moslavačka županija')), + ('SDŽ', _('Splitsko-dalmatinska županija')), + ('ŠKŽ', _('Šibensko-kninska županija')), + ('VŽ', _('Varaždinska županija')), + ('VPŽ', _('Virovitičko-podravska županija')), + ('VSŽ', _('Vukovarsko-srijemska županija')), + ('ZDŽ', _('Zadarska županija')), + ('ZGŽ', _('Zagrebačka županija')), ) """ @@ -42,7 +44,7 @@ Only common license plate prefixes are provided. Special cases and obsolete pref HR_LICENSE_PLATE_PREFIX_CHOICES = ( ('BJ', 'BJ'), ('BM', 'BM'), - (u'ČK', u'ČK'), + ('ČK', 'ČK'), ('DA', 'DA'), ('DE', 'DE'), ('DJ', 'DJ'), @@ -53,27 +55,27 @@ HR_LICENSE_PLATE_PREFIX_CHOICES = ( ('KC', 'KC'), ('KR', 'KR'), ('KT', 'KT'), - (u'KŽ', u'KŽ'), + ('KŽ', 'KŽ'), ('MA', 'MA'), ('NA', 'NA'), ('NG', 'NG'), ('OG', 'OG'), ('OS', 'OS'), ('PU', 'PU'), - (u'PŽ', u'PŽ'), + ('PŽ', 'PŽ'), ('RI', 'RI'), ('SB', 'SB'), ('SK', 'SK'), ('SL', 'SL'), ('ST', 'ST'), - (u'ŠI', u'ŠI'), + ('ŠI', 'ŠI'), ('VK', 'VK'), ('VT', 'VT'), ('VU', 'VU'), - (u'VŽ', u'VŽ'), + ('VŽ', 'VŽ'), ('ZD', 'ZD'), ('ZG', 'ZG'), - (u'ŽU', u'ŽU'), + ('ŽU', 'ŽU'), ) """ diff --git a/django/contrib/localflavor/id/forms.py b/django/contrib/localflavor/id/forms.py index 9439dba594..2005dbc75c 100644 --- a/django/contrib/localflavor/id/forms.py +++ b/django/contrib/localflavor/id/forms.py @@ -2,7 +2,7 @@ ID-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re import time @@ -11,7 +11,7 @@ from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, Select from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text postcode_re = re.compile(r'^[1-9]\d{4}$') @@ -34,7 +34,7 @@ class IDPostCodeField(Field): def clean(self, value): super(IDPostCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip() if not postcode_re.search(value): @@ -47,7 +47,7 @@ class IDPostCodeField(Field): if value[0] == '1' and value[4] != '0': raise ValidationError(self.error_messages['invalid']) - return u'%s' % (value, ) + return '%s' % (value, ) class IDProvinceSelect(Select): @@ -75,12 +75,12 @@ class IDPhoneNumberField(Field): def clean(self, value): super(IDPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' - phone_number = re.sub(r'[\-\s\(\)]', '', smart_unicode(value)) + phone_number = re.sub(r'[\-\s\(\)]', '', smart_text(value)) if phone_re.search(phone_number): - return smart_unicode(value) + return smart_text(value) raise ValidationError(self.error_messages['invalid']) @@ -117,10 +117,10 @@ class IDLicensePlateField(Field): from django.contrib.localflavor.id.id_choices import LICENSE_PLATE_PREFIX_CHOICES super(IDLicensePlateField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' plate_number = re.sub(r'\s+', ' ', - smart_unicode(value.strip())).upper() + smart_text(value.strip())).upper() matches = plate_re.search(plate_number) if matches is None: @@ -179,9 +179,9 @@ class IDNationalIdentityNumberField(Field): def clean(self, value): super(IDNationalIdentityNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' - value = re.sub(r'[\s.]', '', smart_unicode(value)) + value = re.sub(r'[\s.]', '', smart_text(value)) if not nik_re.search(value): raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/il/forms.py b/django/contrib/localflavor/il/forms.py index a14358737e..de6ba6b23d 100644 --- a/django/contrib/localflavor/il/forms.py +++ b/django/contrib/localflavor/il/forms.py @@ -1,6 +1,7 @@ """ Israeli-specific form helpers """ +from __future__ import unicode_literals import re from django.core.exceptions import ValidationError @@ -28,7 +29,7 @@ class ILPostalCodeField(RegexField): """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXXXX'), + 'invalid': _('Enter a postal code in the format XXXXX'), } def __init__(self, *args, **kwargs): @@ -47,14 +48,14 @@ class ILIDNumberField(Field): """ default_error_messages = { - 'invalid': _(u'Enter a valid ID number.'), + 'invalid': _('Enter a valid ID number.'), } def clean(self, value): value = super(ILIDNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = id_number_re.match(value) if not match: diff --git a/django/contrib/localflavor/in_/forms.py b/django/contrib/localflavor/in_/forms.py index 11011e1df2..5c1d009ef4 100644 --- a/django/contrib/localflavor/in_/forms.py +++ b/django/contrib/localflavor/in_/forms.py @@ -2,7 +2,7 @@ India-specific Form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -10,7 +10,7 @@ from django.contrib.localflavor.in_.in_states import STATES_NORMALIZED, STATE_CH from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, CharField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -38,7 +38,7 @@ phone_digits_re = re.compile(r""" class INZipCodeField(RegexField): default_error_messages = { - 'invalid': _(u'Enter a zip code in the format XXXXXX or XXX XXX.'), + 'invalid': _('Enter a zip code in the format XXXXXX or XXX XXX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -48,7 +48,7 @@ class INZipCodeField(RegexField): def clean(self, value): super(INZipCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' # Convert to "NNNNNN" if "NNN NNN" given value = re.sub(r'^(\d{3})\s(\d{3})$', r'\1\2', value) return value @@ -61,20 +61,20 @@ class INStateField(Field): registration abbreviation for the given state or union territory """ default_error_messages = { - 'invalid': _(u'Enter an Indian state or territory.'), + 'invalid': _('Enter an Indian state or territory.'), } def clean(self, value): super(INStateField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' try: value = value.strip().lower() except AttributeError: pass else: try: - return smart_unicode(STATES_NORMALIZED[value.strip().lower()]) + return smart_text(STATES_NORMALIZED[value.strip().lower()]) except KeyError: pass raise ValidationError(self.error_messages['invalid']) @@ -106,10 +106,10 @@ class INPhoneNumberField(CharField): def clean(self, value): super(INPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' - value = smart_unicode(value) + return '' + value = smart_text(value) m = phone_digits_re.match(value) if m: - return u'%s' % (value) + return '%s' % (value) raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/is_/forms.py b/django/contrib/localflavor/is_/forms.py index ca7bd5004e..1ae3e012a1 100644 --- a/django/contrib/localflavor/is_/forms.py +++ b/django/contrib/localflavor/is_/forms.py @@ -2,14 +2,14 @@ Iceland specific form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.localflavor.is_.is_postalcodes import IS_POSTALCODES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import RegexField from django.forms.widgets import Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -20,7 +20,7 @@ class ISIdNumberField(RegexField): """ default_error_messages = { 'invalid': _('Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.'), - 'checksum': _(u'The Icelandic identification number is not valid.'), + 'checksum': _('The Icelandic identification number is not valid.'), } def __init__(self, max_length=11, min_length=10, *args, **kwargs): @@ -31,7 +31,7 @@ class ISIdNumberField(RegexField): value = super(ISIdNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = self._canonify(value) if self._validate(value): @@ -58,7 +58,7 @@ class ISIdNumberField(RegexField): Takes in the value in canonical form and returns it in the common display format. """ - return smart_unicode(value[:6]+'-'+value[6:]) + return smart_text(value[:6]+'-'+value[6:]) class ISPhoneNumberField(RegexField): """ @@ -73,7 +73,7 @@ class ISPhoneNumberField(RegexField): value = super(ISPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' return value.replace('-', '').replace(' ', '') diff --git a/django/contrib/localflavor/is_/is_postalcodes.py b/django/contrib/localflavor/is_/is_postalcodes.py index 4feca9c013..f1f3357c1c 100644 --- a/django/contrib/localflavor/is_/is_postalcodes.py +++ b/django/contrib/localflavor/is_/is_postalcodes.py @@ -1,151 +1,152 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals IS_POSTALCODES = ( - ('101', u'101 Reykjavík'), - ('103', u'103 Reykjavík'), - ('104', u'104 Reykjavík'), - ('105', u'105 Reykjavík'), - ('107', u'107 Reykjavík'), - ('108', u'108 Reykjavík'), - ('109', u'109 Reykjavík'), - ('110', u'110 Reykjavík'), - ('111', u'111 Reykjavík'), - ('112', u'112 Reykjavík'), - ('113', u'113 Reykjavík'), - ('116', u'116 Kjalarnes'), - ('121', u'121 Reykjavík'), - ('123', u'123 Reykjavík'), - ('124', u'124 Reykjavík'), - ('125', u'125 Reykjavík'), - ('127', u'127 Reykjavík'), - ('128', u'128 Reykjavík'), - ('129', u'129 Reykjavík'), - ('130', u'130 Reykjavík'), - ('132', u'132 Reykjavík'), - ('150', u'150 Reykjavík'), - ('155', u'155 Reykjavík'), - ('170', u'170 Seltjarnarnes'), - ('172', u'172 Seltjarnarnes'), - ('190', u'190 Vogar'), - ('200', u'200 Kópavogur'), - ('201', u'201 Kópavogur'), - ('202', u'202 Kópavogur'), - ('203', u'203 Kópavogur'), - ('210', u'210 Garðabær'), - ('212', u'212 Garðabær'), - ('220', u'220 Hafnarfjörður'), - ('221', u'221 Hafnarfjörður'), - ('222', u'222 Hafnarfjörður'), - ('225', u'225 Álftanes'), - ('230', u'230 Reykjanesbær'), - ('232', u'232 Reykjanesbær'), - ('233', u'233 Reykjanesbær'), - ('235', u'235 Keflavíkurflugvöllur'), - ('240', u'240 Grindavík'), - ('245', u'245 Sandgerði'), - ('250', u'250 Garður'), - ('260', u'260 Reykjanesbær'), - ('270', u'270 Mosfellsbær'), - ('300', u'300 Akranes'), - ('301', u'301 Akranes'), - ('302', u'302 Akranes'), - ('310', u'310 Borgarnes'), - ('311', u'311 Borgarnes'), - ('320', u'320 Reykholt í Borgarfirði'), - ('340', u'340 Stykkishólmur'), - ('345', u'345 Flatey á Breiðafirði'), - ('350', u'350 Grundarfjörður'), - ('355', u'355 Ólafsvík'), - ('356', u'356 Snæfellsbær'), - ('360', u'360 Hellissandur'), - ('370', u'370 Búðardalur'), - ('371', u'371 Búðardalur'), - ('380', u'380 Reykhólahreppur'), - ('400', u'400 Ísafjörður'), - ('401', u'401 Ísafjörður'), - ('410', u'410 Hnífsdalur'), - ('415', u'415 Bolungarvík'), - ('420', u'420 Súðavík'), - ('425', u'425 Flateyri'), - ('430', u'430 Suðureyri'), - ('450', u'450 Patreksfjörður'), - ('451', u'451 Patreksfjörður'), - ('460', u'460 Tálknafjörður'), - ('465', u'465 Bíldudalur'), - ('470', u'470 Þingeyri'), - ('471', u'471 Þingeyri'), - ('500', u'500 Staður'), - ('510', u'510 Hólmavík'), - ('512', u'512 Hólmavík'), - ('520', u'520 Drangsnes'), - ('522', u'522 Kjörvogur'), - ('523', u'523 Bær'), - ('524', u'524 Norðurfjörður'), - ('530', u'530 Hvammstangi'), - ('531', u'531 Hvammstangi'), - ('540', u'540 Blönduós'), - ('541', u'541 Blönduós'), - ('545', u'545 Skagaströnd'), - ('550', u'550 Sauðárkrókur'), - ('551', u'551 Sauðárkrókur'), - ('560', u'560 Varmahlíð'), - ('565', u'565 Hofsós'), - ('566', u'566 Hofsós'), - ('570', u'570 Fljót'), - ('580', u'580 Siglufjörður'), - ('600', u'600 Akureyri'), - ('601', u'601 Akureyri'), - ('602', u'602 Akureyri'), - ('603', u'603 Akureyri'), - ('610', u'610 Grenivík'), - ('611', u'611 Grímsey'), - ('620', u'620 Dalvík'), - ('621', u'621 Dalvík'), - ('625', u'625 Ólafsfjörður'), - ('630', u'630 Hrísey'), - ('640', u'640 Húsavík'), - ('641', u'641 Húsavík'), - ('645', u'645 Fosshóll'), - ('650', u'650 Laugar'), - ('660', u'660 Mývatn'), - ('670', u'670 Kópasker'), - ('671', u'671 Kópasker'), - ('675', u'675 Raufarhöfn'), - ('680', u'680 Þórshöfn'), - ('681', u'681 Þórshöfn'), - ('685', u'685 Bakkafjörður'), - ('690', u'690 Vopnafjörður'), - ('700', u'700 Egilsstaðir'), - ('701', u'701 Egilsstaðir'), - ('710', u'710 Seyðisfjörður'), - ('715', u'715 Mjóifjörður'), - ('720', u'720 Borgarfjörður eystri'), - ('730', u'730 Reyðarfjörður'), - ('735', u'735 Eskifjörður'), - ('740', u'740 Neskaupstaður'), - ('750', u'750 Fáskrúðsfjörður'), - ('755', u'755 Stöðvarfjörður'), - ('760', u'760 Breiðdalsvík'), - ('765', u'765 Djúpivogur'), - ('780', u'780 Höfn í Hornafirði'), - ('781', u'781 Höfn í Hornafirði'), - ('785', u'785 Öræfi'), - ('800', u'800 Selfoss'), - ('801', u'801 Selfoss'), - ('802', u'802 Selfoss'), - ('810', u'810 Hveragerði'), - ('815', u'815 Þorlákshöfn'), - ('820', u'820 Eyrarbakki'), - ('825', u'825 Stokkseyri'), - ('840', u'840 Laugarvatn'), - ('845', u'845 Flúðir'), - ('850', u'850 Hella'), - ('851', u'851 Hella'), - ('860', u'860 Hvolsvöllur'), - ('861', u'861 Hvolsvöllur'), - ('870', u'870 Vík'), - ('871', u'871 Vík'), - ('880', u'880 Kirkjubæjarklaustur'), - ('900', u'900 Vestmannaeyjar'), - ('902', u'902 Vestmannaeyjar') + ('101', '101 Reykjavík'), + ('103', '103 Reykjavík'), + ('104', '104 Reykjavík'), + ('105', '105 Reykjavík'), + ('107', '107 Reykjavík'), + ('108', '108 Reykjavík'), + ('109', '109 Reykjavík'), + ('110', '110 Reykjavík'), + ('111', '111 Reykjavík'), + ('112', '112 Reykjavík'), + ('113', '113 Reykjavík'), + ('116', '116 Kjalarnes'), + ('121', '121 Reykjavík'), + ('123', '123 Reykjavík'), + ('124', '124 Reykjavík'), + ('125', '125 Reykjavík'), + ('127', '127 Reykjavík'), + ('128', '128 Reykjavík'), + ('129', '129 Reykjavík'), + ('130', '130 Reykjavík'), + ('132', '132 Reykjavík'), + ('150', '150 Reykjavík'), + ('155', '155 Reykjavík'), + ('170', '170 Seltjarnarnes'), + ('172', '172 Seltjarnarnes'), + ('190', '190 Vogar'), + ('200', '200 Kópavogur'), + ('201', '201 Kópavogur'), + ('202', '202 Kópavogur'), + ('203', '203 Kópavogur'), + ('210', '210 Garðabær'), + ('212', '212 Garðabær'), + ('220', '220 Hafnarfjörður'), + ('221', '221 Hafnarfjörður'), + ('222', '222 Hafnarfjörður'), + ('225', '225 Álftanes'), + ('230', '230 Reykjanesbær'), + ('232', '232 Reykjanesbær'), + ('233', '233 Reykjanesbær'), + ('235', '235 Keflavíkurflugvöllur'), + ('240', '240 Grindavík'), + ('245', '245 Sandgerði'), + ('250', '250 Garður'), + ('260', '260 Reykjanesbær'), + ('270', '270 Mosfellsbær'), + ('300', '300 Akranes'), + ('301', '301 Akranes'), + ('302', '302 Akranes'), + ('310', '310 Borgarnes'), + ('311', '311 Borgarnes'), + ('320', '320 Reykholt í Borgarfirði'), + ('340', '340 Stykkishólmur'), + ('345', '345 Flatey á Breiðafirði'), + ('350', '350 Grundarfjörður'), + ('355', '355 Ólafsvík'), + ('356', '356 Snæfellsbær'), + ('360', '360 Hellissandur'), + ('370', '370 Búðardalur'), + ('371', '371 Búðardalur'), + ('380', '380 Reykhólahreppur'), + ('400', '400 Ísafjörður'), + ('401', '401 Ísafjörður'), + ('410', '410 Hnífsdalur'), + ('415', '415 Bolungarvík'), + ('420', '420 Súðavík'), + ('425', '425 Flateyri'), + ('430', '430 Suðureyri'), + ('450', '450 Patreksfjörður'), + ('451', '451 Patreksfjörður'), + ('460', '460 Tálknafjörður'), + ('465', '465 Bíldudalur'), + ('470', '470 Þingeyri'), + ('471', '471 Þingeyri'), + ('500', '500 Staður'), + ('510', '510 Hólmavík'), + ('512', '512 Hólmavík'), + ('520', '520 Drangsnes'), + ('522', '522 Kjörvogur'), + ('523', '523 Bær'), + ('524', '524 Norðurfjörður'), + ('530', '530 Hvammstangi'), + ('531', '531 Hvammstangi'), + ('540', '540 Blönduós'), + ('541', '541 Blönduós'), + ('545', '545 Skagaströnd'), + ('550', '550 Sauðárkrókur'), + ('551', '551 Sauðárkrókur'), + ('560', '560 Varmahlíð'), + ('565', '565 Hofsós'), + ('566', '566 Hofsós'), + ('570', '570 Fljót'), + ('580', '580 Siglufjörður'), + ('600', '600 Akureyri'), + ('601', '601 Akureyri'), + ('602', '602 Akureyri'), + ('603', '603 Akureyri'), + ('610', '610 Grenivík'), + ('611', '611 Grímsey'), + ('620', '620 Dalvík'), + ('621', '621 Dalvík'), + ('625', '625 Ólafsfjörður'), + ('630', '630 Hrísey'), + ('640', '640 Húsavík'), + ('641', '641 Húsavík'), + ('645', '645 Fosshóll'), + ('650', '650 Laugar'), + ('660', '660 Mývatn'), + ('670', '670 Kópasker'), + ('671', '671 Kópasker'), + ('675', '675 Raufarhöfn'), + ('680', '680 Þórshöfn'), + ('681', '681 Þórshöfn'), + ('685', '685 Bakkafjörður'), + ('690', '690 Vopnafjörður'), + ('700', '700 Egilsstaðir'), + ('701', '701 Egilsstaðir'), + ('710', '710 Seyðisfjörður'), + ('715', '715 Mjóifjörður'), + ('720', '720 Borgarfjörður eystri'), + ('730', '730 Reyðarfjörður'), + ('735', '735 Eskifjörður'), + ('740', '740 Neskaupstaður'), + ('750', '750 Fáskrúðsfjörður'), + ('755', '755 Stöðvarfjörður'), + ('760', '760 Breiðdalsvík'), + ('765', '765 Djúpivogur'), + ('780', '780 Höfn í Hornafirði'), + ('781', '781 Höfn í Hornafirði'), + ('785', '785 Öræfi'), + ('800', '800 Selfoss'), + ('801', '801 Selfoss'), + ('802', '802 Selfoss'), + ('810', '810 Hveragerði'), + ('815', '815 Þorlákshöfn'), + ('820', '820 Eyrarbakki'), + ('825', '825 Stokkseyri'), + ('840', '840 Laugarvatn'), + ('845', '845 Flúðir'), + ('850', '850 Hella'), + ('851', '851 Hella'), + ('860', '860 Hvolsvöllur'), + ('861', '861 Hvolsvöllur'), + ('870', '870 Vík'), + ('871', '871 Vík'), + ('880', '880 Kirkjubæjarklaustur'), + ('900', '900 Vestmannaeyjar'), + ('902', '902 Vestmannaeyjar') ) diff --git a/django/contrib/localflavor/it/forms.py b/django/contrib/localflavor/it/forms.py index 0060b486bd..916ce9bb3d 100644 --- a/django/contrib/localflavor/it/forms.py +++ b/django/contrib/localflavor/it/forms.py @@ -2,7 +2,7 @@ IT-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -13,7 +13,7 @@ from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text class ITZipCodeField(RegexField): @@ -45,7 +45,7 @@ class ITSocialSecurityNumberField(RegexField): 'Informazioni sulla codificazione delle persone fisiche'. """ default_error_messages = { - 'invalid': _(u'Enter a valid Social Security number.'), + 'invalid': _('Enter a valid Social Security number.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -55,8 +55,8 @@ class ITSocialSecurityNumberField(RegexField): def clean(self, value): value = super(ITSocialSecurityNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' - value = re.sub('\s', u'', value).upper() + return '' + value = re.sub('\s', '', value).upper() try: check_digit = ssn_check_digit(value) except ValueError: @@ -70,13 +70,13 @@ class ITVatNumberField(Field): A form field that validates Italian VAT numbers (partita IVA). """ default_error_messages = { - 'invalid': _(u'Enter a valid VAT number.'), + 'invalid': _('Enter a valid VAT number.'), } def clean(self, value): value = super(ITVatNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' try: vat_number = int(value) except ValueError: @@ -85,4 +85,4 @@ class ITVatNumberField(Field): check_digit = vat_number_check_digit(vat_number[0:10]) if not vat_number[10] == check_digit: raise ValidationError(self.error_messages['invalid']) - return smart_unicode(vat_number) + return smart_text(vat_number) diff --git a/django/contrib/localflavor/it/it_province.py b/django/contrib/localflavor/it/it_province.py index dcaad98c63..5aad1611dd 100644 --- a/django/contrib/localflavor/it/it_province.py +++ b/django/contrib/localflavor/it/it_province.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -* +from __future__ import unicode_literals PROVINCE_CHOICES = ( ('AG', 'Agrigento'), @@ -45,7 +46,7 @@ PROVINCE_CHOICES = ( ('IM', 'Imperia'), ('IS', 'Isernia'), ('SP', 'La Spezia'), - ('AQ', u'L’Aquila'), + ('AQ', 'L’Aquila'), ('LT', 'Latina'), ('LE', 'Lecce'), ('LC', 'Lecco'), diff --git a/django/contrib/localflavor/it/it_region.py b/django/contrib/localflavor/it/it_region.py index 0700b46ea8..e12a1e731b 100644 --- a/django/contrib/localflavor/it/it_region.py +++ b/django/contrib/localflavor/it/it_region.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -* +from __future__ import unicode_literals REGION_CHOICES = ( ('ABR', 'Abruzzo'), @@ -19,6 +20,6 @@ REGION_CHOICES = ( ('TOS', 'Toscana'), ('TAA', 'Trentino-Alto Adige'), ('UMB', 'Umbria'), - ('VAO', u'Valle d’Aosta'), + ('VAO', 'Valle d’Aosta'), ('VEN', 'Veneto'), ) diff --git a/django/contrib/localflavor/it/util.py b/django/contrib/localflavor/it/util.py index c162ff7eff..e1aa9c0419 100644 --- a/django/contrib/localflavor/it/util.py +++ b/django/contrib/localflavor/it/util.py @@ -1,4 +1,4 @@ -from django.utils.encoding import smart_str, smart_unicode +from django.utils.encoding import smart_text def ssn_check_digit(value): "Calculate Italian social security number check digit." @@ -34,11 +34,11 @@ def ssn_check_digit(value): def vat_number_check_digit(vat_number): "Calculate Italian VAT number check digit." - normalized_vat_number = smart_str(vat_number).zfill(10) + normalized_vat_number = smart_text(vat_number).zfill(10) total = 0 for i in range(0, 10, 2): total += int(normalized_vat_number[i]) for i in range(1, 11, 2): quotient , remainder = divmod(int(normalized_vat_number[i]) * 2, 10) total += quotient + remainder - return smart_unicode((10 - total % 10) % 10) + return smart_text((10 - total % 10) % 10) diff --git a/django/contrib/localflavor/kw/forms.py b/django/contrib/localflavor/kw/forms.py index e671408ec8..2c2b023e70 100644 --- a/django/contrib/localflavor/kw/forms.py +++ b/django/contrib/localflavor/kw/forms.py @@ -1,6 +1,8 @@ """ Kuwait-specific Form helpers """ +from __future__ import unicode_literals + import re from datetime import date @@ -40,7 +42,7 @@ class KWCivilIDNumberField(Field): def clean(self, value): super(KWCivilIDNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not re.match(r'^\d{12}$', value): raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/mk/forms.py b/django/contrib/localflavor/mk/forms.py index 33dbfc71a0..3189f0dec6 100644 --- a/django/contrib/localflavor/mk/forms.py +++ b/django/contrib/localflavor/mk/forms.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime @@ -15,14 +15,14 @@ class MKIdentityCardNumberField(RegexField): A Macedonian ID card number. Accepts both old and new format. """ default_error_messages = { - 'invalid': _(u'Identity card numbers must contain' + 'invalid': _('Identity card numbers must contain' ' either 4 to 7 digits or an uppercase letter and 7 digits.'), } def __init__(self, *args, **kwargs): kwargs['min_length'] = None kwargs['max_length'] = 8 - regex = ur'(^[A-Z]{1}\d{7}$)|(^\d{4,7}$)' + regex = r'(^[A-Z]{1}\d{7}$)|(^\d{4,7}$)' super(MKIdentityCardNumberField, self).__init__(regex, *args, **kwargs) @@ -54,9 +54,9 @@ class UMCNField(RegexField): * The last digit of the UMCN passes a checksum test """ default_error_messages = { - 'invalid': _(u'This field should contain exactly 13 digits.'), - 'date': _(u'The first 7 digits of the UMCN must represent a valid past date.'), - 'checksum': _(u'The UMCN is not valid.'), + 'invalid': _('This field should contain exactly 13 digits.'), + 'date': _('The first 7 digits of the UMCN must represent a valid past date.'), + 'checksum': _('The UMCN is not valid.'), } def __init__(self, *args, **kwargs): @@ -68,7 +68,7 @@ class UMCNField(RegexField): value = super(UMCNField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not self._validate_date_part(value): raise ValidationError(self.error_messages['date']) diff --git a/django/contrib/localflavor/mk/mk_choices.py b/django/contrib/localflavor/mk/mk_choices.py index d6d1efa049..fb705ca820 100644 --- a/django/contrib/localflavor/mk/mk_choices.py +++ b/django/contrib/localflavor/mk/mk_choices.py @@ -2,91 +2,93 @@ """ Macedonian municipalities per the reorganization from 2004. """ +from __future__ import unicode_literals + from django.utils.translation import ugettext_lazy as _ MK_MUNICIPALITIES = ( - ('AD', _(u'Aerodrom')), - ('AR', _(u'Aračinovo')), - ('BR', _(u'Berovo')), - ('TL', _(u'Bitola')), - ('BG', _(u'Bogdanci')), - ('VJ', _(u'Bogovinje')), - ('BS', _(u'Bosilovo')), - ('BN', _(u'Brvenica')), - ('BU', _(u'Butel')), - ('VA', _(u'Valandovo')), - ('VL', _(u'Vasilevo')), - ('VV', _(u'Vevčani')), - ('VE', _(u'Veles')), - ('NI', _(u'Vinica')), - ('VC', _(u'Vraneštica')), - ('VH', _(u'Vrapčište')), - ('GB', _(u'Gazi Baba')), - ('GV', _(u'Gevgelija')), - ('GT', _(u'Gostivar')), - ('GR', _(u'Gradsko')), - ('DB', _(u'Debar')), - ('DA', _(u'Debarca')), - ('DL', _(u'Delčevo')), - ('DK', _(u'Demir Kapija')), - ('DM', _(u'Demir Hisar')), - ('DE', _(u'Dolneni')), - ('DR', _(u'Drugovo')), - ('GP', _(u'Gjorče Petrov')), - ('ZE', _(u'Želino')), - ('ZA', _(u'Zajas')), - ('ZK', _(u'Zelenikovo')), - ('ZR', _(u'Zrnovci')), - ('IL', _(u'Ilinden')), - ('JG', _(u'Jegunovce')), - ('AV', _(u'Kavadarci')), - ('KB', _(u'Karbinci')), - ('KX', _(u'Karpoš')), - ('VD', _(u'Kisela Voda')), - ('KH', _(u'Kičevo')), - ('KN', _(u'Konče')), - ('OC', _(u'Koćani')), - ('KY', _(u'Kratovo')), - ('KZ', _(u'Kriva Palanka')), - ('KG', _(u'Krivogaštani')), - ('KS', _(u'Kruševo')), - ('UM', _(u'Kumanovo')), - ('LI', _(u'Lipkovo')), - ('LO', _(u'Lozovo')), - ('MR', _(u'Mavrovo i Rostuša')), - ('MK', _(u'Makedonska Kamenica')), - ('MD', _(u'Makedonski Brod')), - ('MG', _(u'Mogila')), - ('NG', _(u'Negotino')), - ('NV', _(u'Novaci')), - ('NS', _(u'Novo Selo')), - ('OS', _(u'Oslomej')), - ('OD', _(u'Ohrid')), - ('PE', _(u'Petrovec')), - ('PH', _(u'Pehčevo')), - ('PN', _(u'Plasnica')), - ('PP', _(u'Prilep')), - ('PT', _(u'Probištip')), - ('RV', _(u'Radoviš')), - ('RN', _(u'Rankovce')), - ('RE', _(u'Resen')), - ('RO', _(u'Rosoman')), - ('AJ', _(u'Saraj')), - ('SL', _(u'Sveti Nikole')), - ('SS', _(u'Sopište')), - ('SD', _(u'Star Dojran')), - ('NA', _(u'Staro Nagoričane')), - ('UG', _(u'Struga')), - ('RU', _(u'Strumica')), - ('SU', _(u'Studeničani')), - ('TR', _(u'Tearce')), - ('ET', _(u'Tetovo')), - ('CE', _(u'Centar')), - ('CZ', _(u'Centar-Župa')), - ('CI', _(u'Čair')), - ('CA', _(u'Čaška')), - ('CH', _(u'Češinovo-Obleševo')), - ('CS', _(u'Čučer-Sandevo')), - ('ST', _(u'Štip')), - ('SO', _(u'Šuto Orizari')), + ('AD', _('Aerodrom')), + ('AR', _('Aračinovo')), + ('BR', _('Berovo')), + ('TL', _('Bitola')), + ('BG', _('Bogdanci')), + ('VJ', _('Bogovinje')), + ('BS', _('Bosilovo')), + ('BN', _('Brvenica')), + ('BU', _('Butel')), + ('VA', _('Valandovo')), + ('VL', _('Vasilevo')), + ('VV', _('Vevčani')), + ('VE', _('Veles')), + ('NI', _('Vinica')), + ('VC', _('Vraneštica')), + ('VH', _('Vrapčište')), + ('GB', _('Gazi Baba')), + ('GV', _('Gevgelija')), + ('GT', _('Gostivar')), + ('GR', _('Gradsko')), + ('DB', _('Debar')), + ('DA', _('Debarca')), + ('DL', _('Delčevo')), + ('DK', _('Demir Kapija')), + ('DM', _('Demir Hisar')), + ('DE', _('Dolneni')), + ('DR', _('Drugovo')), + ('GP', _('Gjorče Petrov')), + ('ZE', _('Želino')), + ('ZA', _('Zajas')), + ('ZK', _('Zelenikovo')), + ('ZR', _('Zrnovci')), + ('IL', _('Ilinden')), + ('JG', _('Jegunovce')), + ('AV', _('Kavadarci')), + ('KB', _('Karbinci')), + ('KX', _('Karpoš')), + ('VD', _('Kisela Voda')), + ('KH', _('Kičevo')), + ('KN', _('Konče')), + ('OC', _('Koćani')), + ('KY', _('Kratovo')), + ('KZ', _('Kriva Palanka')), + ('KG', _('Krivogaštani')), + ('KS', _('Kruševo')), + ('UM', _('Kumanovo')), + ('LI', _('Lipkovo')), + ('LO', _('Lozovo')), + ('MR', _('Mavrovo i Rostuša')), + ('MK', _('Makedonska Kamenica')), + ('MD', _('Makedonski Brod')), + ('MG', _('Mogila')), + ('NG', _('Negotino')), + ('NV', _('Novaci')), + ('NS', _('Novo Selo')), + ('OS', _('Oslomej')), + ('OD', _('Ohrid')), + ('PE', _('Petrovec')), + ('PH', _('Pehčevo')), + ('PN', _('Plasnica')), + ('PP', _('Prilep')), + ('PT', _('Probištip')), + ('RV', _('Radoviš')), + ('RN', _('Rankovce')), + ('RE', _('Resen')), + ('RO', _('Rosoman')), + ('AJ', _('Saraj')), + ('SL', _('Sveti Nikole')), + ('SS', _('Sopište')), + ('SD', _('Star Dojran')), + ('NA', _('Staro Nagoričane')), + ('UG', _('Struga')), + ('RU', _('Strumica')), + ('SU', _('Studeničani')), + ('TR', _('Tearce')), + ('ET', _('Tetovo')), + ('CE', _('Centar')), + ('CZ', _('Centar-Župa')), + ('CI', _('Čair')), + ('CA', _('Čaška')), + ('CH', _('Češinovo-Obleševo')), + ('CS', _('Čučer-Sandevo')), + ('ST', _('Štip')), + ('SO', _('Šuto Orizari')), ) diff --git a/django/contrib/localflavor/mx/forms.py b/django/contrib/localflavor/mx/forms.py index deecb4ea41..b42bf22b89 100644 --- a/django/contrib/localflavor/mx/forms.py +++ b/django/contrib/localflavor/mx/forms.py @@ -2,10 +2,12 @@ """ Mexican-specific form helpers. """ +from __future__ import unicode_literals import re from django.forms import ValidationError from django.forms.fields import Select, RegexField +from django.utils import six from django.utils.translation import ugettext_lazy as _ from django.core.validators import EMPTY_VALUES from django.contrib.localflavor.mx.mx_states import STATE_CHOICES @@ -19,12 +21,12 @@ document described in the next link: """ RFC_INCONVENIENT_WORDS = [ - u'BUEI', u'BUEY', u'CACA', u'CACO', u'CAGA', u'CAGO', u'CAKA', u'CAKO', - u'COGE', u'COJA', u'COJE', u'COJI', u'COJO', u'CULO', u'FETO', u'GUEY', - u'JOTO', u'KACA', u'KACO', u'KAGA', u'KAGO', u'KOGE', u'KOJO', u'KAKA', - u'KULO', u'MAME', u'MAMO', u'MEAR', u'MEAS', u'MEON', u'MION', u'MOCO', - u'MULA', u'PEDA', u'PEDO', u'PENE', u'PUTA', u'PUTO', u'QULO', u'RATA', - u'RUIN', + 'BUEI', 'BUEY', 'CACA', 'CACO', 'CAGA', 'CAGO', 'CAKA', 'CAKO', + 'COGE', 'COJA', 'COJE', 'COJI', 'COJO', 'CULO', 'FETO', 'GUEY', + 'JOTO', 'KACA', 'KACO', 'KAGA', 'KAGO', 'KOGE', 'KOJO', 'KAKA', + 'KULO', 'MAME', 'MAMO', 'MEAR', 'MEAS', 'MEON', 'MION', 'MOCO', + 'MULA', 'PEDA', 'PEDO', 'PENE', 'PUTA', 'PUTO', 'QULO', 'RATA', + 'RUIN', ] """ @@ -33,17 +35,17 @@ document described in the next link: http://portal.veracruz.gob.mx/pls/portal/url/ITEM/444112558A57C6E0E040A8C02E00695C """ CURP_INCONVENIENT_WORDS = [ - u'BACA', u'BAKA', u'BUEI', u'BUEY', u'CACA', u'CACO', u'CAGA', u'CAGO', - u'CAKA', u'CAKO', u'COGE', u'COGI', u'COJA', u'COJE', u'COJI', u'COJO', - u'COLA', u'CULO', u'FALO', u'FETO', u'GETA', u'GUEI', u'GUEY', u'JETA', - u'JOTO', u'KACA', u'KACO', u'KAGA', u'KAGO', u'KAKA', u'KAKO', u'KOGE', - u'KOGI', u'KOJA', u'KOJE', u'KOJI', u'KOJO', u'KOLA', u'KULO', u'LILO', - u'LOCA', u'LOCO', u'LOKA', u'LOKO', u'MAME', u'MAMO', u'MEAR', u'MEAS', - u'MEON', u'MIAR', u'MION', u'MOCO', u'MOKO', u'MULA', u'MULO', u'NACA', - u'NACO', u'PEDA', u'PEDO', u'PENE', u'PIPI', u'PITO', u'POPO', u'PUTA', - u'PUTO', u'QULO', u'RATA', u'ROBA', u'ROBE', u'ROBO', u'RUIN', u'SENO', - u'TETA', u'VACA', u'VAGA', u'VAGO', u'VAKA', u'VUEI', u'VUEY', u'WUEI', - u'WUEY', + 'BACA', 'BAKA', 'BUEI', 'BUEY', 'CACA', 'CACO', 'CAGA', 'CAGO', + 'CAKA', 'CAKO', 'COGE', 'COGI', 'COJA', 'COJE', 'COJI', 'COJO', + 'COLA', 'CULO', 'FALO', 'FETO', 'GETA', 'GUEI', 'GUEY', 'JETA', + 'JOTO', 'KACA', 'KACO', 'KAGA', 'KAGO', 'KAKA', 'KAKO', 'KOGE', + 'KOGI', 'KOJA', 'KOJE', 'KOJI', 'KOJO', 'KOLA', 'KULO', 'LILO', + 'LOCA', 'LOCO', 'LOKA', 'LOKO', 'MAME', 'MAMO', 'MEAR', 'MEAS', + 'MEON', 'MIAR', 'MION', 'MOCO', 'MOKO', 'MULA', 'MULO', 'NACA', + 'NACO', 'PEDA', 'PEDO', 'PENE', 'PIPI', 'PITO', 'POPO', 'PUTA', + 'PUTO', 'QULO', 'RATA', 'ROBA', 'ROBE', 'ROBO', 'RUIN', 'SENO', + 'TETA', 'VACA', 'VAGA', 'VAGO', 'VAKA', 'VUEI', 'VUEY', 'WUEI', + 'WUEY', ] class MXStateSelect(Select): @@ -62,11 +64,11 @@ class MXZipCodeField(RegexField): http://en.wikipedia.org/wiki/List_of_postal_codes_in_Mexico """ default_error_messages = { - 'invalid': _(u'Enter a valid zip code in the format XXXXX.'), + 'invalid': _('Enter a valid zip code in the format XXXXX.'), } def __init__(self, *args, **kwargs): - zip_code_re = ur'^(0[1-9]|[1][0-6]|[2-9]\d)(\d{3})$' + zip_code_re = r'^(0[1-9]|[1][0-6]|[2-9]\d)(\d{3})$' super(MXZipCodeField, self).__init__(zip_code_re, *args, **kwargs) @@ -110,7 +112,7 @@ class MXRFCField(RegexField): } def __init__(self, min_length=9, max_length=13, *args, **kwargs): - rfc_re = re.compile(ur'^([A-Z&Ññ]{3}|[A-Z][AEIOU][A-Z]{2})%s([A-Z0-9]{2}[0-9A])?$' % DATE_RE, + rfc_re = re.compile(r'^([A-Z&Ññ]{3}|[A-Z][AEIOU][A-Z]{2})%s([A-Z0-9]{2}[0-9A])?$' % DATE_RE, re.IGNORECASE) super(MXRFCField, self).__init__(rfc_re, min_length=min_length, max_length=max_length, *args, **kwargs) @@ -118,7 +120,7 @@ class MXRFCField(RegexField): def clean(self, value): value = super(MXRFCField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.upper() if self._has_homoclave(value): if not value[-1] == self._checksum(value[:-1]): @@ -133,7 +135,7 @@ class MXRFCField(RegexField): since the current algorithm to calculate it had not been created for the first RFCs ever in Mexico. """ - rfc_without_homoclave_re = re.compile(ur'^[A-Z&Ññ]{3,4}%s$' % DATE_RE, + rfc_without_homoclave_re = re.compile(r'^[A-Z&Ññ]{3,4}%s$' % DATE_RE, re.IGNORECASE) return not rfc_without_homoclave_re.match(rfc) @@ -142,19 +144,19 @@ class MXRFCField(RegexField): More info about this procedure: www.sisi.org.mx/jspsi/documentos/2005/seguimiento/06101/0610100162005_065.doc """ - chars = u'0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ-Ñ' + chars = '0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ-Ñ' if len(rfc) == 11: rfc = '-' + rfc - sum_ = sum(i * chars.index(c) for i, c in zip(reversed(xrange(14)), rfc)) + sum_ = sum(i * chars.index(c) for i, c in zip(reversed(range(14)), rfc)) checksum = 11 - sum_ % 11 if checksum == 10: - return u'A' + return 'A' elif checksum == 11: - return u'0' + return '0' - return unicode(checksum) + return six.text_type(checksum) def _has_inconvenient_word(self, rfc): first_four = rfc[:4] @@ -187,13 +189,13 @@ class MXCURPField(RegexField): """ default_error_messages = { 'invalid': _('Enter a valid CURP.'), - 'invalid_checksum': _(u'Invalid checksum for CURP.'), + 'invalid_checksum': _('Invalid checksum for CURP.'), } def __init__(self, min_length=18, max_length=18, *args, **kwargs): states_re = r'(AS|BC|BS|CC|CL|CM|CS|CH|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS|NE)' consonants_re = r'[B-DF-HJ-NP-TV-Z]' - curp_re = (ur'^[A-Z][AEIOU][A-Z]{2}%s[HM]%s%s{3}[0-9A-Z]\d$' % + curp_re = (r'^[A-Z][AEIOU][A-Z]{2}%s[HM]%s%s{3}[0-9A-Z]\d$' % (DATE_RE, states_re, consonants_re)) curp_re = re.compile(curp_re, re.IGNORECASE) super(MXCURPField, self).__init__(curp_re, min_length=min_length, @@ -202,7 +204,7 @@ class MXCURPField(RegexField): def clean(self, value): value = super(MXCURPField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.upper() if value[-1] != self._checksum(value[:-1]): raise ValidationError(self.default_error_messages['invalid_checksum']) @@ -211,14 +213,14 @@ class MXCURPField(RegexField): return value def _checksum(self, value): - chars = u'0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ' + chars = '0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ' - s = sum(i * chars.index(c) for i, c in zip(reversed(xrange(19)), value)) + s = sum(i * chars.index(c) for i, c in zip(reversed(range(19)), value)) checksum = 10 - s % 10 if checksum == 10: - return u'0' - return unicode(checksum) + return '0' + return six.text_type(checksum) def _has_inconvenient_word(self, curp): first_four = curp[:4] diff --git a/django/contrib/localflavor/mx/mx_states.py b/django/contrib/localflavor/mx/mx_states.py index 2aba63ef26..6ae08ccb12 100644 --- a/django/contrib/localflavor/mx/mx_states.py +++ b/django/contrib/localflavor/mx/mx_states.py @@ -5,41 +5,42 @@ A list of Mexican states for use as `choices` in a formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ # All 31 states, plus the `Distrito Federal`. STATE_CHOICES = ( - ('AGU', _(u'Aguascalientes')), - ('BCN', _(u'Baja California')), - ('BCS', _(u'Baja California Sur')), - ('CAM', _(u'Campeche')), - ('CHH', _(u'Chihuahua')), - ('CHP', _(u'Chiapas')), - ('COA', _(u'Coahuila')), - ('COL', _(u'Colima')), - ('DIF', _(u'Distrito Federal')), - ('DUR', _(u'Durango')), - ('GRO', _(u'Guerrero')), - ('GUA', _(u'Guanajuato')), - ('HID', _(u'Hidalgo')), - ('JAL', _(u'Jalisco')), - ('MEX', _(u'Estado de México')), - ('MIC', _(u'Michoacán')), - ('MOR', _(u'Morelos')), - ('NAY', _(u'Nayarit')), - ('NLE', _(u'Nuevo León')), - ('OAX', _(u'Oaxaca')), - ('PUE', _(u'Puebla')), - ('QUE', _(u'Querétaro')), - ('ROO', _(u'Quintana Roo')), - ('SIN', _(u'Sinaloa')), - ('SLP', _(u'San Luis Potosí')), - ('SON', _(u'Sonora')), - ('TAB', _(u'Tabasco')), - ('TAM', _(u'Tamaulipas')), - ('TLA', _(u'Tlaxcala')), - ('VER', _(u'Veracruz')), - ('YUC', _(u'Yucatán')), - ('ZAC', _(u'Zacatecas')), + ('AGU', _('Aguascalientes')), + ('BCN', _('Baja California')), + ('BCS', _('Baja California Sur')), + ('CAM', _('Campeche')), + ('CHH', _('Chihuahua')), + ('CHP', _('Chiapas')), + ('COA', _('Coahuila')), + ('COL', _('Colima')), + ('DIF', _('Distrito Federal')), + ('DUR', _('Durango')), + ('GRO', _('Guerrero')), + ('GUA', _('Guanajuato')), + ('HID', _('Hidalgo')), + ('JAL', _('Jalisco')), + ('MEX', _('Estado de México')), + ('MIC', _('Michoacán')), + ('MOR', _('Morelos')), + ('NAY', _('Nayarit')), + ('NLE', _('Nuevo León')), + ('OAX', _('Oaxaca')), + ('PUE', _('Puebla')), + ('QUE', _('Querétaro')), + ('ROO', _('Quintana Roo')), + ('SIN', _('Sinaloa')), + ('SLP', _('San Luis Potosí')), + ('SON', _('Sonora')), + ('TAB', _('Tabasco')), + ('TAM', _('Tamaulipas')), + ('TLA', _('Tlaxcala')), + ('VER', _('Veracruz')), + ('YUC', _('Yucatán')), + ('ZAC', _('Zacatecas')), ) diff --git a/django/contrib/localflavor/nl/forms.py b/django/contrib/localflavor/nl/forms.py index 66900808c2..a05dd38f7f 100644 --- a/django/contrib/localflavor/nl/forms.py +++ b/django/contrib/localflavor/nl/forms.py @@ -2,7 +2,7 @@ NL-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -10,7 +10,7 @@ from django.contrib.localflavor.nl.nl_provinces import PROVINCE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -29,7 +29,7 @@ class NLZipCodeField(Field): def clean(self, value): super(NLZipCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip().upper().replace(' ', '') if not pc_re.search(value): @@ -38,7 +38,7 @@ class NLZipCodeField(Field): if int(value[:4]) < 1000: raise ValidationError(self.error_messages['invalid']) - return u'%s %s' % (value[:4], value[4:]) + return '%s %s' % (value[:4], value[4:]) class NLProvinceSelect(Select): """ @@ -59,9 +59,9 @@ class NLPhoneNumberField(Field): def clean(self, value): super(NLPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' - phone_nr = re.sub('[\-\s\(\)]', '', smart_unicode(value)) + phone_nr = re.sub('[\-\s\(\)]', '', smart_text(value)) if len(phone_nr) == 10 and numeric_re.search(phone_nr): return value @@ -85,7 +85,7 @@ class NLSoFiNumberField(Field): def clean(self, value): super(NLSoFiNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not sofi_re.search(value): raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/no/forms.py b/django/contrib/localflavor/no/forms.py index 3e5e58bd1c..4bd780a312 100644 --- a/django/contrib/localflavor/no/forms.py +++ b/django/contrib/localflavor/no/forms.py @@ -2,7 +2,7 @@ Norwegian-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re import datetime @@ -36,13 +36,13 @@ class NOSocialSecurityNumber(Field): Algorithm is documented at http://no.wikipedia.org/wiki/Personnummer """ default_error_messages = { - 'invalid': _(u'Enter a valid Norwegian social security number.'), + 'invalid': _('Enter a valid Norwegian social security number.'), } def clean(self, value): super(NOSocialSecurityNumber, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not re.match(r'^\d{11}$', value): raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/no/no_municipalities.py b/django/contrib/localflavor/no/no_municipalities.py index d6bacda275..d84915caa2 100644 --- a/django/contrib/localflavor/no/no_municipalities.py +++ b/django/contrib/localflavor/no/no_municipalities.py @@ -6,27 +6,28 @@ in a formfield. This exists in this standalone file so that it's on ly imported into memory when explicitly needed. """ +from __future__ import unicode_literals MUNICIPALITY_CHOICES = ( - ('akershus', u'Akershus'), - ('austagder', u'Aust-Agder'), - ('buskerud', u'Buskerud'), - ('finnmark', u'Finnmark'), - ('hedmark', u'Hedmark'), - ('hordaland', u'Hordaland'), - ('janmayen', u'Jan Mayen'), - ('moreogromsdal', u'Møre og Romsdal'), - ('nordtrondelag', u'Nord-Trøndelag'), - ('nordland', u'Nordland'), - ('oppland', u'Oppland'), - ('oslo', u'Oslo'), - ('rogaland', u'Rogaland'), - ('sognogfjordane', u'Sogn og Fjordane'), - ('svalbard', u'Svalbard'), - ('sortrondelag', u'Sør-Trøndelag'), - ('telemark', u'Telemark'), - ('troms', u'Troms'), - ('vestagder', u'Vest-Agder'), - ('vestfold', u'Vestfold'), - ('ostfold', u'Østfold') + ('akershus', 'Akershus'), + ('austagder', 'Aust-Agder'), + ('buskerud', 'Buskerud'), + ('finnmark', 'Finnmark'), + ('hedmark', 'Hedmark'), + ('hordaland', 'Hordaland'), + ('janmayen', 'Jan Mayen'), + ('moreogromsdal', 'Møre og Romsdal'), + ('nordtrondelag', 'Nord-Trøndelag'), + ('nordland', 'Nordland'), + ('oppland', 'Oppland'), + ('oslo', 'Oslo'), + ('rogaland', 'Rogaland'), + ('sognogfjordane', 'Sogn og Fjordane'), + ('svalbard', 'Svalbard'), + ('sortrondelag', 'Sør-Trøndelag'), + ('telemark', 'Telemark'), + ('troms', 'Troms'), + ('vestagder', 'Vest-Agder'), + ('vestfold', 'Vestfold'), + ('ostfold', 'Østfold') ) diff --git a/django/contrib/localflavor/pe/forms.py b/django/contrib/localflavor/pe/forms.py index 0eca2b8ac7..5100bbf575 100644 --- a/django/contrib/localflavor/pe/forms.py +++ b/django/contrib/localflavor/pe/forms.py @@ -3,7 +3,7 @@ PE-specific Form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.localflavor.pe.pe_region import REGION_CHOICES from django.core.validators import EMPTY_VALUES @@ -38,7 +38,7 @@ class PEDNIField(CharField): """ value = super(PEDNIField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not value.isdigit(): raise ValidationError(self.error_messages['invalid']) if len(value) != 8: @@ -66,7 +66,7 @@ class PERUCField(RegexField): """ value = super(PERUCField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not value.isdigit(): raise ValidationError(self.error_messages['invalid']) if len(value) != 11: diff --git a/django/contrib/localflavor/pe/pe_region.py b/django/contrib/localflavor/pe/pe_region.py index 9863bd3d15..9270bcecf1 100644 --- a/django/contrib/localflavor/pe/pe_region.py +++ b/django/contrib/localflavor/pe/pe_region.py @@ -5,31 +5,32 @@ A list of Peru regions as `choices` in a formfield. This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals REGION_CHOICES = ( - ('AMA', u'Amazonas'), - ('ANC', u'Ancash'), - ('APU', u'Apurímac'), - ('ARE', u'Arequipa'), - ('AYA', u'Ayacucho'), - ('CAJ', u'Cajamarca'), - ('CAL', u'Callao'), - ('CUS', u'Cusco'), - ('HUV', u'Huancavelica'), - ('HUC', u'Huánuco'), - ('ICA', u'Ica'), - ('JUN', u'Junín'), - ('LAL', u'La Libertad'), - ('LAM', u'Lambayeque'), - ('LIM', u'Lima'), - ('LOR', u'Loreto'), - ('MDD', u'Madre de Dios'), - ('MOQ', u'Moquegua'), - ('PAS', u'Pasco'), - ('PIU', u'Piura'), - ('PUN', u'Puno'), - ('SAM', u'San Martín'), - ('TAC', u'Tacna'), - ('TUM', u'Tumbes'), - ('UCA', u'Ucayali'), + ('AMA', 'Amazonas'), + ('ANC', 'Ancash'), + ('APU', 'Apurímac'), + ('ARE', 'Arequipa'), + ('AYA', 'Ayacucho'), + ('CAJ', 'Cajamarca'), + ('CAL', 'Callao'), + ('CUS', 'Cusco'), + ('HUV', 'Huancavelica'), + ('HUC', 'Huánuco'), + ('ICA', 'Ica'), + ('JUN', 'Junín'), + ('LAL', 'La Libertad'), + ('LAM', 'Lambayeque'), + ('LIM', 'Lima'), + ('LOR', 'Loreto'), + ('MDD', 'Madre de Dios'), + ('MOQ', 'Moquegua'), + ('PAS', 'Pasco'), + ('PIU', 'Piura'), + ('PUN', 'Puno'), + ('SAM', 'San Martín'), + ('TAC', 'Tacna'), + ('TUM', 'Tumbes'), + ('UCA', 'Ucayali'), ) diff --git a/django/contrib/localflavor/pl/forms.py b/django/contrib/localflavor/pl/forms.py index 3e8d73f0f2..12d9f3d763 100644 --- a/django/contrib/localflavor/pl/forms.py +++ b/django/contrib/localflavor/pl/forms.py @@ -2,7 +2,7 @@ Polish-specific form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -39,8 +39,8 @@ class PLPESELField(RegexField): The algorithm is documented at http://en.wikipedia.org/wiki/PESEL. """ default_error_messages = { - 'invalid': _(u'National Identification Number consists of 11 digits.'), - 'checksum': _(u'Wrong checksum for the National Identification Number.'), + 'invalid': _('National Identification Number consists of 11 digits.'), + 'checksum': _('Wrong checksum for the National Identification Number.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -50,10 +50,10 @@ class PLPESELField(RegexField): def clean(self, value): super(PLPESELField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not self.has_valid_checksum(value): raise ValidationError(self.error_messages['checksum']) - return u'%s' % value + return '%s' % value def has_valid_checksum(self, number): """ @@ -76,8 +76,8 @@ class PLNationalIDCardNumberField(RegexField): The algorithm is documented at http://en.wikipedia.org/wiki/Polish_identity_card. """ default_error_messages = { - 'invalid': _(u'National ID Card Number consists of 3 letters and 6 digits.'), - 'checksum': _(u'Wrong checksum for the National ID Card Number.'), + 'invalid': _('National ID Card Number consists of 3 letters and 6 digits.'), + 'checksum': _('Wrong checksum for the National ID Card Number.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -87,13 +87,13 @@ class PLNationalIDCardNumberField(RegexField): def clean(self,value): super(PLNationalIDCardNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.upper() if not self.has_valid_checksum(value): raise ValidationError(self.error_messages['checksum']) - return u'%s' % value + return '%s' % value def has_valid_checksum(self, number): """ @@ -128,8 +128,8 @@ class PLNIPField(RegexField): http://wipos.p.lodz.pl/zylla/ut/nip-rego.html """ default_error_messages = { - 'invalid': _(u'Enter a tax number field (NIP) in the format XXX-XXX-XX-XX, XXX-XX-XX-XXX or XXXXXXXXXX.'), - 'checksum': _(u'Wrong checksum for the Tax Number (NIP).'), + 'invalid': _('Enter a tax number field (NIP) in the format XXX-XXX-XX-XX, XXX-XX-XX-XXX or XXXXXXXXXX.'), + 'checksum': _('Wrong checksum for the Tax Number (NIP).'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -139,11 +139,11 @@ class PLNIPField(RegexField): def clean(self,value): super(PLNIPField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = re.sub("[-]", "", value) if not self.has_valid_checksum(value): raise ValidationError(self.error_messages['checksum']) - return u'%s' % value + return '%s' % value def has_valid_checksum(self, number): """ @@ -168,8 +168,8 @@ class PLREGONField(RegexField): See http://www.stat.gov.pl/bip/regon_ENG_HTML.htm for more information. """ default_error_messages = { - 'invalid': _(u'National Business Register Number (REGON) consists of 9 or 14 digits.'), - 'checksum': _(u'Wrong checksum for the National Business Register Number (REGON).'), + 'invalid': _('National Business Register Number (REGON) consists of 9 or 14 digits.'), + 'checksum': _('Wrong checksum for the National Business Register Number (REGON).'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -179,10 +179,10 @@ class PLREGONField(RegexField): def clean(self,value): super(PLREGONField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if not self.has_valid_checksum(value): raise ValidationError(self.error_messages['checksum']) - return u'%s' % value + return '%s' % value def has_valid_checksum(self, number): """ @@ -209,7 +209,7 @@ class PLPostalCodeField(RegexField): Valid code is XX-XXX where X is digit. """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XX-XXX.'), + 'invalid': _('Enter a postal code in the format XX-XXX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): diff --git a/django/contrib/localflavor/pl/pl_administrativeunits.py b/django/contrib/localflavor/pl/pl_administrativeunits.py index 9777ea2b61..f5263f19d2 100644 --- a/django/contrib/localflavor/pl/pl_administrativeunits.py +++ b/django/contrib/localflavor/pl/pl_administrativeunits.py @@ -2,384 +2,385 @@ """ Polish administrative units as in http://pl.wikipedia.org/wiki/Podzia%C5%82_administracyjny_Polski """ +from __future__ import unicode_literals ADMINISTRATIVE_UNIT_CHOICES = ( - ('wroclaw', u'Wrocław'), - ('jeleniagora', u'Jelenia Góra'), - ('legnica', u'Legnica'), - ('boleslawiecki', u'bolesławiecki'), - ('dzierzoniowski', u'dzierżoniowski'), - ('glogowski', u'głogowski'), - ('gorowski', u'górowski'), - ('jaworski', u'jaworski'), - ('jeleniogorski', u'jeleniogórski'), - ('kamiennogorski', u'kamiennogórski'), - ('klodzki', u'kłodzki'), - ('legnicki', u'legnicki'), - ('lubanski', u'lubański'), - ('lubinski', u'lubiński'), - ('lwowecki', u'lwówecki'), - ('milicki', u'milicki'), - ('olesnicki', u'oleśnicki'), - ('olawski', u'oławski'), - ('polkowicki', u'polkowicki'), - ('strzelinski', u'strzeliński'), - ('sredzki', u'średzki'), - ('swidnicki', u'świdnicki'), - ('trzebnicki', u'trzebnicki'), - ('walbrzyski', u'wałbrzyski'), - ('wolowski', u'wołowski'), - ('wroclawski', u'wrocławski'), - ('zabkowicki', u'ząbkowicki'), - ('zgorzelecki', u'zgorzelecki'), - ('zlotoryjski', u'złotoryjski'), - ('bydgoszcz', u'Bydgoszcz'), - ('torun', u'Toruń'), - ('wloclawek', u'Włocławek'), - ('grudziadz', u'Grudziądz'), - ('aleksandrowski', u'aleksandrowski'), - ('brodnicki', u'brodnicki'), - ('bydgoski', u'bydgoski'), - ('chelminski', u'chełmiński'), - ('golubsko-dobrzynski', u'golubsko-dobrzyński'), - ('grudziadzki', u'grudziądzki'), - ('inowroclawski', u'inowrocławski'), - ('lipnowski', u'lipnowski'), - ('mogilenski', u'mogileński'), - ('nakielski', u'nakielski'), - ('radziejowski', u'radziejowski'), - ('rypinski', u'rypiński'), - ('sepolenski', u'sępoleński'), - ('swiecki', u'świecki'), - ('torunski', u'toruński'), - ('tucholski', u'tucholski'), - ('wabrzeski', u'wąbrzeski'), - ('wloclawski', u'wrocławski'), - ('zninski', u'źniński'), - ('lublin', u'Lublin'), - ('biala-podlaska', u'Biała Podlaska'), - ('chelm', u'Chełm'), - ('zamosc', u'Zamość'), - ('bialski', u'bialski'), - ('bilgorajski', u'biłgorajski'), - ('chelmski', u'chełmski'), - ('hrubieszowski', u'hrubieszowski'), - ('janowski', u'janowski'), - ('krasnostawski', u'krasnostawski'), - ('krasnicki', u'kraśnicki'), - ('lubartowski', u'lubartowski'), - ('lubelski', u'lubelski'), - ('leczynski', u'łęczyński'), - ('lukowski', u'łukowski'), - ('opolski', u'opolski'), - ('parczewski', u'parczewski'), - ('pulawski', u'puławski'), - ('radzynski', u'radzyński'), - ('rycki', u'rycki'), - ('swidnicki', u'świdnicki'), - ('tomaszowski', u'tomaszowski'), - ('wlodawski', u'włodawski'), - ('zamojski', u'zamojski'), - ('gorzow-wielkopolski', u'Gorzów Wielkopolski'), - ('zielona-gora', u'Zielona Góra'), - ('gorzowski', u'gorzowski'), - ('krosnienski', u'krośnieński'), - ('miedzyrzecki', u'międzyrzecki'), - ('nowosolski', u'nowosolski'), - ('slubicki', u'słubicki'), - ('strzelecko-drezdenecki', u'strzelecko-drezdenecki'), - ('sulecinski', u'suleńciński'), - ('swiebodzinski', u'świebodziński'), - ('wschowski', u'wschowski'), - ('zielonogorski', u'zielonogórski'), - ('zaganski', u'żagański'), - ('zarski', u'żarski'), - ('lodz', u'Łódź'), - ('piotrkow-trybunalski', u'Piotrków Trybunalski'), - ('skierniewice', u'Skierniewice'), - ('belchatowski', u'bełchatowski'), - ('brzezinski', u'brzeziński'), - ('kutnowski', u'kutnowski'), - ('laski', u'łaski'), - ('leczycki', u'łęczycki'), - ('lowicki', u'łowicki'), - ('lodzki wschodni', u'łódzki wschodni'), - ('opoczynski', u'opoczyński'), - ('pabianicki', u'pabianicki'), - ('pajeczanski', u'pajęczański'), - ('piotrkowski', u'piotrkowski'), - ('poddebicki', u'poddębicki'), - ('radomszczanski', u'radomszczański'), - ('rawski', u'rawski'), - ('sieradzki', u'sieradzki'), - ('skierniewicki', u'skierniewicki'), - ('tomaszowski', u'tomaszowski'), - ('wielunski', u'wieluński'), - ('wieruszowski', u'wieruszowski'), - ('zdunskowolski', u'zduńskowolski'), - ('zgierski', u'zgierski'), - ('krakow', u'Kraków'), - ('tarnow', u'Tarnów'), - ('nowy-sacz', u'Nowy Sącz'), - ('bochenski', u'bocheński'), - ('brzeski', u'brzeski'), - ('chrzanowski', u'chrzanowski'), - ('dabrowski', u'dąbrowski'), - ('gorlicki', u'gorlicki'), - ('krakowski', u'krakowski'), - ('limanowski', u'limanowski'), - ('miechowski', u'miechowski'), - ('myslenicki', u'myślenicki'), - ('nowosadecki', u'nowosądecki'), - ('nowotarski', u'nowotarski'), - ('olkuski', u'olkuski'), - ('oswiecimski', u'oświęcimski'), - ('proszowicki', u'proszowicki'), - ('suski', u'suski'), - ('tarnowski', u'tarnowski'), - ('tatrzanski', u'tatrzański'), - ('wadowicki', u'wadowicki'), - ('wielicki', u'wielicki'), - ('warszawa', u'Warszawa'), - ('ostroleka', u'Ostrołęka'), - ('plock', u'Płock'), - ('radom', u'Radom'), - ('siedlce', u'Siedlce'), - ('bialobrzeski', u'białobrzeski'), - ('ciechanowski', u'ciechanowski'), - ('garwolinski', u'garwoliński'), - ('gostyninski', u'gostyniński'), - ('grodziski', u'grodziski'), - ('grojecki', u'grójecki'), - ('kozienicki', u'kozenicki'), - ('legionowski', u'legionowski'), - ('lipski', u'lipski'), - ('losicki', u'łosicki'), - ('makowski', u'makowski'), - ('minski', u'miński'), - ('mlawski', u'mławski'), - ('nowodworski', u'nowodworski'), - ('ostrolecki', u'ostrołęcki'), - ('ostrowski', u'ostrowski'), - ('otwocki', u'otwocki'), - ('piaseczynski', u'piaseczyński'), - ('plocki', u'płocki'), - ('plonski', u'płoński'), - ('pruszkowski', u'pruszkowski'), - ('przasnyski', u'przasnyski'), - ('przysuski', u'przysuski'), - ('pultuski', u'pułtuski'), - ('radomski', u'radomski'), - ('siedlecki', u'siedlecki'), - ('sierpecki', u'sierpecki'), - ('sochaczewski', u'sochaczewski'), - ('sokolowski', u'sokołowski'), - ('szydlowiecki', u'szydłowiecki'), - ('warszawski-zachodni', u'warszawski zachodni'), - ('wegrowski', u'węgrowski'), - ('wolominski', u'wołomiński'), - ('wyszkowski', u'wyszkowski'), - ('zwolenski', u'zwoleński'), - ('zurominski', u'żuromiński'), - ('zyrardowski', u'żyrardowski'), - ('opole', u'Opole'), - ('brzeski', u'brzeski'), - ('glubczycki', u'głubczyski'), - ('kedzierzynsko-kozielski', u'kędzierzyński-kozielski'), - ('kluczborski', u'kluczborski'), - ('krapkowicki', u'krapkowicki'), - ('namyslowski', u'namysłowski'), - ('nyski', u'nyski'), - ('oleski', u'oleski'), - ('opolski', u'opolski'), - ('prudnicki', u'prudnicki'), - ('strzelecki', u'strzelecki'), - ('rzeszow', u'Rzeszów'), - ('krosno', u'Krosno'), - ('przemysl', u'Przemyśl'), - ('tarnobrzeg', u'Tarnobrzeg'), - ('bieszczadzki', u'bieszczadzki'), - ('brzozowski', u'brzozowski'), - ('debicki', u'dębicki'), - ('jaroslawski', u'jarosławski'), - ('jasielski', u'jasielski'), - ('kolbuszowski', u'kolbuszowski'), - ('krosnienski', u'krośnieński'), - ('leski', u'leski'), - ('lezajski', u'leżajski'), - ('lubaczowski', u'lubaczowski'), - ('lancucki', u'łańcucki'), - ('mielecki', u'mielecki'), - ('nizanski', u'niżański'), - ('przemyski', u'przemyski'), - ('przeworski', u'przeworski'), - ('ropczycko-sedziszowski', u'ropczycko-sędziszowski'), - ('rzeszowski', u'rzeszowski'), - ('sanocki', u'sanocki'), - ('stalowowolski', u'stalowowolski'), - ('strzyzowski', u'strzyżowski'), - ('tarnobrzeski', u'tarnobrzeski'), - ('bialystok', u'Białystok'), - ('lomza', u'Łomża'), - ('suwalki', u'Suwałki'), - ('augustowski', u'augustowski'), - ('bialostocki', u'białostocki'), - ('bielski', u'bielski'), - ('grajewski', u'grajewski'), - ('hajnowski', u'hajnowski'), - ('kolnenski', u'kolneński'), - ('łomzynski', u'łomżyński'), - ('moniecki', u'moniecki'), - ('sejnenski', u'sejneński'), - ('siemiatycki', u'siematycki'), - ('sokolski', u'sokólski'), - ('suwalski', u'suwalski'), - ('wysokomazowiecki', u'wysokomazowiecki'), - ('zambrowski', u'zambrowski'), - ('gdansk', u'Gdańsk'), - ('gdynia', u'Gdynia'), - ('slupsk', u'Słupsk'), - ('sopot', u'Sopot'), - ('bytowski', u'bytowski'), - ('chojnicki', u'chojnicki'), - ('czluchowski', u'człuchowski'), - ('kartuski', u'kartuski'), - ('koscierski', u'kościerski'), - ('kwidzynski', u'kwidzyński'), - ('leborski', u'lęborski'), - ('malborski', u'malborski'), - ('nowodworski', u'nowodworski'), - ('gdanski', u'gdański'), - ('pucki', u'pucki'), - ('slupski', u'słupski'), - ('starogardzki', u'starogardzki'), - ('sztumski', u'sztumski'), - ('tczewski', u'tczewski'), - ('wejherowski', u'wejcherowski'), - ('katowice', u'Katowice'), - ('bielsko-biala', u'Bielsko-Biała'), - ('bytom', u'Bytom'), - ('chorzow', u'Chorzów'), - ('czestochowa', u'Częstochowa'), - ('dabrowa-gornicza', u'Dąbrowa Górnicza'), - ('gliwice', u'Gliwice'), - ('jastrzebie-zdroj', u'Jastrzębie Zdrój'), - ('jaworzno', u'Jaworzno'), - ('myslowice', u'Mysłowice'), - ('piekary-slaskie', u'Piekary Śląskie'), - ('ruda-slaska', u'Ruda Śląska'), - ('rybnik', u'Rybnik'), - ('siemianowice-slaskie', u'Siemianowice Śląskie'), - ('sosnowiec', u'Sosnowiec'), - ('swietochlowice', u'Świętochłowice'), - ('tychy', u'Tychy'), - ('zabrze', u'Zabrze'), - ('zory', u'Żory'), - ('bedzinski', u'będziński'), - ('bielski', u'bielski'), - ('bierunsko-ledzinski', u'bieruńsko-lędziński'), - ('cieszynski', u'cieszyński'), - ('czestochowski', u'częstochowski'), - ('gliwicki', u'gliwicki'), - ('klobucki', u'kłobucki'), - ('lubliniecki', u'lubliniecki'), - ('mikolowski', u'mikołowski'), - ('myszkowski', u'myszkowski'), - ('pszczynski', u'pszczyński'), - ('raciborski', u'raciborski'), - ('rybnicki', u'rybnicki'), - ('tarnogorski', u'tarnogórski'), - ('wodzislawski', u'wodzisławski'), - ('zawiercianski', u'zawierciański'), - ('zywiecki', u'żywiecki'), - ('kielce', u'Kielce'), - ('buski', u'buski'), - ('jedrzejowski', u'jędrzejowski'), - ('kazimierski', u'kazimierski'), - ('kielecki', u'kielecki'), - ('konecki', u'konecki'), - ('opatowski', u'opatowski'), - ('ostrowiecki', u'ostrowiecki'), - ('pinczowski', u'pińczowski'), - ('sandomierski', u'sandomierski'), - ('skarzyski', u'skarżyski'), - ('starachowicki', u'starachowicki'), - ('staszowski', u'staszowski'), - ('wloszczowski', u'włoszczowski'), - ('olsztyn', u'Olsztyn'), - ('elblag', u'Elbląg'), - ('bartoszycki', u'bartoszycki'), - ('braniewski', u'braniewski'), - ('dzialdowski', u'działdowski'), - ('elblaski', u'elbląski'), - ('elcki', u'ełcki'), - ('gizycki', u'giżycki'), - ('goldapski', u'gołdapski'), - ('ilawski', u'iławski'), - ('ketrzynski', u'kętrzyński'), - ('lidzbarski', u'lidzbarski'), - ('mragowski', u'mrągowski'), - ('nidzicki', u'nidzicki'), - ('nowomiejski', u'nowomiejski'), - ('olecki', u'olecki'), - ('olsztynski', u'olsztyński'), - ('ostrodzki', u'ostródzki'), - ('piski', u'piski'), - ('szczycienski', u'szczycieński'), - ('wegorzewski', u'węgorzewski'), - ('poznan', u'Poznań'), - ('kalisz', u'Kalisz'), - ('konin', u'Konin'), - ('leszno', u'Leszno'), - ('chodzieski', u'chodziejski'), - ('czarnkowsko-trzcianecki', u'czarnkowsko-trzcianecki'), - ('gnieznienski', u'gnieźnieński'), - ('gostynski', u'gostyński'), - ('grodziski', u'grodziski'), - ('jarocinski', u'jarociński'), - ('kaliski', u'kaliski'), - ('kepinski', u'kępiński'), - ('kolski', u'kolski'), - ('koninski', u'koniński'), - ('koscianski', u'kościański'), - ('krotoszynski', u'krotoszyński'), - ('leszczynski', u'leszczyński'), - ('miedzychodzki', u'międzychodzki'), - ('nowotomyski', u'nowotomyski'), - ('obornicki', u'obornicki'), - ('ostrowski', u'ostrowski'), - ('ostrzeszowski', u'ostrzeszowski'), - ('pilski', u'pilski'), - ('pleszewski', u'pleszewski'), - ('poznanski', u'poznański'), - ('rawicki', u'rawicki'), - ('slupecki', u'słupecki'), - ('szamotulski', u'szamotulski'), - ('sredzki', u'średzki'), - ('sremski', u'śremski'), - ('turecki', u'turecki'), - ('wagrowiecki', u'wągrowiecki'), - ('wolsztynski', u'wolsztyński'), - ('wrzesinski', u'wrzesiński'), - ('zlotowski', u'złotowski'), - ('bialogardzki', u'białogardzki'), - ('choszczenski', u'choszczeński'), - ('drawski', u'drawski'), - ('goleniowski', u'goleniowski'), - ('gryficki', u'gryficki'), - ('gryfinski', u'gryfiński'), - ('kamienski', u'kamieński'), - ('kolobrzeski', u'kołobrzeski'), - ('koszalinski', u'koszaliński'), - ('lobeski', u'łobeski'), - ('mysliborski', u'myśliborski'), - ('policki', u'policki'), - ('pyrzycki', u'pyrzycki'), - ('slawienski', u'sławieński'), - ('stargardzki', u'stargardzki'), - ('szczecinecki', u'szczecinecki'), - ('swidwinski', u'świdwiński'), - ('walecki', u'wałecki'), + ('wroclaw', 'Wrocław'), + ('jeleniagora', 'Jelenia Góra'), + ('legnica', 'Legnica'), + ('boleslawiecki', 'bolesławiecki'), + ('dzierzoniowski', 'dzierżoniowski'), + ('glogowski', 'głogowski'), + ('gorowski', 'górowski'), + ('jaworski', 'jaworski'), + ('jeleniogorski', 'jeleniogórski'), + ('kamiennogorski', 'kamiennogórski'), + ('klodzki', 'kłodzki'), + ('legnicki', 'legnicki'), + ('lubanski', 'lubański'), + ('lubinski', 'lubiński'), + ('lwowecki', 'lwówecki'), + ('milicki', 'milicki'), + ('olesnicki', 'oleśnicki'), + ('olawski', 'oławski'), + ('polkowicki', 'polkowicki'), + ('strzelinski', 'strzeliński'), + ('sredzki', 'średzki'), + ('swidnicki', 'świdnicki'), + ('trzebnicki', 'trzebnicki'), + ('walbrzyski', 'wałbrzyski'), + ('wolowski', 'wołowski'), + ('wroclawski', 'wrocławski'), + ('zabkowicki', 'ząbkowicki'), + ('zgorzelecki', 'zgorzelecki'), + ('zlotoryjski', 'złotoryjski'), + ('bydgoszcz', 'Bydgoszcz'), + ('torun', 'Toruń'), + ('wloclawek', 'Włocławek'), + ('grudziadz', 'Grudziądz'), + ('aleksandrowski', 'aleksandrowski'), + ('brodnicki', 'brodnicki'), + ('bydgoski', 'bydgoski'), + ('chelminski', 'chełmiński'), + ('golubsko-dobrzynski', 'golubsko-dobrzyński'), + ('grudziadzki', 'grudziądzki'), + ('inowroclawski', 'inowrocławski'), + ('lipnowski', 'lipnowski'), + ('mogilenski', 'mogileński'), + ('nakielski', 'nakielski'), + ('radziejowski', 'radziejowski'), + ('rypinski', 'rypiński'), + ('sepolenski', 'sępoleński'), + ('swiecki', 'świecki'), + ('torunski', 'toruński'), + ('tucholski', 'tucholski'), + ('wabrzeski', 'wąbrzeski'), + ('wloclawski', 'wrocławski'), + ('zninski', 'źniński'), + ('lublin', 'Lublin'), + ('biala-podlaska', 'Biała Podlaska'), + ('chelm', 'Chełm'), + ('zamosc', 'Zamość'), + ('bialski', 'bialski'), + ('bilgorajski', 'biłgorajski'), + ('chelmski', 'chełmski'), + ('hrubieszowski', 'hrubieszowski'), + ('janowski', 'janowski'), + ('krasnostawski', 'krasnostawski'), + ('krasnicki', 'kraśnicki'), + ('lubartowski', 'lubartowski'), + ('lubelski', 'lubelski'), + ('leczynski', 'łęczyński'), + ('lukowski', 'łukowski'), + ('opolski', 'opolski'), + ('parczewski', 'parczewski'), + ('pulawski', 'puławski'), + ('radzynski', 'radzyński'), + ('rycki', 'rycki'), + ('swidnicki', 'świdnicki'), + ('tomaszowski', 'tomaszowski'), + ('wlodawski', 'włodawski'), + ('zamojski', 'zamojski'), + ('gorzow-wielkopolski', 'Gorzów Wielkopolski'), + ('zielona-gora', 'Zielona Góra'), + ('gorzowski', 'gorzowski'), + ('krosnienski', 'krośnieński'), + ('miedzyrzecki', 'międzyrzecki'), + ('nowosolski', 'nowosolski'), + ('slubicki', 'słubicki'), + ('strzelecko-drezdenecki', 'strzelecko-drezdenecki'), + ('sulecinski', 'suleńciński'), + ('swiebodzinski', 'świebodziński'), + ('wschowski', 'wschowski'), + ('zielonogorski', 'zielonogórski'), + ('zaganski', 'żagański'), + ('zarski', 'żarski'), + ('lodz', 'Łódź'), + ('piotrkow-trybunalski', 'Piotrków Trybunalski'), + ('skierniewice', 'Skierniewice'), + ('belchatowski', 'bełchatowski'), + ('brzezinski', 'brzeziński'), + ('kutnowski', 'kutnowski'), + ('laski', 'łaski'), + ('leczycki', 'łęczycki'), + ('lowicki', 'łowicki'), + ('lodzki wschodni', 'łódzki wschodni'), + ('opoczynski', 'opoczyński'), + ('pabianicki', 'pabianicki'), + ('pajeczanski', 'pajęczański'), + ('piotrkowski', 'piotrkowski'), + ('poddebicki', 'poddębicki'), + ('radomszczanski', 'radomszczański'), + ('rawski', 'rawski'), + ('sieradzki', 'sieradzki'), + ('skierniewicki', 'skierniewicki'), + ('tomaszowski', 'tomaszowski'), + ('wielunski', 'wieluński'), + ('wieruszowski', 'wieruszowski'), + ('zdunskowolski', 'zduńskowolski'), + ('zgierski', 'zgierski'), + ('krakow', 'Kraków'), + ('tarnow', 'Tarnów'), + ('nowy-sacz', 'Nowy Sącz'), + ('bochenski', 'bocheński'), + ('brzeski', 'brzeski'), + ('chrzanowski', 'chrzanowski'), + ('dabrowski', 'dąbrowski'), + ('gorlicki', 'gorlicki'), + ('krakowski', 'krakowski'), + ('limanowski', 'limanowski'), + ('miechowski', 'miechowski'), + ('myslenicki', 'myślenicki'), + ('nowosadecki', 'nowosądecki'), + ('nowotarski', 'nowotarski'), + ('olkuski', 'olkuski'), + ('oswiecimski', 'oświęcimski'), + ('proszowicki', 'proszowicki'), + ('suski', 'suski'), + ('tarnowski', 'tarnowski'), + ('tatrzanski', 'tatrzański'), + ('wadowicki', 'wadowicki'), + ('wielicki', 'wielicki'), + ('warszawa', 'Warszawa'), + ('ostroleka', 'Ostrołęka'), + ('plock', 'Płock'), + ('radom', 'Radom'), + ('siedlce', 'Siedlce'), + ('bialobrzeski', 'białobrzeski'), + ('ciechanowski', 'ciechanowski'), + ('garwolinski', 'garwoliński'), + ('gostyninski', 'gostyniński'), + ('grodziski', 'grodziski'), + ('grojecki', 'grójecki'), + ('kozienicki', 'kozenicki'), + ('legionowski', 'legionowski'), + ('lipski', 'lipski'), + ('losicki', 'łosicki'), + ('makowski', 'makowski'), + ('minski', 'miński'), + ('mlawski', 'mławski'), + ('nowodworski', 'nowodworski'), + ('ostrolecki', 'ostrołęcki'), + ('ostrowski', 'ostrowski'), + ('otwocki', 'otwocki'), + ('piaseczynski', 'piaseczyński'), + ('plocki', 'płocki'), + ('plonski', 'płoński'), + ('pruszkowski', 'pruszkowski'), + ('przasnyski', 'przasnyski'), + ('przysuski', 'przysuski'), + ('pultuski', 'pułtuski'), + ('radomski', 'radomski'), + ('siedlecki', 'siedlecki'), + ('sierpecki', 'sierpecki'), + ('sochaczewski', 'sochaczewski'), + ('sokolowski', 'sokołowski'), + ('szydlowiecki', 'szydłowiecki'), + ('warszawski-zachodni', 'warszawski zachodni'), + ('wegrowski', 'węgrowski'), + ('wolominski', 'wołomiński'), + ('wyszkowski', 'wyszkowski'), + ('zwolenski', 'zwoleński'), + ('zurominski', 'żuromiński'), + ('zyrardowski', 'żyrardowski'), + ('opole', 'Opole'), + ('brzeski', 'brzeski'), + ('glubczycki', 'głubczyski'), + ('kedzierzynsko-kozielski', 'kędzierzyński-kozielski'), + ('kluczborski', 'kluczborski'), + ('krapkowicki', 'krapkowicki'), + ('namyslowski', 'namysłowski'), + ('nyski', 'nyski'), + ('oleski', 'oleski'), + ('opolski', 'opolski'), + ('prudnicki', 'prudnicki'), + ('strzelecki', 'strzelecki'), + ('rzeszow', 'Rzeszów'), + ('krosno', 'Krosno'), + ('przemysl', 'Przemyśl'), + ('tarnobrzeg', 'Tarnobrzeg'), + ('bieszczadzki', 'bieszczadzki'), + ('brzozowski', 'brzozowski'), + ('debicki', 'dębicki'), + ('jaroslawski', 'jarosławski'), + ('jasielski', 'jasielski'), + ('kolbuszowski', 'kolbuszowski'), + ('krosnienski', 'krośnieński'), + ('leski', 'leski'), + ('lezajski', 'leżajski'), + ('lubaczowski', 'lubaczowski'), + ('lancucki', 'łańcucki'), + ('mielecki', 'mielecki'), + ('nizanski', 'niżański'), + ('przemyski', 'przemyski'), + ('przeworski', 'przeworski'), + ('ropczycko-sedziszowski', 'ropczycko-sędziszowski'), + ('rzeszowski', 'rzeszowski'), + ('sanocki', 'sanocki'), + ('stalowowolski', 'stalowowolski'), + ('strzyzowski', 'strzyżowski'), + ('tarnobrzeski', 'tarnobrzeski'), + ('bialystok', 'Białystok'), + ('lomza', 'Łomża'), + ('suwalki', 'Suwałki'), + ('augustowski', 'augustowski'), + ('bialostocki', 'białostocki'), + ('bielski', 'bielski'), + ('grajewski', 'grajewski'), + ('hajnowski', 'hajnowski'), + ('kolnenski', 'kolneński'), + ('łomzynski', 'łomżyński'), + ('moniecki', 'moniecki'), + ('sejnenski', 'sejneński'), + ('siemiatycki', 'siematycki'), + ('sokolski', 'sokólski'), + ('suwalski', 'suwalski'), + ('wysokomazowiecki', 'wysokomazowiecki'), + ('zambrowski', 'zambrowski'), + ('gdansk', 'Gdańsk'), + ('gdynia', 'Gdynia'), + ('slupsk', 'Słupsk'), + ('sopot', 'Sopot'), + ('bytowski', 'bytowski'), + ('chojnicki', 'chojnicki'), + ('czluchowski', 'człuchowski'), + ('kartuski', 'kartuski'), + ('koscierski', 'kościerski'), + ('kwidzynski', 'kwidzyński'), + ('leborski', 'lęborski'), + ('malborski', 'malborski'), + ('nowodworski', 'nowodworski'), + ('gdanski', 'gdański'), + ('pucki', 'pucki'), + ('slupski', 'słupski'), + ('starogardzki', 'starogardzki'), + ('sztumski', 'sztumski'), + ('tczewski', 'tczewski'), + ('wejherowski', 'wejcherowski'), + ('katowice', 'Katowice'), + ('bielsko-biala', 'Bielsko-Biała'), + ('bytom', 'Bytom'), + ('chorzow', 'Chorzów'), + ('czestochowa', 'Częstochowa'), + ('dabrowa-gornicza', 'Dąbrowa Górnicza'), + ('gliwice', 'Gliwice'), + ('jastrzebie-zdroj', 'Jastrzębie Zdrój'), + ('jaworzno', 'Jaworzno'), + ('myslowice', 'Mysłowice'), + ('piekary-slaskie', 'Piekary Śląskie'), + ('ruda-slaska', 'Ruda Śląska'), + ('rybnik', 'Rybnik'), + ('siemianowice-slaskie', 'Siemianowice Śląskie'), + ('sosnowiec', 'Sosnowiec'), + ('swietochlowice', 'Świętochłowice'), + ('tychy', 'Tychy'), + ('zabrze', 'Zabrze'), + ('zory', 'Żory'), + ('bedzinski', 'będziński'), + ('bielski', 'bielski'), + ('bierunsko-ledzinski', 'bieruńsko-lędziński'), + ('cieszynski', 'cieszyński'), + ('czestochowski', 'częstochowski'), + ('gliwicki', 'gliwicki'), + ('klobucki', 'kłobucki'), + ('lubliniecki', 'lubliniecki'), + ('mikolowski', 'mikołowski'), + ('myszkowski', 'myszkowski'), + ('pszczynski', 'pszczyński'), + ('raciborski', 'raciborski'), + ('rybnicki', 'rybnicki'), + ('tarnogorski', 'tarnogórski'), + ('wodzislawski', 'wodzisławski'), + ('zawiercianski', 'zawierciański'), + ('zywiecki', 'żywiecki'), + ('kielce', 'Kielce'), + ('buski', 'buski'), + ('jedrzejowski', 'jędrzejowski'), + ('kazimierski', 'kazimierski'), + ('kielecki', 'kielecki'), + ('konecki', 'konecki'), + ('opatowski', 'opatowski'), + ('ostrowiecki', 'ostrowiecki'), + ('pinczowski', 'pińczowski'), + ('sandomierski', 'sandomierski'), + ('skarzyski', 'skarżyski'), + ('starachowicki', 'starachowicki'), + ('staszowski', 'staszowski'), + ('wloszczowski', 'włoszczowski'), + ('olsztyn', 'Olsztyn'), + ('elblag', 'Elbląg'), + ('bartoszycki', 'bartoszycki'), + ('braniewski', 'braniewski'), + ('dzialdowski', 'działdowski'), + ('elblaski', 'elbląski'), + ('elcki', 'ełcki'), + ('gizycki', 'giżycki'), + ('goldapski', 'gołdapski'), + ('ilawski', 'iławski'), + ('ketrzynski', 'kętrzyński'), + ('lidzbarski', 'lidzbarski'), + ('mragowski', 'mrągowski'), + ('nidzicki', 'nidzicki'), + ('nowomiejski', 'nowomiejski'), + ('olecki', 'olecki'), + ('olsztynski', 'olsztyński'), + ('ostrodzki', 'ostródzki'), + ('piski', 'piski'), + ('szczycienski', 'szczycieński'), + ('wegorzewski', 'węgorzewski'), + ('poznan', 'Poznań'), + ('kalisz', 'Kalisz'), + ('konin', 'Konin'), + ('leszno', 'Leszno'), + ('chodzieski', 'chodziejski'), + ('czarnkowsko-trzcianecki', 'czarnkowsko-trzcianecki'), + ('gnieznienski', 'gnieźnieński'), + ('gostynski', 'gostyński'), + ('grodziski', 'grodziski'), + ('jarocinski', 'jarociński'), + ('kaliski', 'kaliski'), + ('kepinski', 'kępiński'), + ('kolski', 'kolski'), + ('koninski', 'koniński'), + ('koscianski', 'kościański'), + ('krotoszynski', 'krotoszyński'), + ('leszczynski', 'leszczyński'), + ('miedzychodzki', 'międzychodzki'), + ('nowotomyski', 'nowotomyski'), + ('obornicki', 'obornicki'), + ('ostrowski', 'ostrowski'), + ('ostrzeszowski', 'ostrzeszowski'), + ('pilski', 'pilski'), + ('pleszewski', 'pleszewski'), + ('poznanski', 'poznański'), + ('rawicki', 'rawicki'), + ('slupecki', 'słupecki'), + ('szamotulski', 'szamotulski'), + ('sredzki', 'średzki'), + ('sremski', 'śremski'), + ('turecki', 'turecki'), + ('wagrowiecki', 'wągrowiecki'), + ('wolsztynski', 'wolsztyński'), + ('wrzesinski', 'wrzesiński'), + ('zlotowski', 'złotowski'), + ('bialogardzki', 'białogardzki'), + ('choszczenski', 'choszczeński'), + ('drawski', 'drawski'), + ('goleniowski', 'goleniowski'), + ('gryficki', 'gryficki'), + ('gryfinski', 'gryfiński'), + ('kamienski', 'kamieński'), + ('kolobrzeski', 'kołobrzeski'), + ('koszalinski', 'koszaliński'), + ('lobeski', 'łobeski'), + ('mysliborski', 'myśliborski'), + ('policki', 'policki'), + ('pyrzycki', 'pyrzycki'), + ('slawienski', 'sławieński'), + ('stargardzki', 'stargardzki'), + ('szczecinecki', 'szczecinecki'), + ('swidwinski', 'świdwiński'), + ('walecki', 'wałecki'), ) diff --git a/django/contrib/localflavor/pt/forms.py b/django/contrib/localflavor/pt/forms.py index 3de7376eac..01cdd101b2 100644 --- a/django/contrib/localflavor/pt/forms.py +++ b/django/contrib/localflavor/pt/forms.py @@ -1,12 +1,14 @@ """ PT-specific Form helpers """ +from __future__ import unicode_literals + import re from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ phone_digits_re = re.compile(r'^(\d{9}|(00|\+)\d*)$') @@ -24,10 +26,10 @@ class PTZipCodeField(RegexField): def clean(self,value): cleaned = super(PTZipCodeField, self).clean(value) if len(cleaned) == 7: - return u'%s-%s' % (cleaned[:4],cleaned[4:]) + return '%s-%s' % (cleaned[:4],cleaned[4:]) else: return cleaned - + class PTPhoneNumberField(Field): """ Validate local Portuguese phone number (including international ones) @@ -40,9 +42,9 @@ class PTPhoneNumberField(Field): def clean(self, value): super(PTPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' - value = re.sub('(\.|\s)', '', smart_unicode(value)) + return '' + value = re.sub('(\.|\s)', '', smart_text(value)) m = phone_digits_re.search(value) if m: - return u'%s' % value + return '%s' % value raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/py/py_department.py b/django/contrib/localflavor/py/py_department.py index 28c613b9a0..619bae3edb 100644 --- a/django/contrib/localflavor/py/py_department.py +++ b/django/contrib/localflavor/py/py_department.py @@ -1,45 +1,46 @@ # -*- coding: utf-8 -*- # http://www.statoids.com/upy.html +from __future__ import unicode_literals DEPARTMENT_CHOICES = ( - ('AG', u'Alto Paraguay'), - ('AA', u'Alto Paraná'), - ('AM', u'Amambay'), - ('AS', u'Asunción'), - ('BQ', u'Boquerón'), - ('CG', u'Caaguazú'), - ('CZ', u'Caazapá'), - ('CY', u'Canindeyú'), - ('CE', u'Central'), - ('CN', u'Concepción'), - ('CR', u'Cordillera'), - ('GU', u'Guairá'), - ('IT', u'Itapúa'), - ('MI', u'Misiones'), - ('NE', u'Ñeembucú'), - ('PG', u'Paraguarí'), - ('PH', u'Pdte. Hayes'), - ('SP', u'San Pedro'), + ('AG', 'Alto Paraguay'), + ('AA', 'Alto Paraná'), + ('AM', 'Amambay'), + ('AS', 'Asunción'), + ('BQ', 'Boquerón'), + ('CG', 'Caaguazú'), + ('CZ', 'Caazapá'), + ('CY', 'Canindeyú'), + ('CE', 'Central'), + ('CN', 'Concepción'), + ('CR', 'Cordillera'), + ('GU', 'Guairá'), + ('IT', 'Itapúa'), + ('MI', 'Misiones'), + ('NE', 'Ñeembucú'), + ('PG', 'Paraguarí'), + ('PH', 'Pdte. Hayes'), + ('SP', 'San Pedro'), ) DEPARTMENT_ROMAN_CHOICES = ( - ('CN', u'I Concepción'), - ('SP', u'II San Pedro'), - ('CR', u'III Cordillera'), - ('GU', u'IV Guairá'), - ('CG', u'V Caaguazú'), - ('CZ', u'VI Caazapá'), - ('IT', u'VII Itapúa'), - ('MI', u'VIII Misiones'), - ('PG', u'IX Paraguarí'), - ('AA', u'X Alto Paraná'), - ('CE', u'XI Central'), - ('NE', u'XII Ñeembucú'), - ('AM', u'XIII Amambay'), - ('CY', u'XIV Canindeyú'), - ('PH', u'XV Pdte. Hayes'), - ('AG', u'XVI Alto Paraguay'), - ('BQ', u'XVII Boquerón'), - ('AS', u'XVIII Asunción'), + ('CN', 'I Concepción'), + ('SP', 'II San Pedro'), + ('CR', 'III Cordillera'), + ('GU', 'IV Guairá'), + ('CG', 'V Caaguazú'), + ('CZ', 'VI Caazapá'), + ('IT', 'VII Itapúa'), + ('MI', 'VIII Misiones'), + ('PG', 'IX Paraguarí'), + ('AA', 'X Alto Paraná'), + ('CE', 'XI Central'), + ('NE', 'XII Ñeembucú'), + ('AM', 'XIII Amambay'), + ('CY', 'XIV Canindeyú'), + ('PH', 'XV Pdte. Hayes'), + ('AG', 'XVI Alto Paraguay'), + ('BQ', 'XVII Boquerón'), + ('AS', 'XVIII Asunción'), ) diff --git a/django/contrib/localflavor/ro/forms.py b/django/contrib/localflavor/ro/forms.py index 5c458390b2..bdbed5c476 100644 --- a/django/contrib/localflavor/ro/forms.py +++ b/django/contrib/localflavor/ro/forms.py @@ -2,7 +2,7 @@ """ Romanian specific form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.localflavor.ro.ro_counties import COUNTIES_CHOICES from django.core.validators import EMPTY_VALUES @@ -30,7 +30,7 @@ class ROCIFField(RegexField): """ value = super(ROCIFField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' # strip RO part if value[0:2] == 'RO': value = value[2:] @@ -67,7 +67,7 @@ class ROCNPField(RegexField): """ value = super(ROCNPField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' # check birthdate digits import datetime try: @@ -100,13 +100,13 @@ class ROCountyField(Field): Arges => invalid """ default_error_messages = { - 'invalid': u'Enter a Romanian county code or name.', + 'invalid': 'Enter a Romanian county code or name.', } def clean(self, value): super(ROCountyField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' try: value = value.strip().upper() except AttributeError: @@ -152,7 +152,7 @@ class ROIBANField(RegexField): """ value = super(ROIBANField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.replace('-','') value = value.replace(' ','') value = value.upper() @@ -184,7 +184,7 @@ class ROPhoneNumberField(RegexField): """ value = super(ROPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.replace('-','') value = value.replace('(','') value = value.replace(')','') diff --git a/django/contrib/localflavor/ro/ro_counties.py b/django/contrib/localflavor/ro/ro_counties.py index 40423ddc87..282cfc5193 100644 --- a/django/contrib/localflavor/ro/ro_counties.py +++ b/django/contrib/localflavor/ro/ro_counties.py @@ -5,48 +5,49 @@ 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. """ +from __future__ import unicode_literals 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'), + ('AB', 'Alba'), + ('AR', 'Arad'), + ('AG', 'Argeş'), + ('BC', 'Bacău'), + ('BH', 'Bihor'), + ('BN', 'Bistriţa-Năsăud'), + ('BT', 'Botoşani'), + ('BV', 'Braşov'), + ('BR', 'Brăila'), + ('B', 'Bucureşti'), + ('BZ', 'Buzău'), + ('CS', 'Caraş-Severin'), + ('CL', 'Călăraşi'), + ('CJ', 'Cluj'), + ('CT', 'Constanţa'), + ('CV', 'Covasna'), + ('DB', 'Dâmboviţa'), + ('DJ', 'Dolj'), + ('GL', 'Galaţi'), + ('GR', 'Giurgiu'), + ('GJ', 'Gorj'), + ('HR', 'Harghita'), + ('HD', 'Hunedoara'), + ('IL', 'Ialomiţa'), + ('IS', 'Iaşi'), + ('IF', 'Ilfov'), + ('MM', 'Maramureş'), + ('MH', 'Mehedinţi'), + ('MS', 'Mureş'), + ('NT', 'Neamţ'), + ('OT', 'Olt'), + ('PH', 'Prahova'), + ('SM', 'Satu Mare'), + ('SJ', 'Sălaj'), + ('SB', 'Sibiu'), + ('SV', 'Suceava'), + ('TR', 'Teleorman'), + ('TM', 'Timiş'), + ('TL', 'Tulcea'), + ('VS', 'Vaslui'), + ('VL', 'Vâlcea'), + ('VN', 'Vrancea'), ) diff --git a/django/contrib/localflavor/ru/forms.py b/django/contrib/localflavor/ru/forms.py index d01f5a1e3b..03114d0629 100644 --- a/django/contrib/localflavor/ru/forms.py +++ b/django/contrib/localflavor/ru/forms.py @@ -1,7 +1,7 @@ """ Russian-specific forms helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -34,7 +34,7 @@ class RUPostalCodeField(RegexField): Format: XXXXXX, where X is any digit, and first digit is not zero. """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXXXXX.'), + 'invalid': _('Enter a postal code in the format XXXXXX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): super(RUPostalCodeField, self).__init__(r'^\d{6}$', @@ -47,7 +47,7 @@ class RUPassportNumberField(RegexField): XXXX XXXXXX where X - any digit. """ default_error_messages = { - 'invalid': _(u'Enter a passport number in the format XXXX XXXXXX.'), + 'invalid': _('Enter a passport number in the format XXXX XXXXXX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): super(RUPassportNumberField, self).__init__(r'^\d{4} \d{6}$', @@ -60,7 +60,7 @@ class RUAlienPassportNumberField(RegexField): XX XXXXXXX where X - any digit. """ default_error_messages = { - 'invalid': _(u'Enter a passport number in the format XX XXXXXXX.'), + 'invalid': _('Enter a passport number in the format XX XXXXXXX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): super(RUAlienPassportNumberField, self).__init__(r'^\d{2} \d{7}$', diff --git a/django/contrib/localflavor/se/forms.py b/django/contrib/localflavor/se/forms.py index 5c4e2325a9..43d06a08ec 100644 --- a/django/contrib/localflavor/se/forms.py +++ b/django/contrib/localflavor/se/forms.py @@ -2,7 +2,7 @@ """ Swedish specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -58,7 +58,7 @@ class SEOrganisationNumberField(forms.CharField): value = super(SEOrganisationNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = SWEDISH_ID_NUMBER.match(value) if not match: @@ -116,7 +116,7 @@ class SEPersonalIdentityNumberField(forms.CharField): value = super(SEPersonalIdentityNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = SWEDISH_ID_NUMBER.match(value) if match is None: diff --git a/django/contrib/localflavor/se/se_counties.py b/django/contrib/localflavor/se/se_counties.py index db54fb9f39..20090d3b30 100644 --- a/django/contrib/localflavor/se/se_counties.py +++ b/django/contrib/localflavor/se/se_counties.py @@ -8,29 +8,30 @@ This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ COUNTY_CHOICES = ( - ('AB', _(u'Stockholm')), - ('AC', _(u'Västerbotten')), - ('BD', _(u'Norrbotten')), - ('C', _(u'Uppsala')), - ('D', _(u'Södermanland')), - ('E', _(u'Östergötland')), - ('F', _(u'Jönköping')), - ('G', _(u'Kronoberg')), - ('H', _(u'Kalmar')), - ('I', _(u'Gotland')), - ('K', _(u'Blekinge')), - ('M', _(u'Skåne')), - ('N', _(u'Halland')), - ('O', _(u'Västra Götaland')), - ('S', _(u'Värmland')), - ('T', _(u'Örebro')), - ('U', _(u'Västmanland')), - ('W', _(u'Dalarna')), - ('X', _(u'Gävleborg')), - ('Y', _(u'Västernorrland')), - ('Z', _(u'Jämtland')), + ('AB', _('Stockholm')), + ('AC', _('Västerbotten')), + ('BD', _('Norrbotten')), + ('C', _('Uppsala')), + ('D', _('Södermanland')), + ('E', _('Östergötland')), + ('F', _('Jönköping')), + ('G', _('Kronoberg')), + ('H', _('Kalmar')), + ('I', _('Gotland')), + ('K', _('Blekinge')), + ('M', _('Skåne')), + ('N', _('Halland')), + ('O', _('Västra Götaland')), + ('S', _('Värmland')), + ('T', _('Örebro')), + ('U', _('Västmanland')), + ('W', _('Dalarna')), + ('X', _('Gävleborg')), + ('Y', _('Västernorrland')), + ('Z', _('Jämtland')), ) diff --git a/django/contrib/localflavor/se/utils.py b/django/contrib/localflavor/se/utils.py index 5e7c2b7dae..783062ebb4 100644 --- a/django/contrib/localflavor/se/utils.py +++ b/django/contrib/localflavor/se/utils.py @@ -1,4 +1,5 @@ import datetime +from django.utils import six def id_number_checksum(gd): """ @@ -65,7 +66,7 @@ def validate_id_birthday(gd, fix_coordination_number_day=True): def format_personal_id_number(birth_day, gd): # birth_day.strftime cannot be used, since it does not support dates < 1900 - return unicode(str(birth_day.year) + gd['month'] + gd['day'] + gd['serial'] + gd['checksum']) + return six.text_type(str(birth_day.year) + gd['month'] + gd['day'] + gd['serial'] + gd['checksum']) def format_organisation_number(gd): if gd['century'] is None: @@ -73,7 +74,7 @@ def format_organisation_number(gd): else: century = gd['century'] - return unicode(century + gd['year'] + gd['month'] + gd['day'] + gd['serial'] + gd['checksum']) + return six.text_type(century + gd['year'] + gd['month'] + gd['day'] + gd['serial'] + gd['checksum']) def valid_organisation(gd): return gd['century'] in (None, 16) and \ diff --git a/django/contrib/localflavor/si/forms.py b/django/contrib/localflavor/si/forms.py index f1188dd222..bab35935fd 100644 --- a/django/contrib/localflavor/si/forms.py +++ b/django/contrib/localflavor/si/forms.py @@ -2,7 +2,7 @@ Slovenian specific form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import datetime import re @@ -21,16 +21,16 @@ class SIEMSOField(CharField): """ default_error_messages = { - 'invalid': _(u'This field should contain exactly 13 digits.'), - 'date': _(u'The first 7 digits of the EMSO must represent a valid past date.'), - 'checksum': _(u'The EMSO is not valid.'), + 'invalid': _('This field should contain exactly 13 digits.'), + 'date': _('The first 7 digits of the EMSO must represent a valid past date.'), + 'checksum': _('The EMSO is not valid.'), } emso_regex = re.compile('^(\d{2})(\d{2})(\d{3})(\d{2})(\d{3})(\d)$') def clean(self, value): super(SIEMSOField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip() @@ -41,7 +41,7 @@ class SIEMSOField(CharField): # Validate EMSO s = 0 int_values = [int(i) for i in value] - for a, b in zip(int_values, range(7, 1, -1) * 2): + for a, b in zip(int_values, list(range(7, 1, -1)) * 2): s += a * b chk = s % 11 if chk == 0: @@ -83,14 +83,14 @@ class SITaxNumberField(CharField): """ default_error_messages = { - 'invalid': _(u'Enter a valid tax number in form SIXXXXXXXX'), + 'invalid': _('Enter a valid tax number in form SIXXXXXXXX'), } sitax_regex = re.compile('^(?:SI)?([1-9]\d{7})$') def clean(self, value): super(SITaxNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.strip() @@ -148,14 +148,14 @@ class SIPhoneNumberField(CharField): """ default_error_messages = { - 'invalid': _(u'Enter phone number in form +386XXXXXXXX or 0XXXXXXXX.'), + 'invalid': _('Enter phone number in form +386XXXXXXXX or 0XXXXXXXX.'), } phone_regex = re.compile('^(?:(?:00|\+)386|0)(\d{7,8})$') def clean(self, value): super(SIPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' value = value.replace(' ', '').replace('-', '').replace('/', '') m = self.phone_regex.match(value) diff --git a/django/contrib/localflavor/si/si_postalcodes.py b/django/contrib/localflavor/si/si_postalcodes.py index 7baac94e03..4d027afcff 100644 --- a/django/contrib/localflavor/si/si_postalcodes.py +++ b/django/contrib/localflavor/si/si_postalcodes.py @@ -1,469 +1,470 @@ # *-* coding: utf-8 *-* +from __future__ import unicode_literals SI_POSTALCODES = [ - (1000, u'Ljubljana'), - (1215, u'Medvode'), - (1216, u'Smlednik'), - (1217, u'Vodice'), - (1218, u'Komenda'), - (1219, u'Laze v Tuhinju'), - (1221, u'Motnik'), - (1222, u'Trojane'), - (1223, u'Blagovica'), - (1225, u'Lukovica'), - (1230, u'Dom\u017eale'), - (1233, u'Dob'), - (1234, u'Menge\u0161'), - (1235, u'Radomlje'), - (1236, u'Trzin'), - (1241, u'Kamnik'), - (1242, u'Stahovica'), - (1251, u'Morav\u010de'), - (1252, u'Va\u010de'), - (1262, u'Dol pri Ljubljani'), - (1270, u'Litija'), - (1272, u'Pol\u0161nik'), - (1273, u'Dole pri Litiji'), - (1274, u'Gabrovka'), - (1275, u'\u0160martno pri Litiji'), - (1276, u'Primskovo'), - (1281, u'Kresnice'), - (1282, u'Sava'), - (1290, u'Grosuplje'), - (1291, u'\u0160kofljica'), - (1292, u'Ig'), - (1293, u'\u0160marje - Sap'), - (1294, u'Vi\u0161nja Gora'), - (1295, u'Ivan\u010dna Gorica'), - (1296, u'\u0160entvid pri Sti\u010dni'), - (1301, u'Krka'), - (1303, u'Zagradec'), - (1310, u'Ribnica'), - (1311, u'Turjak'), - (1312, u'Videm - Dobrepolje'), - (1313, u'Struge'), - (1314, u'Rob'), - (1315, u'Velike La\u0161\u010de'), - (1316, u'Ortnek'), - (1317, u'Sodra\u017eica'), - (1318, u'Lo\u0161ki Potok'), - (1319, u'Draga'), - (1330, u'Ko\u010devje'), - (1331, u'Dolenja vas'), - (1332, u'Stara Cerkev'), - (1336, u'Kostel'), - (1337, u'Osilnica'), - (1338, u'Ko\u010devska Reka'), - (1351, u'Brezovica pri Ljubljani'), - (1352, u'Preserje'), - (1353, u'Borovnica'), - (1354, u'Horjul'), - (1355, u'Polhov Gradec'), - (1356, u'Dobrova'), - (1357, u'Notranje Gorice'), - (1358, u'Log pri Brezovici'), - (1360, u'Vrhnika'), - (1370, u'Logatec'), - (1372, u'Hotedr\u0161ica'), - (1373, u'Rovte'), - (1380, u'Cerknica'), - (1381, u'Rakek'), - (1382, u'Begunje pri Cerknici'), - (1384, u'Grahovo'), - (1385, u'Nova vas'), - (1386, u'Stari trg pri Lo\u017eu'), - (1410, u'Zagorje ob Savi'), - (1411, u'Izlake'), - (1412, u'Kisovec'), - (1413, u'\u010cem\u0161enik'), - (1414, u'Podkum'), - (1420, u'Trbovlje'), - (1423, u'Dobovec'), - (1430, u'Hrastnik'), - (1431, u'Dol pri Hrastniku'), - (1432, u'Zidani Most'), - (1433, u'Rade\u010de'), - (1434, u'Loka pri Zidanem Mostu'), - (2000, u'Maribor'), - (2201, u'Zgornja Kungota'), - (2204, u'Miklav\u017e na Dravskem polju'), - (2205, u'Star\u0161e'), - (2206, u'Marjeta na Dravskem polju'), - (2208, u'Pohorje'), - (2211, u'Pesnica pri Mariboru'), - (2212, u'\u0160entilj v Slovenskih goricah'), - (2213, u'Zgornja Velka'), - (2214, u'Sladki vrh'), - (2215, u'Cer\u0161ak'), - (2221, u'Jarenina'), - (2222, u'Jakobski Dol'), - (2223, u'Jurovski Dol'), - (2229, u'Male\u010dnik'), - (2230, u'Lenart v Slovenskih goricah'), - (2231, u'Pernica'), - (2232, u'Voli\u010dina'), - (2233, u'Sveta Ana v Slovenskih goricah'), - (2234, u'Benedikt'), - (2235, u'Sveta Trojica v Slovenskih goricah'), - (2236, u'Cerkvenjak'), - (2241, u'Spodnji Duplek'), - (2242, u'Zgornja Korena'), - (2250, u'Ptuj'), - (2252, u'Dornava'), - (2253, u'Destrnik'), - (2254, u'Trnovska vas'), - (2255, u'Vitomarci'), - (2256, u'Jur\u0161inci'), - (2257, u'Polen\u0161ak'), - (2258, u'Sveti Toma\u017e'), - (2259, u'Ivanjkovci'), - (2270, u'Ormo\u017e'), - (2272, u'Gori\u0161nica'), - (2273, u'Podgorci'), - (2274, u'Velika Nedelja'), - (2275, u'Miklav\u017e pri Ormo\u017eu'), - (2276, u'Kog'), - (2277, u'Sredi\u0161\u010de ob Dravi'), - (2281, u'Markovci'), - (2282, u'Cirkulane'), - (2283, u'Zavr\u010d'), - (2284, u'Videm pri Ptuju'), - (2285, u'Zgornji Leskovec'), - (2286, u'Podlehnik'), - (2287, u'\u017detale'), - (2288, u'Hajdina'), - (2289, u'Stoperce'), - (2310, u'Slovenska Bistrica'), - (2311, u'Ho\u010de'), - (2312, u'Orehova vas'), - (2313, u'Fram'), - (2314, u'Zgornja Polskava'), - (2315, u'\u0160martno na Pohorju'), - (2316, u'Zgornja Lo\u017enica'), - (2317, u'Oplotnica'), - (2318, u'Laporje'), - (2319, u'Polj\u010dane'), - (2321, u'Makole'), - (2322, u'Maj\u0161perk'), - (2323, u'Ptujska Gora'), - (2324, u'Lovrenc na Dravskem polju'), - (2325, u'Kidri\u010devo'), - (2326, u'Cirkovce'), - (2327, u'Ra\u010de'), - (2331, u'Pragersko'), - (2341, u'Limbu\u0161'), - (2342, u'Ru\u0161e'), - (2343, u'Fala'), - (2344, u'Lovrenc na Pohorju'), - (2345, u'Bistrica ob Dravi'), - (2351, u'Kamnica'), - (2352, u'Selnica ob Dravi'), - (2353, u'Sv. Duh na Ostrem Vrhu'), - (2354, u'Bresternica'), - (2360, u'Radlje ob Dravi'), - (2361, u'O\u017ebalt'), - (2362, u'Kapla'), - (2363, u'Podvelka'), - (2364, u'Ribnica na Pohorju'), - (2365, u'Vuhred'), - (2366, u'Muta'), - (2367, u'Vuzenica'), - (2370, u'Dravograd'), - (2371, u'Trbonje'), - (2372, u'Libeli\u010de'), - (2373, u'\u0160entjan\u017e pri Dravogradu'), - (2380, u'Slovenj Gradec'), - (2381, u'Podgorje pri Slovenj Gradcu'), - (2382, u'Mislinja'), - (2383, u'\u0160martno pri Slovenj Gradcu'), - (2390, u'Ravne na Koro\u0161kem'), - (2391, u'Prevalje'), - (2392, u'Me\u017eica'), - (2393, u'\u010crna na Koro\u0161kem'), - (2394, u'Kotlje'), - (3000, u'Celje'), - (3201, u'\u0160martno v Ro\u017eni dolini'), - (3202, u'Ljube\u010dna'), - (3203, u'Nova Cerkev'), - (3204, u'Dobrna'), - (3205, u'Vitanje'), - (3206, u'Stranice'), - (3210, u'Slovenske Konjice'), - (3211, u'\u0160kofja vas'), - (3212, u'Vojnik'), - (3213, u'Frankolovo'), - (3214, u'Zre\u010de'), - (3215, u'Lo\u010de'), - (3220, u'\u0160tore'), - (3221, u'Teharje'), - (3222, u'Dramlje'), - (3223, u'Loka pri \u017dusmu'), - (3224, u'Dobje pri Planini'), - (3225, u'Planina pri Sevnici'), - (3230, u'\u0160entjur'), - (3231, u'Grobelno'), - (3232, u'Ponikva'), - (3233, u'Kalobje'), - (3240, u'\u0160marje pri Jel\u0161ah'), - (3241, u'Podplat'), - (3250, u'Roga\u0161ka Slatina'), - (3252, u'Rogatec'), - (3253, u'Pristava pri Mestinju'), - (3254, u'Pod\u010detrtek'), - (3255, u'Bu\u010de'), - (3256, u'Bistrica ob Sotli'), - (3257, u'Podsreda'), - (3260, u'Kozje'), - (3261, u'Lesi\u010dno'), - (3262, u'Prevorje'), - (3263, u'Gorica pri Slivnici'), - (3264, u'Sveti \u0160tefan'), - (3270, u'La\u0161ko'), - (3271, u'\u0160entrupert'), - (3272, u'Rimske Toplice'), - (3273, u'Jurklo\u0161ter'), - (3301, u'Petrov\u010de'), - (3302, u'Gri\u017ee'), - (3303, u'Gomilsko'), - (3304, u'Tabor'), - (3305, u'Vransko'), - (3310, u'\u017dalec'), - (3311, u'\u0160empeter v Savinjski dolini'), - (3312, u'Prebold'), - (3313, u'Polzela'), - (3314, u'Braslov\u010de'), - (3320, u'Velenje - dostava'), - (3322, u'Velenje - po\u0161tni predali'), - (3325, u'\u0160o\u0161tanj'), - (3326, u'Topol\u0161ica'), - (3327, u'\u0160martno ob Paki'), - (3330, u'Mozirje'), - (3331, u'Nazarje'), - (3332, u'Re\u010dica ob Savinji'), - (3333, u'Ljubno ob Savinji'), - (3334, u'Lu\u010de'), - (3335, u'Sol\u010dava'), - (3341, u'\u0160martno ob Dreti'), - (3342, u'Gornji Grad'), - (4000, u'Kranj'), - (4201, u'Zgornja Besnica'), - (4202, u'Naklo'), - (4203, u'Duplje'), - (4204, u'Golnik'), - (4205, u'Preddvor'), - (4206, u'Zgornje Jezersko'), - (4207, u'Cerklje na Gorenjskem'), - (4208, u'\u0160en\u010dur'), - (4209, u'\u017dabnica'), - (4210, u'Brnik - aerodrom'), - (4211, u'Mav\u010di\u010de'), - (4212, u'Visoko'), - (4220, u'\u0160kofja Loka'), - (4223, u'Poljane nad \u0160kofjo Loko'), - (4224, u'Gorenja vas'), - (4225, u'Sovodenj'), - (4226, u'\u017diri'), - (4227, u'Selca'), - (4228, u'\u017delezniki'), - (4229, u'Sorica'), - (4240, u'Radovljica'), - (4243, u'Brezje'), - (4244, u'Podnart'), - (4245, u'Kropa'), - (4246, u'Kamna Gorica'), - (4247, u'Zgornje Gorje'), - (4248, u'Lesce'), - (4260, u'Bled'), - (4263, u'Bohinjska Bela'), - (4264, u'Bohinjska Bistrica'), - (4265, u'Bohinjsko jezero'), - (4267, u'Srednja vas v Bohinju'), - (4270, u'Jesenice'), - (4273, u'Blejska Dobrava'), - (4274, u'\u017dirovnica'), - (4275, u'Begunje na Gorenjskem'), - (4276, u'Hru\u0161ica'), - (4280, u'Kranjska Gora'), - (4281, u'Mojstrana'), - (4282, u'Gozd Martuljek'), - (4283, u'Rate\u010de - Planica'), - (4290, u'Tr\u017ei\u010d'), - (4294, u'Kri\u017ee'), - (5000, u'Nova Gorica'), - (5210, u'Deskle'), - (5211, u'Kojsko'), - (5212, u'Dobrovo v Brdih'), - (5213, u'Kanal'), - (5214, u'Kal nad Kanalom'), - (5215, u'Ro\u010dinj'), - (5216, u'Most na So\u010di'), - (5220, u'Tolmin'), - (5222, u'Kobarid'), - (5223, u'Breginj'), - (5224, u'Srpenica'), - (5230, u'Bovec'), - (5231, u'Log pod Mangartom'), - (5232, u'So\u010da'), - (5242, u'Grahovo ob Ba\u010di'), - (5243, u'Podbrdo'), - (5250, u'Solkan'), - (5251, u'Grgar'), - (5252, u'Trnovo pri Gorici'), - (5253, u'\u010cepovan'), - (5261, u'\u0160empas'), - (5262, u'\u010crni\u010de'), - (5263, u'Dobravlje'), - (5270, u'Ajdov\u0161\u010dina'), - (5271, u'Vipava'), - (5272, u'Podnanos'), - (5273, u'Col'), - (5274, u'\u010crni Vrh nad Idrijo'), - (5275, u'Godovi\u010d'), - (5280, u'Idrija'), - (5281, u'Spodnja Idrija'), - (5282, u'Cerkno'), - (5283, u'Slap ob Idrijci'), - (5290, u'\u0160empeter pri Gorici'), - (5291, u'Miren'), - (5292, u'Ren\u010de'), - (5293, u'Vol\u010dja Draga'), - (5294, u'Dornberk'), - (5295, u'Branik'), - (5296, u'Kostanjevica na Krasu'), - (5297, u'Prva\u010dina'), - (6000, u'Koper'), - (6210, u'Se\u017eana'), - (6215, u'Diva\u010da'), - (6216, u'Podgorje'), - (6217, u'Vremski Britof'), - (6219, u'Lokev'), - (6221, u'Dutovlje'), - (6222, u'\u0160tanjel'), - (6223, u'Komen'), - (6224, u'Seno\u017ee\u010de'), - (6225, u'Hru\u0161evje'), - (6230, u'Postojna'), - (6232, u'Planina'), - (6240, u'Kozina'), - (6242, u'Materija'), - (6243, u'Obrov'), - (6244, u'Podgrad'), - (6250, u'Ilirska Bistrica'), - (6251, u'Ilirska Bistrica - Trnovo'), - (6253, u'Kne\u017eak'), - (6254, u'Jel\u0161ane'), - (6255, u'Prem'), - (6256, u'Ko\u0161ana'), - (6257, u'Pivka'), - (6258, u'Prestranek'), - (6271, u'Dekani'), - (6272, u'Gra\u010di\u0161\u010de'), - (6273, u'Marezige'), - (6274, u'\u0160marje'), - (6275, u'\u010crni Kal'), - (6276, u'Pobegi'), - (6280, u'Ankaran - Ancarano'), - (6281, u'\u0160kofije'), - (6310, u'Izola - Isola'), - (6320, u'Portoro\u017e - Portorose'), - (6330, u'Piran - Pirano'), - (6333, u'Se\u010dovlje - Sicciole'), - (8000, u'Novo mesto'), - (8210, u'Trebnje'), - (8211, u'Dobrni\u010d'), - (8212, u'Velika Loka'), - (8213, u'Veliki Gaber'), - (8216, u'Mirna Pe\u010d'), - (8220, u'\u0160marje\u0161ke Toplice'), - (8222, u'Oto\u010dec'), - (8230, u'Mokronog'), - (8231, u'Trebelno'), - (8232, u'\u0160entrupert'), - (8233, u'Mirna'), - (8250, u'Bre\u017eice'), - (8251, u'\u010cate\u017e ob Savi'), - (8253, u'Arti\u010de'), - (8254, u'Globoko'), - (8255, u'Pi\u0161ece'), - (8256, u'Sromlje'), - (8257, u'Dobova'), - (8258, u'Kapele'), - (8259, u'Bizeljsko'), - (8261, u'Jesenice na Dolenjskem'), - (8262, u'Kr\u0161ka vas'), - (8263, u'Cerklje ob Krki'), - (8270, u'Kr\u0161ko'), - (8272, u'Zdole'), - (8273, u'Leskovec pri Kr\u0161kem'), - (8274, u'Raka'), - (8275, u'\u0160kocjan'), - (8276, u'Bu\u010dka'), - (8280, u'Brestanica'), - (8281, u'Senovo'), - (8282, u'Koprivnica'), - (8283, u'Blanca'), - (8290, u'Sevnica'), - (8292, u'Zabukovje'), - (8293, u'Studenec'), - (8294, u'Bo\u0161tanj'), - (8295, u'Tr\u017ei\u0161\u010de'), - (8296, u'Krmelj'), - (8297, u'\u0160entjan\u017e'), - (8310, u'\u0160entjernej'), - (8311, u'Kostanjevica na Krki'), - (8312, u'Podbo\u010dje'), - (8321, u'Brusnice'), - (8322, u'Stopi\u010de'), - (8323, u'Ur\u0161na sela'), - (8330, u'Metlika'), - (8331, u'Suhor'), - (8332, u'Gradac'), - (8333, u'Semi\u010d'), - (8340, u'\u010crnomelj'), - (8341, u'Adle\u0161i\u010di'), - (8342, u'Stari trg ob Kolpi'), - (8343, u'Dragatu\u0161'), - (8344, u'Vinica pri \u010crnomlju'), - (8350, u'Dolenjske Toplice'), - (8351, u'Stra\u017ea'), - (8360, u'\u017du\u017eemberk'), - (8361, u'Dvor'), - (8362, u'Hinje'), - (9000, u'Murska Sobota'), - (9201, u'Puconci'), - (9202, u'Ma\u010dkovci'), - (9203, u'Petrovci'), - (9204, u'\u0160alovci'), - (9205, u'Hodo\u0161 - Hodos'), - (9206, u'Kri\u017eevci'), - (9207, u'Prosenjakovci - Partosfalva'), - (9208, u'Fokovci'), - (9220, u'Lendava - Lendva'), - (9221, u'Martjanci'), - (9222, u'Bogojina'), - (9223, u'Dobrovnik - Dobronak'), - (9224, u'Turni\u0161\u010de'), - (9225, u'Velika Polana'), - (9226, u'Moravske Toplice'), - (9227, u'Kobilje'), - (9231, u'Beltinci'), - (9232, u'\u010cren\u0161ovci'), - (9233, u'Odranci'), - (9240, u'Ljutomer'), - (9241, u'Ver\u017eej'), - (9242, u'Kri\u017eevci pri Ljutomeru'), - (9243, u'Mala Nedelja'), - (9244, u'Sveti Jurij ob \u0160\u010davnici'), - (9245, u'Spodnji Ivanjci'), - (9250, u'Gornja Radgona'), - (9251, u'Ti\u0161ina'), - (9252, u'Radenci'), - (9253, u'Apa\u010de'), - (9261, u'Cankova'), - (9262, u'Roga\u0161ovci'), - (9263, u'Kuzma'), - (9264, u'Grad'), - (9265, u'Bodonci'), + (1000, 'Ljubljana'), + (1215, 'Medvode'), + (1216, 'Smlednik'), + (1217, 'Vodice'), + (1218, 'Komenda'), + (1219, 'Laze v Tuhinju'), + (1221, 'Motnik'), + (1222, 'Trojane'), + (1223, 'Blagovica'), + (1225, 'Lukovica'), + (1230, 'Dom\u017eale'), + (1233, 'Dob'), + (1234, 'Menge\u0161'), + (1235, 'Radomlje'), + (1236, 'Trzin'), + (1241, 'Kamnik'), + (1242, 'Stahovica'), + (1251, 'Morav\u010de'), + (1252, 'Va\u010de'), + (1262, 'Dol pri Ljubljani'), + (1270, 'Litija'), + (1272, 'Pol\u0161nik'), + (1273, 'Dole pri Litiji'), + (1274, 'Gabrovka'), + (1275, '\u0160martno pri Litiji'), + (1276, 'Primskovo'), + (1281, 'Kresnice'), + (1282, 'Sava'), + (1290, 'Grosuplje'), + (1291, '\u0160kofljica'), + (1292, 'Ig'), + (1293, '\u0160marje - Sap'), + (1294, 'Vi\u0161nja Gora'), + (1295, 'Ivan\u010dna Gorica'), + (1296, '\u0160entvid pri Sti\u010dni'), + (1301, 'Krka'), + (1303, 'Zagradec'), + (1310, 'Ribnica'), + (1311, 'Turjak'), + (1312, 'Videm - Dobrepolje'), + (1313, 'Struge'), + (1314, 'Rob'), + (1315, 'Velike La\u0161\u010de'), + (1316, 'Ortnek'), + (1317, 'Sodra\u017eica'), + (1318, 'Lo\u0161ki Potok'), + (1319, 'Draga'), + (1330, 'Ko\u010devje'), + (1331, 'Dolenja vas'), + (1332, 'Stara Cerkev'), + (1336, 'Kostel'), + (1337, 'Osilnica'), + (1338, 'Ko\u010devska Reka'), + (1351, 'Brezovica pri Ljubljani'), + (1352, 'Preserje'), + (1353, 'Borovnica'), + (1354, 'Horjul'), + (1355, 'Polhov Gradec'), + (1356, 'Dobrova'), + (1357, 'Notranje Gorice'), + (1358, 'Log pri Brezovici'), + (1360, 'Vrhnika'), + (1370, 'Logatec'), + (1372, 'Hotedr\u0161ica'), + (1373, 'Rovte'), + (1380, 'Cerknica'), + (1381, 'Rakek'), + (1382, 'Begunje pri Cerknici'), + (1384, 'Grahovo'), + (1385, 'Nova vas'), + (1386, 'Stari trg pri Lo\u017eu'), + (1410, 'Zagorje ob Savi'), + (1411, 'Izlake'), + (1412, 'Kisovec'), + (1413, '\u010cem\u0161enik'), + (1414, 'Podkum'), + (1420, 'Trbovlje'), + (1423, 'Dobovec'), + (1430, 'Hrastnik'), + (1431, 'Dol pri Hrastniku'), + (1432, 'Zidani Most'), + (1433, 'Rade\u010de'), + (1434, 'Loka pri Zidanem Mostu'), + (2000, 'Maribor'), + (2201, 'Zgornja Kungota'), + (2204, 'Miklav\u017e na Dravskem polju'), + (2205, 'Star\u0161e'), + (2206, 'Marjeta na Dravskem polju'), + (2208, 'Pohorje'), + (2211, 'Pesnica pri Mariboru'), + (2212, '\u0160entilj v Slovenskih goricah'), + (2213, 'Zgornja Velka'), + (2214, 'Sladki vrh'), + (2215, 'Cer\u0161ak'), + (2221, 'Jarenina'), + (2222, 'Jakobski Dol'), + (2223, 'Jurovski Dol'), + (2229, 'Male\u010dnik'), + (2230, 'Lenart v Slovenskih goricah'), + (2231, 'Pernica'), + (2232, 'Voli\u010dina'), + (2233, 'Sveta Ana v Slovenskih goricah'), + (2234, 'Benedikt'), + (2235, 'Sveta Trojica v Slovenskih goricah'), + (2236, 'Cerkvenjak'), + (2241, 'Spodnji Duplek'), + (2242, 'Zgornja Korena'), + (2250, 'Ptuj'), + (2252, 'Dornava'), + (2253, 'Destrnik'), + (2254, 'Trnovska vas'), + (2255, 'Vitomarci'), + (2256, 'Jur\u0161inci'), + (2257, 'Polen\u0161ak'), + (2258, 'Sveti Toma\u017e'), + (2259, 'Ivanjkovci'), + (2270, 'Ormo\u017e'), + (2272, 'Gori\u0161nica'), + (2273, 'Podgorci'), + (2274, 'Velika Nedelja'), + (2275, 'Miklav\u017e pri Ormo\u017eu'), + (2276, 'Kog'), + (2277, 'Sredi\u0161\u010de ob Dravi'), + (2281, 'Markovci'), + (2282, 'Cirkulane'), + (2283, 'Zavr\u010d'), + (2284, 'Videm pri Ptuju'), + (2285, 'Zgornji Leskovec'), + (2286, 'Podlehnik'), + (2287, '\u017detale'), + (2288, 'Hajdina'), + (2289, 'Stoperce'), + (2310, 'Slovenska Bistrica'), + (2311, 'Ho\u010de'), + (2312, 'Orehova vas'), + (2313, 'Fram'), + (2314, 'Zgornja Polskava'), + (2315, '\u0160martno na Pohorju'), + (2316, 'Zgornja Lo\u017enica'), + (2317, 'Oplotnica'), + (2318, 'Laporje'), + (2319, 'Polj\u010dane'), + (2321, 'Makole'), + (2322, 'Maj\u0161perk'), + (2323, 'Ptujska Gora'), + (2324, 'Lovrenc na Dravskem polju'), + (2325, 'Kidri\u010devo'), + (2326, 'Cirkovce'), + (2327, 'Ra\u010de'), + (2331, 'Pragersko'), + (2341, 'Limbu\u0161'), + (2342, 'Ru\u0161e'), + (2343, 'Fala'), + (2344, 'Lovrenc na Pohorju'), + (2345, 'Bistrica ob Dravi'), + (2351, 'Kamnica'), + (2352, 'Selnica ob Dravi'), + (2353, 'Sv. Duh na Ostrem Vrhu'), + (2354, 'Bresternica'), + (2360, 'Radlje ob Dravi'), + (2361, 'O\u017ebalt'), + (2362, 'Kapla'), + (2363, 'Podvelka'), + (2364, 'Ribnica na Pohorju'), + (2365, 'Vuhred'), + (2366, 'Muta'), + (2367, 'Vuzenica'), + (2370, 'Dravograd'), + (2371, 'Trbonje'), + (2372, 'Libeli\u010de'), + (2373, '\u0160entjan\u017e pri Dravogradu'), + (2380, 'Slovenj Gradec'), + (2381, 'Podgorje pri Slovenj Gradcu'), + (2382, 'Mislinja'), + (2383, '\u0160martno pri Slovenj Gradcu'), + (2390, 'Ravne na Koro\u0161kem'), + (2391, 'Prevalje'), + (2392, 'Me\u017eica'), + (2393, '\u010crna na Koro\u0161kem'), + (2394, 'Kotlje'), + (3000, 'Celje'), + (3201, '\u0160martno v Ro\u017eni dolini'), + (3202, 'Ljube\u010dna'), + (3203, 'Nova Cerkev'), + (3204, 'Dobrna'), + (3205, 'Vitanje'), + (3206, 'Stranice'), + (3210, 'Slovenske Konjice'), + (3211, '\u0160kofja vas'), + (3212, 'Vojnik'), + (3213, 'Frankolovo'), + (3214, 'Zre\u010de'), + (3215, 'Lo\u010de'), + (3220, '\u0160tore'), + (3221, 'Teharje'), + (3222, 'Dramlje'), + (3223, 'Loka pri \u017dusmu'), + (3224, 'Dobje pri Planini'), + (3225, 'Planina pri Sevnici'), + (3230, '\u0160entjur'), + (3231, 'Grobelno'), + (3232, 'Ponikva'), + (3233, 'Kalobje'), + (3240, '\u0160marje pri Jel\u0161ah'), + (3241, 'Podplat'), + (3250, 'Roga\u0161ka Slatina'), + (3252, 'Rogatec'), + (3253, 'Pristava pri Mestinju'), + (3254, 'Pod\u010detrtek'), + (3255, 'Bu\u010de'), + (3256, 'Bistrica ob Sotli'), + (3257, 'Podsreda'), + (3260, 'Kozje'), + (3261, 'Lesi\u010dno'), + (3262, 'Prevorje'), + (3263, 'Gorica pri Slivnici'), + (3264, 'Sveti \u0160tefan'), + (3270, 'La\u0161ko'), + (3271, '\u0160entrupert'), + (3272, 'Rimske Toplice'), + (3273, 'Jurklo\u0161ter'), + (3301, 'Petrov\u010de'), + (3302, 'Gri\u017ee'), + (3303, 'Gomilsko'), + (3304, 'Tabor'), + (3305, 'Vransko'), + (3310, '\u017dalec'), + (3311, '\u0160empeter v Savinjski dolini'), + (3312, 'Prebold'), + (3313, 'Polzela'), + (3314, 'Braslov\u010de'), + (3320, 'Velenje - dostava'), + (3322, 'Velenje - po\u0161tni predali'), + (3325, '\u0160o\u0161tanj'), + (3326, 'Topol\u0161ica'), + (3327, '\u0160martno ob Paki'), + (3330, 'Mozirje'), + (3331, 'Nazarje'), + (3332, 'Re\u010dica ob Savinji'), + (3333, 'Ljubno ob Savinji'), + (3334, 'Lu\u010de'), + (3335, 'Sol\u010dava'), + (3341, '\u0160martno ob Dreti'), + (3342, 'Gornji Grad'), + (4000, 'Kranj'), + (4201, 'Zgornja Besnica'), + (4202, 'Naklo'), + (4203, 'Duplje'), + (4204, 'Golnik'), + (4205, 'Preddvor'), + (4206, 'Zgornje Jezersko'), + (4207, 'Cerklje na Gorenjskem'), + (4208, '\u0160en\u010dur'), + (4209, '\u017dabnica'), + (4210, 'Brnik - aerodrom'), + (4211, 'Mav\u010di\u010de'), + (4212, 'Visoko'), + (4220, '\u0160kofja Loka'), + (4223, 'Poljane nad \u0160kofjo Loko'), + (4224, 'Gorenja vas'), + (4225, 'Sovodenj'), + (4226, '\u017diri'), + (4227, 'Selca'), + (4228, '\u017delezniki'), + (4229, 'Sorica'), + (4240, 'Radovljica'), + (4243, 'Brezje'), + (4244, 'Podnart'), + (4245, 'Kropa'), + (4246, 'Kamna Gorica'), + (4247, 'Zgornje Gorje'), + (4248, 'Lesce'), + (4260, 'Bled'), + (4263, 'Bohinjska Bela'), + (4264, 'Bohinjska Bistrica'), + (4265, 'Bohinjsko jezero'), + (4267, 'Srednja vas v Bohinju'), + (4270, 'Jesenice'), + (4273, 'Blejska Dobrava'), + (4274, '\u017dirovnica'), + (4275, 'Begunje na Gorenjskem'), + (4276, 'Hru\u0161ica'), + (4280, 'Kranjska Gora'), + (4281, 'Mojstrana'), + (4282, 'Gozd Martuljek'), + (4283, 'Rate\u010de - Planica'), + (4290, 'Tr\u017ei\u010d'), + (4294, 'Kri\u017ee'), + (5000, 'Nova Gorica'), + (5210, 'Deskle'), + (5211, 'Kojsko'), + (5212, 'Dobrovo v Brdih'), + (5213, 'Kanal'), + (5214, 'Kal nad Kanalom'), + (5215, 'Ro\u010dinj'), + (5216, 'Most na So\u010di'), + (5220, 'Tolmin'), + (5222, 'Kobarid'), + (5223, 'Breginj'), + (5224, 'Srpenica'), + (5230, 'Bovec'), + (5231, 'Log pod Mangartom'), + (5232, 'So\u010da'), + (5242, 'Grahovo ob Ba\u010di'), + (5243, 'Podbrdo'), + (5250, 'Solkan'), + (5251, 'Grgar'), + (5252, 'Trnovo pri Gorici'), + (5253, '\u010cepovan'), + (5261, '\u0160empas'), + (5262, '\u010crni\u010de'), + (5263, 'Dobravlje'), + (5270, 'Ajdov\u0161\u010dina'), + (5271, 'Vipava'), + (5272, 'Podnanos'), + (5273, 'Col'), + (5274, '\u010crni Vrh nad Idrijo'), + (5275, 'Godovi\u010d'), + (5280, 'Idrija'), + (5281, 'Spodnja Idrija'), + (5282, 'Cerkno'), + (5283, 'Slap ob Idrijci'), + (5290, '\u0160empeter pri Gorici'), + (5291, 'Miren'), + (5292, 'Ren\u010de'), + (5293, 'Vol\u010dja Draga'), + (5294, 'Dornberk'), + (5295, 'Branik'), + (5296, 'Kostanjevica na Krasu'), + (5297, 'Prva\u010dina'), + (6000, 'Koper'), + (6210, 'Se\u017eana'), + (6215, 'Diva\u010da'), + (6216, 'Podgorje'), + (6217, 'Vremski Britof'), + (6219, 'Lokev'), + (6221, 'Dutovlje'), + (6222, '\u0160tanjel'), + (6223, 'Komen'), + (6224, 'Seno\u017ee\u010de'), + (6225, 'Hru\u0161evje'), + (6230, 'Postojna'), + (6232, 'Planina'), + (6240, 'Kozina'), + (6242, 'Materija'), + (6243, 'Obrov'), + (6244, 'Podgrad'), + (6250, 'Ilirska Bistrica'), + (6251, 'Ilirska Bistrica - Trnovo'), + (6253, 'Kne\u017eak'), + (6254, 'Jel\u0161ane'), + (6255, 'Prem'), + (6256, 'Ko\u0161ana'), + (6257, 'Pivka'), + (6258, 'Prestranek'), + (6271, 'Dekani'), + (6272, 'Gra\u010di\u0161\u010de'), + (6273, 'Marezige'), + (6274, '\u0160marje'), + (6275, '\u010crni Kal'), + (6276, 'Pobegi'), + (6280, 'Ankaran - Ancarano'), + (6281, '\u0160kofije'), + (6310, 'Izola - Isola'), + (6320, 'Portoro\u017e - Portorose'), + (6330, 'Piran - Pirano'), + (6333, 'Se\u010dovlje - Sicciole'), + (8000, 'Novo mesto'), + (8210, 'Trebnje'), + (8211, 'Dobrni\u010d'), + (8212, 'Velika Loka'), + (8213, 'Veliki Gaber'), + (8216, 'Mirna Pe\u010d'), + (8220, '\u0160marje\u0161ke Toplice'), + (8222, 'Oto\u010dec'), + (8230, 'Mokronog'), + (8231, 'Trebelno'), + (8232, '\u0160entrupert'), + (8233, 'Mirna'), + (8250, 'Bre\u017eice'), + (8251, '\u010cate\u017e ob Savi'), + (8253, 'Arti\u010de'), + (8254, 'Globoko'), + (8255, 'Pi\u0161ece'), + (8256, 'Sromlje'), + (8257, 'Dobova'), + (8258, 'Kapele'), + (8259, 'Bizeljsko'), + (8261, 'Jesenice na Dolenjskem'), + (8262, 'Kr\u0161ka vas'), + (8263, 'Cerklje ob Krki'), + (8270, 'Kr\u0161ko'), + (8272, 'Zdole'), + (8273, 'Leskovec pri Kr\u0161kem'), + (8274, 'Raka'), + (8275, '\u0160kocjan'), + (8276, 'Bu\u010dka'), + (8280, 'Brestanica'), + (8281, 'Senovo'), + (8282, 'Koprivnica'), + (8283, 'Blanca'), + (8290, 'Sevnica'), + (8292, 'Zabukovje'), + (8293, 'Studenec'), + (8294, 'Bo\u0161tanj'), + (8295, 'Tr\u017ei\u0161\u010de'), + (8296, 'Krmelj'), + (8297, '\u0160entjan\u017e'), + (8310, '\u0160entjernej'), + (8311, 'Kostanjevica na Krki'), + (8312, 'Podbo\u010dje'), + (8321, 'Brusnice'), + (8322, 'Stopi\u010de'), + (8323, 'Ur\u0161na sela'), + (8330, 'Metlika'), + (8331, 'Suhor'), + (8332, 'Gradac'), + (8333, 'Semi\u010d'), + (8340, '\u010crnomelj'), + (8341, 'Adle\u0161i\u010di'), + (8342, 'Stari trg ob Kolpi'), + (8343, 'Dragatu\u0161'), + (8344, 'Vinica pri \u010crnomlju'), + (8350, 'Dolenjske Toplice'), + (8351, 'Stra\u017ea'), + (8360, '\u017du\u017eemberk'), + (8361, 'Dvor'), + (8362, 'Hinje'), + (9000, 'Murska Sobota'), + (9201, 'Puconci'), + (9202, 'Ma\u010dkovci'), + (9203, 'Petrovci'), + (9204, '\u0160alovci'), + (9205, 'Hodo\u0161 - Hodos'), + (9206, 'Kri\u017eevci'), + (9207, 'Prosenjakovci - Partosfalva'), + (9208, 'Fokovci'), + (9220, 'Lendava - Lendva'), + (9221, 'Martjanci'), + (9222, 'Bogojina'), + (9223, 'Dobrovnik - Dobronak'), + (9224, 'Turni\u0161\u010de'), + (9225, 'Velika Polana'), + (9226, 'Moravske Toplice'), + (9227, 'Kobilje'), + (9231, 'Beltinci'), + (9232, '\u010cren\u0161ovci'), + (9233, 'Odranci'), + (9240, 'Ljutomer'), + (9241, 'Ver\u017eej'), + (9242, 'Kri\u017eevci pri Ljutomeru'), + (9243, 'Mala Nedelja'), + (9244, 'Sveti Jurij ob \u0160\u010davnici'), + (9245, 'Spodnji Ivanjci'), + (9250, 'Gornja Radgona'), + (9251, 'Ti\u0161ina'), + (9252, 'Radenci'), + (9253, 'Apa\u010de'), + (9261, 'Cankova'), + (9262, 'Roga\u0161ovci'), + (9263, 'Kuzma'), + (9264, 'Grad'), + (9265, 'Bodonci'), ] SI_POSTALCODES_CHOICES = sorted(SI_POSTALCODES, key=lambda k: k[1]) diff --git a/django/contrib/localflavor/sk/forms.py b/django/contrib/localflavor/sk/forms.py index 83afeb41b9..11d44cc4d2 100644 --- a/django/contrib/localflavor/sk/forms.py +++ b/django/contrib/localflavor/sk/forms.py @@ -2,7 +2,7 @@ Slovak-specific form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.contrib.localflavor.sk.sk_districts import DISTRICT_CHOICES from django.contrib.localflavor.sk.sk_regions import REGION_CHOICES @@ -30,7 +30,7 @@ class SKPostalCodeField(RegexField): Valid form is XXXXX or XXX XX, where X represents integer. """ default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXXXX or XXX XX.'), + 'invalid': _('Enter a postal code in the format XXXXX or XXX XX.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): diff --git a/django/contrib/localflavor/tr/forms.py b/django/contrib/localflavor/tr/forms.py index 77a2b41986..c4f928e670 100644 --- a/django/contrib/localflavor/tr/forms.py +++ b/django/contrib/localflavor/tr/forms.py @@ -2,7 +2,7 @@ TR-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re @@ -10,7 +10,7 @@ from django.contrib.localflavor.tr.tr_provinces import PROVINCE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select, CharField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -18,7 +18,7 @@ phone_digits_re = re.compile(r'^(\+90|0)? ?(([1-9]\d{2})|\([1-9]\d{2}\)) ?([2-9] class TRPostalCodeField(RegexField): default_error_messages = { - 'invalid': _(u'Enter a postal code in the format XXXXX.'), + 'invalid': _('Enter a postal code in the format XXXXX.'), } def __init__(self, max_length=5, min_length=5, *args, **kwargs): @@ -28,7 +28,7 @@ class TRPostalCodeField(RegexField): def clean(self, value): value = super(TRPostalCodeField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if len(value) != 5: raise ValidationError(self.error_messages['invalid']) province_code = int(value[:2]) @@ -39,17 +39,17 @@ class TRPostalCodeField(RegexField): class TRPhoneNumberField(CharField): default_error_messages = { - 'invalid': _(u'Phone numbers must be in 0XXX XXX XXXX format.'), + 'invalid': _('Phone numbers must be in 0XXX XXX XXXX format.'), } def clean(self, value): super(TRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + return '' + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: - return u'%s%s' % (m.group(2), m.group(4)) + return '%s%s' % (m.group(2), m.group(4)) raise ValidationError(self.error_messages['invalid']) class TRIdentificationNumberField(Field): @@ -66,24 +66,24 @@ class TRIdentificationNumberField(Field): sum(1st to 10th) % 10 = 11th digit """ default_error_messages = { - 'invalid': _(u'Enter a valid Turkish Identification number.'), - 'not_11': _(u'Turkish Identification number must be 11 digits.'), + 'invalid': _('Enter a valid Turkish Identification number.'), + 'not_11': _('Turkish Identification number must be 11 digits.'), } def clean(self, value): super(TRIdentificationNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' if len(value) != 11: raise ValidationError(self.error_messages['not_11']) if not re.match(r'^\d{11}$', value): raise ValidationError(self.error_messages['invalid']) if int(value[0]) == 0: raise ValidationError(self.error_messages['invalid']) - chksum = (sum([int(value[i]) for i in xrange(0,9,2)])*7- - sum([int(value[i]) for i in xrange(1,9,2)])) % 10 + chksum = (sum([int(value[i]) for i in range(0, 9, 2)]) * 7 - + sum([int(value[i]) for i in range(1, 9, 2)])) % 10 if chksum != int(value[9]) or \ - (sum([int(value[i]) for i in xrange(10)]) % 10) != int(value[10]): + (sum([int(value[i]) for i in range(10)]) % 10) != int(value[10]): raise ValidationError(self.error_messages['invalid']) return value diff --git a/django/contrib/localflavor/tr/tr_provinces.py b/django/contrib/localflavor/tr/tr_provinces.py index 060a6cdaf6..edad74710d 100644 --- a/django/contrib/localflavor/tr/tr_provinces.py +++ b/django/contrib/localflavor/tr/tr_provinces.py @@ -3,6 +3,7 @@ This exists in this standalone file so that it's only imported into memory when explicitly needed. """ +from __future__ import unicode_literals PROVINCE_CHOICES = ( ('01', ('Adana')), diff --git a/django/contrib/localflavor/us/forms.py b/django/contrib/localflavor/us/forms.py index 0a79c40a47..437bb7c466 100644 --- a/django/contrib/localflavor/us/forms.py +++ b/django/contrib/localflavor/us/forms.py @@ -2,14 +2,14 @@ USA-specific Form helpers """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import re from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select, CharField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -33,11 +33,11 @@ class USPhoneNumberField(CharField): def clean(self, value): super(USPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + return '' + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: - return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) + return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) raise ValidationError(self.error_messages['invalid']) class USSocialSecurityNumberField(Field): @@ -62,7 +62,7 @@ class USSocialSecurityNumberField(Field): def clean(self, value): super(USSocialSecurityNumberField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = re.match(ssn_re, value) if not match: raise ValidationError(self.error_messages['invalid']) @@ -80,7 +80,7 @@ class USSocialSecurityNumberField(Field): value == '078-05-1120' or \ value == '219-09-9999': raise ValidationError(self.error_messages['invalid']) - return u'%s-%s-%s' % (area, group, serial) + return '%s-%s-%s' % (area, group, serial) class USStateField(Field): """ @@ -93,17 +93,17 @@ class USStateField(Field): } def clean(self, value): - from django.contrib.localflavor.us.us_states import STATES_NORMALIZED + from .us_states import STATES_NORMALIZED super(USStateField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' try: value = value.strip().lower() except AttributeError: pass else: try: - return STATES_NORMALIZED[value.strip().lower()].decode('ascii') + return STATES_NORMALIZED[value.strip().lower()] except KeyError: pass raise ValidationError(self.error_messages['invalid']) @@ -113,7 +113,7 @@ class USStateSelect(Select): A Select widget that uses a list of U.S. states/territories as its choices. """ def __init__(self, attrs=None): - from django.contrib.localflavor.us.us_states import STATE_CHOICES + from .us_states import STATE_CHOICES super(USStateSelect, self).__init__(attrs, choices=STATE_CHOICES) class USPSSelect(Select): @@ -122,5 +122,5 @@ class USPSSelect(Select): choices. """ def __init__(self, attrs=None): - from django.contrib.localflavor.us.us_states import USPS_CHOICES + from .us_states import USPS_CHOICES super(USPSSelect, self).__init__(attrs, choices=USPS_CHOICES) diff --git a/django/contrib/localflavor/uy/forms.py b/django/contrib/localflavor/uy/forms.py index 211216222d..658defc0f0 100644 --- a/django/contrib/localflavor/uy/forms.py +++ b/django/contrib/localflavor/uy/forms.py @@ -3,7 +3,7 @@ UY-specific form helpers. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from django.core.validators import EMPTY_VALUES from django.forms.fields import Select, RegexField @@ -47,7 +47,7 @@ class UYCIField(RegexField): value = super(UYCIField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' match = self.regex.match(value) if not match: raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/uy/uy_departaments.py b/django/contrib/localflavor/uy/uy_departaments.py index 97795f8e82..800937c582 100644 --- a/django/contrib/localflavor/uy/uy_departaments.py +++ b/django/contrib/localflavor/uy/uy_departaments.py @@ -1,24 +1,25 @@ # -*- coding: utf-8 -*- """A list of Urguayan departaments as `choices` in a formfield.""" +from __future__ import unicode_literals DEPARTAMENT_CHOICES = ( - ('G', u'Artigas'), - ('A', u'Canelones'), - ('E', u'Cerro Largo'), - ('L', u'Colonia'), - ('Q', u'Durazno'), - ('N', u'Flores'), - ('O', u'Florida'), - ('P', u'Lavalleja'), - ('B', u'Maldonado'), - ('S', u'Montevideo'), - ('I', u'Paysandú'), - ('J', u'Río Negro'), - ('F', u'Rivera'), - ('C', u'Rocha'), - ('H', u'Salto'), - ('M', u'San José'), - ('K', u'Soriano'), - ('R', u'Tacuarembó'), - ('D', u'Treinta y Tres'), + ('G', 'Artigas'), + ('A', 'Canelones'), + ('E', 'Cerro Largo'), + ('L', 'Colonia'), + ('Q', 'Durazno'), + ('N', 'Flores'), + ('O', 'Florida'), + ('P', 'Lavalleja'), + ('B', 'Maldonado'), + ('S', 'Montevideo'), + ('I', 'Paysandú'), + ('J', 'Río Negro'), + ('F', 'Rivera'), + ('C', 'Rocha'), + ('H', 'Salto'), + ('M', 'San José'), + ('K', 'Soriano'), + ('R', 'Tacuarembó'), + ('D', 'Treinta y Tres'), ) diff --git a/django/contrib/localflavor/za/forms.py b/django/contrib/localflavor/za/forms.py index a9e2cd60c8..a818c14428 100644 --- a/django/contrib/localflavor/za/forms.py +++ b/django/contrib/localflavor/za/forms.py @@ -1,6 +1,7 @@ """ South Africa-specific Form helpers """ +from __future__ import unicode_literals from django.core.validators import EMPTY_VALUES from django.forms import ValidationError @@ -18,14 +19,14 @@ class ZAIDField(CharField): check for the birthdate """ default_error_messages = { - 'invalid': _(u'Enter a valid South African ID number'), + 'invalid': _('Enter a valid South African ID number'), } def clean(self, value): super(ZAIDField, self).clean(value) if value in EMPTY_VALUES: - return u'' + return '' # strip spaces and dashes value = value.strip().replace(' ', '').replace('-', '') @@ -52,7 +53,7 @@ class ZAIDField(CharField): class ZAPostCodeField(RegexField): default_error_messages = { - 'invalid': _(u'Enter a valid South African postal code'), + 'invalid': _('Enter a valid South African postal code'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index 84251cf30a..af9c842f42 100644 --- a/django/contrib/markup/templatetags/markup.py +++ b/django/contrib/markup/templatetags/markup.py @@ -13,7 +13,7 @@ markup syntaxes to HTML; currently there is support for: from django import template from django.conf import settings -from django.utils.encoding import smart_str, force_unicode +from django.utils.encoding import smart_bytes, force_text from django.utils.safestring import mark_safe register = template.Library() @@ -25,9 +25,9 @@ def textile(value): except ImportError: if settings.DEBUG: raise template.TemplateSyntaxError("Error in 'textile' filter: The Python textile library isn't installed.") - return force_unicode(value) + return force_text(value) else: - return mark_safe(force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8'))) + return mark_safe(force_text(textile.textile(smart_bytes(value), encoding='utf-8', output='utf-8'))) @register.filter(is_safe=True) def markdown(value, arg=''): @@ -52,23 +52,23 @@ def markdown(value, arg=''): except ImportError: if settings.DEBUG: raise template.TemplateSyntaxError("Error in 'markdown' filter: The Python markdown library isn't installed.") - return force_unicode(value) + return force_text(value) else: markdown_vers = getattr(markdown, "version_info", 0) if markdown_vers < (2, 1): if settings.DEBUG: raise template.TemplateSyntaxError( "Error in 'markdown' filter: Django does not support versions of the Python markdown library < 2.1.") - return force_unicode(value) + return force_text(value) else: extensions = [e for e in arg.split(",") if e] if extensions and extensions[0] == "safe": extensions = extensions[1:] return mark_safe(markdown.markdown( - force_unicode(value), extensions, safe_mode=True, enable_attributes=False)) + force_text(value), extensions, safe_mode=True, enable_attributes=False)) else: return mark_safe(markdown.markdown( - force_unicode(value), extensions, safe_mode=False)) + force_text(value), extensions, safe_mode=False)) @register.filter(is_safe=True) def restructuredtext(value): @@ -77,8 +77,8 @@ def restructuredtext(value): except ImportError: if settings.DEBUG: raise template.TemplateSyntaxError("Error in 'restructuredtext' filter: The Python docutils library isn't installed.") - return force_unicode(value) + return force_text(value) else: docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {}) - parts = publish_parts(source=smart_str(value), writer_name="html4css1", settings_overrides=docutils_settings) - return mark_safe(force_unicode(parts["fragment"])) + parts = publish_parts(source=smart_bytes(value), writer_name="html4css1", settings_overrides=docutils_settings) + return mark_safe(force_text(parts["fragment"])) diff --git a/django/contrib/messages/storage/base.py b/django/contrib/messages/storage/base.py index 65e8526b5c..7fe8a077ed 100644 --- a/django/contrib/messages/storage/base.py +++ b/django/contrib/messages/storage/base.py @@ -1,12 +1,15 @@ +from __future__ import unicode_literals + from django.conf import settings -from django.utils.encoding import force_unicode, StrAndUnicode +from django.utils.encoding import force_text, python_2_unicode_compatible from django.contrib.messages import constants, utils LEVEL_TAGS = utils.get_level_tags() -class Message(StrAndUnicode): +@python_2_unicode_compatible +class Message(object): """ Represents an actual message that can be stored in any of the supported storage classes (typically session- or cookie-based) and rendered in a view @@ -24,24 +27,24 @@ class Message(StrAndUnicode): and ``extra_tags`` to unicode in case they are lazy translations. Known "safe" types (None, int, etc.) are not converted (see Django's - ``force_unicode`` implementation for details). + ``force_text`` implementation for details). """ - self.message = force_unicode(self.message, strings_only=True) - self.extra_tags = force_unicode(self.extra_tags, strings_only=True) + self.message = force_text(self.message, strings_only=True) + self.extra_tags = force_text(self.extra_tags, strings_only=True) def __eq__(self, other): return isinstance(other, Message) and self.level == other.level and \ self.message == other.message - def __unicode__(self): - return force_unicode(self.message) + def __str__(self): + return force_text(self.message) def _get_tags(self): - label_tag = force_unicode(LEVEL_TAGS.get(self.level, ''), + label_tag = force_text(LEVEL_TAGS.get(self.level, ''), strings_only=True) - extra_tags = force_unicode(self.extra_tags, strings_only=True) + extra_tags = force_text(self.extra_tags, strings_only=True) if extra_tags and label_tag: - return u' '.join([extra_tags, label_tag]) + return ' '.join([extra_tags, label_tag]) elif extra_tags: return extra_tags elif label_tag: diff --git a/django/contrib/messages/storage/cookie.py b/django/contrib/messages/storage/cookie.py index 07620050c7..5f64ccd0c5 100644 --- a/django/contrib/messages/storage/cookie.py +++ b/django/contrib/messages/storage/cookie.py @@ -4,6 +4,7 @@ from django.conf import settings from django.contrib.messages.storage.base import BaseStorage, Message from django.http import SimpleCookie from django.utils.crypto import salted_hmac, constant_time_compare +from django.utils import six class MessageEncoder(json.JSONEncoder): @@ -33,7 +34,7 @@ class MessageDecoder(json.JSONDecoder): return [self.process_messages(item) for item in obj] if isinstance(obj, dict): return dict([(key, self.process_messages(value)) - for key, value in obj.iteritems()]) + for key, value in six.iteritems(obj)]) return obj def decode(self, s, **kwargs): diff --git a/django/contrib/messages/tests/base.py b/django/contrib/messages/tests/base.py index 1f64c61ecb..e9a67b0500 100644 --- a/django/contrib/messages/tests/base.py +++ b/django/contrib/messages/tests/base.py @@ -152,7 +152,7 @@ class BaseTest(TestCase): cycle. """ data = { - 'messages': ['Test message %d' % x for x in xrange(10)], + 'messages': ['Test message %d' % x for x in range(10)], } show_url = reverse('django.contrib.messages.tests.urls.show') for level in ('debug', 'info', 'success', 'warning', 'error'): @@ -170,7 +170,7 @@ class BaseTest(TestCase): @override_settings(MESSAGE_LEVEL=constants.DEBUG) def test_with_template_response(self): data = { - 'messages': ['Test message %d' % x for x in xrange(10)], + 'messages': ['Test message %d' % x for x in range(10)], } show_url = reverse('django.contrib.messages.tests.urls.show_template_response') for level in self.levels.keys(): @@ -194,7 +194,7 @@ class BaseTest(TestCase): before a GET. """ data = { - 'messages': ['Test message %d' % x for x in xrange(10)], + 'messages': ['Test message %d' % x for x in range(10)], } show_url = reverse('django.contrib.messages.tests.urls.show') messages = [] @@ -226,7 +226,7 @@ class BaseTest(TestCase): when one attempts to store a message. """ data = { - 'messages': ['Test message %d' % x for x in xrange(10)], + 'messages': ['Test message %d' % x for x in range(10)], } show_url = reverse('django.contrib.messages.tests.urls.show') for level in ('debug', 'info', 'success', 'warning', 'error'): @@ -251,7 +251,7 @@ class BaseTest(TestCase): raised if 'fail_silently' = True """ data = { - 'messages': ['Test message %d' % x for x in xrange(10)], + 'messages': ['Test message %d' % x for x in range(10)], 'fail_silently': True, } show_url = reverse('django.contrib.messages.tests.urls.show') diff --git a/django/contrib/messages/tests/cookie.py b/django/contrib/messages/tests/cookie.py index 19d0e08384..e0668ab604 100644 --- a/django/contrib/messages/tests/cookie.py +++ b/django/contrib/messages/tests/cookie.py @@ -39,7 +39,7 @@ def stored_cookie_messages_count(storage, response): return len(data) -@override_settings(SESSION_COOKIE_DOMAIN='.lawrence.com') +@override_settings(SESSION_COOKIE_DOMAIN='.example.com') class CookieTest(BaseTest): storage_class = CookieStorage @@ -65,7 +65,7 @@ class CookieTest(BaseTest): storage.add(constants.INFO, 'test') storage.update(response) self.assertTrue('test' in response.cookies['messages'].value) - self.assertEqual(response.cookies['messages']['domain'], '.lawrence.com') + self.assertEqual(response.cookies['messages']['domain'], '.example.com') self.assertEqual(response.cookies['messages']['expires'], '') # Test after the messages have been consumed @@ -76,7 +76,7 @@ class CookieTest(BaseTest): pass # Iterate through the storage to simulate consumption of messages. storage.update(response) self.assertEqual(response.cookies['messages'].value, '') - self.assertEqual(response.cookies['messages']['domain'], '.lawrence.com') + self.assertEqual(response.cookies['messages']['domain'], '.example.com') self.assertEqual(response.cookies['messages']['expires'], 'Thu, 01-Jan-1970 00:00:00 GMT') def test_get_bad_cookie(self): @@ -123,7 +123,7 @@ class CookieTest(BaseTest): { 'message': Message(constants.INFO, 'Test message'), 'message_list': [Message(constants.INFO, 'message %s') \ - for x in xrange(5)] + [{'another-message': \ + for x in range(5)] + [{'another-message': \ Message(constants.ERROR, 'error')}], }, Message(constants.INFO, 'message %s'), diff --git a/django/contrib/redirects/models.py b/django/contrib/redirects/models.py index 4233d55793..a0376b5578 100644 --- a/django/contrib/redirects/models.py +++ b/django/contrib/redirects/models.py @@ -1,7 +1,9 @@ from django.db import models from django.contrib.sites.models import Site from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class Redirect(models.Model): site = models.ForeignKey(Site) old_path = models.CharField(_('redirect from'), max_length=200, db_index=True, @@ -15,6 +17,6 @@ class Redirect(models.Model): db_table = 'django_redirect' unique_together=(('site', 'old_path'),) ordering = ('old_path',) - - def __unicode__(self): + + def __str__(self): return "%s ---> %s" % (self.old_path, self.new_path) diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 5a637e24d2..2fb7991b49 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -1,8 +1,10 @@ +from __future__ import unicode_literals + import base64 import time from datetime import datetime, timedelta try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle @@ -12,6 +14,7 @@ from django.utils.crypto import constant_time_compare from django.utils.crypto import get_random_string from django.utils.crypto import salted_hmac from django.utils import timezone +from django.utils.encoding import smart_bytes class CreateError(Exception): """ @@ -78,15 +81,15 @@ class SessionBase(object): "Returns the given session dictionary pickled and encoded as a string." pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL) hash = self._hash(pickled) - return base64.encodestring(hash + ":" + pickled) + return base64.b64encode(hash.encode() + b":" + pickled).decode('ascii') def decode(self, session_data): - encoded_data = base64.decodestring(session_data) + encoded_data = base64.b64decode(smart_bytes(session_data)) try: # could produce ValueError if there is no ':' - hash, pickled = encoded_data.split(':', 1) + hash, pickled = encoded_data.split(b':', 1) expected_hash = self._hash(pickled) - if not constant_time_compare(hash, expected_hash): + if not constant_time_compare(hash.decode(), expected_hash): raise SuspiciousOperation("Session data corrupted") else: return pickle.loads(pickled) diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py index 467d5f1265..b66123b915 100644 --- a/django/contrib/sessions/backends/cache.py +++ b/django/contrib/sessions/backends/cache.py @@ -1,5 +1,6 @@ from django.contrib.sessions.backends.base import SessionBase, CreateError from django.core.cache import cache +from django.utils.six.moves import xrange KEY_PREFIX = "django.contrib.sessions.cache" diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py index 3dd0d9516c..babdb72c27 100644 --- a/django/contrib/sessions/backends/db.py +++ b/django/contrib/sessions/backends/db.py @@ -1,7 +1,6 @@ from django.contrib.sessions.backends.base import SessionBase, CreateError from django.core.exceptions import SuspiciousOperation from django.db import IntegrityError, transaction, router -from django.utils.encoding import force_unicode from django.utils import timezone @@ -18,7 +17,7 @@ class SessionStore(SessionBase): session_key = self.session_key, expire_date__gt=timezone.now() ) - return self.decode(force_unicode(s.session_data)) + return self.decode(s.session_data) except (Session.DoesNotExist, SuspiciousOperation): self.create() return {} diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py index 0f869088ac..20ac2c2087 100644 --- a/django/contrib/sessions/backends/file.py +++ b/django/contrib/sessions/backends/file.py @@ -115,7 +115,7 @@ class SessionStore(SessionBase): renamed = False try: try: - os.write(output_file_fd, self.encode(session_data)) + os.write(output_file_fd, self.encode(session_data).encode()) finally: os.close(output_file_fd) os.rename(output_file_name, session_file_name) diff --git a/django/contrib/sessions/backends/signed_cookies.py b/django/contrib/sessions/backends/signed_cookies.py index 2a0f261441..41ba7af634 100644 --- a/django/contrib/sessions/backends/signed_cookies.py +++ b/django/contrib/sessions/backends/signed_cookies.py @@ -1,5 +1,5 @@ try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 68cb77f7e1..9f65255f47 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -33,11 +33,13 @@ class SessionMiddleware(object): expires_time = time.time() + max_age expires = cookie_date(expires_time) # Save the session data and refresh the client cookie. - request.session.save() - response.set_cookie(settings.SESSION_COOKIE_NAME, - request.session.session_key, max_age=max_age, - expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, - path=settings.SESSION_COOKIE_PATH, - secure=settings.SESSION_COOKIE_SECURE or None, - httponly=settings.SESSION_COOKIE_HTTPONLY or None) + # Skip session save for 500 responses, refs #3881. + if response.status_code != 500: + request.session.save() + response.set_cookie(settings.SESSION_COOKIE_NAME, + request.session.session_key, max_age=max_age, + expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, + path=settings.SESSION_COOKIE_PATH, + secure=settings.SESSION_COOKIE_SECURE or None, + httponly=settings.SESSION_COOKIE_HTTPONLY or None) return response diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 92ea6bbd91..7de2941122 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -16,6 +16,7 @@ from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.http import HttpResponse from django.test import TestCase, RequestFactory from django.test.utils import override_settings +from django.utils import six from django.utils import timezone from django.utils import unittest @@ -86,16 +87,16 @@ class SessionTestsMixin(object): self.assertFalse(self.session.modified) def test_values(self): - self.assertEqual(self.session.values(), []) + self.assertEqual(list(self.session.values()), []) self.assertTrue(self.session.accessed) self.session['some key'] = 1 - self.assertEqual(self.session.values(), [1]) + self.assertEqual(list(self.session.values()), [1]) def test_iterkeys(self): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = self.session.iterkeys() + i = six.iterkeys(self.session) self.assertTrue(hasattr(i, '__iter__')) self.assertTrue(self.session.accessed) self.assertFalse(self.session.modified) @@ -105,7 +106,7 @@ class SessionTestsMixin(object): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = self.session.itervalues() + i = six.itervalues(self.session) self.assertTrue(hasattr(i, '__iter__')) self.assertTrue(self.session.accessed) self.assertFalse(self.session.modified) @@ -115,7 +116,7 @@ class SessionTestsMixin(object): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = self.session.iteritems() + i = six.iteritems(self.session) self.assertTrue(hasattr(i, '__iter__')) self.assertTrue(self.session.accessed) self.assertFalse(self.session.modified) @@ -125,9 +126,9 @@ class SessionTestsMixin(object): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - self.assertEqual(self.session.items(), [('x', 1)]) + self.assertEqual(list(self.session.items()), [('x', 1)]) self.session.clear() - self.assertEqual(self.session.items(), []) + self.assertEqual(list(self.session.items()), []) self.assertTrue(self.session.accessed) self.assertTrue(self.session.modified) @@ -154,10 +155,10 @@ class SessionTestsMixin(object): self.session['a'], self.session['b'] = 'c', 'd' self.session.save() prev_key = self.session.session_key - prev_data = self.session.items() + prev_data = list(self.session.items()) self.session.cycle_key() self.assertNotEqual(self.session.session_key, prev_key) - self.assertEqual(self.session.items(), prev_data) + self.assertEqual(list(self.session.items()), prev_data) def test_invalid_key(self): # Submitting an invalid session key (either by guessing, or if the db has @@ -409,6 +410,22 @@ class SessionMiddlewareTests(unittest.TestCase): self.assertNotIn('httponly', str(response.cookies[settings.SESSION_COOKIE_NAME])) + def test_session_save_on_500(self): + request = RequestFactory().get('/') + response = HttpResponse('Horrible error') + response.status_code = 500 + middleware = SessionMiddleware() + + # Simulate a request the modifies the session + middleware.process_request(request) + request.session['hello'] = 'world' + + # Handle the response through the middleware + response = middleware.process_response(request, response) + + # Check that the value wasn't saved above. + self.assertNotIn('hello', request.session.load()) + class CookieSessionTests(SessionTestsMixin, TestCase): diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py index 53b375a48b..7d03ef19f5 100644 --- a/django/contrib/sitemaps/__init__.py +++ b/django/contrib/sitemaps/__init__.py @@ -1,7 +1,11 @@ from django.contrib.sites.models import Site from django.core import urlresolvers, paginator from django.core.exceptions import ImproperlyConfigured -import urllib +try: + from urllib.parse import urlencode + from urllib.request import urlopen +except ImportError: # Python 2 + from urllib import urlencode, urlopen PING_URL = "http://www.google.com/webmasters/tools/ping" @@ -32,8 +36,8 @@ def ping_google(sitemap_url=None, ping_url=PING_URL): from django.contrib.sites.models import Site current_site = Site.objects.get_current() url = "http://%s%s" % (current_site.domain, sitemap_url) - params = urllib.urlencode({'sitemap':url}) - urllib.urlopen("%s?%s" % (ping_url, params)) + params = urlencode({'sitemap':url}) + urlopen("%s?%s" % (ping_url, params)) class Sitemap(object): # This limit is defined by Google. See the index documentation at diff --git a/django/contrib/sitemaps/tests/flatpages.py b/django/contrib/sitemaps/tests/flatpages.py index a40876e859..930f24f22c 100644 --- a/django/contrib/sitemaps/tests/flatpages.py +++ b/django/contrib/sitemaps/tests/flatpages.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf import settings from django.utils.unittest import skipUnless @@ -17,15 +19,15 @@ class FlatpagesSitemapTests(SitemapTestsBase): from django.contrib.flatpages.models import FlatPage public = FlatPage.objects.create( - url=u'/public/', - title=u'Public Page', + url='/public/', + title='Public Page', enable_comments=True, registration_required=False, ) public.sites.add(settings.SITE_ID) private = FlatPage.objects.create( - url=u'/private/', - title=u'Private Page', + url='/private/', + title='Private Page', enable_comments=True, registration_required=True ) diff --git a/django/contrib/sitemaps/tests/generic.py b/django/contrib/sitemaps/tests/generic.py index 5f8b6b8be0..e0b0a827a6 100644 --- a/django/contrib/sitemaps/tests/generic.py +++ b/django/contrib/sitemaps/tests/generic.py @@ -1,7 +1,11 @@ +from __future__ import unicode_literals + from django.contrib.auth.models import User +from django.test.utils import override_settings from .base import SitemapTestsBase +@override_settings(ABSOLUTE_URL_OVERRIDES={}) class GenericViewsSitemapTests(SitemapTestsBase): def test_generic_sitemap(self): @@ -10,8 +14,9 @@ class GenericViewsSitemapTests(SitemapTestsBase): expected = '' for username in User.objects.values_list("username", flat=True): expected += "%s/users/%s/" % (self.base_url, username) - self.assertEqual(response.content, """ + expected_content = """ %s -""" % expected) +""" % expected + self.assertEqual(response.content, expected_content.encode('utf-8')) diff --git a/django/contrib/sitemaps/tests/http.py b/django/contrib/sitemaps/tests/http.py index 5786aa48d5..8da971876f 100644 --- a/django/contrib/sitemaps/tests/http.py +++ b/django/contrib/sitemaps/tests/http.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import os from datetime import date @@ -19,11 +21,12 @@ class HTTPSitemapTests(SitemapTestsBase): def test_simple_sitemap_index(self): "A simple sitemap index can be rendered" response = self.client.get('/simple/index.xml') - self.assertEqual(response.content, """ + expected_content = """ %s/simple/sitemap-simple.xml -""" % self.base_url) +""" % self.base_url + self.assertEqual(response.content, expected_content.encode('utf-8')) @override_settings( TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),) @@ -31,30 +34,34 @@ class HTTPSitemapTests(SitemapTestsBase): def test_simple_sitemap_custom_index(self): "A simple sitemap index can be rendered with a custom template" response = self.client.get('/simple/custom-index.xml') - self.assertEqual(response.content, """ + expected_content = """ %s/simple/sitemap-simple.xml -""" % self.base_url) +""" % self.base_url + self.assertEqual(response.content, expected_content.encode('utf-8')) + def test_simple_sitemap_section(self): "A simple sitemap section can be rendered" response = self.client.get('/simple/sitemap-simple.xml') - self.assertEqual(response.content, """ + expected_content = """ %s/location/%snever0.5 -""" % (self.base_url, date.today())) +""" % (self.base_url, date.today()) + self.assertEqual(response.content, expected_content.encode('utf-8')) def test_simple_sitemap(self): "A simple sitemap can be rendered" response = self.client.get('/simple/sitemap.xml') - self.assertEqual(response.content, """ + expected_content = """ %s/location/%snever0.5 -""" % (self.base_url, date.today())) +""" % (self.base_url, date.today()) + self.assertEqual(response.content, expected_content.encode('utf-8')) @override_settings( TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),) @@ -62,19 +69,20 @@ class HTTPSitemapTests(SitemapTestsBase): def test_simple_custom_sitemap(self): "A simple sitemap can be rendered with a custom template" response = self.client.get('/simple/custom-sitemap.xml') - self.assertEqual(response.content, """ + expected_content = """ %s/location/%snever0.5 -""" % (self.base_url, date.today())) +""" % (self.base_url, date.today()) + self.assertEqual(response.content, expected_content.encode('utf-8')) @skipUnless(settings.USE_I18N, "Internationalization is not enabled") @override_settings(USE_L10N=True) def test_localized_priority(self): "The priority value should not be localized (Refs #14164)" activate('fr') - self.assertEqual(u'0,3', localize(0.3)) + self.assertEqual('0,3', localize(0.3)) # Retrieve the sitemap. Check that priorities # haven't been rendered in localized format @@ -88,11 +96,12 @@ class HTTPSitemapTests(SitemapTestsBase): # installed doesn't raise an exception Site._meta.installed = False response = self.client.get('/simple/sitemap.xml') - self.assertEqual(response.content, """ + expected_content = """ http://testserver/location/%snever0.5 -""" % date.today()) +""" % date.today() + self.assertEqual(response.content, expected_content.encode('utf-8')) @skipUnless("django.contrib.sites" in settings.INSTALLED_APPS, "django.contrib.sites app not installed.") @@ -129,8 +138,9 @@ class HTTPSitemapTests(SitemapTestsBase): Check that a cached sitemap index can be rendered (#2713). """ response = self.client.get('/cached/index.xml') - self.assertEqual(response.content, """ + expected_content = """ %s/cached/sitemap-simple.xml -""" % self.base_url) +""" % self.base_url + self.assertEqual(response.content, expected_content.encode('utf-8')) diff --git a/django/contrib/sitemaps/tests/https.py b/django/contrib/sitemaps/tests/https.py index d4f9053fc8..26241eb30b 100644 --- a/django/contrib/sitemaps/tests/https.py +++ b/django/contrib/sitemaps/tests/https.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from datetime import date from django.test.utils import override_settings @@ -11,20 +13,22 @@ class HTTPSSitemapTests(SitemapTestsBase): def test_secure_sitemap_index(self): "A secure sitemap index can be rendered" response = self.client.get('/secure/index.xml') - self.assertEqual(response.content, """ + expected_content = """ %s/secure/sitemap-simple.xml -""" % self.base_url) +""" % self.base_url + self.assertEqual(response.content, expected_content.encode('utf-8')) def test_secure_sitemap_section(self): "A secure sitemap section can be rendered" response = self.client.get('/secure/sitemap-simple.xml') - self.assertEqual(response.content, """ + expected_content = """ %s/location/%snever0.5 -""" % (self.base_url, date.today())) +""" % (self.base_url, date.today()) + self.assertEqual(response.content, expected_content.encode('utf-8')) @override_settings(SECURE_PROXY_SSL_HEADER=False) @@ -34,17 +38,19 @@ class HTTPSDetectionSitemapTests(SitemapTestsBase): def test_sitemap_index_with_https_request(self): "A sitemap index requested in HTTPS is rendered with HTTPS links" response = self.client.get('/simple/index.xml', **self.extra) - self.assertEqual(response.content, """ + expected_content = """ %s/simple/sitemap-simple.xml -""" % self.base_url.replace('http://', 'https://')) +""" % self.base_url.replace('http://', 'https://') + self.assertEqual(response.content, expected_content.encode('utf-8')) def test_sitemap_section_with_https_request(self): "A sitemap section requested in HTTPS is rendered with HTTPS links" response = self.client.get('/simple/sitemap-simple.xml', **self.extra) - self.assertEqual(response.content, """ + expected_content = """ %s/location/%snever0.5 -""" % (self.base_url.replace('http://', 'https://'), date.today())) +""" % (self.base_url.replace('http://', 'https://'), date.today()) + self.assertEqual(response.content, expected_content.encode('utf-8')) diff --git a/django/contrib/sitemaps/views.py b/django/contrib/sitemaps/views.py index b90a39e954..cfe3aa66a9 100644 --- a/django/contrib/sitemaps/views.py +++ b/django/contrib/sitemaps/views.py @@ -3,6 +3,7 @@ from django.core import urlresolvers from django.core.paginator import EmptyPage, PageNotAnInteger from django.http import Http404 from django.template.response import TemplateResponse +from django.utils import six def index(request, sitemaps, template_name='sitemap_index.xml', mimetype='application/xml', @@ -35,7 +36,7 @@ def sitemap(request, sitemaps, section=None, raise Http404("No sitemap available for section: %r" % section) maps = [sitemaps[section]] else: - maps = sitemaps.values() + maps = list(six.itervalues(sitemaps)) page = request.GET.get("p", 1) urls = [] diff --git a/django/contrib/sites/models.py b/django/contrib/sites/models.py index fecbff79d8..8590740658 100644 --- a/django/contrib/sites/models.py +++ b/django/contrib/sites/models.py @@ -1,5 +1,6 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import python_2_unicode_compatible SITE_CACHE = {} @@ -32,6 +33,7 @@ class SiteManager(models.Manager): SITE_CACHE = {} +@python_2_unicode_compatible class Site(models.Model): domain = models.CharField(_('domain name'), max_length=100) @@ -44,7 +46,7 @@ class Site(models.Model): verbose_name_plural = _('sites') ordering = ('domain',) - def __unicode__(self): + def __str__(self): return self.domain def save(self, *args, **kwargs): @@ -62,6 +64,7 @@ class Site(models.Model): pass +@python_2_unicode_compatible class RequestSite(object): """ A class that shares the primary interface of Site (i.e., it has @@ -73,7 +76,7 @@ class RequestSite(object): def __init__(self, request): self.domain = self.name = request.get_host() - def __unicode__(self): + def __str__(self): return self.domain def save(self, force_insert=False, force_update=False): diff --git a/django/contrib/sites/tests.py b/django/contrib/sites/tests.py index 828badb386..1bb2495e6b 100644 --- a/django/contrib/sites/tests.py +++ b/django/contrib/sites/tests.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf import settings from django.contrib.sites.models import Site, RequestSite, get_current_site from django.core.exceptions import ObjectDoesNotExist @@ -32,12 +34,12 @@ class SitesFrameworkTests(TestCase): # After updating a Site object (e.g. via the admin), we shouldn't return a # bogus value from the SITE_CACHE. site = Site.objects.get_current() - self.assertEqual(u"example.com", site.name) + self.assertEqual("example.com", site.name) s2 = Site.objects.get(id=settings.SITE_ID) s2.name = "Example site" s2.save() site = Site.objects.get_current() - self.assertEqual(u"Example site", site.name) + self.assertEqual("Example site", site.name) def test_get_current_site(self): # Test that the correct Site object is returned @@ -59,4 +61,4 @@ class SitesFrameworkTests(TestCase): Site._meta.installed = False site = get_current_site(request) self.assertTrue(isinstance(site, RequestSite)) - self.assertEqual(site.name, u"example.com") + self.assertEqual(site.name, "example.com") diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py index 766687cf7d..9b06c2cf60 100644 --- a/django/contrib/staticfiles/finders.py +++ b/django/contrib/staticfiles/finders.py @@ -6,6 +6,7 @@ from django.utils.datastructures import SortedDict from django.utils.functional import empty, memoize, LazyObject from django.utils.importlib import import_module from django.utils._os import safe_join +from django.utils import six from django.contrib.staticfiles import utils from django.contrib.staticfiles.storage import AppStaticStorage @@ -132,7 +133,7 @@ class AppDirectoriesFinder(BaseFinder): """ List all files in all app storages. """ - for storage in self.storages.itervalues(): + for storage in six.itervalues(self.storages): if storage.exists(''): # check if storage location exists for path in utils.get_files(storage, ignore_patterns): yield path, storage diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py index f475b22d9c..9067a0e75e 100644 --- a/django/contrib/staticfiles/handlers.py +++ b/django/contrib/staticfiles/handlers.py @@ -1,5 +1,9 @@ -import urllib -from urlparse import urlparse +try: + from urllib.parse import urlparse + from urllib.request import url2pathname +except ImportError: # Python 2 + from urllib import url2pathname + from urlparse import urlparse from django.conf import settings from django.core.handlers.wsgi import WSGIHandler @@ -42,7 +46,7 @@ class StaticFilesHandler(WSGIHandler): Returns the relative path to the media file on disk for the given URL. """ relative_url = url[len(self.base_url[2]):] - return urllib.url2pathname(relative_url) + return url2pathname(relative_url) def serve(self, request): """ diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index 669c04043b..7dac0ffb4c 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -1,11 +1,14 @@ +from __future__ import unicode_literals + import os import sys from optparse import make_option from django.core.files.storage import FileSystemStorage from django.core.management.base import CommandError, NoArgsCommand -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.datastructures import SortedDict +from django.utils.six.moves import input from django.contrib.staticfiles import finders, storage @@ -117,11 +120,11 @@ class Command(NoArgsCommand): dry_run=self.dry_run) for original_path, processed_path, processed in processor: if processed: - self.log(u"Post-processed '%s' as '%s" % + self.log("Post-processed '%s' as '%s" % (original_path, processed_path), level=1) self.post_processed_files.append(original_path) else: - self.log(u"Skipped post-processing '%s'" % original_path) + self.log("Skipped post-processing '%s'" % original_path) return { 'modified': self.copied_files + self.symlinked_files, @@ -146,7 +149,7 @@ class Command(NoArgsCommand): clear_display = 'This will overwrite existing files!' if self.interactive: - confirm = raw_input(u""" + confirm = input(""" You have requested to collect static files at the destination location as specified in your settings%s @@ -195,10 +198,10 @@ Type 'yes' to continue, or 'no' to cancel: """ for f in files: fpath = os.path.join(path, f) if self.dry_run: - self.log(u"Pretending to delete '%s'" % - smart_unicode(fpath), level=1) + self.log("Pretending to delete '%s'" % + smart_text(fpath), level=1) else: - self.log(u"Deleting '%s'" % smart_unicode(fpath), level=1) + self.log("Deleting '%s'" % smart_text(fpath), level=1) self.storage.delete(fpath) for d in dirs: self.clear_dir(os.path.join(path, d)) @@ -235,13 +238,13 @@ Type 'yes' to continue, or 'no' to cancel: """ and os.path.islink(full_path))): if prefixed_path not in self.unmodified_files: self.unmodified_files.append(prefixed_path) - self.log(u"Skipping '%s' (not modified)" % path) + self.log("Skipping '%s' (not modified)" % path) return False # Then delete the existing file if really needed if self.dry_run: - self.log(u"Pretending to delete '%s'" % path) + self.log("Pretending to delete '%s'" % path) else: - self.log(u"Deleting '%s'" % path) + self.log("Deleting '%s'" % path) self.storage.delete(prefixed_path) return True @@ -251,7 +254,7 @@ Type 'yes' to continue, or 'no' to cancel: """ """ # Skip this file if it was already copied earlier if prefixed_path in self.symlinked_files: - return self.log(u"Skipping '%s' (already linked earlier)" % path) + return self.log("Skipping '%s' (already linked earlier)" % path) # Delete the target file if needed or break if not self.delete_file(path, prefixed_path, source_storage): return @@ -259,9 +262,9 @@ Type 'yes' to continue, or 'no' to cancel: """ source_path = source_storage.path(path) # Finally link the file if self.dry_run: - self.log(u"Pretending to link '%s'" % source_path, level=1) + self.log("Pretending to link '%s'" % source_path, level=1) else: - self.log(u"Linking '%s'" % source_path, level=1) + self.log("Linking '%s'" % source_path, level=1) full_path = self.storage.path(prefixed_path) try: os.makedirs(os.path.dirname(full_path)) @@ -277,7 +280,7 @@ Type 'yes' to continue, or 'no' to cancel: """ """ # Skip this file if it was already copied earlier if prefixed_path in self.copied_files: - return self.log(u"Skipping '%s' (already copied earlier)" % path) + return self.log("Skipping '%s' (already copied earlier)" % path) # Delete the target file if needed or break if not self.delete_file(path, prefixed_path, source_storage): return @@ -285,9 +288,9 @@ Type 'yes' to continue, or 'no' to cancel: """ source_path = source_storage.path(path) # Finally start copying if self.dry_run: - self.log(u"Pretending to copy '%s'" % source_path, level=1) + self.log("Pretending to copy '%s'" % source_path, level=1) else: - self.log(u"Copying '%s'" % source_path, level=1) + self.log("Copying '%s'" % source_path, level=1) if self.local: full_path = self.storage.path(prefixed_path) try: diff --git a/django/contrib/staticfiles/management/commands/findstatic.py b/django/contrib/staticfiles/management/commands/findstatic.py index dc4406e458..dc1e88d778 100644 --- a/django/contrib/staticfiles/management/commands/findstatic.py +++ b/django/contrib/staticfiles/management/commands/findstatic.py @@ -1,7 +1,9 @@ +from __future__ import unicode_literals + import os from optparse import make_option from django.core.management.base import LabelCommand -from django.utils.encoding import smart_str, smart_unicode +from django.utils.encoding import smart_text from django.contrib.staticfiles import finders @@ -17,13 +19,13 @@ class Command(LabelCommand): def handle_label(self, path, **options): verbosity = int(options.get('verbosity', 1)) result = finders.find(path, all=options['all']) - path = smart_unicode(path) + path = smart_text(path) if result: if not isinstance(result, (list, tuple)): result = [result] - output = u'\n '.join( - (smart_unicode(os.path.realpath(path)) for path in result)) - self.stdout.write(u"Found '%s' here:\n %s" % (path, output)) + output = '\n '.join( + (smart_text(os.path.realpath(path)) for path in result)) + self.stdout.write("Found '%s' here:\n %s" % (path, output)) else: if verbosity >= 1: self.stderr.write("No matching file found for '%s'." % path) diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index fd8f9efb02..4be7540c6e 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -1,9 +1,13 @@ +from __future__ import unicode_literals import hashlib import os import posixpath import re -from urllib import unquote -from urlparse import urlsplit, urlunsplit, urldefrag +try: + from urllib.parse import unquote, urlsplit, urlunsplit, urldefrag +except ImportError: # Python 2 + from urllib import unquote + from urlparse import urlsplit, urlunsplit, urldefrag from django.conf import settings from django.core.cache import (get_cache, InvalidCacheBackendError, @@ -12,7 +16,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.files.base import ContentFile from django.core.files.storage import FileSystemStorage, get_storage_class from django.utils.datastructures import SortedDict -from django.utils.encoding import force_unicode, smart_str +from django.utils.encoding import force_text, smart_bytes from django.utils.functional import LazyObject from django.utils.importlib import import_module @@ -44,10 +48,11 @@ class StaticFilesStorage(FileSystemStorage): class CachedFilesMixin(object): + default_template = """url("%s")""" patterns = ( ("*.css", ( r"""(url\(['"]{0,1}\s*(.*?)["']{0,1}\))""", - r"""(@import\s*["']\s*(.*?)["'])""", + (r"""(@import\s*["']\s*(.*?)["'])""", """@import url("%s")"""), )), ) @@ -61,8 +66,12 @@ class CachedFilesMixin(object): self._patterns = SortedDict() for extension, patterns in self.patterns: for pattern in patterns: + if isinstance(pattern, (tuple, list)): + pattern, template = pattern + else: + template = self.default_template compiled = re.compile(pattern) - self._patterns.setdefault(extension, []).append(compiled) + self._patterns.setdefault(extension, []).append((compiled, template)) def file_hash(self, name, content=None): """ @@ -78,6 +87,7 @@ class CachedFilesMixin(object): def hashed_name(self, name, content=None): parsed_name = urlsplit(unquote(name)) clean_name = parsed_name.path.strip() + opened = False if content is None: if not self.exists(clean_name): raise ValueError("The file '%s' could not be found with %r." % @@ -87,12 +97,17 @@ class CachedFilesMixin(object): except IOError: # Handle directory paths and fragments return name + opened = True + try: + file_hash = self.file_hash(clean_name, content) + finally: + if opened: + content.close() path, filename = os.path.split(clean_name) root, ext = os.path.splitext(filename) - file_hash = self.file_hash(clean_name, content) if file_hash is not None: - file_hash = u".%s" % file_hash - hashed_name = os.path.join(path, u"%s%s%s" % + file_hash = ".%s" % file_hash + hashed_name = os.path.join(path, "%s%s%s" % (root, file_hash, ext)) unparsed_name = list(parsed_name) unparsed_name[2] = hashed_name @@ -103,7 +118,7 @@ class CachedFilesMixin(object): return urlunsplit(unparsed_name) def cache_key(self, name): - return u'staticfiles:%s' % hashlib.md5(smart_str(name)).hexdigest() + return 'staticfiles:%s' % hashlib.md5(smart_bytes(name)).hexdigest() def url(self, name, force=False): """ @@ -139,10 +154,13 @@ class CachedFilesMixin(object): return unquote(final_url) - def url_converter(self, name): + def url_converter(self, name, template=None): """ Returns the custom URL converter for the given file name. """ + if template is None: + template = self.default_template + def converter(matchobj): """ Converts the matched URL depending on the parent level (`..`) @@ -152,7 +170,7 @@ class CachedFilesMixin(object): matched, url = matchobj.groups() # Completely ignore http(s) prefixed URLs, # fragments and data-uri URLs - if url.startswith(('#', 'http:', 'https:', 'data:')): + if url.startswith(('#', 'http:', 'https:', 'data:', '//')): return matched name_parts = name.split(os.sep) # Using posix normpath here to remove duplicates @@ -177,7 +195,8 @@ class CachedFilesMixin(object): relative_url = '/'.join(url.split('/')[:-1] + file_name) # Return the hashed version to the file - return 'url("%s")' % unquote(relative_url) + return template % unquote(relative_url) + return converter def post_process(self, paths, dry_run=False, **options): @@ -227,17 +246,17 @@ class CachedFilesMixin(object): # ..to apply each replacement pattern to the content if name in adjustable_paths: - content = original_file.read() - converter = self.url_converter(name) + content = original_file.read().decode(settings.FILE_CHARSET) for patterns in self._patterns.values(): - for pattern in patterns: + for pattern, template in patterns: + converter = self.url_converter(name, template) content = pattern.sub(converter, content) if hashed_file_exists: self.delete(hashed_name) # then save the processed result - content_file = ContentFile(smart_str(content)) + content_file = ContentFile(smart_bytes(content)) saved_name = self._save(hashed_name, content_file) - hashed_name = force_unicode(saved_name.replace('\\', '/')) + hashed_name = force_text(saved_name.replace('\\', '/')) processed = True else: # or handle the case in which neither processing nor @@ -245,10 +264,10 @@ class CachedFilesMixin(object): if not hashed_file_exists: processed = True saved_name = self._save(hashed_name, original_file) - hashed_name = force_unicode(saved_name.replace('\\', '/')) + hashed_name = force_text(saved_name.replace('\\', '/')) # and then set the cache accordingly - hashed_paths[self.cache_key(name)] = hashed_name + hashed_paths[self.cache_key(name.replace('\\', '/'))] = hashed_name yield name, hashed_name, processed # Finally set the cache diff --git a/django/contrib/staticfiles/templatetags/staticfiles.py b/django/contrib/staticfiles/templatetags/staticfiles.py index 788f06ec16..71339ea8cd 100644 --- a/django/contrib/staticfiles/templatetags/staticfiles.py +++ b/django/contrib/staticfiles/templatetags/staticfiles.py @@ -1,13 +1,37 @@ from django import template +from django.templatetags.static import StaticNode from django.contrib.staticfiles.storage import staticfiles_storage register = template.Library() -@register.simple_tag -def static(path): +class StaticFilesNode(StaticNode): + + def url(self, context): + path = self.path.resolve(context) + return staticfiles_storage.url(path) + + +@register.tag('static') +def do_static(parser, token): """ A template tag that returns the URL to a file using staticfiles' storage backend + + Usage:: + + {% static path [as varname] %} + + Examples:: + + {% static "myapp/css/base.css" %} + {% static variable_with_path %} + {% static "myapp/css/base.css" as admin_base_css %} + {% static variable_with_path as varname %} + """ - return staticfiles_storage.url(path) + return StaticFilesNode.handle_token(parser, token) + + +def static(path): + return StaticNode.handle_simple(path) diff --git a/django/contrib/staticfiles/views.py b/django/contrib/staticfiles/views.py index 1a9c166ad7..85459812ad 100644 --- a/django/contrib/staticfiles/views.py +++ b/django/contrib/staticfiles/views.py @@ -5,7 +5,10 @@ development, and SHOULD NOT be used in a production setting. """ import os import posixpath -import urllib +try: + from urllib.parse import unquote +except ImportError: # Python 2 + from urllib import unquote from django.conf import settings from django.core.exceptions import ImproperlyConfigured @@ -31,7 +34,7 @@ def serve(request, path, document_root=None, insecure=False, **kwargs): raise ImproperlyConfigured("The staticfiles view can only be used in " "debug mode or if the the --insecure " "option of 'runserver' is used") - normalized_path = posixpath.normpath(urllib.unquote(path)).lstrip('/') + normalized_path = posixpath.normpath(unquote(path)).lstrip('/') absolute_path = finders.find(normalized_path) if not absolute_path: if path.endswith('/') or path == '': diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py index 462b3e94cf..4815ce5567 100644 --- a/django/contrib/syndication/views.py +++ b/django/contrib/syndication/views.py @@ -1,13 +1,16 @@ +from __future__ import unicode_literals + from django.conf import settings from django.contrib.sites.models import get_current_site from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.http import HttpResponse, Http404 from django.template import loader, TemplateDoesNotExist, RequestContext from django.utils import feedgenerator, tzinfo -from django.utils.encoding import force_unicode, iri_to_uri, smart_unicode +from django.utils.encoding import force_text, iri_to_uri, smart_text from django.utils.html import escape from django.utils.timezone import is_naive + def add_domain(domain, url, secure=False): protocol = 'https' if secure else 'http' if url.startswith('//'): @@ -16,9 +19,7 @@ def add_domain(domain, url, secure=False): elif not (url.startswith('http://') or url.startswith('https://') or url.startswith('mailto:')): - # 'url' must already be ASCII and URL-quoted, so no need for encoding - # conversions here. - url = iri_to_uri(u'%s://%s%s' % (protocol, domain, url)) + url = iri_to_uri('%s://%s%s' % (protocol, domain, url)) return url class FeedDoesNotExist(ObjectDoesNotExist): @@ -42,10 +43,10 @@ class Feed(object): def item_title(self, item): # Titles should be double escaped by default (see #6533) - return escape(force_unicode(item)) + return escape(force_text(item)) def item_description(self, item): - return force_unicode(item) + return force_text(item) def item_link(self, item): try: @@ -59,14 +60,14 @@ class Feed(object): except AttributeError: return default if callable(attr): - # Check func_code.co_argcount rather than try/excepting the + # Check __code__.co_argcount rather than try/excepting the # function and catching the TypeError, because something inside # the function may raise the TypeError. This technique is more # accurate. - if hasattr(attr, 'func_code'): - argcount = attr.func_code.co_argcount + if hasattr(attr, '__code__'): + argcount = attr.__code__.co_argcount else: - argcount = attr.__call__.func_code.co_argcount + argcount = attr.__call__.__code__.co_argcount if argcount == 2: # one argument is 'self' return attr(obj) else: @@ -105,7 +106,7 @@ class Feed(object): subtitle = self.__get_dynamic_attr('subtitle', obj), link = link, description = self.__get_dynamic_attr('description', obj), - language = settings.LANGUAGE_CODE.decode(), + language = settings.LANGUAGE_CODE, feed_url = add_domain( current_site.domain, self.__get_dynamic_attr('feed_url', obj) or request.path, @@ -153,9 +154,9 @@ class Feed(object): enc_url = self.__get_dynamic_attr('item_enclosure_url', item) if enc_url: enc = feedgenerator.Enclosure( - url = smart_unicode(enc_url), - length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)), - mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item)) + url = smart_text(enc_url), + length = smart_text(self.__get_dynamic_attr('item_enclosure_length', item)), + mime_type = smart_text(self.__get_dynamic_attr('item_enclosure_mime_type', item)) ) author_name = self.__get_dynamic_attr('item_author_name', item) if author_name is not None: diff --git a/django/contrib/webdesign/lorem_ipsum.py b/django/contrib/webdesign/lorem_ipsum.py index 4ad175d033..01d8f220f5 100644 --- a/django/contrib/webdesign/lorem_ipsum.py +++ b/django/contrib/webdesign/lorem_ipsum.py @@ -2,6 +2,8 @@ Utility functions for generating "lorem ipsum" Latin text. """ +from __future__ import unicode_literals + import random COMMON_P = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' @@ -49,10 +51,10 @@ def sentence(): """ # Determine the number of comma-separated sections and number of words in # each section for this sentence. - sections = [u' '.join(random.sample(WORDS, random.randint(3, 12))) for i in range(random.randint(1, 5))] - s = u', '.join(sections) + sections = [' '.join(random.sample(WORDS, random.randint(3, 12))) for i in range(random.randint(1, 5))] + s = ', '.join(sections) # Convert to sentence case and add end punctuation. - return u'%s%s%s' % (s[0].upper(), s[1:], random.choice('?.')) + return '%s%s%s' % (s[0].upper(), s[1:], random.choice('?.')) def paragraph(): """ @@ -60,7 +62,7 @@ def paragraph(): The paragraph consists of between 1 and 4 sentences, inclusive. """ - return u' '.join([sentence() for i in range(random.randint(1, 4))]) + return ' '.join([sentence() for i in range(random.randint(1, 4))]) def paragraphs(count, common=True): """ @@ -98,4 +100,4 @@ def words(count, common=True): word_list += random.sample(WORDS, c) else: word_list = word_list[:count] - return u' '.join(word_list) + return ' '.join(word_list) diff --git a/django/contrib/webdesign/templatetags/webdesign.py b/django/contrib/webdesign/templatetags/webdesign.py index 05d8dc7f54..b870299cda 100644 --- a/django/contrib/webdesign/templatetags/webdesign.py +++ b/django/contrib/webdesign/templatetags/webdesign.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.contrib.webdesign.lorem_ipsum import words, paragraphs from django import template @@ -18,7 +20,7 @@ class LoremNode(template.Node): paras = paragraphs(count, common=self.common) if self.method == 'p': paras = ['

    %s

    ' % p for p in paras] - return u'\n\n'.join(paras) + return '\n\n'.join(paras) @register.tag def lorem(parser, token): diff --git a/django/contrib/webdesign/tests.py b/django/contrib/webdesign/tests.py index 8907ea3ba7..16ec501e44 100644 --- a/django/contrib/webdesign/tests.py +++ b/django/contrib/webdesign/tests.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals import unittest @@ -9,7 +10,7 @@ from django.template import loader, Context class WebdesignTest(unittest.TestCase): def test_words(self): - self.assertEqual(words(7), u'lorem ipsum dolor sit amet consectetur adipisicing') + self.assertEqual(words(7), 'lorem ipsum dolor sit amet consectetur adipisicing') def test_paragraphs(self): self.assertEqual(paragraphs(1), @@ -18,4 +19,4 @@ class WebdesignTest(unittest.TestCase): def test_lorem_tag(self): t = loader.get_template_from_string("{% load webdesign %}{% lorem 3 w %}") self.assertEqual(t.render(Context({})), - u'lorem ipsum dolor') + 'lorem ipsum dolor') diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py index 2a9e1a700b..f496c35e2b 100644 --- a/django/core/cache/__init__.py +++ b/django/core/cache/__init__.py @@ -14,7 +14,10 @@ cache class. See docs/topics/cache.txt for information on the public API. """ -from urlparse import parse_qsl +try: + from urllib.parse import parse_qsl +except ImportError: # Python 2 + from urlparse import parse_qsl from django.conf import settings from django.core import signals diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py index f7573b2e31..06e8952bfb 100644 --- a/django/core/cache/backends/base.py +++ b/django/core/cache/backends/base.py @@ -1,9 +1,9 @@ "Base Cache class." +from __future__ import unicode_literals import warnings from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning -from django.utils.encoding import smart_str from django.utils.importlib import import_module class InvalidCacheBackendError(ImproperlyConfigured): @@ -23,7 +23,7 @@ def default_key_func(key, key_prefix, version): the `key_prefix'. KEY_FUNCTION can be used to specify an alternate function with custom key making behavior. """ - return ':'.join([key_prefix, str(version), smart_str(key)]) + return ':'.join([key_prefix, str(version), key]) def get_key_func(key_func): """ @@ -62,7 +62,7 @@ class BaseCache(object): except (ValueError, TypeError): self._cull_frequency = 3 - self.key_prefix = smart_str(params.get('KEY_PREFIX', '')) + self.key_prefix = params.get('KEY_PREFIX', '') self.version = params.get('VERSION', 1) self.key_func = get_key_func(params.get('KEY_FUNCTION', None)) diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py index 62ea5c420b..52db4d1b1d 100644 --- a/django/core/cache/backends/db.py +++ b/django/core/cache/backends/db.py @@ -4,7 +4,7 @@ import time from datetime import datetime try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle @@ -12,6 +12,7 @@ from django.conf import settings from django.core.cache.backends.base import BaseCache from django.db import connections, router, transaction, DatabaseError from django.utils import timezone +from django.utils.encoding import smart_bytes class Options(object): @@ -72,7 +73,7 @@ class DatabaseCache(BaseDatabaseCache): transaction.commit_unless_managed(using=db) return default value = connections[db].ops.process_clob(row[1]) - return pickle.loads(base64.decodestring(value)) + return pickle.loads(base64.b64decode(smart_bytes(value))) def set(self, key, value, timeout=None, version=None): key = self.make_key(key, version=version) @@ -103,7 +104,7 @@ class DatabaseCache(BaseDatabaseCache): if num > self._max_entries: self._cull(db, cursor, now) pickled = pickle.dumps(value, pickle.HIGHEST_PROTOCOL) - encoded = base64.encodestring(pickled).strip() + encoded = base64.b64encode(pickled).strip() cursor.execute("SELECT cache_key, expires FROM %s " "WHERE cache_key = %%s" % table, [key]) try: @@ -166,18 +167,10 @@ class DatabaseCache(BaseDatabaseCache): cursor.execute("SELECT COUNT(*) FROM %s" % table) num = cursor.fetchone()[0] if num > self._max_entries: - cull_num = num / self._cull_frequency - if connections[db].vendor == 'oracle': - # Oracle doesn't support LIMIT + OFFSET - cursor.execute("""SELECT cache_key FROM -(SELECT ROW_NUMBER() OVER (ORDER BY cache_key) AS counter, cache_key FROM %s) -WHERE counter > %%s AND COUNTER <= %%s""" % table, [cull_num, cull_num + 1]) - else: - # This isn't standard SQL, it's likely to break - # with some non officially supported databases - cursor.execute("SELECT cache_key FROM %s " - "ORDER BY cache_key " - "LIMIT 1 OFFSET %%s" % table, [cull_num]) + cull_num = num // self._cull_frequency + cursor.execute( + connections[db].ops.cache_key_culling_sql() % table, + [cull_num]) cursor.execute("DELETE FROM %s " "WHERE cache_key < %%s" % table, [cursor.fetchone()[0]]) diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py index 7f9f7175be..c54e8d280f 100644 --- a/django/core/cache/backends/filebased.py +++ b/django/core/cache/backends/filebased.py @@ -5,11 +5,12 @@ import os import shutil import time try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle from django.core.cache.backends.base import BaseCache +from django.utils.encoding import smart_bytes class FileBasedCache(BaseCache): def __init__(self, dir, params): @@ -136,7 +137,7 @@ class FileBasedCache(BaseCache): Thus, a cache key of "foo" gets turnned into a file named ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``. """ - path = hashlib.md5(key).hexdigest() + path = hashlib.md5(smart_bytes(key)).hexdigest() path = os.path.join(path[:2], path[2:4], path[4:]) return os.path.join(self._dir, path) diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py index 9196b3b42b..76667e9609 100644 --- a/django/core/cache/backends/locmem.py +++ b/django/core/cache/backends/locmem.py @@ -2,7 +2,7 @@ import time try: - import cPickle as pickle + from django.utils.six.moves import cPickle as pickle except ImportError: import pickle diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py index 951c1eda26..75ce26d20e 100644 --- a/django/core/cache/backends/memcached.py +++ b/django/core/cache/backends/memcached.py @@ -5,10 +5,13 @@ from threading import local from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError +from django.utils import six +from django.utils.encoding import smart_str + class BaseMemcachedCache(BaseCache): def __init__(self, server, params, library, value_not_found_exception): super(BaseMemcachedCache, self).__init__(params) - if isinstance(server, basestring): + if isinstance(server, six.string_types): self._servers = server.split(';') else: self._servers = server @@ -48,6 +51,10 @@ class BaseMemcachedCache(BaseCache): timeout += int(time.time()) return int(timeout) + def make_key(self, key, version=None): + # Python 2 memcache requires the key to be a byte string. + return smart_str(super(BaseMemcachedCache, self).make_key(key, version)) + def add(self, key, value, timeout=0, version=None): key = self.make_key(key, version=version) return self._cache.add(key, value, self._get_memcache_timeout(timeout)) diff --git a/django/core/context_processors.py b/django/core/context_processors.py index 3ba519188b..ca1ac68f55 100644 --- a/django/core/context_processors.py +++ b/django/core/context_processors.py @@ -6,11 +6,15 @@ and returns a dictionary to add to the context. These are referenced from the setting TEMPLATE_CONTEXT_PROCESSORS and used by RequestContext. """ +from __future__ import unicode_literals from django.conf import settings from django.middleware.csrf import get_token +from django.utils import six +from django.utils.encoding import smart_text from django.utils.functional import lazy + def csrf(request): """ Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if @@ -22,10 +26,10 @@ def csrf(request): # In order to be able to provide debugging info in the # case of misconfiguration, we use a sentinel value # instead of returning an empty dict. - return b'NOTPROVIDED' + return 'NOTPROVIDED' else: - return token - _get_val = lazy(_get_val, str) + return smart_text(token) + _get_val = lazy(_get_val, six.text_type) return {'csrf_token': _get_val() } diff --git a/django/core/exceptions.py b/django/core/exceptions.py index 4856ada911..233af40f88 100644 --- a/django/core/exceptions.py +++ b/django/core/exceptions.py @@ -55,7 +55,7 @@ class ValidationError(Exception): """An error while validating data.""" def __init__(self, message, code=None, params=None): import operator - from django.utils.encoding import force_unicode + from django.utils.encoding import force_text """ ValidationError can be passed any object that can be printed (usually a string), a list of objects or a dictionary. @@ -66,11 +66,11 @@ class ValidationError(Exception): message = reduce(operator.add, message.values()) if isinstance(message, list): - self.messages = [force_unicode(msg) for msg in message] + self.messages = [force_text(msg) for msg in message] else: self.code = code self.params = params - message = force_unicode(message) + message = force_text(message) self.messages = [message] def __str__(self): diff --git a/django/core/files/base.py b/django/core/files/base.py index a2d703c963..4a422be90d 100644 --- a/django/core/files/base.py +++ b/django/core/files/base.py @@ -1,9 +1,13 @@ +from __future__ import unicode_literals + import os -from io import BytesIO +from io import BytesIO, UnsupportedOperation -from django.utils.encoding import smart_str, smart_unicode +from django.utils.encoding import smart_bytes, smart_text from django.core.files.utils import FileProxyMixin +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class File(FileProxyMixin): DEFAULT_CHUNK_SIZE = 64 * 2**10 @@ -16,16 +20,14 @@ class File(FileProxyMixin): self.mode = file.mode def __str__(self): - return smart_str(self.name or '') - - def __unicode__(self): - return smart_unicode(self.name or u'') + return smart_text(self.name or '') def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self or "None") - def __nonzero__(self): + def __bool__(self): return bool(self.name) + __nonzero__ = __bool__ # Python 2 def __len__(self): return self.size @@ -62,8 +64,10 @@ class File(FileProxyMixin): if not chunk_size: chunk_size = self.DEFAULT_CHUNK_SIZE - if hasattr(self, 'seek'): + try: self.seek(0) + except (AttributeError, UnsupportedOperation): + pass while True: data = self.read(chunk_size) @@ -121,6 +125,7 @@ class File(FileProxyMixin): def close(self): self.file.close() +@python_2_unicode_compatible class ContentFile(File): """ A File-like object that takes just raw content, rather than an actual file. @@ -133,8 +138,9 @@ class ContentFile(File): def __str__(self): return 'Raw content' - def __nonzero__(self): + def __bool__(self): return True + __nonzero__ = __bool__ # Python 2 def open(self, mode=None): self.seek(0) diff --git a/django/core/files/images.py b/django/core/files/images.py index 228a7118c5..7d7eac65db 100644 --- a/django/core/files/images.py +++ b/django/core/files/images.py @@ -47,13 +47,18 @@ def get_image_dimensions(file_or_path, close=False): file = open(file_or_path, 'rb') close = True try: + # Most of the time PIL only needs a small chunk to parse the image and + # get the dimensions, but with some TIFF files PIL needs to parse the + # whole file. + chunk_size = 1024 while 1: - data = file.read(1024) + data = file.read(chunk_size) if not data: break p.feed(data) if p.image: return p.image.size + chunk_size = chunk_size*2 return None finally: if close: diff --git a/django/core/files/move.py b/django/core/files/move.py index f9060fd3d8..3af02634fe 100644 --- a/django/core/files/move.py +++ b/django/core/files/move.py @@ -66,7 +66,7 @@ def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_ove try: locks.lock(fd, locks.LOCK_EX) current_chunk = None - while current_chunk != '': + while current_chunk != b'': current_chunk = old_file.read(chunk_size) os.write(fd, current_chunk) finally: diff --git a/django/core/files/storage.py b/django/core/files/storage.py index ba88674dbd..7542dcda46 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -1,6 +1,9 @@ import os import errno -import urlparse +try: + from urllib.parse import urljoin +except ImportError: # Python 2 + from urlparse import urljoin import itertools from datetime import datetime @@ -8,7 +11,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.core.files import locks, File from django.core.files.move import file_move_safe -from django.utils.encoding import force_unicode, filepath_to_uri +from django.utils.encoding import force_text, filepath_to_uri from django.utils.functional import LazyObject from django.utils.importlib import import_module from django.utils.text import get_valid_filename @@ -45,7 +48,7 @@ class Storage(object): name = self._save(name, content) # Store filenames with forward slashes, even on Windows - return force_unicode(name.replace('\\', '/')) + return force_text(name.replace('\\', '/')) # These methods are part of the public API, with default implementations. @@ -252,7 +255,7 @@ class FileSystemStorage(Storage): def url(self, name): if self.base_url is None: raise ValueError("This file is not accessible via a URL.") - return urlparse.urljoin(self.base_url, filepath_to_uri(name)) + return urljoin(self.base_url, filepath_to_uri(name)) def accessed_time(self, name): return datetime.fromtimestamp(os.path.getatime(self.path(name))) diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py index 88f78904bb..c422945d6f 100644 --- a/django/core/files/uploadhandler.py +++ b/django/core/files/uploadhandler.py @@ -2,12 +2,15 @@ Base file upload handler classes, and the built-in concrete subclasses """ +from __future__ import unicode_literals + from io import BytesIO from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile from django.utils import importlib +from django.utils.encoding import python_2_unicode_compatible __all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler', 'TemporaryFileUploadHandler', 'MemoryFileUploadHandler', @@ -19,6 +22,7 @@ class UploadFileException(Exception): """ pass +@python_2_unicode_compatible class StopUpload(UploadFileException): """ This exception is raised when an upload must abort. @@ -31,11 +35,11 @@ class StopUpload(UploadFileException): """ self.connection_reset = connection_reset - def __unicode__(self): + def __str__(self): if self.connection_reset: - return u'StopUpload: Halt current upload.' + return 'StopUpload: Halt current upload.' else: - return u'StopUpload: Consume request data, then halt.' + return 'StopUpload: Consume request data, then halt.' class SkipFile(UploadFileException): """ diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 4c9dfc07bc..791382bac0 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -1,10 +1,14 @@ +from __future__ import unicode_literals + import sys +import types from django import http from django.core import signals -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.importlib import import_module from django.utils.log import getLogger +from django.utils import six logger = getLogger('django.request') @@ -122,10 +126,10 @@ class BaseHandler(object): # Complain if the view returned None (a common error). if response is None: - try: - view_name = callback.func_name # If it's a function - except AttributeError: - view_name = callback.__class__.__name__ + '.__call__' # If it's a class + if isinstance(callback, types.FunctionType): # FBV + view_name = callback.__name__ + else: # CBV + view_name = callback.__class__.__name__ + '.__call__' raise ValueError("The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)) # If the response supports deferred rendering, apply template @@ -149,10 +153,8 @@ class BaseHandler(object): callback, param_dict = resolver.resolve404() response = callback(request, **param_dict) except: - try: - response = self.handle_uncaught_exception(request, resolver, sys.exc_info()) - finally: - signals.got_request_exception.send(sender=self.__class__, request=request) + signals.got_request_exception.send(sender=self.__class__, request=request) + response = self.handle_uncaught_exception(request, resolver, sys.exc_info()) except exceptions.PermissionDenied: logger.warning( 'Forbidden (Permission denied): %s', request.path, @@ -164,12 +166,10 @@ class BaseHandler(object): callback, param_dict = resolver.resolve403() response = callback(request, **param_dict) except: - try: - response = self.handle_uncaught_exception(request, - resolver, sys.exc_info()) - finally: - signals.got_request_exception.send( + signals.got_request_exception.send( sender=self.__class__, request=request) + response = self.handle_uncaught_exception(request, + resolver, sys.exc_info()) except SystemExit: # Allow sys.exit() to actually exit. See tickets #1023 and #4701 raise @@ -222,7 +222,7 @@ class BaseHandler(object): # If Http500 handler is not installed, re-raise last exception if resolver.urlconf_module is None: - raise exc_info[1], None, exc_info[2] + six.reraise(*exc_info) # Return an HttpResponse that displays a friendly error message. callback, param_dict = resolver.resolve500() return callback(request, **param_dict) @@ -247,16 +247,16 @@ def get_script_name(environ): """ from django.conf import settings if settings.FORCE_SCRIPT_NAME is not None: - return force_unicode(settings.FORCE_SCRIPT_NAME) + return force_text(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 Web server (lighttpd!) passes this # information through all the time, so FORCE_SCRIPT_NAME, above, is still # needed. - script_url = environ.get('SCRIPT_URL', u'') + script_url = environ.get('SCRIPT_URL', '') if not script_url: - script_url = environ.get('REDIRECT_URL', u'') + script_url = environ.get('REDIRECT_URL', '') if script_url: - return force_unicode(script_url[:-len(environ.get('PATH_INFO', ''))]) - return force_unicode(environ.get('SCRIPT_NAME', u'')) + return force_text(script_url[:-len(environ.get('PATH_INFO', ''))]) + return force_text(environ.get('SCRIPT_NAME', '')) diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 128ff7ac64..a0186e552f 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import sys from io import BytesIO from threading import Lock @@ -7,7 +9,7 @@ from django.core import signals from django.core.handlers import base from django.core.urlresolvers import set_script_prefix from django.utils import datastructures -from django.utils.encoding import force_unicode, iri_to_uri +from django.utils.encoding import force_text, smart_str, iri_to_uri from django.utils.log import getLogger logger = getLogger('django.request') @@ -125,7 +127,7 @@ class LimitedStream(object): class WSGIRequest(http.HttpRequest): def __init__(self, environ): script_name = base.get_script_name(environ) - path_info = force_unicode(environ.get('PATH_INFO', u'/')) + path_info = force_text(environ.get('PATH_INFO', '/')) if not path_info or path_info == script_name: # Sometimes PATH_INFO exists, but is empty (e.g. accessing # the SCRIPT_NAME URL without a trailing slash). We really need to @@ -134,7 +136,7 @@ class WSGIRequest(http.HttpRequest): # # (The comparison of path_info to script_name is to work around an # apparent bug in flup 1.0.1. See Django ticket #8490). - path_info = u'/' + path_info = '/' self.environ = environ self.path_info = path_info self.path = '%s%s' % (script_name, path_info) @@ -208,8 +210,7 @@ class WSGIHandler(base.BaseHandler): # Set up middleware if needed. We couldn't do this earlier, because # settings weren't available. if self._request_middleware is None: - self.initLock.acquire() - try: + with self.initLock: try: # Check that middleware is still uninitialised. if self._request_middleware is None: @@ -218,8 +219,6 @@ class WSGIHandler(base.BaseHandler): # Unload whatever middleware we got self._request_middleware = None raise - finally: - self.initLock.release() set_script_prefix(base.get_script_name(environ)) signals.request_started.send(sender=self.__class__) @@ -246,6 +245,6 @@ class WSGIHandler(base.BaseHandler): status = '%s %s' % (response.status_code, status_text) response_headers = [(str(k), str(v)) for k, v in response.items()] for c in response.cookies.values(): - response_headers.append(('Set-Cookie', str(c.output(header='')))) - start_response(status, response_headers) + response_headers.append((str('Set-Cookie'), str(c.output(header='')))) + start_response(smart_str(status), response_headers) return response diff --git a/django/core/mail/__init__.py b/django/core/mail/__init__.py index 1bee0cac9f..08f9702934 100644 --- a/django/core/mail/__init__.py +++ b/django/core/mail/__init__.py @@ -1,6 +1,7 @@ """ Tools for sending email. """ +from __future__ import unicode_literals from django.conf import settings from django.core.exceptions import ImproperlyConfigured @@ -89,7 +90,7 @@ def mail_admins(subject, message, fail_silently=False, connection=None, """Sends a message to the admins, as defined by the ADMINS setting.""" if not settings.ADMINS: return - mail = EmailMultiAlternatives(u'%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), + mail = EmailMultiAlternatives('%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), message, settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS], connection=connection) if html_message: @@ -102,7 +103,7 @@ def mail_managers(subject, message, fail_silently=False, connection=None, """Sends a message to the managers, as defined by the MANAGERS setting.""" if not settings.MANAGERS: return - mail = EmailMultiAlternatives(u'%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), + mail = EmailMultiAlternatives('%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), message, settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS], connection=connection) if html_message: diff --git a/django/core/mail/backends/console.py b/django/core/mail/backends/console.py index 705497520a..0baae0c01e 100644 --- a/django/core/mail/backends/console.py +++ b/django/core/mail/backends/console.py @@ -16,19 +16,17 @@ class EmailBackend(BaseEmailBackend): """Write all messages to the stream in a thread-safe way.""" if not email_messages: return - self._lock.acquire() - try: - stream_created = self.open() - for message in email_messages: - self.stream.write('%s\n' % message.message().as_string()) - self.stream.write('-'*79) - self.stream.write('\n') - self.stream.flush() # flush after each message - if stream_created: - self.close() - except: - if not self.fail_silently: - raise - finally: - self._lock.release() + with self._lock: + try: + stream_created = self.open() + for message in email_messages: + self.stream.write('%s\n' % message.message().as_string()) + self.stream.write('-'*79) + self.stream.write('\n') + self.stream.flush() # flush after each message + if stream_created: + self.close() + except: + if not self.fail_silently: + raise return len(email_messages) diff --git a/django/core/mail/backends/filebased.py b/django/core/mail/backends/filebased.py index 674ca32f3f..4a74c34f1f 100644 --- a/django/core/mail/backends/filebased.py +++ b/django/core/mail/backends/filebased.py @@ -6,6 +6,7 @@ import os from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.mail.backends.console import EmailBackend as ConsoleEmailBackend +from django.utils import six class EmailBackend(ConsoleEmailBackend): def __init__(self, *args, **kwargs): @@ -15,7 +16,7 @@ class EmailBackend(ConsoleEmailBackend): else: self.file_path = getattr(settings, 'EMAIL_FILE_PATH',None) # Make sure self.file_path is a string. - if not isinstance(self.file_path, basestring): + if not isinstance(self.file_path, six.string_types): raise ImproperlyConfigured('Path for saving emails is invalid: %r' % self.file_path) self.file_path = os.path.abspath(self.file_path) # Make sure that self.file_path is an directory if it exists. diff --git a/django/core/mail/backends/smtp.py b/django/core/mail/backends/smtp.py index 3ee283b5f1..18437c6282 100644 --- a/django/core/mail/backends/smtp.py +++ b/django/core/mail/backends/smtp.py @@ -80,8 +80,7 @@ class EmailBackend(BaseEmailBackend): """ if not email_messages: return - self._lock.acquire() - try: + with self._lock: new_conn_created = self.open() if not self.connection: # We failed silently on open(). @@ -94,8 +93,6 @@ class EmailBackend(BaseEmailBackend): num_sent += 1 if new_conn_created: self.close() - finally: - self._lock.release() return num_sent def _send(self, email_message): diff --git a/django/core/mail/message.py b/django/core/mail/message.py index 2618c7f17d..db9023a0bb 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import mimetypes import os import random @@ -9,11 +11,11 @@ from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase from email.header import Header from email.utils import formatdate, getaddresses, formataddr, parseaddr -from io import BytesIO from django.conf import settings from django.core.mail.utils import DNS_NAME -from django.utils.encoding import smart_str, force_unicode +from django.utils.encoding import force_text +from django.utils import six # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from @@ -76,38 +78,43 @@ ADDRESS_HEADERS = set([ def forbid_multi_line_headers(name, val, encoding): """Forbids multi-line headers, to prevent header injection.""" encoding = encoding or settings.DEFAULT_CHARSET - val = force_unicode(val) + val = force_text(val) if '\n' in val or '\r' in val: raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) try: - val = val.encode('ascii') + val.encode('ascii') except UnicodeEncodeError: if name.lower() in ADDRESS_HEADERS: val = ', '.join(sanitize_address(addr, encoding) for addr in getaddresses((val,))) else: - val = str(Header(val, encoding)) + val = Header(val, encoding).encode() else: if name.lower() == 'subject': - val = Header(val) - return name, val + val = Header(val).encode() + return str(name), val def sanitize_address(addr, encoding): - if isinstance(addr, basestring): - addr = parseaddr(force_unicode(addr)) + if isinstance(addr, six.string_types): + addr = parseaddr(force_text(addr)) nm, addr = addr - nm = str(Header(nm, encoding)) + # This try-except clause is needed on Python 3 < 3.2.4 + # http://bugs.python.org/issue14291 try: - addr = addr.encode('ascii') + nm = Header(nm, encoding).encode() + except UnicodeEncodeError: + nm = Header(nm, 'utf-8').encode() + try: + addr.encode('ascii') except UnicodeEncodeError: # IDN - if u'@' in addr: - localpart, domain = addr.split(u'@', 1) + if '@' in addr: + localpart, domain = addr.split('@', 1) localpart = str(Header(localpart, encoding)) - domain = domain.encode('idna') + domain = domain.encode('idna').decode('ascii') addr = '@'.join([localpart, domain]) else: - addr = str(Header(addr, encoding)) + addr = Header(addr, encoding).encode() return formataddr((nm, addr)) @@ -129,7 +136,7 @@ class SafeMIMEText(MIMEText): This overrides the default as_string() implementation to not mangle lines that begin with 'From '. See bug #13433 for details. """ - fp = BytesIO() + fp = six.StringIO() g = Generator(fp, mangle_from_ = False) g.flatten(self, unixfrom=unixfrom) return fp.getvalue() @@ -153,7 +160,7 @@ class SafeMIMEMultipart(MIMEMultipart): This overrides the default as_string() implementation to not mangle lines that begin with 'From '. See bug #13433 for details. """ - fp = BytesIO() + fp = six.StringIO() g = Generator(fp, mangle_from_ = False) g.flatten(self, unixfrom=unixfrom) return fp.getvalue() @@ -178,17 +185,17 @@ class EmailMessage(object): necessary encoding conversions. """ if to: - assert not isinstance(to, basestring), '"to" argument must be a list or tuple' + assert not isinstance(to, six.string_types), '"to" argument must be a list or tuple' self.to = list(to) else: self.to = [] if cc: - assert not isinstance(cc, basestring), '"cc" argument must be a list or tuple' + assert not isinstance(cc, six.string_types), '"cc" argument must be a list or tuple' self.cc = list(cc) else: self.cc = [] if bcc: - assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple' + assert not isinstance(bcc, six.string_types), '"bcc" argument must be a list or tuple' self.bcc = list(bcc) else: self.bcc = [] @@ -207,8 +214,7 @@ class EmailMessage(object): def message(self): encoding = self.encoding or settings.DEFAULT_CHARSET - msg = SafeMIMEText(smart_str(self.body, encoding), - self.content_subtype, encoding) + msg = SafeMIMEText(self.body, self.content_subtype, encoding) msg = self._create_message(msg) msg['Subject'] = self.subject msg['From'] = self.extra_headers.get('From', self.from_email) @@ -290,7 +296,7 @@ class EmailMessage(object): basetype, subtype = mimetype.split('/', 1) if basetype == 'text': encoding = self.encoding or settings.DEFAULT_CHARSET - attachment = SafeMIMEText(smart_str(content, encoding), subtype, encoding) + attachment = SafeMIMEText(content, subtype, encoding) else: # Encode non-text attachments with base64. attachment = MIMEBase(basetype, subtype) @@ -310,9 +316,11 @@ class EmailMessage(object): attachment = self._create_mime_attachment(content, mimetype) if filename: try: - filename = filename.encode('ascii') + filename.encode('ascii') except UnicodeEncodeError: - filename = ('utf-8', '', filename.encode('utf-8')) + if not six.PY3: + filename = filename.encode('utf-8') + filename = ('utf-8', '', filename) attachment.add_header('Content-Disposition', 'attachment', filename=filename) return attachment diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 0464eb27bb..98f75e0310 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -8,6 +8,7 @@ import warnings from django.core.management.base import BaseCommand, CommandError, handle_default_options from django.core.management.color import color_style from django.utils.importlib import import_module +from django.utils import six # For backwards compatibility: get_version() used to be in this module. from django import get_version @@ -50,14 +51,19 @@ def find_management_module(app_name): # module, we need look for the case where the project name is part # of the app_name but the project directory itself isn't on the path. try: - f, path, descr = imp.find_module(part,path) + f, path, descr = imp.find_module(part, path) except ImportError as e: if os.path.basename(os.getcwd()) != part: raise e + else: + if f: + f.close() while parts: part = parts.pop() f, path, descr = imp.find_module(part, path and [path] or None) + if f: + f.close() return path def load_command_class(app_name, name): @@ -228,7 +234,7 @@ class ManagementUtility(object): "Available subcommands:", ] commands_dict = collections.defaultdict(lambda: []) - for name, app in get_commands().iteritems(): + for name, app in six.iteritems(get_commands()): if app == 'django.core': app = 'django' else: @@ -294,7 +300,7 @@ class ManagementUtility(object): except IndexError: curr = '' - subcommands = get_commands().keys() + ['help'] + subcommands = list(get_commands()) + ['help'] options = [('--help', None)] # subcommand @@ -324,7 +330,7 @@ class ManagementUtility(object): subcommand_cls.option_list] # filter out previously specified options from available options prev_opts = [x.split('=')[0] for x in cwords[1:cword-1]] - options = filter(lambda (x, v): x not in prev_opts, options) + options = [opt for opt in options if opt[0] not in prev_opts] # filter options by current input options = sorted([(k, v) for k, v in options if k.startswith(curr)]) diff --git a/django/core/management/base.py b/django/core/management/base.py index a204f6f0bc..5e630d5207 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -6,7 +6,6 @@ be executed through ``django-admin.py`` or ``manage.py``). import os import sys -from io import BytesIO from optparse import make_option, OptionParser import traceback @@ -14,6 +13,7 @@ import django from django.core.exceptions import ImproperlyConfigured from django.core.management.color import color_style from django.utils.encoding import smart_str +from django.utils.six import StringIO class CommandError(Exception): @@ -273,7 +273,7 @@ class BaseCommand(object): """ from django.core.management.validation import get_validation_errors - s = BytesIO() + s = StringIO() num_errors = get_validation_errors(s, app) if num_errors: s.seek(0) diff --git a/django/core/management/commands/compilemessages.py b/django/core/management/commands/compilemessages.py index fdc3535cf6..b7392b9173 100644 --- a/django/core/management/commands/compilemessages.py +++ b/django/core/management/commands/compilemessages.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import codecs import os import sys @@ -7,7 +9,7 @@ from django.core.management.base import BaseCommand, CommandError def has_bom(fn): with open(fn, 'rb') as f: sample = f.read(4) - return sample[:3] == '\xef\xbb\xbf' or \ + return sample[:3] == b'\xef\xbb\xbf' or \ sample.startswith(codecs.BOM_UTF16_LE) or \ sample.startswith(codecs.BOM_UTF16_BE) diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index bcc47e17c8..411042ee76 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -4,6 +4,8 @@ from django.core.cache.backends.db import BaseDatabaseCache from django.core.management.base import LabelCommand, CommandError from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS from django.db.utils import DatabaseError +from django.utils.encoding import force_text + class Command(LabelCommand): help = "Creates the table needed to use the SQL cache backend." @@ -58,7 +60,7 @@ class Command(LabelCommand): transaction.rollback_unless_managed(using=db) raise CommandError( "Cache table '%s' could not be created.\nThe error was: %s." % - (tablename, e)) + (tablename, force_text(e))) for statement in index_output: curs.execute(statement) transaction.commit_unless_managed(using=db) diff --git a/django/core/management/commands/diffsettings.py b/django/core/management/commands/diffsettings.py index 98b53b405d..aa7395e5ee 100644 --- a/django/core/management/commands/diffsettings.py +++ b/django/core/management/commands/diffsettings.py @@ -22,9 +22,7 @@ class Command(NoArgsCommand): default_settings = module_to_dict(global_settings) output = [] - keys = user_settings.keys() - keys.sort() - for key in keys: + for key in sorted(user_settings.keys()): if key not in default_settings: output.append("%s = %s ###" % (key, user_settings[key])) elif user_settings[key] != default_settings[key]: diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 9059625dec..d3650b1eb8 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -150,11 +150,11 @@ def sort_dependencies(app_list): for field in model._meta.fields: if hasattr(field.rel, 'to'): rel_model = field.rel.to - if hasattr(rel_model, 'natural_key'): + if hasattr(rel_model, 'natural_key') and rel_model != model: deps.append(rel_model) for field in model._meta.many_to_many: rel_model = field.rel.to - if hasattr(rel_model, 'natural_key'): + if hasattr(rel_model, 'natural_key') and rel_model != model: deps.append(rel_model) model_dependencies.append((model, deps)) diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index ce3c6e856b..b8b78434ce 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -7,6 +7,7 @@ from django.core.management.base import NoArgsCommand, CommandError from django.core.management.color import no_style from django.core.management.sql import sql_flush, emit_post_sync_signal from django.utils.importlib import import_module +from django.utils.six.moves import input class Command(NoArgsCommand): @@ -16,6 +17,8 @@ class Command(NoArgsCommand): make_option('--database', action='store', dest='database', default=DEFAULT_DB_ALIAS, help='Nominates a database to flush. ' 'Defaults to the "default" database.'), + make_option('--no-initial-data', action='store_false', dest='load_initial_data', default=True, + help='Tells Django not to load any initial data after database synchronization.'), ) help = ('Returns the database to the state it was in immediately after ' 'syncdb was executed. This means that all data will be removed ' @@ -27,6 +30,8 @@ class Command(NoArgsCommand): connection = connections[db] verbosity = int(options.get('verbosity')) interactive = options.get('interactive') + # 'reset_sequences' is a stealth option + reset_sequences = options.get('reset_sequences', True) self.style = no_style() @@ -38,10 +43,10 @@ class Command(NoArgsCommand): except ImportError: pass - sql_list = sql_flush(self.style, connection, only_django=True) + sql_list = sql_flush(self.style, connection, only_django=True, reset_sequences=reset_sequences) if interactive: - confirm = raw_input("""You have requested a flush of the database. + confirm = input("""You have requested a flush of the database. This will IRREVERSIBLY DESTROY all data currently in the %r database, and return each table to the state it was in after syncdb. Are you sure you want to do this? @@ -79,7 +84,9 @@ The full error: %s""" % (connection.settings_dict['NAME'], e)) # Reinstall the initial_data fixture. kwargs = options.copy() kwargs['database'] = db - call_command('loaddata', 'initial_data', **kwargs) + if options.get('load_initial_data'): + # Reinstall the initial_data fixture. + call_command('loaddata', 'initial_data', **options) else: self.stdout.write("Flush cancelled.\n") diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py index a524e64f65..7c868e4b60 100644 --- a/django/core/management/commands/inspectdb.py +++ b/django/core/management/commands/inspectdb.py @@ -3,6 +3,7 @@ from optparse import make_option from django.core.management.base import NoArgsCommand, CommandError from django.db import connections, DEFAULT_DB_ALIAS +from django.utils import six class Command(NoArgsCommand): help = "Introspects the database tables in the given database and outputs a Django model module." @@ -115,7 +116,7 @@ class Command(NoArgsCommand): if att_name[0].isdigit(): att_name = 'number_%s' % att_name - extra_params['db_column'] = unicode(column_name) + extra_params['db_column'] = six.text_type(column_name) comment_notes.append("Field renamed because it wasn't a " "valid Python identifier.") diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index f44edf7ade..1896e53cee 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import sys import os import gzip @@ -7,11 +9,12 @@ import traceback from django.conf import settings from django.core import serializers -from django.core.management.base import BaseCommand +from django.core.management.base import BaseCommand, CommandError from django.core.management.color import no_style from django.db import (connections, router, transaction, DEFAULT_DB_ALIAS, IntegrityError, DatabaseError) from django.db.models import get_apps +from django.utils.encoding import force_text from itertools import product try: @@ -36,11 +39,10 @@ class Command(BaseCommand): connection = connections[using] if not len(fixture_labels): - self.stderr.write( + raise CommandError( "No database fixture specified. Please provide the path of at " "least one fixture in the command line." ) - return verbosity = int(options.get('verbosity')) show_traceback = options.get('traceback') @@ -126,13 +128,9 @@ class Command(BaseCommand): if verbosity >= 2: self.stdout.write("Loading '%s' fixtures..." % fixture_name) else: - self.stderr.write( + raise CommandError( "Problem installing fixture '%s': %s is not a known serialization format." % (fixture_name, format)) - if commit: - transaction.rollback(using=using) - transaction.leave_transaction_management(using=using) - return if os.path.isabs(fixture_name): fixture_dirs = [fixture_name] @@ -167,12 +165,8 @@ class Command(BaseCommand): else: try: if label_found: - self.stderr.write("Multiple fixtures named '%s' in %s. Aborting." % + raise CommandError("Multiple fixtures named '%s' in %s. Aborting." % (fixture_name, humanize(fixture_dir))) - if commit: - transaction.rollback(using=using) - transaction.leave_transaction_management(using=using) - return fixture_count += 1 objects_in_fixture = 0 @@ -191,13 +185,13 @@ class Command(BaseCommand): try: obj.save(using=using) except (DatabaseError, IntegrityError) as e: - msg = "Could not load %(app_label)s.%(object_name)s(pk=%(pk)s): %(error_msg)s" % { + e.args = ("Could not load %(app_label)s.%(object_name)s(pk=%(pk)s): %(error_msg)s" % { 'app_label': obj.object._meta.app_label, 'object_name': obj.object._meta.object_name, 'pk': obj.object.pk, - 'error_msg': e - } - raise e.__class__, e.__class__(msg), sys.exc_info()[2] + 'error_msg': force_text(e) + },) + raise loaded_object_count += loaded_objects_in_fixture fixture_object_count += objects_in_fixture @@ -208,13 +202,9 @@ class Command(BaseCommand): # If the fixture we loaded contains 0 objects, assume that an # error was encountered during fixture loading. if objects_in_fixture == 0: - self.stderr.write( + raise CommandError( "No fixture data found for '%s'. (File format may be invalid.)" % (fixture_name)) - if commit: - transaction.rollback(using=using) - transaction.leave_transaction_management(using=using) - return # Since we disabled constraint checks, we must manually check for # any invalid keys that might have been added @@ -223,19 +213,13 @@ class Command(BaseCommand): except (SystemExit, KeyboardInterrupt): raise - except Exception: + except Exception as e: if commit: transaction.rollback(using=using) transaction.leave_transaction_management(using=using) - if show_traceback: - traceback.print_exc() - else: - self.stderr.write( - "Problem installing fixture '%s': %s" % - (full_path, ''.join(traceback.format_exception(sys.exc_type, - sys.exc_value, sys.exc_traceback)))) - return - + if not isinstance(e, CommandError): + e.args = ("Problem installing fixture '%s': %s" % (full_path, e),) + raise # If we found even one object in a fixture, we need to reset the # database sequences. diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py index 046ffb48f2..7bdd2472d3 100644 --- a/django/core/management/commands/makemessages.py +++ b/django/core/management/commands/makemessages.py @@ -13,6 +13,7 @@ from django.utils.text import get_text_list from django.utils.jslex import prepare_js_for_gettext plural_forms_re = re.compile(r'^(?P"Plural-Forms.+?\\n")\s*$', re.MULTILINE | re.DOTALL) +STATUS_OK = 0 def handle_extensions(extensions=('html',), ignored=('py',)): """ @@ -43,7 +44,8 @@ def _popen(cmd): Friendly wrapper around Popen for Windows """ p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=os.name != 'nt', universal_newlines=True) - return p.communicate() + output, errors = p.communicate() + return output, errors, p.returncode def walk(root, topdown=True, onerror=None, followlinks=False, ignore_patterns=None, verbosity=0, stdout=sys.stdout): @@ -53,8 +55,7 @@ def walk(root, topdown=True, onerror=None, followlinks=False, if ignore_patterns is None: ignore_patterns = [] dir_suffix = '%s*' % os.sep - norm_patterns = map(lambda p: p.endswith(dir_suffix) - and p[:-len(dir_suffix)] or p, ignore_patterns) + norm_patterns = [p[:-len(dir_suffix)] if p.endswith(dir_suffix) else p for p in ignore_patterns] for dirpath, dirnames, filenames in os.walk(root, topdown, onerror): remove_dirs = [] for dirname in dirnames: @@ -142,7 +143,7 @@ def write_pot_file(potfile, msgs, file, work_file, is_templatized): msgs = '\n'.join(dropwhile(len, msgs.split('\n'))) else: msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8') - with open(potfile, 'ab') as fp: + with open(potfile, 'a') as fp: fp.write(msgs) def process_file(file, dirpath, potfile, domain, verbosity, @@ -198,15 +199,19 @@ def process_file(file, dirpath, potfile, domain, verbosity, (domain, wrap, location, work_file)) else: return - msgs, errors = _popen(cmd) + msgs, errors, status = _popen(cmd) if errors: - if is_templatized: - os.unlink(work_file) - if os.path.exists(potfile): - os.unlink(potfile) - raise CommandError( - "errors happened while running xgettext on %s\n%s" % - (file, errors)) + if status != STATUS_OK: + if is_templatized: + os.unlink(work_file) + if os.path.exists(potfile): + os.unlink(potfile) + raise CommandError( + "errors happened while running xgettext on %s\n%s" % + (file, errors)) + elif verbosity > 0: + # Print warnings + stdout.write(errors) if msgs: write_pot_file(potfile, msgs, orig_file, work_file, is_templatized) if is_templatized: @@ -220,33 +225,45 @@ def write_po_file(pofile, potfile, domain, locale, verbosity, stdout, Uses mguniq, msgmerge, and msgattrib GNU gettext utilities. """ - msgs, errors = _popen('msguniq %s %s --to-code=utf-8 "%s"' % - (wrap, location, potfile)) + msgs, errors, status = _popen('msguniq %s %s --to-code=utf-8 "%s"' % + (wrap, location, potfile)) if errors: - os.unlink(potfile) - raise CommandError("errors happened while running msguniq\n%s" % errors) + if status != STATUS_OK: + os.unlink(potfile) + raise CommandError( + "errors happened while running msguniq\n%s" % errors) + elif verbosity > 0: + stdout.write(errors) + if os.path.exists(pofile): with open(potfile, 'w') as fp: fp.write(msgs) - msgs, errors = _popen('msgmerge %s %s -q "%s" "%s"' % - (wrap, location, pofile, potfile)) + msgs, errors, status = _popen('msgmerge %s %s -q "%s" "%s"' % + (wrap, location, pofile, potfile)) if errors: - os.unlink(potfile) - raise CommandError( - "errors happened while running msgmerge\n%s" % errors) + if status != STATUS_OK: + os.unlink(potfile) + raise CommandError( + "errors happened while running msgmerge\n%s" % errors) + elif verbosity > 0: + stdout.write(errors) elif copy_pforms: msgs = copy_plural_forms(msgs, locale, domain, verbosity, stdout) msgs = msgs.replace( "#. #-#-#-#-# %s.pot (PACKAGE VERSION) #-#-#-#-#\n" % domain, "") - with open(pofile, 'wb') as fp: + with open(pofile, 'w') as fp: fp.write(msgs) os.unlink(potfile) if no_obsolete: - msgs, errors = _popen('msgattrib %s %s -o "%s" --no-obsolete "%s"' % - (wrap, location, pofile, pofile)) + msgs, errors, status = _popen( + 'msgattrib %s %s -o "%s" --no-obsolete "%s"' % + (wrap, location, pofile, pofile)) if errors: - raise CommandError( - "errors happened while running msgattrib\n%s" % errors) + if status != STATUS_OK: + raise CommandError( + "errors happened while running msgattrib\n%s" % errors) + elif verbosity > 0: + stdout.write(errors) def make_messages(locale=None, domain='django', verbosity=1, all=False, extensions=None, symlinks=False, ignore_patterns=None, no_wrap=False, @@ -291,7 +308,10 @@ def make_messages(locale=None, domain='django', verbosity=1, all=False, raise CommandError(message) # We require gettext version 0.15 or newer. - output = _popen('xgettext --version')[0] + output, errors, status = _popen('xgettext --version') + if status != STATUS_OK: + raise CommandError("Error running xgettext. Note that Django " + "internationalization requires GNU gettext 0.15 or newer.") match = re.search(r'(?P\d+)\.(?P\d+)', output) if match: xversion = (int(match.group('major')), int(match.group('minor'))) diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index 26cbd7f005..4e7d1dbbf4 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -2,13 +2,19 @@ import os from django.core.management.base import NoArgsCommand from optparse import make_option + class Command(NoArgsCommand): + shells = ['ipython', 'bpython'] + option_list = NoArgsCommand.option_list + ( make_option('--plain', action='store_true', dest='plain', - help='Tells Django to use plain Python, not IPython.'), + help='Tells Django to use plain Python, not IPython or bpython.'), + make_option('-i', '--interface', action='store', type='choice', choices=shells, + dest='interface', + help='Specify an interactive interpreter interface. Available options: "ipython" and "bpython"'), + ) - help = "Runs a Python interactive interpreter. Tries to use IPython, if it's available." - shells = ['ipython', 'bpython'] + help = "Runs a Python interactive interpreter. Tries to use IPython or bpython, if one of them is available." requires_model_validation = False def ipython(self): @@ -31,8 +37,10 @@ class Command(NoArgsCommand): import bpython bpython.embed() - def run_shell(self): - for shell in self.shells: + def run_shell(self, shell=None): + available_shells = [shell] if shell else self.shells + + for shell in available_shells: try: return getattr(self, shell)() except ImportError: @@ -46,19 +54,21 @@ class Command(NoArgsCommand): get_models() use_plain = options.get('plain', False) + interface = options.get('interface', None) try: if use_plain: # Don't bother loading IPython, because the user wants plain Python. raise ImportError - self.run_shell() + + self.run_shell(shell=interface) except ImportError: import code # Set up a dictionary to serve as the environment for the shell, so # that tab completion works on objects that are imported at runtime. # See ticket 5082. imported_objects = {} - try: # Try activating rlcompleter, because it's handy. + try: # Try activating rlcompleter, because it's handy. import readline except ImportError: pass diff --git a/django/core/management/commands/sql.py b/django/core/management/commands/sql.py index 59b2e77b69..52b2058650 100644 --- a/django/core/management/commands/sql.py +++ b/django/core/management/commands/sql.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -16,4 +18,4 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - return u'\n'.join(sql_create(app, self.style, connections[options.get('database')])).encode('utf-8') + return '\n'.join(sql_create(app, self.style, connections[options.get('database')])) diff --git a/django/core/management/commands/sqlall.py b/django/core/management/commands/sqlall.py index 15966ece66..0e2c05ba82 100644 --- a/django/core/management/commands/sqlall.py +++ b/django/core/management/commands/sqlall.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -17,4 +19,4 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - return u'\n'.join(sql_all(app, self.style, connections[options.get('database')])).encode('utf-8') + return '\n'.join(sql_all(app, self.style, connections[options.get('database')])) diff --git a/django/core/management/commands/sqlclear.py b/django/core/management/commands/sqlclear.py index b8f491392b..ec2602d2a3 100644 --- a/django/core/management/commands/sqlclear.py +++ b/django/core/management/commands/sqlclear.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -16,4 +18,4 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - return u'\n'.join(sql_delete(app, self.style, connections[options.get('database')])).encode('utf-8') + return '\n'.join(sql_delete(app, self.style, connections[options.get('database')])) diff --git a/django/core/management/commands/sqlcustom.py b/django/core/management/commands/sqlcustom.py index 6a984560de..0d46c4ec70 100644 --- a/django/core/management/commands/sqlcustom.py +++ b/django/core/management/commands/sqlcustom.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -16,4 +18,4 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - return u'\n'.join(sql_custom(app, self.style, connections[options.get('database')])).encode('utf-8') + return '\n'.join(sql_custom(app, self.style, connections[options.get('database')])) diff --git a/django/core/management/commands/sqlflush.py b/django/core/management/commands/sqlflush.py index 19054fbfa9..b98ecfd8ff 100644 --- a/django/core/management/commands/sqlflush.py +++ b/django/core/management/commands/sqlflush.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import NoArgsCommand @@ -16,4 +18,4 @@ class Command(NoArgsCommand): output_transaction = True def handle_noargs(self, **options): - return u'\n'.join(sql_flush(self.style, connections[options.get('database')], only_django=True)).encode('utf-8') + return '\n'.join(sql_flush(self.style, connections[options.get('database')], only_django=True)) diff --git a/django/core/management/commands/sqlindexes.py b/django/core/management/commands/sqlindexes.py index bf55b0dffc..f95d4f158c 100644 --- a/django/core/management/commands/sqlindexes.py +++ b/django/core/management/commands/sqlindexes.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -17,4 +19,4 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - return u'\n'.join(sql_indexes(app, self.style, connections[options.get('database')])).encode('utf-8') + return '\n'.join(sql_indexes(app, self.style, connections[options.get('database')])) diff --git a/django/core/management/commands/sqlsequencereset.py b/django/core/management/commands/sqlsequencereset.py index 6460f00062..7b9e85a9ee 100644 --- a/django/core/management/commands/sqlsequencereset.py +++ b/django/core/management/commands/sqlsequencereset.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from optparse import make_option from django.core.management.base import AppCommand @@ -17,4 +19,4 @@ class Command(AppCommand): def handle_app(self, app, **options): connection = connections[options.get('database')] - return u'\n'.join(connection.ops.sequence_reset_sql(self.style, models.get_models(app, include_auto_created=True))).encode('utf-8') + return '\n'.join(connection.ops.sequence_reset_sql(self.style, models.get_models(app, include_auto_created=True))) diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index 973441dc1c..4ce2910fb5 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -2,6 +2,7 @@ from optparse import make_option import traceback from django.conf import settings +from django.core.management import call_command from django.core.management.base import NoArgsCommand from django.core.management.color import no_style from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal @@ -14,6 +15,8 @@ class Command(NoArgsCommand): option_list = NoArgsCommand.option_list + ( make_option('--noinput', action='store_false', dest='interactive', default=True, help='Tells Django to NOT prompt the user for input of any kind.'), + make_option('--no-initial-data', action='store_false', dest='load_initial_data', default=True, + help='Tells Django not to load any initial data after database synchronization.'), make_option('--database', action='store', dest='database', default=DEFAULT_DB_ALIAS, help='Nominates a database to synchronize. ' 'Defaults to the "default" database.'), @@ -25,10 +28,7 @@ class Command(NoArgsCommand): verbosity = int(options.get('verbosity')) interactive = options.get('interactive') show_traceback = options.get('traceback') - - # Stealth option -- 'load_initial_data' is used by the testing setup - # process to disable initial fixture loading. - load_initial_data = options.get('load_initial_data', True) + load_initial_data = options.get('load_initial_data') self.style = no_style() @@ -76,7 +76,7 @@ class Command(NoArgsCommand): (opts.auto_created and converter(opts.auto_created._meta.db_table) in tables)) manifest = SortedDict( - (app_name, filter(model_installed, model_list)) + (app_name, list(filter(model_installed, model_list))) for app_name, model_list in all_models ) @@ -159,6 +159,5 @@ class Command(NoArgsCommand): # Load initial_data fixtures (unless that has been disabled) if load_initial_data: - from django.core.management import call_command call_command('loaddata', 'initial_data', verbosity=verbosity, database=db, skip_validation=True) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index c10c100214..ac16a5b358 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -1,3 +1,6 @@ +from __future__ import unicode_literals + +import codecs import os import re @@ -99,7 +102,7 @@ def sql_delete(app, style, connection): return output[::-1] # Reverse it, to deal with table dependencies. -def sql_flush(style, connection, only_django=False): +def sql_flush(style, connection, only_django=False, reset_sequences=True): """ Returns a list of the SQL statements used to flush the database. @@ -110,9 +113,8 @@ def sql_flush(style, connection, only_django=False): tables = connection.introspection.django_table_names(only_existing=True) else: tables = connection.introspection.table_names() - statements = connection.ops.sql_flush( - style, tables, connection.introspection.sequence_list() - ) + seqs = connection.introspection.sequence_list() if reset_sequences else () + statements = connection.ops.sql_flush(style, tables, seqs) return statements @@ -141,6 +143,21 @@ def sql_all(app, style, connection): return sql_create(app, style, connection) + sql_custom(app, style, connection) + sql_indexes(app, style, connection) +def _split_statements(content): + comment_re = re.compile(r"^((?:'[^']*'|[^'])*?)--.*$") + statements = [] + statement = "" + for line in content.split("\n"): + cleaned_line = comment_re.sub(r"\1", line).strip() + if not cleaned_line: + continue + statement += cleaned_line + if statement.endswith(";"): + statements.append(statement) + statement = "" + return statements + + def custom_sql_for_model(model, style, connection): opts = model._meta app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql')) @@ -154,23 +171,16 @@ def custom_sql_for_model(model, style, connection): for f in post_sql_fields: output.extend(f.post_create_sql(style, model._meta.db_table)) - # Some backends can't execute more than one SQL statement at a time, - # so split into separate statements. - statements = re.compile(r";[ \t]*$", re.M) - # Find custom SQL, if it's available. backend_name = connection.settings_dict['ENGINE'].split('.')[-1] sql_files = [os.path.join(app_dir, "%s.%s.sql" % (opts.object_name.lower(), backend_name)), os.path.join(app_dir, "%s.sql" % opts.object_name.lower())] for sql_file in sql_files: if os.path.exists(sql_file): - with open(sql_file, 'U') as fp: - for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)): - # Remove any comments from the file - statement = re.sub(ur"--.*([\n\Z]|$)", "", statement) - if statement.strip(): - output.append(statement + u";") - + with codecs.open(sql_file, 'U', encoding=settings.FILE_CHARSET) as fp: + # Some backends can't execute more than one SQL statement at a time, + # so split into separate statements. + output.extend(_split_statements(fp.read())) return output diff --git a/django/core/management/templates.py b/django/core/management/templates.py index 623aa69deb..52d0e5c89d 100644 --- a/django/core/management/templates.py +++ b/django/core/management/templates.py @@ -8,7 +8,10 @@ import shutil import stat import sys import tempfile -import urllib +try: + from urllib.request import urlretrieve +except ImportError: # Python 2 + from urllib import urlretrieve from optparse import make_option from os import path @@ -112,7 +115,7 @@ class TemplateCommand(BaseCommand): context = Context(dict(options, **{ base_name: name, base_directory: top_dir, - })) + }), autoescape=False) # Setup a stub settings environment for template rendering from django.conf import settings @@ -227,8 +230,7 @@ class TemplateCommand(BaseCommand): if self.verbosity >= 2: self.stdout.write("Downloading %s\n" % display_url) try: - the_path, info = urllib.urlretrieve(url, - path.join(tempdir, filename)) + the_path, info = urlretrieve(url, path.join(tempdir, filename)) except IOError as e: raise CommandError("couldn't download URL %s to %s: %s" % (url, filename, e)) diff --git a/django/core/management/validation.py b/django/core/management/validation.py index f613009e98..c1cd3d8776 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -1,7 +1,9 @@ import sys from django.core.management.color import color_style +from django.utils.encoding import smart_str from django.utils.itercompat import is_iterable +from django.utils import six class ModelErrorCollection: @@ -12,7 +14,7 @@ class ModelErrorCollection: def add(self, context, error): self.errors.append((context, error)) - self.outfile.write(self.style.ERROR("%s: %s\n" % (context, error))) + self.outfile.write(self.style.ERROR(smart_str("%s: %s\n" % (context, error)))) def get_validation_errors(outfile, app=None): @@ -94,7 +96,7 @@ def get_validation_errors(outfile, app=None): if isinstance(f, models.FilePathField) and not (f.allow_files or f.allow_folders): e.add(opts, '"%s": FilePathFields must have either allow_files or allow_folders set to True.' % f.name) if f.choices: - if isinstance(f.choices, basestring) or not is_iterable(f.choices): + if isinstance(f.choices, six.string_types) or not is_iterable(f.choices): e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name) else: for c in f.choices: @@ -120,7 +122,7 @@ def get_validation_errors(outfile, app=None): e.add(opts, "'%s' has a relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to)) # it is a string and we could not find the model it refers to # so skip the next section - if isinstance(f.rel.to, (str, unicode)): + if isinstance(f.rel.to, six.string_types): continue # Make sure the model we're related hasn't been swapped out @@ -166,7 +168,7 @@ def get_validation_errors(outfile, app=None): e.add(opts, "'%s' has an m2m relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to)) # it is a string and we could not find the model it refers to # so skip the next section - if isinstance(f.rel.to, (str, unicode)): + if isinstance(f.rel.to, six.string_types): continue # Make sure the model we're related hasn't been swapped out @@ -177,7 +179,7 @@ def get_validation_errors(outfile, app=None): if f.unique: e.add(opts, "ManyToManyFields cannot be unique. Remove the unique argument on '%s'." % f.name) - if f.rel.through is not None and not isinstance(f.rel.through, basestring): + if f.rel.through is not None and not isinstance(f.rel.through, six.string_types): from_model, to_model = cls, f.rel.to if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.auto_created: e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.") @@ -248,7 +250,7 @@ def get_validation_errors(outfile, app=None): "to %s and %s" % (f.name, f.rel.through._meta.object_name, f.rel.to._meta.object_name, cls._meta.object_name) ) - elif isinstance(f.rel.through, basestring): + elif isinstance(f.rel.through, six.string_types): e.add(opts, "'%s' specifies an m2m relation through model %s, " "which has not been installed" % (f.name, f.rel.through) ) diff --git a/django/core/paginator.py b/django/core/paginator.py index 8b4d2891b2..6b0b3542f8 100644 --- a/django/core/paginator.py +++ b/django/core/paginator.py @@ -132,10 +132,10 @@ class Page(object): return self.has_previous() or self.has_next() def next_page_number(self): - return self.number + 1 + return self.paginator.validate_number(self.number + 1) def previous_page_number(self): - return self.number - 1 + return self.paginator.validate_number(self.number - 1) def start_index(self): """ diff --git a/django/core/serializers/__init__.py b/django/core/serializers/__init__.py index 09bcb651d5..cf7e66190f 100644 --- a/django/core/serializers/__init__.py +++ b/django/core/serializers/__init__.py @@ -18,6 +18,7 @@ To add your own serializers, use the SERIALIZATION_MODULES setting:: from django.conf import settings from django.utils import importlib +from django.utils import six from django.core.serializers.base import SerializerDoesNotExist # Built-in serializers @@ -75,12 +76,12 @@ def get_serializer(format): def get_serializer_formats(): if not _serializers: _load_serializers() - return _serializers.keys() + return list(_serializers) def get_public_serializer_formats(): if not _serializers: _load_serializers() - return [k for k, v in _serializers.iteritems() if not v.Serializer.internal_use_only] + return [k for k, v in six.iteritems(_serializers) if not v.Serializer.internal_use_only] def get_deserializer(format): if not _serializers: diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index 04053c1f8f..276f9a4738 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -2,10 +2,9 @@ Module for abstract serializer/unserializer base classes. """ -from io import BytesIO - from django.db import models -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text +from django.utils import six class SerializerDoesNotExist(KeyError): """The requested serializer was not found.""" @@ -34,7 +33,7 @@ class Serializer(object): """ self.options = options - self.stream = options.pop("stream", BytesIO()) + self.stream = options.pop("stream", six.StringIO()) self.selected_fields = options.pop("fields", None) self.use_natural_keys = options.pop("use_natural_keys", False) @@ -123,8 +122,8 @@ class Deserializer(object): Init this serializer given a stream or a string """ self.options = options - if isinstance(stream_or_string, basestring): - self.stream = BytesIO(stream_or_string) + if isinstance(stream_or_string, six.string_types): + self.stream = six.StringIO(stream_or_string) else: self.stream = stream_or_string # hack to make sure that the models have all been loaded before @@ -135,10 +134,12 @@ class Deserializer(object): def __iter__(self): return self - def next(self): + def __next__(self): """Iteration iterface -- return the next item in the stream""" raise NotImplementedError + next = __next__ # Python 2 compatibility + class DeserializedObject(object): """ A deserialized model. diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py index fce00600f4..4ba0d7fd79 100644 --- a/django/core/serializers/json.py +++ b/django/core/serializers/json.py @@ -8,11 +8,12 @@ from __future__ import absolute_import import datetime import decimal import json -from io import BytesIO from django.core.serializers.base import DeserializationError from django.core.serializers.python import Serializer as PythonSerializer from django.core.serializers.python import Deserializer as PythonDeserializer +from django.utils.encoding import smart_bytes +from django.utils import six from django.utils.timezone import is_aware class Serializer(PythonSerializer): @@ -52,21 +53,21 @@ class Serializer(PythonSerializer): self._current = None def getvalue(self): - # overwrite PythonSerializer.getvalue() with base Serializer.getvalue() - if callable(getattr(self.stream, 'getvalue', None)): - return self.stream.getvalue() + # Grand-parent super + return super(PythonSerializer, self).getvalue() def Deserializer(stream_or_string, **options): """ Deserialize a stream or string of JSON data. """ - if isinstance(stream_or_string, basestring): - stream = BytesIO(stream_or_string) - else: - stream = stream_or_string + if not isinstance(stream_or_string, (bytes, six.string_types)): + stream_or_string = stream_or_string.read() + if isinstance(stream_or_string, bytes): + stream_or_string = stream_or_string.decode('utf-8') try: - for obj in PythonDeserializer(json.load(stream), **options): + objects = json.loads(stream_or_string) + for obj in PythonDeserializer(objects, **options): yield obj except GeneratorExit: raise diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 49120434eb..a1fff6f9bb 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -3,11 +3,13 @@ A Python "serializer". Doesn't do much serializing per se -- just converts to and from basic Python data types (lists, dicts, strings, etc.). Useful as a basis for other serializers. """ +from __future__ import unicode_literals from django.conf import settings from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS -from django.utils.encoding import smart_unicode, is_protected_type +from django.utils.encoding import smart_text, is_protected_type +from django.utils import six class Serializer(base.Serializer): """ @@ -32,8 +34,8 @@ class Serializer(base.Serializer): def get_dump_object(self, obj): return { - "pk": smart_unicode(obj._get_pk_val(), strings_only=True), - "model": smart_unicode(obj._meta), + "pk": smart_text(obj._get_pk_val(), strings_only=True), + "model": smart_text(obj._meta), "fields": self._current } @@ -63,7 +65,7 @@ class Serializer(base.Serializer): if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'): m2m_value = lambda value: value.natural_key() else: - m2m_value = lambda value: smart_unicode(value._get_pk_val(), strings_only=True) + m2m_value = lambda value: smart_text(value._get_pk_val(), strings_only=True) self._current[field.name] = [m2m_value(related) for related in getattr(obj, field.name).iterator()] @@ -86,9 +88,9 @@ def Deserializer(object_list, **options): m2m_data = {} # Handle each field - for (field_name, field_value) in d["fields"].iteritems(): + for (field_name, field_value) in six.iteritems(d["fields"]): if isinstance(field_value, str): - field_value = smart_unicode(field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True) + field_value = smart_text(field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True) field = Model._meta.get_field(field_name) @@ -96,19 +98,19 @@ def Deserializer(object_list, **options): if field.rel and isinstance(field.rel, models.ManyToManyRel): if hasattr(field.rel.to._default_manager, 'get_by_natural_key'): def m2m_convert(value): - if hasattr(value, '__iter__'): + if hasattr(value, '__iter__') and not isinstance(value, six.text_type): return field.rel.to._default_manager.db_manager(db).get_by_natural_key(*value).pk else: - return smart_unicode(field.rel.to._meta.pk.to_python(value)) + return smart_text(field.rel.to._meta.pk.to_python(value)) else: - m2m_convert = lambda v: smart_unicode(field.rel.to._meta.pk.to_python(v)) + m2m_convert = lambda v: smart_text(field.rel.to._meta.pk.to_python(v)) m2m_data[field.name] = [m2m_convert(pk) for pk in field_value] # Handle FK fields elif field.rel and isinstance(field.rel, models.ManyToOneRel): if field_value is not None: if hasattr(field.rel.to._default_manager, 'get_by_natural_key'): - if hasattr(field_value, '__iter__'): + if hasattr(field_value, '__iter__') and not isinstance(field_value, six.text_type): obj = field.rel.to._default_manager.db_manager(db).get_by_natural_key(*field_value) value = getattr(obj, field.rel.field_name) # If this is a natural foreign key to an object that @@ -138,5 +140,5 @@ def _get_model(model_identifier): except TypeError: Model = None if Model is None: - raise base.DeserializationError(u"Invalid model identifier: '%s'" % model_identifier) + raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier) return Model diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py index 5effda5b46..9be1ea4492 100644 --- a/django/core/serializers/pyyaml.py +++ b/django/core/serializers/pyyaml.py @@ -6,12 +6,15 @@ Requires PyYaml (http://pyyaml.org/), but that's checked for in __init__. import decimal import yaml -from io import BytesIO +from io import StringIO from django.db import models from django.core.serializers.base import DeserializationError from django.core.serializers.python import Serializer as PythonSerializer from django.core.serializers.python import Deserializer as PythonDeserializer +from django.utils.encoding import smart_bytes +from django.utils import six + class DjangoSafeDumper(yaml.SafeDumper): def represent_decimal(self, data): @@ -42,14 +45,17 @@ class Serializer(PythonSerializer): yaml.dump(self.objects, self.stream, Dumper=DjangoSafeDumper, **self.options) def getvalue(self): - return self.stream.getvalue() + # Grand-parent super + return super(PythonSerializer, self).getvalue() def Deserializer(stream_or_string, **options): """ Deserialize a stream or string of YAML data. """ - if isinstance(stream_or_string, basestring): - stream = BytesIO(stream_or_string) + if isinstance(stream_or_string, bytes): + stream_or_string = stream_or_string.decode('utf-8') + if isinstance(stream_or_string, six.string_types): + stream = StringIO(stream_or_string) else: stream = stream_or_string try: diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index a5edeac5af..666587dc77 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -2,11 +2,13 @@ XML serializer. """ +from __future__ import unicode_literals + from django.conf import settings from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS from django.utils.xmlutils import SimplerXMLGenerator -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from xml.dom import pulldom class Serializer(base.Serializer): @@ -44,11 +46,11 @@ class Serializer(base.Serializer): self.indent(1) obj_pk = obj._get_pk_val() if obj_pk is None: - attrs = {"model": smart_unicode(obj._meta),} + attrs = {"model": smart_text(obj._meta),} else: attrs = { - "pk": smart_unicode(obj._get_pk_val()), - "model": smart_unicode(obj._meta), + "pk": smart_text(obj._get_pk_val()), + "model": smart_text(obj._meta), } self.xml.startElement("object", attrs) @@ -94,10 +96,10 @@ class Serializer(base.Serializer): # Iterable natural keys are rolled out as subelements for key_value in related: self.xml.startElement("natural", {}) - self.xml.characters(smart_unicode(key_value)) + self.xml.characters(smart_text(key_value)) self.xml.endElement("natural") else: - self.xml.characters(smart_unicode(related_att)) + self.xml.characters(smart_text(related_att)) else: self.xml.addQuickElement("None") self.xml.endElement("field") @@ -118,13 +120,13 @@ class Serializer(base.Serializer): self.xml.startElement("object", {}) for key_value in natural: self.xml.startElement("natural", {}) - self.xml.characters(smart_unicode(key_value)) + self.xml.characters(smart_text(key_value)) self.xml.endElement("natural") self.xml.endElement("object") else: def handle_m2m(value): self.xml.addQuickElement("object", attrs={ - 'pk' : smart_unicode(value._get_pk_val()) + 'pk' : smart_text(value._get_pk_val()) }) for relobj in getattr(obj, field.name).iterator(): handle_m2m(relobj) @@ -139,7 +141,7 @@ class Serializer(base.Serializer): self.xml.startElement("field", { "name" : field.name, "rel" : field.rel.__class__.__name__, - "to" : smart_unicode(field.rel.to._meta), + "to" : smart_text(field.rel.to._meta), }) class Deserializer(base.Deserializer): @@ -152,13 +154,15 @@ class Deserializer(base.Deserializer): self.event_stream = pulldom.parse(self.stream) self.db = options.pop('using', DEFAULT_DB_ALIAS) - def next(self): + def __next__(self): for event, node in self.event_stream: if event == "START_ELEMENT" and node.nodeName == "object": self.event_stream.expandNode(node) return self._handle_object(node) raise StopIteration + next = __next__ # Python 2 compatibility + def _handle_object(self, node): """ Convert an node to a DeserializedObject. @@ -289,4 +293,4 @@ def getInnerText(node): inner_text.extend(getInnerText(child)) else: pass - return u"".join(inner_text) + return "".join(inner_text) diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index be2962d660..19b287af87 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -7,13 +7,18 @@ This is a simple server for use in testing or debugging Django apps. It hasn't been reviewed for security issues. DON'T USE IT FOR PRODUCTION USE! """ +from __future__ import unicode_literals + import os import socket import sys import traceback -import urllib -import urlparse -from SocketServer import ThreadingMixIn +try: + from urllib.parse import unquote, urljoin +except ImportError: # Python 2 + from urllib import unquote + from urlparse import urljoin +from django.utils.six.moves import socketserver from wsgiref import simple_server from wsgiref.util import FileWrapper # for backwards compatibility @@ -68,12 +73,12 @@ class WSGIServerException(Exception): class ServerHandler(simple_server.ServerHandler, object): - error_status = "500 INTERNAL SERVER ERROR" + error_status = str("500 INTERNAL SERVER ERROR") def write(self, data): - """'write()' callable as specified by PEP 333""" + """'write()' callable as specified by PEP 3333""" - assert isinstance(data, str), "write() argument must be string" + assert isinstance(data, bytes), "write() argument must be bytestring" if not self.status: raise AssertionError("write() before start_response()") @@ -127,7 +132,7 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object): def __init__(self, *args, **kwargs): from django.conf import settings - self.admin_static_prefix = urlparse.urljoin(settings.STATIC_URL, 'admin/') + self.admin_static_prefix = urljoin(settings.STATIC_URL, 'admin/') # We set self.path to avoid crashes in log_message() on unsupported # requests (like "OPTIONS"). self.path = '' @@ -143,7 +148,7 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object): else: path,query = self.path,'' - env['PATH_INFO'] = urllib.unquote(path) + env['PATH_INFO'] = unquote(path) env['QUERY_STRING'] = query env['REMOTE_ADDR'] = self.client_address[0] env['CONTENT_TYPE'] = self.headers.get('content-type', 'text/plain') @@ -197,7 +202,7 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object): def run(addr, port, wsgi_handler, ipv6=False, threading=False): server_address = (addr, port) if threading: - httpd_cls = type('WSGIServer', (ThreadingMixIn, WSGIServer), {}) + httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {}) else: httpd_cls = WSGIServer httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6) diff --git a/django/core/signing.py b/django/core/signing.py index cd9759e536..6fc76bc201 100644 --- a/django/core/signing.py +++ b/django/core/signing.py @@ -32,6 +32,8 @@ start of the base64 JSON. There are 65 url-safe characters: the 64 used by url-safe base64 and the ':'. These functions make use of all of them. """ +from __future__ import unicode_literals + import base64 import json import time @@ -41,7 +43,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.utils import baseconv from django.utils.crypto import constant_time_compare, salted_hmac -from django.utils.encoding import force_unicode, smart_str +from django.utils.encoding import smart_bytes from django.utils.importlib import import_module @@ -60,12 +62,12 @@ class SignatureExpired(BadSignature): def b64_encode(s): - return base64.urlsafe_b64encode(s).strip('=') + return base64.urlsafe_b64encode(smart_bytes(s)).decode('ascii').strip('=') def b64_decode(s): pad = '=' * (-len(s) % 4) - return base64.urlsafe_b64decode(s + pad) + return base64.urlsafe_b64decode(smart_bytes(s + pad)).decode('ascii') def base64_hmac(salt, value, key): @@ -121,7 +123,7 @@ def dumps(obj, key=None, salt='django.core.signing', serializer=JSONSerializer, if compress: # Avoid zlib dependency unless compress is being used - compressed = zlib.compress(data) + compressed = zlib.compress(smart_bytes(data)) if len(compressed) < (len(data) - 1): data = compressed is_compressed = True @@ -135,8 +137,7 @@ def loads(s, key=None, salt='django.core.signing', serializer=JSONSerializer, ma """ Reverse of dumps(), raises BadSignature if signature fails """ - base64d = smart_str( - TimestampSigner(key, salt=salt).unsign(s, max_age=max_age)) + base64d = TimestampSigner(key, salt=salt).unsign(s, max_age=max_age) decompress = False if base64d[0] == '.': # It's compressed; uncompress it first @@ -159,16 +160,14 @@ class Signer(object): return base64_hmac(self.salt + 'signer', value, self.key) def sign(self, value): - value = smart_str(value) return '%s%s%s' % (value, self.sep, self.signature(value)) def unsign(self, signed_value): - signed_value = smart_str(signed_value) if not self.sep in signed_value: raise BadSignature('No "%s" found in value' % self.sep) value, sig = signed_value.rsplit(self.sep, 1) if constant_time_compare(sig, self.signature(value)): - return force_unicode(value) + return value raise BadSignature('Signature "%s" does not match' % sig) @@ -178,7 +177,7 @@ class TimestampSigner(Signer): return baseconv.base62.encode(int(time.time())) def sign(self, value): - value = smart_str('%s%s%s' % (value, self.sep, self.timestamp())) + value = '%s%s%s' % (value, self.sep, self.timestamp()) return '%s%s%s' % (value, self.sep, self.signature(value)) def unsign(self, value, max_age=None): diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 78ce00511c..b83f2f51b0 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -6,6 +6,7 @@ a string) and returns a tuple in this format: (view_function, function_args, function_kwargs) """ +from __future__ import unicode_literals import re from threading import local @@ -13,11 +14,12 @@ from threading import local from django.http import Http404 from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.utils.datastructures import MultiValueDict -from django.utils.encoding import iri_to_uri, force_unicode, smart_str +from django.utils.encoding import iri_to_uri, force_text, smart_str from django.utils.functional import memoize, lazy from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule from django.utils.regex_helper import normalize +from django.utils import six from django.utils.translation import get_language @@ -87,18 +89,11 @@ def get_callable(lookup_view, can_fail=False): """ if not callable(lookup_view): mod_name, func_name = get_mod_func(lookup_view) + if func_name == '': + return lookup_view + try: - if func_name != '': - lookup_view = getattr(import_module(mod_name), func_name) - if not callable(lookup_view): - raise ViewDoesNotExist( - "Could not import %s.%s. View is not callable." % - (mod_name, func_name)) - except AttributeError: - if not can_fail: - raise ViewDoesNotExist( - "Could not import %s. View does not exist in module %s." % - (lookup_view, mod_name)) + mod = import_module(mod_name) except ImportError: parentmod, submod = get_mod_func(mod_name) if (not can_fail and submod != '' and @@ -108,6 +103,18 @@ def get_callable(lookup_view, can_fail=False): (lookup_view, mod_name)) if not can_fail: raise + else: + try: + lookup_view = getattr(mod, func_name) + if not callable(lookup_view): + raise ViewDoesNotExist( + "Could not import %s.%s. View is not callable." % + (mod_name, func_name)) + except AttributeError: + if not can_fail: + raise ViewDoesNotExist( + "Could not import %s. View does not exist in module %s." % + (lookup_view, mod_name)) return lookup_view get_callable = memoize(get_callable, _callable_cache, 1) @@ -158,11 +165,17 @@ class LocaleRegexProvider(object): """ language_code = get_language() if language_code not in self._regex_dict: - if isinstance(self._regex, basestring): - compiled_regex = re.compile(self._regex, re.UNICODE) + if isinstance(self._regex, six.string_types): + regex = self._regex else: - regex = force_unicode(self._regex) + regex = force_text(self._regex) + try: compiled_regex = re.compile(regex, re.UNICODE) + except re.error as e: + raise ImproperlyConfigured( + '"%s" is not a valid regular expression: %s' % + (regex, six.text_type(e))) + self._regex_dict[language_code] = compiled_regex return self._regex_dict[language_code] @@ -182,7 +195,7 @@ class RegexURLPattern(LocaleRegexProvider): self.name = name def __repr__(self): - return smart_str(u'<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)) + return smart_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)) def add_prefix(self, prefix): """ @@ -221,7 +234,7 @@ class RegexURLResolver(LocaleRegexProvider): LocaleRegexProvider.__init__(self, regex) # urlconf_name is a string representing the module containing URLconfs. self.urlconf_name = urlconf_name - if not isinstance(urlconf_name, basestring): + if not isinstance(urlconf_name, six.string_types): self._urlconf_module = self.urlconf_name self.callback = None self.default_kwargs = default_kwargs or {} @@ -232,7 +245,9 @@ class RegexURLResolver(LocaleRegexProvider): self._app_dict = {} def __repr__(self): - return smart_str(u'<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)) + return smart_str('<%s %s (%s:%s) %s>' % ( + self.__class__.__name__, self.urlconf_name, self.app_name, + self.namespace, self.regex.pattern)) def _populate(self): lookups = MultiValueDict() @@ -365,10 +380,10 @@ class RegexURLResolver(LocaleRegexProvider): if args: if len(args) != len(params) + len(prefix_args): continue - unicode_args = [force_unicode(val) for val in args] + unicode_args = [force_text(val) for val in args] candidate = (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args)) else: - if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys() + prefix_args): + if set(kwargs.keys()) | set(defaults.keys()) != set(params) | set(defaults.keys()) | set(prefix_args): continue matches = True for k, v in defaults.items(): @@ -377,9 +392,9 @@ class RegexURLResolver(LocaleRegexProvider): break if not matches: continue - unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()]) + unicode_kwargs = dict([(k, force_text(v)) for (k, v) in kwargs.items()]) candidate = (prefix_norm + result) % unicode_kwargs - if re.search(u'^%s%s' % (_prefix, pattern), candidate, re.UNICODE): + if re.search('^%s%s' % (_prefix, pattern), candidate, re.UNICODE): return candidate # lookup_view can be URL label, or dotted path, or callable, Any of # these can be passed in at the top, but callables are not friendly in @@ -427,7 +442,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current if prefix is None: prefix = get_script_prefix() - if not isinstance(viewname, basestring): + if not isinstance(viewname, six.string_types): view = viewname else: parts = viewname.split(':') @@ -497,7 +512,7 @@ def get_script_prefix(): wishes to construct their own URLs manually (although accessing the request instance is normally going to be a lot cleaner). """ - return getattr(_prefixes, "value", u'/') + return getattr(_prefixes, "value", '/') def set_urlconf(urlconf_name): """ diff --git a/django/core/validators.py b/django/core/validators.py index 02ed4fb86a..08e7e95fce 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -1,10 +1,16 @@ +from __future__ import unicode_literals + import re -import urlparse +try: + from urllib.parse import urlsplit, urlunsplit +except ImportError: # Python 2 + from urlparse import urlsplit, urlunsplit from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.ipv6 import is_valid_ipv6_address +from django.utils import six # These values, if given to validate(), will trigger the self.required check. EMPTY_VALUES = (None, '', [], (), {}) @@ -12,7 +18,7 @@ EMPTY_VALUES = (None, '', [], (), {}) class RegexValidator(object): regex = '' - message = _(u'Enter a valid value.') + message = _('Enter a valid value.') code = 'invalid' def __init__(self, regex=None, message=None, code=None): @@ -24,14 +30,14 @@ class RegexValidator(object): self.code = code # Compile the regex if it was not passed pre-compiled. - if isinstance(self.regex, basestring): + if isinstance(self.regex, six.string_types): self.regex = re.compile(self.regex) def __call__(self, value): """ Validates that the input matches the regular expression. """ - if not self.regex.search(smart_unicode(value)): + if not self.regex.search(smart_text(value)): raise ValidationError(self.message, code=self.code) @@ -40,7 +46,8 @@ class URLValidator(RegexValidator): r'^(?:http|ftp)s?://' # http:// or https:// r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... r'localhost|' # localhost... - r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 + r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 r'(?::\d+)?' # optional port r'(?:/?|[/?]\S+)$', re.IGNORECASE) @@ -50,13 +57,13 @@ class URLValidator(RegexValidator): except ValidationError as e: # Trivial case failed. Try for possible IDN domain if value: - value = smart_unicode(value) - scheme, netloc, path, query, fragment = urlparse.urlsplit(value) + value = smart_text(value) + scheme, netloc, path, query, fragment = urlsplit(value) try: - netloc = netloc.encode('idna') # IDN -> ACE + netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE except UnicodeError: # invalid domain part raise e - url = urlparse.urlunsplit((scheme, netloc, path, query, fragment)) + url = urlunsplit((scheme, netloc, path, query, fragment)) super(URLValidator, self).__call__(url) else: raise @@ -78,13 +85,13 @@ class EmailValidator(RegexValidator): super(EmailValidator, self).__call__(value) except ValidationError as e: # Trivial case failed. Try for possible IDN domain-part - if value and u'@' in value: - parts = value.split(u'@') + if value and '@' in value: + parts = value.split('@') try: - parts[-1] = parts[-1].encode('idna') + parts[-1] = parts[-1].encode('idna').decode('ascii') except UnicodeError: raise e - super(EmailValidator, self).__call__(u'@'.join(parts)) + super(EmailValidator, self).__call__('@'.join(parts)) else: raise @@ -94,18 +101,18 @@ email_re = re.compile( r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$)' # domain r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE) # literal form, ipv4 address (SMTP 4.1.3) -validate_email = EmailValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid') +validate_email = EmailValidator(email_re, _('Enter a valid e-mail address.'), 'invalid') -slug_re = re.compile(r'^[-\w]+$') -validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid') +slug_re = re.compile(r'^[-a-zA-Z0-9_]+$') +validate_slug = RegexValidator(slug_re, _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid') ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') -validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid') +validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid') def validate_ipv6_address(value): if not is_valid_ipv6_address(value): - raise ValidationError(_(u'Enter a valid IPv6 address.'), code='invalid') + raise ValidationError(_('Enter a valid IPv6 address.'), code='invalid') def validate_ipv46_address(value): @@ -115,7 +122,7 @@ def validate_ipv46_address(value): try: validate_ipv6_address(value) except ValidationError: - raise ValidationError(_(u'Enter a valid IPv4 or IPv6 address.'), code='invalid') + raise ValidationError(_('Enter a valid IPv4 or IPv6 address.'), code='invalid') ip_address_validator_map = { 'both': ([validate_ipv46_address], _('Enter a valid IPv4 or IPv6 address.')), @@ -138,16 +145,16 @@ def ip_address_validators(protocol, unpack_ipv4): return ip_address_validator_map[protocol.lower()] except KeyError: raise ValueError("The protocol '%s' is unknown. Supported: %s" - % (protocol, ip_address_validator_map.keys())) + % (protocol, list(ip_address_validator_map))) comma_separated_int_list_re = re.compile('^[\d,]+$') -validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid') +validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _('Enter only digits separated by commas.'), 'invalid') class BaseValidator(object): compare = lambda self, a, b: a is not b clean = lambda self, x: x - message = _(u'Ensure this value is %(limit_value)s (it is %(show_value)s).') + message = _('Ensure this value is %(limit_value)s (it is %(show_value)s).') code = 'limit_value' def __init__(self, limit_value): @@ -166,25 +173,25 @@ class BaseValidator(object): class MaxValueValidator(BaseValidator): compare = lambda self, a, b: a > b - message = _(u'Ensure this value is less than or equal to %(limit_value)s.') + message = _('Ensure this value is less than or equal to %(limit_value)s.') code = 'max_value' class MinValueValidator(BaseValidator): compare = lambda self, a, b: a < b - message = _(u'Ensure this value is greater than or equal to %(limit_value)s.') + message = _('Ensure this value is greater than or equal to %(limit_value)s.') code = 'min_value' class MinLengthValidator(BaseValidator): compare = lambda self, a, b: a < b clean = lambda self, x: len(x) - message = _(u'Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).') + message = _('Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).') code = 'min_length' class MaxLengthValidator(BaseValidator): compare = lambda self, a, b: a > b clean = lambda self, x: len(x) - message = _(u'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).') + message = _('Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).') code = 'max_length' diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index f26653f7b4..649f807f26 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -1,16 +1,18 @@ from django.db.utils import DatabaseError try: - import thread + from django.utils.six.moves import _thread as thread except ImportError: - import dummy_thread as thread + from django.utils.six.moves import _dummy_thread as thread from contextlib import contextmanager from django.conf import settings from django.db import DEFAULT_DB_ALIAS from django.db.backends import util from django.db.transaction import TransactionManagementError +from django.utils.functional import cached_property from django.utils.importlib import import_module +from django.utils import six from django.utils.timezone import is_aware @@ -45,6 +47,8 @@ class BaseDatabaseWrapper(object): def __ne__(self, other): return not self == other + __hash__ = object.__hash__ + def _commit(self): if self.connection is not None: return self.connection.commit() @@ -108,16 +112,18 @@ class BaseDatabaseWrapper(object): over to the surrounding block, as a commit will commit all changes, even those from outside. (Commits are on connection level.) """ - self._leave_transaction_management(self.is_managed()) if self.transaction_state: del self.transaction_state[-1] else: - raise TransactionManagementError("This code isn't under transaction " - "management") + raise TransactionManagementError( + "This code isn't under transaction management") + # We will pass the next status (after leaving the previous state + # behind) to subclass hook. + self._leave_transaction_management(self.is_managed()) if self._dirty: self.rollback() - raise TransactionManagementError("Transaction managed block ended with " - "pending COMMIT/ROLLBACK") + raise TransactionManagementError( + "Transaction managed block ended with pending COMMIT/ROLLBACK") self._dirty = False def validate_thread_sharing(self): @@ -175,6 +181,8 @@ class BaseDatabaseWrapper(object): """ if self.transaction_state: return self.transaction_state[-1] + # Note that this setting isn't documented, and is only used here, and + # in enter_transaction_management() return settings.TRANSACTIONS_MANAGED def managed(self, flag=True): @@ -402,12 +410,10 @@ class BaseDatabaseFeatures(object): # Does the backend reset sequences between tests? supports_sequence_reset = True - # Features that need to be confirmed at runtime - # Cache whether the confirmation has been performed. - _confirmed = False - supports_transactions = None - supports_stddev = None - can_introspect_foreign_keys = None + # Confirm support for introspected foreign keys + # Every database can do this reliably, except MySQL, + # which can't do it for MyISAM tables + can_introspect_foreign_keys = True # Support for the DISTINCT ON clause can_distinct_on_fields = False @@ -415,28 +421,31 @@ class BaseDatabaseFeatures(object): def __init__(self, connection): self.connection = connection - def confirm(self): - "Perform manual checks of any database features that might vary between installs" - if not self._confirmed: - self._confirmed = True - self.supports_transactions = self._supports_transactions() - self.supports_stddev = self._supports_stddev() - self.can_introspect_foreign_keys = self._can_introspect_foreign_keys() - - def _supports_transactions(self): + @cached_property + def supports_transactions(self): "Confirm support for transactions" - cursor = self.connection.cursor() - cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)') - self.connection._commit() - cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)') - self.connection._rollback() - cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST') - count, = cursor.fetchone() - cursor.execute('DROP TABLE ROLLBACK_TEST') - self.connection._commit() + try: + # Make sure to run inside a managed transaction block, + # otherwise autocommit will cause the confimation to + # fail. + self.connection.enter_transaction_management() + self.connection.managed(True) + cursor = self.connection.cursor() + cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)') + self.connection._commit() + cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)') + self.connection._rollback() + cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST') + count, = cursor.fetchone() + cursor.execute('DROP TABLE ROLLBACK_TEST') + self.connection._commit() + self.connection._dirty = False + finally: + self.connection.leave_transaction_management() return count == 0 - def _supports_stddev(self): + @cached_property + def supports_stddev(self): "Confirm support for STDDEV and related stats functions" class StdDevPop(object): sql_function = 'STDDEV_POP' @@ -447,12 +456,6 @@ class BaseDatabaseFeatures(object): except NotImplementedError: return False - def _can_introspect_foreign_keys(self): - "Confirm support for introspected foreign keys" - # Every database can do this reliably, except MySQL, - # which can't do it for MyISAM tables - return True - class BaseDatabaseOperations(object): """ @@ -475,6 +478,24 @@ class BaseDatabaseOperations(object): """ return None + def bulk_batch_size(self, fields, objs): + """ + Returns the maximum allowed batch size for the backend. The fields + are the fields going to be inserted in the batch, the objs contains + all the objects to be inserted. + """ + return len(objs) + + def cache_key_culling_sql(self): + """ + Returns a SQL query that retrieves the first cache key greater than the + n smallest. + + This is used by the 'db' cache backend to determine where to start + culling. + """ + return "SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" + def date_extract_sql(self, lookup_type, field_name): """ Given a lookup_type of 'year', 'month' or 'day', returns the SQL that @@ -512,6 +533,17 @@ class BaseDatabaseOperations(object): """ return '' + def distinct_sql(self, fields): + """ + Returns an SQL DISTINCT clause which removes duplicate rows from the + result set. If any fields are given, only the given fields are being + checked for duplicates. + """ + if fields: + raise NotImplementedError('DISTINCT ON fields is not supported by this database backend') + else: + return 'DISTINCT' + def drop_foreignkey_sql(self): """ Returns the SQL command that drops a foreign key. @@ -567,17 +599,6 @@ class BaseDatabaseOperations(object): """ raise NotImplementedError('Full-text search is not implemented for this database backend') - def distinct_sql(self, fields): - """ - Returns an SQL DISTINCT clause which removes duplicate rows from the - result set. If any fields are given, only the given fields are being - checked for duplicates. - """ - if fields: - raise NotImplementedError('DISTINCT ON fields is not supported by this database backend') - else: - return 'DISTINCT' - def last_executed_query(self, cursor, sql, params): """ Returns a string of the query last executed by the given cursor, with @@ -588,16 +609,16 @@ class BaseDatabaseOperations(object): exists for database backends to provide a better implementation according to their own quoting schemes. """ - from django.utils.encoding import smart_unicode, force_unicode + from django.utils.encoding import smart_text, force_text # Convert params to contain Unicode values. - to_unicode = lambda s: force_unicode(s, strings_only=True, errors='replace') + to_unicode = lambda s: force_text(s, strings_only=True, errors='replace') if isinstance(params, (list, tuple)): u_params = tuple([to_unicode(val) for val in params]) else: u_params = dict([(to_unicode(k), to_unicode(v)) for k, v in params.items()]) - return smart_unicode(sql) % u_params + return smart_text(sql) % u_params def last_insert_id(self, cursor, table_name, pk_name): """ @@ -729,11 +750,24 @@ class BaseDatabaseOperations(object): the given database tables (without actually removing the tables themselves). + The returned value also includes SQL statements required to reset DB + sequences passed in :param sequences:. + The `style` argument is a Style object as returned by either color_style() or no_style() in django.core.management.color. """ raise NotImplementedError() + def sequence_reset_by_name_sql(self, style, sequences): + """ + Returns a list of the SQL statements required to reset sequences + passed in :param sequences:. + + The `style` argument is a Style object as returned by either + color_style() or no_style() in django.core.management.color. + """ + return [] + def sequence_reset_sql(self, style, model_list): """ Returns a list of the SQL statements required to reset sequences for @@ -768,8 +802,8 @@ class BaseDatabaseOperations(object): def prep_for_like_query(self, x): """Prepares a value for use in a LIKE query.""" - from django.utils.encoding import smart_unicode - return smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") + from django.utils.encoding import smart_text + return smart_text(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") # Same as prep_for_like_query(), but called for "iexact" matches, which # need not necessarily be implemented using "LIKE" in the backend. @@ -790,7 +824,7 @@ class BaseDatabaseOperations(object): """ if value is None: return None - return unicode(value) + return six.text_type(value) def value_to_db_datetime(self, value): """ @@ -799,7 +833,7 @@ class BaseDatabaseOperations(object): """ if value is None: return None - return unicode(value) + return six.text_type(value) def value_to_db_time(self, value): """ @@ -810,7 +844,7 @@ class BaseDatabaseOperations(object): return None if is_aware(value): raise ValueError("Django does not support timezone-aware times.") - return unicode(value) + return six.text_type(value) def value_to_db_decimal(self, value, max_digits, decimal_places): """ @@ -846,19 +880,21 @@ class BaseDatabaseOperations(object): return self.year_lookup_bounds(value) def convert_values(self, value, field): - """Coerce the value returned by the database backend into a consistent type that - is compatible with the field type. + """ + Coerce the value returned by the database backend into a consistent type + that is compatible with the field type. """ internal_type = field.get_internal_type() if internal_type == 'DecimalField': return value - elif internal_type and internal_type.endswith('IntegerField') or internal_type == 'AutoField': + elif internal_type == 'FloatField': + return float(value) + elif (internal_type and (internal_type.endswith('IntegerField') + or internal_type == 'AutoField')): return int(value) elif internal_type in ('DateField', 'DateTimeField', 'TimeField'): return value - # No field, or the field isn't known to be a decimal or integer - # Default to a float - return float(value) + return value def check_aggregate_support(self, aggregate_func): """Check that the backend supports the provided aggregate @@ -957,7 +993,7 @@ class BaseDatabaseIntrospection(object): for model in models.get_models(app): if router.allow_syncdb(self.connection.alias, model): all_models.append(model) - tables = map(self.table_name_converter, tables) + tables = list(map(self.table_name_converter, tables)) return set([ m for m in all_models if self.table_name_converter(m._meta.db_table) in tables diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index eb50dc0cbb..b68fa8f795 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -3,6 +3,7 @@ import time from django.conf import settings from django.db.utils import load_backend +from django.utils.six.moves import input # The prefix to put on the default database name when creating # the test database. @@ -26,7 +27,7 @@ class BaseDatabaseCreation(object): Generates a 32-bit digest of a set of arguments that can be used to shorten identifying names. """ - return '%x' % (abs(hash(args)) % 4294967296L) # 2**32 + return '%x' % (abs(hash(args)) % 4294967296) # 2**32 def sql_create_model(self, model, style, known_models=set()): """ @@ -264,9 +265,6 @@ class BaseDatabaseCreation(object): self.connection.close() self.connection.settings_dict["NAME"] = test_database_name - # Confirm the feature set of the test database - self.connection.features.confirm() - # Report syncdb messages at one level lower than that requested. # This ensures we don't get flooded with messages during testing # (unless you really ask to be flooded) @@ -333,7 +331,7 @@ class BaseDatabaseCreation(object): sys.stderr.write( "Got an error creating the test database: %s\n" % e) if not autoclobber: - confirm = raw_input( + confirm = input( "Type 'yes' if you would like to try deleting the test " "database '%s', or 'no' to cancel: " % test_database_name) if autoclobber or confirm == 'yes': diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 1df487bd92..cec3b04f7e 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -3,6 +3,7 @@ MySQL database backend for Django. Requires MySQLdb: http://sourceforge.net/projects/mysql-python """ +from __future__ import unicode_literals import datetime import re @@ -36,7 +37,9 @@ from django.db.backends.mysql.client import DatabaseClient from django.db.backends.mysql.creation import DatabaseCreation from django.db.backends.mysql.introspection import DatabaseIntrospection from django.db.backends.mysql.validation import DatabaseValidation -from django.utils.safestring import SafeString, SafeUnicode +from django.utils.functional import cached_property +from django.utils.safestring import SafeBytes, SafeText +from django.utils import six from django.utils import timezone # Raise exceptions for database warnings if DEBUG is on @@ -61,8 +64,8 @@ def adapt_datetime_with_timezone_support(value, conv): # Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL. if settings.USE_TZ: if timezone.is_naive(value): - warnings.warn(u"SQLite received a naive datetime (%s)" - u" while time zone support is active." % value, + warnings.warn("SQLite received a naive datetime (%s)" + " while time zone support is active." % value, RuntimeWarning) default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) @@ -72,7 +75,7 @@ def adapt_datetime_with_timezone_support(value, conv): # MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like # timedelta in terms of actual behavior as they are signed and include days -- # and Django expects time, so we still need to override that. We also need to -# add special handling for SafeUnicode and SafeString as MySQLdb's type +# add special handling for SafeText and SafeBytes as MySQLdb's type # checking is too tight to catch those (see Django ticket #6052). # Finally, MySQLdb always returns naive datetime objects. However, when # timezone support is active, Django expects timezone-aware datetime objects. @@ -115,29 +118,29 @@ class CursorWrapper(object): try: return self.cursor.execute(query, args) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.OperationalError as e: # Map some error codes to IntegrityError, since they seem to be # misclassified and Django would prefer the more logical place. if e[0] in self.codes_for_integrityerror: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def executemany(self, query, args): try: return self.cursor.executemany(query, args) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.OperationalError as e: # Map some error codes to IntegrityError, since they seem to be # misclassified and Django would prefer the more logical place. if e[0] in self.codes_for_integrityerror: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def __getattr__(self, attr): if attr in self.__dict__: @@ -169,26 +172,25 @@ class DatabaseFeatures(BaseDatabaseFeatures): def __init__(self, connection): super(DatabaseFeatures, self).__init__(connection) - self._storage_engine = None + @cached_property def _mysql_storage_engine(self): "Internal method used in Django tests. Don't rely on this from your code" - if self._storage_engine is None: - cursor = self.connection.cursor() - cursor.execute('CREATE TABLE INTROSPECT_TEST (X INT)') - # This command is MySQL specific; the second column - # will tell you the default table type of the created - # table. Since all Django's test tables will have the same - # table type, that's enough to evaluate the feature. - cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'") - result = cursor.fetchone() - cursor.execute('DROP TABLE INTROSPECT_TEST') - self._storage_engine = result[1] - return self._storage_engine + cursor = self.connection.cursor() + cursor.execute('CREATE TABLE INTROSPECT_TEST (X INT)') + # This command is MySQL specific; the second column + # will tell you the default table type of the created + # table. Since all Django's test tables will have the same + # table type, that's enough to evaluate the feature. + cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'") + result = cursor.fetchone() + cursor.execute('DROP TABLE INTROSPECT_TEST') + return result[1] - def _can_introspect_foreign_keys(self): + @cached_property + def can_introspect_foreign_keys(self): "Confirm support for introspected foreign keys" - return self._mysql_storage_engine() != 'MyISAM' + return self._mysql_storage_engine != 'MyISAM' class DatabaseOperations(BaseDatabaseOperations): compiler_module = "django.db.backends.mysql.compiler" @@ -237,11 +239,11 @@ class DatabaseOperations(BaseDatabaseOperations): # With MySQLdb, cursor objects have an (undocumented) "_last_executed" # attribute where the exact query sent to the database is saved. # See MySQLdb/cursors.py in the source distribution. - return cursor._last_executed + return cursor._last_executed.decode('utf-8') def no_limit_value(self): # 2**64 - 1, as recommended by the MySQL documentation - return 18446744073709551615L + return 18446744073709551615 def quote_name(self, name): if name.startswith("`") and name.endswith("`"): @@ -260,22 +262,25 @@ class DatabaseOperations(BaseDatabaseOperations): for table in tables: sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table)))) sql.append('SET FOREIGN_KEY_CHECKS = 1;') - - # Truncate already resets the AUTO_INCREMENT field from - # MySQL version 5.0.13 onwards. Refs #16961. - if self.connection.mysql_version < (5,0,13): - sql.extend( - ["%s %s %s %s %s;" % \ - (style.SQL_KEYWORD('ALTER'), - style.SQL_KEYWORD('TABLE'), - style.SQL_TABLE(self.quote_name(sequence['table'])), - style.SQL_KEYWORD('AUTO_INCREMENT'), - style.SQL_FIELD('= 1'), - ) for sequence in sequences]) + sql.extend(self.sequence_reset_by_name_sql(style, sequences)) return sql else: return [] + def sequence_reset_by_name_sql(self, style, sequences): + # Truncate already resets the AUTO_INCREMENT field from + # MySQL version 5.0.13 onwards. Refs #16961. + if self.connection.mysql_version < (5, 0, 13): + return ["%s %s %s %s %s;" % \ + (style.SQL_KEYWORD('ALTER'), + style.SQL_KEYWORD('TABLE'), + style.SQL_TABLE(self.quote_name(sequence['table'])), + style.SQL_KEYWORD('AUTO_INCREMENT'), + style.SQL_FIELD('= 1'), + ) for sequence in sequences] + else: + return [] + def validate_autopk_value(self, value): # MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653. if value == 0: @@ -295,7 +300,7 @@ class DatabaseOperations(BaseDatabaseOperations): raise ValueError("MySQL backend does not support timezone-aware datetimes when USE_TZ is False.") # MySQL doesn't support microseconds - return unicode(value.replace(microsecond=0)) + return six.text_type(value.replace(microsecond=0)) def value_to_db_time(self, value): if value is None: @@ -306,7 +311,7 @@ class DatabaseOperations(BaseDatabaseOperations): raise ValueError("MySQL backend does not support timezone-aware times.") # MySQL doesn't support microseconds - return unicode(value.replace(microsecond=0)) + return six.text_type(value.replace(microsecond=0)) def year_lookup_bounds(self, value): # Again, no microseconds @@ -397,8 +402,8 @@ class DatabaseWrapper(BaseDatabaseWrapper): kwargs['client_flag'] = CLIENT.FOUND_ROWS kwargs.update(settings_dict['OPTIONS']) self.connection = Database.connect(**kwargs) - self.connection.encoders[SafeUnicode] = self.connection.encoders[unicode] - self.connection.encoders[SafeString] = self.connection.encoders[str] + self.connection.encoders[SafeText] = self.connection.encoders[six.text_type] + self.connection.encoders[SafeBytes] = self.connection.encoders[bytes] connection_created.send(sender=self.__class__, connection=self) cursor = self.connection.cursor() if new_connection: diff --git a/django/db/backends/mysql/compiler.py b/django/db/backends/mysql/compiler.py index bd4105ff8e..012bca5da6 100644 --- a/django/db/backends/mysql/compiler.py +++ b/django/db/backends/mysql/compiler.py @@ -3,7 +3,7 @@ from django.db.models.sql import compiler class SQLCompiler(compiler.SQLCompiler): def resolve_columns(self, row, fields=()): values = [] - index_extra_select = len(self.query.extra_select.keys()) + index_extra_select = len(self.query.extra_select) for value, field in map(None, row[index_extra_select:], fields): if (field and field.get_internal_type() in ("BooleanField", "NullBooleanField") and value in (0, 1)): diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index e6f18b819f..6aab0b99ab 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -1,4 +1,5 @@ from django.db.backends import BaseDatabaseIntrospection +from django.utils import six from MySQLdb import ProgrammingError, OperationalError from MySQLdb.constants import FIELD_TYPE import re @@ -79,7 +80,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): """ Returns the name of the primary key column for the given table """ - for column in self.get_indexes(cursor, table_name).iteritems(): + for column in six.iteritems(self.get_indexes(cursor, table_name)): if column[1]['primary_key']: return column[0] return None diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index fe512dfee5..7d4f2b445a 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -3,13 +3,14 @@ Oracle database backend for Django. Requires cx_Oracle: http://cx-oracle.sourceforge.net/ """ - +from __future__ import unicode_literals import datetime import decimal import sys import warnings +from django.utils import six def _setup_environment(environ): import platform @@ -52,7 +53,8 @@ from django.db.backends.signals import connection_created from django.db.backends.oracle.client import DatabaseClient from django.db.backends.oracle.creation import DatabaseCreation from django.db.backends.oracle.introspection import DatabaseIntrospection -from django.utils.encoding import smart_str, force_unicode +from django.utils.encoding import smart_bytes, force_text +from django.utils import six from django.utils import timezone DatabaseError = Database.DatabaseError @@ -62,9 +64,9 @@ IntegrityError = Database.IntegrityError # Check whether cx_Oracle was compiled with the WITH_UNICODE option. This will # also be True in Python 3.0. if int(Database.version.split('.', 1)[0]) >= 5 and not hasattr(Database, 'UNICODE'): - convert_unicode = force_unicode + convert_unicode = force_text else: - convert_unicode = smart_str + convert_unicode = smart_bytes class DatabaseFeatures(BaseDatabaseFeatures): @@ -118,6 +120,13 @@ WHEN (new.%(col_name)s IS NULL) /""" % locals() return sequence_sql, trigger_sql + def cache_key_culling_sql(self): + return """ + SELECT cache_key + FROM (SELECT cache_key, rank() OVER (ORDER BY cache_key) AS rank FROM %s) + WHERE rank = %%s + 1 + """ + def date_extract_sql(self, lookup_type, field_name): # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163 if lookup_type == 'week_day': @@ -153,14 +162,14 @@ WHEN (new.%(col_name)s IS NULL) if isinstance(value, Database.LOB): value = value.read() if field and field.get_internal_type() == 'TextField': - value = force_unicode(value) + value = force_text(value) # Oracle stores empty strings as null. We need to undo this in # order to adhere to the Django convention of using the empty # string instead of null, but only if the field accepts the # empty string. if value is None and field and field.empty_strings_allowed: - value = u'' + value = '' # Convert 1 or 0 to True or False elif value in (1, 0) and field and field.get_internal_type() in ('BooleanField', 'NullBooleanField'): value = bool(value) @@ -201,7 +210,7 @@ WHEN (new.%(col_name)s IS NULL) return "DROP SEQUENCE %s;" % self.quote_name(self._get_sequence_name(table)) def fetch_returned_insert_id(self, cursor): - return long(cursor._insert_id_var.getvalue()) + return int(cursor._insert_id_var.getvalue()) def field_cast_sql(self, db_type): if db_type and db_type.endswith('LOB'): @@ -212,7 +221,10 @@ WHEN (new.%(col_name)s IS NULL) def last_executed_query(self, cursor, sql, params): # http://cx-oracle.sourceforge.net/html/cursor.html#Cursor.statement # The DB API definition does not define this attribute. - return cursor.statement + if six.PY3: + return cursor.statement + else: + return cursor.statement.decode("utf-8") def last_insert_id(self, cursor, table_name, pk_name): sq_name = self._get_sequence_name(table_name) @@ -235,8 +247,8 @@ WHEN (new.%(col_name)s IS NULL) def process_clob(self, value): if value is None: - return u'' - return force_unicode(value.read()) + return '' + return force_text(value.read()) def quote_name(self, name): # SQL92 requires delimited (quoted) names to be case-sensitive. When @@ -289,18 +301,23 @@ WHEN (new.%(col_name)s IS NULL) for table in tables] # Since we've just deleted all the rows, running our sequence # ALTER code will reset the sequence to 0. - for sequence_info in sequences: - sequence_name = self._get_sequence_name(sequence_info['table']) - table_name = self.quote_name(sequence_info['table']) - column_name = self.quote_name(sequence_info['column'] or 'id') - query = _get_sequence_reset_sql() % {'sequence': sequence_name, - 'table': table_name, - 'column': column_name} - sql.append(query) + sql.extend(self.sequence_reset_by_name_sql(style, sequences)) return sql else: return [] + def sequence_reset_by_name_sql(self, style, sequences): + sql = [] + for sequence_info in sequences: + sequence_name = self._get_sequence_name(sequence_info['table']) + table_name = self.quote_name(sequence_info['table']) + column_name = self.quote_name(sequence_info['column'] or 'id') + query = _get_sequence_reset_sql() % {'sequence': sequence_name, + 'table': table_name, + 'column': column_name} + sql.append(query) + return sql + def sequence_reset_sql(self, style, model_list): from django.db import models output = [] @@ -347,13 +364,13 @@ WHEN (new.%(col_name)s IS NULL) else: raise ValueError("Oracle backend does not support timezone-aware datetimes when USE_TZ is False.") - return unicode(value) + return six.text_type(value) def value_to_db_time(self, value): if value is None: return None - if isinstance(value, basestring): + if isinstance(value, six.string_types): return datetime.datetime.strptime(value, '%H:%M:%S') # Oracle doesn't support tz-aware times @@ -479,13 +496,19 @@ class DatabaseWrapper(BaseDatabaseWrapper): del conn_params['use_returning_into'] self.connection = Database.connect(conn_string, **conn_params) cursor = FormatStylePlaceholderCursor(self.connection) + # Set the territory first. The territory overrides NLS_DATE_FORMAT + # and NLS_TIMESTAMP_FORMAT to the territory default. When all of + # these are set in single statement it isn't clear what is supposed + # to happen. + cursor.execute("ALTER SESSION SET NLS_TERRITORY = 'AMERICA'") # Set oracle date to ansi date format. This only needs to execute # once when we create a new connection. We also set the Territory - # to 'AMERICA' which forces Sunday to evaluate to a '1' in TO_CHAR(). - cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'" - " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'" - " NLS_TERRITORY = 'AMERICA'" - + (" TIME_ZONE = 'UTC'" if settings.USE_TZ else '')) + # to 'AMERICA' which forces Sunday to evaluate to a '1' in + # TO_CHAR(). + cursor.execute( + "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'" + " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'" + + (" TIME_ZONE = 'UTC'" if settings.USE_TZ else '')) if 'operators' not in self.__dict__: # Ticket #14149: Check whether our LIKE implementation will @@ -536,7 +559,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): except Database.IntegrityError as e: # In case cx_Oracle implements (now or in a future version) # raising this specific exception - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: # cx_Oracle 5.0.4 raises a cx_Oracle.DatabaseError exception # with the following attributes and values: @@ -548,8 +571,8 @@ class DatabaseWrapper(BaseDatabaseWrapper): x = e.args[0] if hasattr(x, 'code') and hasattr(x, 'message') \ and x.code == 2091 and 'ORA-02291' in x.message: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) class OracleParam(object): @@ -567,22 +590,28 @@ class OracleParam(object): # without being converted by DateTimeField.get_db_prep_value. if settings.USE_TZ and isinstance(param, datetime.datetime): if timezone.is_naive(param): - warnings.warn(u"Oracle received a naive datetime (%s)" - u" while time zone support is active." % param, + warnings.warn("Oracle received a naive datetime (%s)" + " while time zone support is active." % param, RuntimeWarning) default_timezone = timezone.get_default_timezone() param = timezone.make_aware(param, default_timezone) param = param.astimezone(timezone.utc).replace(tzinfo=None) + # Oracle doesn't recognize True and False correctly in Python 3. + # The conversion done below works both in 2 and 3. + if param is True: + param = "1" + elif param is False: + param = "0" if hasattr(param, 'bind_parameter'): - self.smart_str = param.bind_parameter(cursor) + self.smart_bytes = param.bind_parameter(cursor) else: - self.smart_str = convert_unicode(param, cursor.charset, + self.smart_bytes = convert_unicode(param, cursor.charset, strings_only) if hasattr(param, 'input_size'): # If parameter has `input_size` attribute, use that. self.input_size = param.input_size - elif isinstance(param, basestring) and len(param) > 4000: + elif isinstance(param, six.string_types) and len(param) > 4000: # Mark any string param greater than 4000 characters as a CLOB. self.input_size = Database.CLOB else: @@ -656,7 +685,7 @@ class FormatStylePlaceholderCursor(object): self.setinputsizes(*sizes) def _param_generator(self, params): - return [p.smart_str for p in params] + return [p.smart_bytes for p in params] def execute(self, query, params=None): if params is None: @@ -675,12 +704,12 @@ class FormatStylePlaceholderCursor(object): try: return self.cursor.execute(query, self._param_generator(params)) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, IntegrityError): - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def executemany(self, query, params=None): # cx_Oracle doesn't support iterators, convert them to lists @@ -704,12 +733,12 @@ class FormatStylePlaceholderCursor(object): return self.cursor.executemany(query, [self._param_generator(p) for p in formatted]) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400. if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, IntegrityError): - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def fetchone(self): row = self.cursor.fetchone() @@ -754,9 +783,11 @@ class CursorIterator(object): def __iter__(self): return self - def next(self): + def __next__(self): return _rowfactory(next(self.iter), self.cursor) + next = __next__ # Python 2 compatibility + def _rowfactory(row, cursor): # Cast numeric values as the appropriate Python type based upon the @@ -810,8 +841,8 @@ def to_unicode(s): Convert strings to Unicode objects (and return all other data types unchanged). """ - if isinstance(s, basestring): - return force_unicode(s) + if isinstance(s, six.string_types): + return force_text(s) return s diff --git a/django/db/backends/oracle/compiler.py b/django/db/backends/oracle/compiler.py index c7b10429c2..24030cdffc 100644 --- a/django/db/backends/oracle/compiler.py +++ b/django/db/backends/oracle/compiler.py @@ -1,4 +1,9 @@ from django.db.models.sql import compiler +# The izip_longest was renamed to zip_longest in py3 +try: + from itertools import zip_longest +except ImportError: + from itertools import izip_longest as zip_longest class SQLCompiler(compiler.SQLCompiler): @@ -10,10 +15,10 @@ class SQLCompiler(compiler.SQLCompiler): rn_offset = 1 else: rn_offset = 0 - index_start = rn_offset + len(self.query.extra_select.keys()) + index_start = rn_offset + len(self.query.extra_select) values = [self.query.convert_values(v, None, connection=self.connection) for v in row[rn_offset:index_start]] - for value, field in map(None, row[index_start:], fields): + for value, field in zip_longest(row[index_start:], fields): values.append(self.query.convert_values(value, field, connection=self.connection)) return tuple(values) diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index 2f096f735a..d9bf3dfea2 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -1,6 +1,7 @@ import sys import time from django.db.backends.creation import BaseDatabaseCreation +from django.utils.six.moves import input TEST_DATABASE_PREFIX = 'test_' PASSWORD = 'Im_a_lumberjack' @@ -65,7 +66,7 @@ class DatabaseCreation(BaseDatabaseCreation): except Exception as e: sys.stderr.write("Got an error creating the test database: %s\n" % e) if not autoclobber: - confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_NAME) + confirm = input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_NAME) if autoclobber or confirm == 'yes': try: if verbosity >= 1: @@ -87,7 +88,7 @@ class DatabaseCreation(BaseDatabaseCreation): except Exception as e: sys.stderr.write("Got an error creating the test user: %s\n" % e) if not autoclobber: - confirm = raw_input("It appears the test user, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_USER) + confirm = input("It appears the test user, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_USER) if autoclobber or confirm == 'yes': try: if verbosity >= 1: diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 61be680d83..f6f534da8c 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -14,7 +14,8 @@ from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation from django.db.backends.postgresql_psycopg2.version import get_version from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection from django.utils.log import getLogger -from django.utils.safestring import SafeUnicode, SafeString +from django.utils.safestring import SafeText, SafeBytes +from django.utils import six from django.utils.timezone import utc try: @@ -28,8 +29,8 @@ DatabaseError = Database.DatabaseError IntegrityError = Database.IntegrityError psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) -psycopg2.extensions.register_adapter(SafeString, psycopg2.extensions.QuotedString) -psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedString) +psycopg2.extensions.register_adapter(SafeBytes, psycopg2.extensions.QuotedString) +psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString) logger = getLogger('django.db.backends') @@ -51,17 +52,17 @@ class CursorWrapper(object): try: return self.cursor.execute(query, args) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def executemany(self, query, args): try: return self.cursor.executemany(query, args) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def __getattr__(self, attr): if attr in self.__dict__: @@ -157,9 +158,11 @@ class DatabaseWrapper(BaseDatabaseWrapper): def _cursor(self): settings_dict = self.settings_dict if self.connection is None: - if settings_dict['NAME'] == '': + if not settings_dict['NAME']: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured("You need to specify NAME in your Django settings file.") + raise ImproperlyConfigured( + "settings.DATABASES is improperly configured. " + "Please supply the NAME value.") conn_params = { 'database': settings_dict['NAME'], } @@ -234,4 +237,4 @@ class DatabaseWrapper(BaseDatabaseWrapper): try: return self.connection.commit() except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py index fbf7a22769..99573b9019 100644 --- a/django/db/backends/postgresql_psycopg2/introspection.py +++ b/django/db/backends/postgresql_psycopg2/introspection.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.db.backends import BaseDatabaseIntrospection @@ -43,7 +45,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): WHERE table_name = %s""", [table_name]) null_map = dict(cursor.fetchall()) cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name)) - return [tuple([item for item in line[:6]] + [null_map[line[0]]==u'YES']) + return [tuple([item for item in line[:6]] + [null_map[line[0]]=='YES']) for line in cursor.description] def get_relations(self, cursor, table_name): diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py index 395cd92047..40fe629110 100644 --- a/django/db/backends/postgresql_psycopg2/operations.py +++ b/django/db/backends/postgresql_psycopg2/operations.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.db.backends import BaseDatabaseOperations @@ -21,14 +23,14 @@ class DatabaseOperations(BaseDatabaseOperations): """ modifiers = [] if timedelta.days: - modifiers.append(u'%s days' % timedelta.days) + modifiers.append('%s days' % timedelta.days) if timedelta.seconds: - modifiers.append(u'%s seconds' % timedelta.seconds) + modifiers.append('%s seconds' % timedelta.seconds) if timedelta.microseconds: - modifiers.append(u'%s microseconds' % timedelta.microseconds) - mods = u' '.join(modifiers) - conn = u' %s ' % connector - return u'(%s)' % conn.join([sql, u'interval \'%s\'' % mods]) + modifiers.append('%s microseconds' % timedelta.microseconds) + mods = ' '.join(modifiers) + conn = ' %s ' % connector + return '(%s)' % conn.join([sql, 'interval \'%s\'' % mods]) def date_trunc_sql(self, lookup_type, field_name): # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC @@ -83,25 +85,29 @@ class DatabaseOperations(BaseDatabaseOperations): (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(', '.join([self.quote_name(table) for table in tables])) )] - - # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements - # to reset sequence indices - for sequence_info in sequences: - table_name = sequence_info['table'] - column_name = sequence_info['column'] - if not (column_name and len(column_name) > 0): - # This will be the case if it's an m2m using an autogenerated - # intermediate table (see BaseDatabaseIntrospection.sequence_list) - column_name = 'id' - sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \ - (style.SQL_KEYWORD('SELECT'), - style.SQL_TABLE(self.quote_name(table_name)), - style.SQL_FIELD(column_name)) - ) + sql.extend(self.sequence_reset_by_name_sql(style, sequences)) return sql else: return [] + def sequence_reset_by_name_sql(self, style, sequences): + # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements + # to reset sequence indices + sql = [] + for sequence_info in sequences: + table_name = sequence_info['table'] + column_name = sequence_info['column'] + if not (column_name and len(column_name) > 0): + # This will be the case if it's an m2m using an autogenerated + # intermediate table (see BaseDatabaseIntrospection.sequence_list) + column_name = 'id' + sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \ + (style.SQL_KEYWORD('SELECT'), + style.SQL_TABLE(self.quote_name(table_name)), + style.SQL_FIELD(column_name)) + ) + return sql + def tablespace_sql(self, tablespace, inline=False): if inline: return "USING INDEX TABLESPACE %s" % self.quote_name(tablespace) @@ -191,7 +197,9 @@ class DatabaseOperations(BaseDatabaseOperations): def last_executed_query(self, cursor, sql, params): # http://initd.org/psycopg/docs/cursor.html#cursor.query # The query attribute is a Psycopg extension to the DB API 2.0. - return cursor.query + if cursor.query is not None: + return cursor.query.decode('utf-8') + return None def return_insert_id(self): return "RETURNING %s", () diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 4fba304e23..31a16b6a2b 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -4,6 +4,7 @@ SQLite3 backend for django. Works with either the pysqlite2 module or the sqlite3 module in the standard library. """ +from __future__ import unicode_literals import datetime import decimal @@ -18,7 +19,9 @@ from django.db.backends.sqlite3.client import DatabaseClient from django.db.backends.sqlite3.creation import DatabaseCreation from django.db.backends.sqlite3.introspection import DatabaseIntrospection from django.utils.dateparse import parse_date, parse_datetime, parse_time -from django.utils.safestring import SafeString +from django.utils.functional import cached_property +from django.utils.safestring import SafeBytes +from django.utils import six from django.utils import timezone try: @@ -45,21 +48,29 @@ def adapt_datetime_with_timezone_support(value): # Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL. if settings.USE_TZ: if timezone.is_naive(value): - warnings.warn(u"SQLite received a naive datetime (%s)" - u" while time zone support is active." % value, + warnings.warn("SQLite received a naive datetime (%s)" + " while time zone support is active." % value, RuntimeWarning) default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) value = value.astimezone(timezone.utc).replace(tzinfo=None) - return value.isoformat(b" ") + return value.isoformat(str(" ")) + +def decoder(conv_func): + """ The Python sqlite3 interface returns always byte strings. + This function converts the received value to a regular string before + passing it to the receiver function. + """ + return lambda s: conv_func(s.decode('utf-8')) + +Database.register_converter(str("bool"), decoder(lambda s: s == '1')) +Database.register_converter(str("time"), decoder(parse_time)) +Database.register_converter(str("date"), decoder(parse_date)) +Database.register_converter(str("datetime"), decoder(parse_datetime_with_timezone_support)) +Database.register_converter(str("timestamp"), decoder(parse_datetime_with_timezone_support)) +Database.register_converter(str("TIMESTAMP"), decoder(parse_datetime_with_timezone_support)) +Database.register_converter(str("decimal"), decoder(util.typecast_decimal)) -Database.register_converter(b"bool", lambda s: str(s) == '1') -Database.register_converter(b"time", parse_time) -Database.register_converter(b"date", parse_date) -Database.register_converter(b"datetime", parse_datetime_with_timezone_support) -Database.register_converter(b"timestamp", parse_datetime_with_timezone_support) -Database.register_converter(b"TIMESTAMP", parse_datetime_with_timezone_support) -Database.register_converter(b"decimal", util.typecast_decimal) Database.register_adapter(datetime.datetime, adapt_datetime_with_timezone_support) Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal) if Database.version_info >= (2, 4, 1): @@ -69,7 +80,7 @@ if Database.version_info >= (2, 4, 1): # slow-down, this adapter is only registered for sqlite3 versions # needing it (Python 2.6 and up). Database.register_adapter(str, lambda s: s.decode('utf-8')) - Database.register_adapter(SafeString, lambda s: s.decode('utf-8')) + Database.register_adapter(SafeBytes, lambda s: s.decode('utf-8')) class DatabaseFeatures(BaseDatabaseFeatures): # SQLite cannot handle us only partially reading from a cursor's result set @@ -83,9 +94,10 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_1000_query_parameters = False supports_mixed_date_datetime_comparisons = False has_bulk_insert = True - can_combine_inserts_with_and_without_auto_increment_pk = True + can_combine_inserts_with_and_without_auto_increment_pk = False - def _supports_stddev(self): + @cached_property + def supports_stddev(self): """Confirm support for STDDEV and related stats functions SQLite supports STDDEV as an extension package; so @@ -104,6 +116,13 @@ class DatabaseFeatures(BaseDatabaseFeatures): return has_support class DatabaseOperations(BaseDatabaseOperations): + def bulk_batch_size(self, fields, objs): + """ + SQLite has a compile-time default (SQLITE_LIMIT_VARIABLE_NUMBER) of + 999 variables per query. + """ + return (999 // len(fields)) if len(fields) > 0 else len(objs) + def date_extract_sql(self, lookup_type, field_name): # sqlite doesn't support extract, so we fake it with the user-defined # function django_extract that's registered in connect(). Note that @@ -118,7 +137,7 @@ class DatabaseOperations(BaseDatabaseOperations): # values differently. So instead we register our own function that # formats the datetime combined with the delta in a manner suitable # for comparisons. - return u'django_format_dtdelta(%s, "%s", "%d", "%d", "%d")' % (sql, + return 'django_format_dtdelta(%s, "%s", "%d", "%d", "%d")' % (sql, connector, timedelta.days, timedelta.seconds, timedelta.microseconds) def date_trunc_sql(self, lookup_type, field_name): @@ -166,7 +185,7 @@ class DatabaseOperations(BaseDatabaseOperations): else: raise ValueError("SQLite backend does not support timezone-aware datetimes when USE_TZ is False.") - return unicode(value) + return six.text_type(value) def value_to_db_time(self, value): if value is None: @@ -176,7 +195,7 @@ class DatabaseOperations(BaseDatabaseOperations): if timezone.is_aware(value): raise ValueError("SQLite backend does not support timezone-aware times.") - return unicode(value) + return six.text_type(value) def year_lookup_bounds(self, value): first = '%s-01-01' @@ -247,7 +266,9 @@ class DatabaseWrapper(BaseDatabaseWrapper): settings_dict = self.settings_dict if not settings_dict['NAME']: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured("Please fill out the database NAME in the settings module before using the database.") + raise ImproperlyConfigured( + "settings.DATABASES is improperly configured. " + "Please supply the NAME value.") kwargs = { 'database': settings_dict['NAME'], 'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES, @@ -336,18 +357,18 @@ class SQLiteCursorWrapper(Database.Cursor): try: return Database.Cursor.execute(self, query, params) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def executemany(self, query, param_list): query = self.convert_query(query) try: return Database.Cursor.executemany(self, query, param_list) except Database.IntegrityError as e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2]) except Database.DatabaseError as e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2]) def convert_query(self, query): return FORMAT_QMARK_REGEX.sub('?', query).replace('%%','%') diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py index efdc457be0..c022b56c85 100644 --- a/django/db/backends/sqlite3/creation.py +++ b/django/db/backends/sqlite3/creation.py @@ -1,6 +1,7 @@ import os import sys from django.db.backends.creation import BaseDatabaseCreation +from django.utils.six.moves import input class DatabaseCreation(BaseDatabaseCreation): # SQLite doesn't actually support most of these types, but it "does the right @@ -53,7 +54,7 @@ class DatabaseCreation(BaseDatabaseCreation): print("Destroying old test database '%s'..." % self.connection.alias) if os.access(test_database_name, os.F_OK): if not autoclobber: - confirm = raw_input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % test_database_name) + confirm = input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % test_database_name) if autoclobber or confirm == 'yes': try: os.remove(test_database_name) diff --git a/django/db/backends/util.py b/django/db/backends/util.py index 32e0f4f702..9d70248ebf 100644 --- a/django/db/backends/util.py +++ b/django/db/backends/util.py @@ -1,9 +1,12 @@ +from __future__ import unicode_literals + import datetime import decimal import hashlib from time import time from django.conf import settings +from django.utils.encoding import smart_bytes from django.utils.log import getLogger from django.utils.timezone import utc @@ -135,7 +138,7 @@ def truncate_name(name, length=None, hash_len=4): if length is None or len(name) <= length: return name - hsh = hashlib.md5(name).hexdigest()[:hash_len] + hsh = hashlib.md5(smart_bytes(name)).hexdigest()[:hash_len] return '%s%s' % (name[:length-hash_len], hsh) def format_number(value, max_digits, decimal_places): @@ -146,6 +149,6 @@ def format_number(value, max_digits, decimal_places): if isinstance(value, decimal.Decimal): context = decimal.getcontext().copy() context.prec = max_digits - return u'%s' % str(value.quantize(decimal.Decimal(".1") ** decimal_places, context=context)) + return '%s' % str(value.quantize(decimal.Decimal(".1") ** decimal_places, context=context)) else: - return u"%.*f" % (decimal_places, value) + return "%.*f" % (decimal_places, value) diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 3582720e55..2ad89b3ed5 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -15,8 +15,6 @@ from django.db.models.deletion import CASCADE, PROTECT, SET, SET_NULL, SET_DEFAU from django.db.models import signals from django.utils.decorators import wraps -# Admin stages. -ADD, CHANGE, BOTH = 1, 2, 3 def permalink(func): """ diff --git a/django/db/models/base.py b/django/db/models/base.py index c35979cd6a..e3418d5a1c 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1,7 +1,9 @@ +from __future__ import unicode_literals + import copy import sys from functools import update_wrapper -from future_builtins import zip +from django.utils.six.moves import zip import django.db.models.manager # Imported to register signal handler. from django.conf import settings @@ -14,24 +16,52 @@ from django.db.models.fields.related import (ManyToOneRel, from django.db import (router, transaction, DatabaseError, DEFAULT_DB_ALIAS) from django.db.models.query import Q -from django.db.models.query_utils import DeferredAttribute +from django.db.models.query_utils import DeferredAttribute, deferred_class_factory from django.db.models.deletion import Collector from django.db.models.options import Options from django.db.models import signals from django.db.models.loading import register_models, get_model from django.utils.translation import ugettext_lazy as _ from django.utils.functional import curry -from django.utils.encoding import smart_str, force_unicode +from django.utils.encoding import smart_str, force_text +from django.utils import six from django.utils.text import get_text_list, capfirst +def subclass_exception(name, parents, module, attached_to=None): + """ + Create exception subclass. Used by ModelBase below. + + If 'attached_to' is supplied, the exception will be created in a way that + allows it to be pickled, assuming the returned exception class will be added + as an attribute to the 'attached_to' class. + """ + class_dict = {'__module__': module} + if attached_to is not None: + def __reduce__(self): + # Exceptions are special - they've got state that isn't + # in self.__dict__. We assume it is all in self.args. + return (unpickle_inner_exception, (attached_to, name), self.args) + + def __setstate__(self, args): + self.args = args + + class_dict['__reduce__'] = __reduce__ + class_dict['__setstate__'] = __setstate__ + + return type(name, parents, class_dict) + + class ModelBase(type): """ Metaclass for all models. """ def __new__(cls, name, bases, attrs): super_new = super(ModelBase, cls).__new__ - parents = [b for b in bases if isinstance(b, ModelBase)] + # six.with_metaclass() inserts an extra class called 'NewBase' in the + # inheritance tree: Model -> NewBase -> object. Ignore this class. + parents = [b for b in bases if isinstance(b, ModelBase) and + not (b.__name__ == 'NewBase' and b.__mro__ == (b, object))] if not parents: # If this isn't a subclass of Model, don't do anything special. return super_new(cls, name, bases, attrs) @@ -57,14 +87,16 @@ class ModelBase(type): new_class.add_to_class('_meta', Options(meta, **kwargs)) if not abstract: - new_class.add_to_class('DoesNotExist', subclass_exception(b'DoesNotExist', + new_class.add_to_class('DoesNotExist', subclass_exception(str('DoesNotExist'), tuple(x.DoesNotExist - for x in parents if hasattr(x, '_meta') and not x._meta.abstract) - or (ObjectDoesNotExist,), module)) - new_class.add_to_class('MultipleObjectsReturned', subclass_exception(b'MultipleObjectsReturned', + for x in parents if hasattr(x, '_meta') and not x._meta.abstract) + or (ObjectDoesNotExist,), + module, attached_to=new_class)) + new_class.add_to_class('MultipleObjectsReturned', subclass_exception(str('MultipleObjectsReturned'), tuple(x.MultipleObjectsReturned - for x in parents if hasattr(x, '_meta') and not x._meta.abstract) - or (MultipleObjectsReturned,), module)) + for x in parents if hasattr(x, '_meta') and not x._meta.abstract) + or (MultipleObjectsReturned,), + module, attached_to=new_class)) if base_meta and not base_meta.abstract: # Non-abstract child classes inherit some attributes from their # non-abstract parent (unless an ABC comes before it in the @@ -274,8 +306,7 @@ class ModelState(object): self.adding = True -class Model(object): - __metaclass__ = ModelBase +class Model(six.with_metaclass(ModelBase, object)): _deferred = False def __init__(self, *args, **kwargs): @@ -360,27 +391,27 @@ class Model(object): setattr(self, field.attname, val) if kwargs: - for prop in kwargs.keys(): + for prop in list(kwargs): try: if isinstance(getattr(self.__class__, prop), property): setattr(self, prop, kwargs.pop(prop)) except AttributeError: pass if kwargs: - raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]) + raise TypeError("'%s' is an invalid keyword argument for this function" % list(kwargs)[0]) super(Model, self).__init__() signals.post_init.send(sender=self.__class__, instance=self) def __repr__(self): try: - u = unicode(self) + u = six.text_type(self) except (UnicodeEncodeError, UnicodeDecodeError): u = '[Bad Unicode data]' - return smart_str(u'<%s: %s>' % (self.__class__.__name__, u)) + return smart_str('<%s: %s>' % (self.__class__.__name__, u)) def __str__(self): - if hasattr(self, '__unicode__'): - return force_unicode(self).encode('utf-8') + if not six.PY3 and hasattr(self, '__unicode__'): + return force_text(self).encode('utf-8') return '%s object' % self.__class__.__name__ def __eq__(self, other): @@ -399,25 +430,16 @@ class Model(object): need to do things manually, as they're dynamically created classes and only module-level classes can be pickled by the default path. """ + if not self._deferred: + return super(Model, self).__reduce__() data = self.__dict__ - model = self.__class__ - # The obvious thing to do here is to invoke super().__reduce__() - # for the non-deferred case. Don't do that. - # On Python 2.4, there is something weird with __reduce__, - # and as a result, the super call will cause an infinite recursion. - # See #10547 and #12121. defers = [] - if self._deferred: - from django.db.models.query_utils import deferred_class_factory - factory = deferred_class_factory - for field in self._meta.fields: - if isinstance(self.__class__.__dict__.get(field.attname), - DeferredAttribute): - defers.append(field.attname) - model = self._meta.proxy_for_model - else: - factory = simple_class_factory - return (model_unpickle, (model, defers, factory), data) + for field in self._meta.fields: + if isinstance(self.__class__.__dict__.get(field.attname), + DeferredAttribute): + defers.append(field.attname) + model = self._meta.proxy_for_model + return (model_unpickle, (model, defers), data) def _get_pk_val(self, meta=None): if not meta: @@ -456,6 +478,7 @@ class Model(object): that the "save" must be an SQL insert or update (or equivalent for non-SQL backends), respectively. Normally, they should not be set. """ + using = using or router.db_for_write(self.__class__, instance=self) if force_insert and (force_update or update_fields): raise ValueError("Cannot force both insert and updating in model saving.") @@ -467,8 +490,15 @@ class Model(object): return update_fields = frozenset(update_fields) - field_names = set([field.name for field in self._meta.fields - if not field.primary_key]) + field_names = set() + + for field in self._meta.fields: + if not field.primary_key: + field_names.add(field.name) + + if field.name != field.attname: + field_names.add(field.attname) + non_model_fields = update_fields.difference(field_names) if non_model_fields: @@ -476,6 +506,23 @@ class Model(object): "model or are m2m fields: %s" % ', '.join(non_model_fields)) + # If saving to the same database, and this model is deferred, then + # automatically do a "update_fields" save on the loaded fields. + elif not force_insert and self._deferred and using == self._state.db: + field_names = set() + for field in self._meta.fields: + if not field.primary_key and not hasattr(field, 'through'): + field_names.add(field.attname) + deferred_fields = [ + f.attname for f in self._meta.fields + if f.attname not in self.__dict__ + and isinstance(self.__class__.__dict__[f.attname], + DeferredAttribute)] + + loaded_fields = field_names.difference(deferred_fields) + if loaded_fields: + update_fields = frozenset(loaded_fields) + self.save_base(using=using, force_insert=force_insert, force_update=force_update, update_fields=update_fields) save.alters_data = True @@ -533,7 +580,7 @@ class Model(object): non_pks = [f for f in meta.local_fields if not f.primary_key] if update_fields: - non_pks = [f for f in non_pks if f.name in update_fields] + non_pks = [f for f in non_pks if f.name in update_fields or f.attname in update_fields] # First, try an UPDATE. If that doesn't update anything, do an INSERT. pk_val = self._get_pk_val(meta) @@ -602,14 +649,14 @@ class Model(object): def _get_FIELD_display(self, field): value = getattr(self, field.attname) - return force_unicode(dict(field.flatchoices).get(value, value), strings_only=True) + return force_text(dict(field.flatchoices).get(value, value), strings_only=True) def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs): if not self.pk: raise ValueError("get_next/get_previous cannot be used on unsaved objects.") op = is_next and 'gt' or 'lt' order = not is_next and '-' or '' - param = smart_str(getattr(self, field.attname)) + param = force_text(getattr(self, field.attname)) q = Q(**{'%s__%s' % (field.name, op): param}) q = q | Q(**{field.name: param, 'pk__%s' % op: self.pk}) qs = self.__class__._default_manager.using(self._state.db).filter(**kwargs).filter(q).order_by('%s%s' % (order, field.name), '%spk' % order) @@ -734,7 +781,7 @@ class Model(object): lookup_kwargs[str(field_name)] = lookup_value # some fields were skipped, no reason to do the check - if len(unique_check) != len(lookup_kwargs.keys()): + if len(unique_check) != len(lookup_kwargs): continue qs = model_class._default_manager.filter(**lookup_kwargs) @@ -788,9 +835,9 @@ class Model(object): def date_error_message(self, lookup_type, field, unique_for): opts = self._meta - return _(u"%(field_name)s must be unique for %(date_field)s %(lookup)s.") % { - 'field_name': unicode(capfirst(opts.get_field(field).verbose_name)), - 'date_field': unicode(capfirst(opts.get_field(unique_for).verbose_name)), + return _("%(field_name)s must be unique for %(date_field)s %(lookup)s.") % { + 'field_name': six.text_type(capfirst(opts.get_field(field).verbose_name)), + 'date_field': six.text_type(capfirst(opts.get_field(unique_for).verbose_name)), 'lookup': lookup_type, } @@ -805,16 +852,16 @@ class Model(object): field_label = capfirst(field.verbose_name) # Insert the error into the error dict, very sneaky return field.error_messages['unique'] % { - 'model_name': unicode(model_name), - 'field_label': unicode(field_label) + 'model_name': six.text_type(model_name), + 'field_label': six.text_type(field_label) } # unique_together else: - field_labels = map(lambda f: capfirst(opts.get_field(f).verbose_name), unique_check) + field_labels = [capfirst(opts.get_field(f).verbose_name) for f in unique_check] field_labels = get_text_list(field_labels, _('and')) - return _(u"%(model_name)s with this %(field_label)s already exists.") % { - 'model_name': unicode(model_name), - 'field_label': unicode(field_labels) + return _("%(model_name)s with this %(field_label)s already exists.") % { + 'model_name': six.text_type(model_name), + 'field_label': six.text_type(field_labels) } def full_clean(self, exclude=None): @@ -918,24 +965,16 @@ class Empty(object): pass -def simple_class_factory(model, attrs): - """Used to unpickle Models without deferred fields. - - We need to do this the hard way, rather than just using - the default __reduce__ implementation, because of a - __deepcopy__ problem in Python 2.4 - """ - return model - - -def model_unpickle(model, attrs, factory): +def model_unpickle(model, attrs): """ Used to unpickle Model subclasses with deferred fields. """ - cls = factory(model, attrs) + cls = deferred_class_factory(model, attrs) return cls.__new__(cls) model_unpickle.__safe_for_unpickle__ = True -def subclass_exception(name, parents, module): - return type(name, parents, {'__module__': module}) +def unpickle_inner_exception(klass, exception_name): + # Get the exception class from the class it is attached to: + exception = getattr(klass, exception_name) + return exception.__new__(exception) diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index d8bb8f2e66..4449b75a81 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -4,6 +4,7 @@ from operator import attrgetter from django.db import connections, transaction, IntegrityError from django.db.models import signals, sql from django.utils.datastructures import SortedDict +from django.utils import six class ProtectedError(IntegrityError): @@ -157,7 +158,7 @@ class Collector(object): # Recursively collect concrete model's parent models, but not their # related objects. These will be found by meta.get_all_related_objects() concrete_model = model._meta.concrete_model - for ptr in concrete_model._meta.parents.itervalues(): + for ptr in six.itervalues(concrete_model._meta.parents): if ptr: parent_objs = [getattr(obj, ptr.name) for obj in new_objs] self.collect(parent_objs, source=model, @@ -199,14 +200,14 @@ class Collector(object): ) def instances_with_model(self): - for model, instances in self.data.iteritems(): + for model, instances in six.iteritems(self.data): for obj in instances: yield model, obj def sort(self): sorted_models = [] concrete_models = set() - models = self.data.keys() + models = list(self.data) while len(sorted_models) < len(models): found = False for model in models: @@ -241,24 +242,24 @@ class Collector(object): ) # update fields - for model, instances_for_fieldvalues in self.field_updates.iteritems(): + for model, instances_for_fieldvalues in six.iteritems(self.field_updates): query = sql.UpdateQuery(model) - for (field, value), instances in instances_for_fieldvalues.iteritems(): + for (field, value), instances in six.iteritems(instances_for_fieldvalues): query.update_batch([obj.pk for obj in instances], {field.name: value}, self.using) # reverse instance collections - for instances in self.data.itervalues(): + for instances in six.itervalues(self.data): instances.reverse() # delete batches - for model, batches in self.batches.iteritems(): + for model, batches in six.iteritems(self.batches): query = sql.DeleteQuery(model) - for field, instances in batches.iteritems(): + for field, instances in six.iteritems(batches): query.delete_batch([obj.pk for obj in instances], self.using, field) # delete instances - for model, instances in self.data.iteritems(): + for model, instances in six.iteritems(self.data): query = sql.DeleteQuery(model) pk_list = [obj.pk for obj in instances] query.delete_batch(pk_list, self.using) @@ -271,10 +272,10 @@ class Collector(object): ) # update collected instances - for model, instances_for_fieldvalues in self.field_updates.iteritems(): - for (field, value), instances in instances_for_fieldvalues.iteritems(): + for model, instances_for_fieldvalues in six.iteritems(self.field_updates): + for (field, value), instances in six.iteritems(instances_for_fieldvalues): for obj in instances: setattr(obj, field.attname, value) - for model, instances in self.data.iteritems(): + for model, instances in six.iteritems(self.data): for instance in instances: setattr(instance, model._meta.pk.attname, None) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index a71f4a33ef..639ef6ee10 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -58,8 +58,9 @@ class ExpressionNode(tree.Node): def __mul__(self, other): return self._combine(other, self.MUL, False) - def __div__(self, other): + def __truediv__(self, other): return self._combine(other, self.DIV, False) + __div__ = __truediv__ # Python 2 compatibility def __mod__(self, other): return self._combine(other, self.MOD, False) @@ -79,8 +80,9 @@ class ExpressionNode(tree.Node): def __rmul__(self, other): return self._combine(other, self.MUL, True) - def __rdiv__(self, other): + def __rtruediv__(self, other): return self._combine(other, self.DIV, True) + __rdiv__ = __rtruediv__ # Python 2 compatibility def __rmod__(self, other): return self._combine(other, self.MOD, True) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index d572cce28f..d07851bbf5 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import collections import copy import datetime @@ -17,8 +19,9 @@ from django.utils.functional import curry, total_ordering from django.utils.text import capfirst from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode, force_unicode +from django.utils.encoding import smart_text, force_text from django.utils.ipv6 import clean_ipv6_address +from django.utils import six class NOT_PROVIDED: pass @@ -61,16 +64,16 @@ class Field(object): auto_creation_counter = -1 default_validators = [] # Default set of validators default_error_messages = { - 'invalid_choice': _(u'Value %r is not a valid choice.'), - 'null': _(u'This field cannot be null.'), - 'blank': _(u'This field cannot be blank.'), - 'unique': _(u'%(model_name)s with this %(field_label)s ' - u'already exists.'), + 'invalid_choice': _('Value %r is not a valid choice.'), + 'null': _('This field cannot be null.'), + 'blank': _('This field cannot be blank.'), + 'unique': _('%(model_name)s with this %(field_label)s ' + 'already exists.'), } # Generic field type description, usually overriden by subclasses def _description(self): - return _(u'Field of type: %(field_type)s') % { + return _('Field of type: %(field_type)s') % { 'field_type': self.__class__.__name__ } description = property(_description) @@ -132,6 +135,8 @@ class Field(object): return self.creation_counter < other.creation_counter return NotImplemented + __hash__ = object.__hash__ + def __deepcopy__(self, memodict): # We don't have to deepcopy very much here, since most things are not # intended to be altered after initial creation. @@ -176,7 +181,8 @@ class Field(object): if not self.editable: # Skip validation for non-editable fields. return - if self._choices and value: + + if self._choices and value not in validators.EMPTY_VALUES: for option_key, option_value in self.choices: if isinstance(option_value, (list, tuple)): # This is an optgroup, so look inside the group for @@ -382,7 +388,7 @@ class Field(object): if self.has_default(): if callable(self.default): return self.default() - return force_unicode(self.default, strings_only=True) + return force_text(self.default, strings_only=True) if (not self.empty_strings_allowed or (self.null and not connection.features.interprets_empty_strings_as_nulls)): return None @@ -400,11 +406,11 @@ class Field(object): rel_model = self.rel.to if hasattr(self.rel, 'get_related_field'): lst = [(getattr(x, self.rel.get_related_field().attname), - smart_unicode(x)) + smart_text(x)) for x in rel_model._default_manager.complex_filter( self.rel.limit_choices_to)] else: - lst = [(x._get_pk_val(), smart_unicode(x)) + lst = [(x._get_pk_val(), smart_text(x)) for x in rel_model._default_manager.complex_filter( self.rel.limit_choices_to)] return first_choice + lst @@ -431,7 +437,7 @@ class Field(object): Returns a string value of this field from the passed obj. This is used by the serialization framework. """ - return smart_unicode(self._get_val_from_obj(obj)) + return smart_text(self._get_val_from_obj(obj)) def bind(self, fieldmapping, original, bound_field_class): return bound_field_class(self, fieldmapping, original) @@ -483,7 +489,7 @@ class Field(object): # Many of the subclass-specific formfield arguments (min_value, # max_value) don't apply for choice fields, so be sure to only pass # the values that TypedChoiceField will understand. - for k in kwargs.keys(): + for k in list(kwargs): if k not in ('coerce', 'empty_value', 'choices', 'required', 'widget', 'label', 'initial', 'help_text', 'error_messages', 'show_hidden_initial'): @@ -512,7 +518,7 @@ class AutoField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value must be an integer."), + 'invalid': _("'%s' value must be an integer."), } def __init__(self, *args, **kwargs): @@ -560,7 +566,7 @@ class AutoField(Field): class BooleanField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value must be either True or False."), + 'invalid': _("'%s' value must be either True or False."), } description = _("Boolean (Either True or False)") @@ -623,9 +629,9 @@ class CharField(Field): return "CharField" def to_python(self, value): - if isinstance(value, basestring) or value is None: + if isinstance(value, six.string_types) or value is None: return value - return smart_unicode(value) + return smart_text(value) def get_prep_value(self, value): return self.to_python(value) @@ -646,7 +652,7 @@ class CommaSeparatedIntegerField(CharField): def formfield(self, **kwargs): defaults = { 'error_messages': { - 'invalid': _(u'Enter only digits separated by commas.'), + 'invalid': _('Enter only digits separated by commas.'), } } defaults.update(kwargs) @@ -655,10 +661,10 @@ class CommaSeparatedIntegerField(CharField): class DateField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value has an invalid date format. It must be " - u"in YYYY-MM-DD format."), - 'invalid_date': _(u"'%s' value has the correct format (YYYY-MM-DD) " - u"but it is an invalid date."), + 'invalid': _("'%s' value has an invalid date format. It must be " + "in YYYY-MM-DD format."), + 'invalid_date': _("'%s' value has the correct format (YYYY-MM-DD) " + "but it is an invalid date."), } description = _("Date (without time)") @@ -743,13 +749,13 @@ class DateField(Field): class DateTimeField(DateField): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value has an invalid format. It must be in " - u"YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."), - 'invalid_date': _(u"'%s' value has the correct format " - u"(YYYY-MM-DD) but it is an invalid date."), - 'invalid_datetime': _(u"'%s' value has the correct format " - u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " - u"but it is an invalid date/time."), + 'invalid': _("'%s' value has an invalid format. It must be in " + "YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."), + 'invalid_date': _("'%s' value has the correct format " + "(YYYY-MM-DD) but it is an invalid date."), + 'invalid_datetime': _("'%s' value has the correct format " + "(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " + "but it is an invalid date/time."), } description = _("Date (with time)") @@ -770,8 +776,8 @@ class DateTimeField(DateField): # local time. This won't work during DST change, but we can't # do much about it, so we let the exceptions percolate up the # call stack. - warnings.warn(u"DateTimeField received a naive datetime (%s)" - u" while time zone support is active." % value, + warnings.warn("DateTimeField received a naive datetime (%s)" + " while time zone support is active." % value, RuntimeWarning) default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) @@ -815,8 +821,8 @@ class DateTimeField(DateField): # For backwards compatibility, interpret naive datetimes in local # time. This won't work during DST change, but we can't do much # about it, so we let the exceptions percolate up the call stack. - warnings.warn(u"DateTimeField received a naive datetime (%s)" - u" while time zone support is active." % value, + warnings.warn("DateTimeField received a naive datetime (%s)" + " while time zone support is active." % value, RuntimeWarning) default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) @@ -840,7 +846,7 @@ class DateTimeField(DateField): class DecimalField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value must be a decimal number."), + 'invalid': _("'%s' value must be a decimal number."), } description = _("Decimal number") @@ -862,7 +868,7 @@ class DecimalField(Field): raise exceptions.ValidationError(msg) def _format(self, value): - if isinstance(value, basestring) or value is None: + if isinstance(value, six.string_types) or value is None: return value else: return self.format_number(value) @@ -1183,9 +1189,9 @@ class TextField(Field): return "TextField" def get_prep_value(self, value): - if isinstance(value, basestring) or value is None: + if isinstance(value, six.string_types) or value is None: return value - return smart_unicode(value) + return smart_text(value) def formfield(self, **kwargs): defaults = {'widget': forms.Textarea} @@ -1195,10 +1201,10 @@ class TextField(Field): class TimeField(Field): empty_strings_allowed = False default_error_messages = { - 'invalid': _(u"'%s' value has an invalid format. It must be in " - u"HH:MM[:ss[.uuuuuu]] format."), - 'invalid_time': _(u"'%s' value has the correct format " - u"(HH:MM[:ss[.uuuuuu]]) but it is an invalid time."), + 'invalid': _("'%s' value has an invalid format. It must be in " + "HH:MM[:ss[.uuuuuu]] format."), + 'invalid_time': _("'%s' value has the correct format " + "(HH:MM[:ss[.uuuuuu]]) but it is an invalid time."), } description = _("Time") diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index 34cf84dbc0..adb9c16fed 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -8,7 +8,8 @@ from django.core.files.base import File from django.core.files.storage import default_storage from django.core.files.images import ImageFile from django.db.models import signals -from django.utils.encoding import force_unicode, smart_str +from django.utils.encoding import force_text, smart_str +from django.utils import six from django.utils.translation import ugettext_lazy as _ class FieldFile(File): @@ -176,7 +177,7 @@ class FileDescriptor(object): # subclasses might also want to subclass the attribute class]. This # object understands how to convert a path to a file, and also how to # handle None. - if isinstance(file, basestring) or file is None: + if isinstance(file, six.string_types) or file is None: attr = self.field.attr_class(instance, self.field, file) instance.__dict__[self.field.name] = attr @@ -207,7 +208,7 @@ class FileDescriptor(object): class FileField(Field): default_error_messages = { - 'max_length': _(u'Filename is %(extra)d characters too long.') + 'max_length': _('Filename is %(extra)d characters too long.') } # The class to wrap instance attributes in. Accessing the file object off @@ -242,7 +243,11 @@ class FileField(Field): # (ie. upload_to='path/to/upload/dir'), the length of the generated # name equals the length of the uploaded name plus a constant. Thus # we can tell the user how much shorter the name should be (roughly). - length = len(self.generate_filename(model_instance, value.name)) + if value and value._committed: + filename = value.name + else: + filename = self.generate_filename(model_instance, value.name) + length = len(filename) if self.max_length and length > self.max_length: error_values = {'extra': length - self.max_length} raise ValidationError(self.error_messages['max_length'] % error_values) @@ -260,7 +265,7 @@ class FileField(Field): # Need to convert File objects provided via a form to unicode for database insertion if value is None: return None - return unicode(value) + return six.text_type(value) def pre_save(self, model_instance, add): "Returns field's value just before saving." @@ -275,7 +280,7 @@ class FileField(Field): setattr(cls, self.name, self.descriptor_class(self)) def get_directory_name(self): - return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to)))) + return os.path.normpath(force_text(datetime.datetime.now().strftime(smart_str(self.upload_to)))) def get_filename(self, filename): return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename))) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index ee71f509aa..c065162aa0 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -9,7 +9,8 @@ from django.db.models.related import RelatedObject from django.db.models.query import QuerySet from django.db.models.query_utils import QueryWrapper from django.db.models.deletion import CASCADE -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text +from django.utils import six from django.utils.translation import ugettext_lazy as _, string_concat from django.utils.functional import curry, cached_property from django.core import exceptions @@ -107,7 +108,7 @@ class RelatedField(object): } other = self.rel.to - if isinstance(other, basestring) or other._meta.pk is None: + if isinstance(other, six.string_types) or other._meta.pk is None: def resolve_related_class(field, model, cls): field.rel.to = model field.do_related_class(model, cls) @@ -244,7 +245,7 @@ class SingleRelatedObjectDescriptor(object): rel_obj_attr = attrgetter(self.related.field.attname) instance_attr = lambda obj: obj._get_pk_val() instances_dict = dict((instance_attr(inst), inst) for inst in instances) - params = {'%s__pk__in' % self.related.field.name: instances_dict.keys()} + params = {'%s__pk__in' % self.related.field.name: list(instances_dict)} qs = self.get_query_set(instance=instances[0]).filter(**params) # Since we're going to assign directly in the cache, # we must manage the reverse relation cache manually. @@ -334,14 +335,14 @@ class ReverseSingleRelatedObjectDescriptor(object): return QuerySet(self.field.rel.to).using(db) def get_prefetch_query_set(self, instances): - rel_obj_attr = attrgetter(self.field.rel.field_name) + other_field = self.field.rel.get_related_field() + rel_obj_attr = attrgetter(other_field.attname) instance_attr = attrgetter(self.field.attname) instances_dict = dict((instance_attr(inst), inst) for inst in instances) - other_field = self.field.rel.get_related_field() if other_field.rel: - params = {'%s__pk__in' % self.field.rel.field_name: instances_dict.keys()} + params = {'%s__pk__in' % self.field.rel.field_name: list(instances_dict)} else: - params = {'%s__in' % self.field.rel.field_name: instances_dict.keys()} + params = {'%s__in' % self.field.rel.field_name: list(instances_dict)} qs = self.get_query_set(instance=instances[0]).filter(**params) # Since we're going to assign directly in the cache, # we must manage the reverse relation cache manually. @@ -364,7 +365,7 @@ class ReverseSingleRelatedObjectDescriptor(object): else: other_field = self.field.rel.get_related_field() if other_field.rel: - params = {'%s__pk' % self.field.rel.field_name: val} + params = {'%s__%s' % (self.field.rel.field_name, other_field.rel.field_name): val} else: params = {'%s__exact' % self.field.rel.field_name: val} qs = self.get_query_set(instance=instance) @@ -489,11 +490,11 @@ class ForeignRelatedObjectsDescriptor(object): return qs def get_prefetch_query_set(self, instances): - rel_obj_attr = attrgetter(rel_field.get_attname()) + rel_obj_attr = attrgetter(rel_field.attname) instance_attr = attrgetter(attname) instances_dict = dict((instance_attr(inst), inst) for inst in instances) db = self._db or router.db_for_read(self.model, instance=instances[0]) - query = {'%s__%s__in' % (rel_field.name, attname): instances_dict.keys()} + query = {'%s__%s__in' % (rel_field.name, attname): list(instances_dict)} qs = super(RelatedManager, self).get_query_set().using(db).filter(**query) # Since we just bypassed this class' get_query_set(), we must manage # the reverse relation manually. @@ -873,7 +874,7 @@ class ManyToOneRel(object): try: to._meta except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT - assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT + assert isinstance(to, six.string_types), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT self.to, self.field_name = to, field_name self.related_name = related_name if limit_choices_to is None: @@ -943,9 +944,9 @@ class ForeignKey(RelatedField, Field): def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): try: - to._meta.object_name.lower() + to_name = to._meta.object_name.lower() except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT - assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) + assert isinstance(to, six.string_types), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) else: assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) # For backwards compatibility purposes, we need to *try* and set @@ -1010,13 +1011,13 @@ class ForeignKey(RelatedField, Field): if not self.blank and self.choices: choice_list = self.get_choices_default() if len(choice_list) == 2: - return smart_unicode(choice_list[1][0]) + return smart_text(choice_list[1][0]) return Field.value_to_string(self, obj) def contribute_to_class(self, cls, name): super(ForeignKey, self).contribute_to_class(cls, name) setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self)) - if isinstance(self.rel.to, basestring): + if isinstance(self.rel.to, six.string_types): target = self.rel.to else: target = self.rel.to._meta.db_table @@ -1034,7 +1035,7 @@ class ForeignKey(RelatedField, Field): def formfield(self, **kwargs): db = kwargs.pop('using', None) - if isinstance(self.rel.to, basestring): + if isinstance(self.rel.to, six.string_types): raise ValueError("Cannot create form field for %r yet, because " "its related model %r has not been loaded yet" % (self.name, self.rel.to)) @@ -1094,14 +1095,14 @@ class OneToOneField(ForeignKey): def create_many_to_many_intermediary_model(field, klass): from django.db import models managed = True - if isinstance(field.rel.to, basestring) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT: + if isinstance(field.rel.to, six.string_types) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT: to_model = field.rel.to to = to_model.split('.')[-1] def set_managed(field, model, cls): field.rel.through._meta.managed = model._meta.managed or cls._meta.managed add_lazy_relation(klass, field, to_model, set_managed) - elif isinstance(field.rel.to, basestring): + elif isinstance(field.rel.to, six.string_types): to = klass._meta.object_name to_model = klass managed = klass._meta.managed @@ -1142,7 +1143,7 @@ class ManyToManyField(RelatedField, Field): try: assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT - assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) + assert isinstance(to, six.string_types), "%s(%r) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) # Python 2.6 and earlier require dictionary keys to be of str type, # not unicode and class names must be ASCII (in Python 2.x), so we # forcibly coerce it here (breaks early if there's a problem). @@ -1222,7 +1223,7 @@ class ManyToManyField(RelatedField, Field): choices_list = self.get_choices_default() if len(choices_list) == 1: data = [choices_list[0][0]] - return smart_unicode(data) + return smart_text(data) def contribute_to_class(self, cls, name): # To support multiple relations to self, it's useful to have a non-None @@ -1251,12 +1252,12 @@ class ManyToManyField(RelatedField, Field): # Populate some necessary rel arguments so that cross-app relations # work correctly. - if isinstance(self.rel.through, basestring): + if isinstance(self.rel.through, six.string_types): def resolve_through_model(field, model, cls): field.rel.through = model add_lazy_relation(cls, self, self.rel.through, resolve_through_model) - if isinstance(self.rel.to, basestring): + if isinstance(self.rel.to, six.string_types): target = self.rel.to else: target = self.rel.to._meta.db_table diff --git a/django/db/models/fields/subclassing.py b/django/db/models/fields/subclassing.py index d4d7d75222..e6153aefe0 100644 --- a/django/db/models/fields/subclassing.py +++ b/django/db/models/fields/subclassing.py @@ -2,8 +2,9 @@ Convenience routines for creating non-trivial Field subclasses, as well as backwards compatibility utilities. -Add SubfieldBase as the __metaclass__ for your Field subclass, implement -to_python() and the other necessary methods and everything will work seamlessly. +Add SubfieldBase as the metaclass for your Field subclass, implement +to_python() and the other necessary methods and everything will work +seamlessly. """ class SubfieldBase(type): diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 69e9126abc..8a0e796f4b 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -5,10 +5,11 @@ from django.core.exceptions import ImproperlyConfigured from django.utils.datastructures import SortedDict from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule +from django.utils import six +import imp import sys import os -import threading __all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models', 'load_app', 'app_cache_ready') @@ -40,7 +41,6 @@ class AppCache(object): handled = {}, postponed = [], nesting_level = 0, - write_lock = threading.RLock(), _get_models_cache = {}, ) @@ -55,7 +55,13 @@ class AppCache(object): """ if self.loaded: return - self.write_lock.acquire() + # Note that we want to use the import lock here - the app loading is + # in many cases initiated implicitly by importing, and thus it is + # possible to end up in deadlock when one thread initiates loading + # without holding the importer lock and another thread then tries to + # import something which also launches the app loading. For details of + # this situation see #18251. + imp.acquire_lock() try: if self.loaded: return @@ -68,7 +74,7 @@ class AppCache(object): self.load_app(app_name) self.loaded = True finally: - self.write_lock.release() + imp.release_lock() def _label_for(self, app_mod): """ @@ -139,7 +145,7 @@ class AppCache(object): the app has no models in it and 'emptyOK' is True, returns None. """ self._populate() - self.write_lock.acquire() + imp.acquire_lock() try: for app_name in settings.INSTALLED_APPS: if app_label == app_name.split('.')[-1]: @@ -152,7 +158,7 @@ class AppCache(object): return mod raise ImproperlyConfigured("App with label %s could not be found" % app_label) finally: - self.write_lock.release() + imp.release_lock() def get_app_errors(self): "Returns the map of known problems with the INSTALLED_APPS." @@ -189,9 +195,9 @@ class AppCache(object): else: if only_installed: app_list = [self.app_models.get(app_label, SortedDict()) - for app_label in self.app_labels.iterkeys()] + for app_label in six.iterkeys(self.app_labels)] else: - app_list = self.app_models.itervalues() + app_list = six.itervalues(self.app_models) model_list = [] for app in app_list: model_list.extend( diff --git a/django/db/models/options.py b/django/db/models/options.py index ac9b3cbf66..446aa54303 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import re from bisect import bisect @@ -8,8 +10,10 @@ from django.db.models.fields import AutoField, FieldDoesNotExist from django.db.models.fields.proxy import OrderWrt from django.db.models.loading import get_models, app_cache_ready from django.utils.translation import activate, deactivate_all, get_language, string_concat -from django.utils.encoding import force_unicode, smart_str +from django.utils.encoding import force_text, smart_text from django.utils.datastructures import SortedDict +from django.utils import six +from django.utils.encoding import python_2_unicode_compatible # Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces". get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip() @@ -20,6 +24,7 @@ DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering', 'abstract', 'managed', 'proxy', 'swappable', 'auto_created') +@python_2_unicode_compatible class Options(object): def __init__(self, meta, app_label=None): self.local_fields, self.local_many_to_many = [], [] @@ -128,7 +133,7 @@ class Options(object): if self.parents: # Promote the first parent link in lieu of adding yet another # field. - field = self.parents.value_for_index(0) + field = next(six.itervalues(self.parents)) # Look for a local field with the same name as the # first parent link. If a local field has already been # created, use it instead of promoting the parent @@ -148,13 +153,13 @@ class Options(object): # self.duplicate_targets will map each duplicate field column to the # columns it duplicates. collections = {} - for column, target in self.duplicate_targets.iteritems(): + for column, target in six.iteritems(self.duplicate_targets): try: collections[target].add(column) except KeyError: collections[target] = set([column]) self.duplicate_targets = {} - for elt in collections.itervalues(): + for elt in six.itervalues(collections): if len(elt) == 1: continue for column in elt: @@ -200,7 +205,7 @@ class Options(object): return '' % self.object_name def __str__(self): - return "%s.%s" % (smart_str(self.app_label), smart_str(self.module_name)) + return "%s.%s" % (smart_text(self.app_label), smart_text(self.module_name)) def verbose_name_raw(self): """ @@ -210,7 +215,7 @@ class Options(object): """ lang = get_language() deactivate_all() - raw = force_unicode(self.verbose_name) + raw = force_text(self.verbose_name) activate(lang) return raw verbose_name_raw = property(verbose_name_raw) @@ -267,7 +272,7 @@ class Options(object): self._m2m_cache except AttributeError: self._fill_m2m_cache() - return self._m2m_cache.keys() + return list(self._m2m_cache) many_to_many = property(_many_to_many) def get_m2m_with_model(self): @@ -278,7 +283,7 @@ class Options(object): self._m2m_cache except AttributeError: self._fill_m2m_cache() - return self._m2m_cache.items() + return list(six.iteritems(self._m2m_cache)) def _fill_m2m_cache(self): cache = SortedDict() @@ -335,8 +340,7 @@ class Options(object): cache = self._name_map except AttributeError: cache = self.init_name_map() - names = cache.keys() - names.sort() + names = sorted(cache.keys()) # Internal-only names end with "+" (symmetrical m2m related names being # the main example). Trim them. return [val for val in names if not val.endswith('+')] @@ -393,7 +397,7 @@ class Options(object): predicates.append(lambda k, v: not k.field.rel.is_hidden()) cache = (self._related_objects_proxy_cache if include_proxy_eq else self._related_objects_cache) - return filter(lambda t: all([p(*t) for p in predicates]), cache.items()) + return [t for t in cache.items() if all(p(*t) for p in predicates)] def _fill_related_objects_cache(self): cache = SortedDict() @@ -410,7 +414,7 @@ class Options(object): proxy_cache = cache.copy() for klass in get_models(include_auto_created=True, only_installed=False): for f in klass._meta.local_fields: - if f.rel and not isinstance(f.rel.to, basestring): + if f.rel and not isinstance(f.rel.to, six.string_types): if self == f.rel.to._meta: cache[RelatedObject(f.rel.to, klass, f)] = None proxy_cache[RelatedObject(f.rel.to, klass, f)] = None @@ -426,7 +430,7 @@ class Options(object): cache = self._fill_related_many_to_many_cache() if local_only: return [k for k, v in cache.items() if not v] - return cache.keys() + return list(cache) def get_all_related_m2m_objects_with_model(self): """ @@ -437,7 +441,7 @@ class Options(object): cache = self._related_many_to_many_cache except AttributeError: cache = self._fill_related_many_to_many_cache() - return cache.items() + return list(six.iteritems(cache)) def _fill_related_many_to_many_cache(self): cache = SortedDict() @@ -452,7 +456,7 @@ class Options(object): cache[obj] = model for klass in get_models(only_installed=False): for f in klass._meta.local_many_to_many: - if f.rel and not isinstance(f.rel.to, basestring) and self == f.rel.to._meta: + if f.rel and not isinstance(f.rel.to, six.string_types) and self == f.rel.to._meta: cache[RelatedObject(f.rel.to, klass, f)] = None if app_cache_ready(): self._related_many_to_many_cache = cache diff --git a/django/db/models/query.py b/django/db/models/query.py index 755820c3b0..090ef0b7be 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -14,6 +14,7 @@ from django.db.models.query_utils import (Q, select_related_descend, from django.db.models.deletion import Collector from django.db.models import sql from django.utils.functional import partition +from django.utils import six # Used to control how many objects are worked with at once in some cases (e.g. # when deleting objects). @@ -119,7 +120,7 @@ class QuerySet(object): if len(self._result_cache) <= pos: self._fill_cache() - def __nonzero__(self): + def __bool__(self): if self._prefetch_related_lookups and not self._prefetch_done: # We need all the results in order to be able to do the prefetch # in one go. To minimize code duplication, we use the __len__ @@ -133,6 +134,7 @@ class QuerySet(object): except StopIteration: return False return True + __nonzero__ = __bool__ # Python 2 def __contains__(self, val): # The 'in' operator works without this method, due to __iter__. This @@ -168,7 +170,7 @@ class QuerySet(object): """ Retrieves an item or slice from the set of results. """ - if not isinstance(k, (slice, int, long)): + if not isinstance(k, (slice,) + six.integer_types): raise TypeError assert ((not isinstance(k, slice) and (k >= 0)) or (isinstance(k, slice) and (k.start is None or k.start >= 0) @@ -244,8 +246,8 @@ class QuerySet(object): requested = None max_depth = self.query.max_depth - extra_select = self.query.extra_select.keys() - aggregate_select = self.query.aggregate_select.keys() + extra_select = list(self.query.extra_select) + aggregate_select = list(self.query.aggregate_select) only_load = self.query.get_loaded_field_names() if not fill_cache: @@ -388,7 +390,7 @@ class QuerySet(object): obj.save(force_insert=True, using=self.db) return obj - def bulk_create(self, objs): + def bulk_create(self, objs, batch_size=None): """ Inserts each of the instances into the database. This does *not* call save() on each of the instances, does not send any pre/post save @@ -401,8 +403,10 @@ class QuerySet(object): # this could be implemented if you didn't have an autoincrement pk, # and 2) you could do it by doing O(n) normal inserts into the parent # tables to get the primary keys back, and then doing a single bulk - # insert into the childmost table. We're punting on these for now - # because they are relatively rare cases. + # insert into the childmost table. Some databases might allow doing + # this by using RETURNING clause for the insert query. We're punting + # on these for now because they are relatively rare cases. + assert batch_size is None or batch_size > 0 if self.model._meta.parents: raise ValueError("Can't bulk create an inherited model") if not objs: @@ -418,13 +422,14 @@ class QuerySet(object): try: if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk and self.model._meta.has_auto_field): - self.model._base_manager._insert(objs, fields=fields, using=self.db) + self._batched_insert(objs, fields, batch_size) else: objs_with_pk, objs_without_pk = partition(lambda o: o.pk is None, objs) if objs_with_pk: - self.model._base_manager._insert(objs_with_pk, fields=fields, using=self.db) + self._batched_insert(objs_with_pk, fields, batch_size) if objs_without_pk: - self.model._base_manager._insert(objs_without_pk, fields=[f for f in fields if not isinstance(f, AutoField)], using=self.db) + fields= [f for f in fields if not isinstance(f, AutoField)] + self._batched_insert(objs_without_pk, fields, batch_size) if forced_managed: transaction.commit(using=self.db) else: @@ -467,7 +472,7 @@ class QuerySet(object): return self.get(**lookup), False except self.model.DoesNotExist: # Re-raise the IntegrityError with its original traceback. - raise exc_info[1], None, exc_info[2] + six.reraise(*exc_info) def latest(self, field_name=None): """ @@ -589,7 +594,7 @@ class QuerySet(object): flat = kwargs.pop('flat', False) if kwargs: raise TypeError('Unexpected keyword arguments to values_list: %s' - % (kwargs.keys(),)) + % (list(kwargs),)) if flat and len(fields) > 1: raise TypeError("'flat' is not valid when values_list is called with more than one field.") return self._clone(klass=ValuesListQuerySet, setup=True, flat=flat, @@ -689,7 +694,7 @@ class QuerySet(object): depth = kwargs.pop('depth', 0) if kwargs: raise TypeError('Unexpected keyword arguments to select_related: %s' - % (kwargs.keys(),)) + % (list(kwargs),)) obj = self._clone() if fields: if depth: @@ -747,7 +752,7 @@ class QuerySet(object): obj = self._clone() - obj._setup_aggregate_query(kwargs.keys()) + obj._setup_aggregate_query(list(kwargs)) # Add the aggregates to the query for (alias, aggregate_expr) in kwargs.items(): @@ -860,6 +865,20 @@ class QuerySet(object): ################### # PRIVATE METHODS # ################### + def _batched_insert(self, objs, fields, batch_size): + """ + A little helper method for bulk_insert to insert the bulk one batch + at a time. Inserts recursively a batch from the front of the bulk and + then _batched_insert() the remaining objects again. + """ + if not objs: + return + ops = connections[self.db].ops + batch_size = (batch_size or max(ops.bulk_batch_size(fields, objs), 1)) + for batch in [objs[i:i+batch_size] + for i in range(0, len(objs), batch_size)]: + self.model._base_manager._insert(batch, fields=fields, + using=self.db) def _clone(self, klass=None, setup=False, **kwargs): if klass is None: @@ -948,9 +967,9 @@ class ValuesQuerySet(QuerySet): def iterator(self): # Purge any extra columns that haven't been explicitly asked for - extra_names = self.query.extra_select.keys() + extra_names = list(self.query.extra_select) field_names = self.field_names - aggregate_names = self.query.aggregate_select.keys() + aggregate_names = list(self.query.aggregate_select) names = extra_names + field_names + aggregate_names @@ -1079,16 +1098,16 @@ class ValuesListQuerySet(ValuesQuerySet): # When extra(select=...) or an annotation is involved, the extra # cols are always at the start of the row, and we need to reorder # the fields to match the order in self._fields. - extra_names = self.query.extra_select.keys() + extra_names = list(self.query.extra_select) field_names = self.field_names - aggregate_names = self.query.aggregate_select.keys() + aggregate_names = list(self.query.aggregate_select) names = extra_names + field_names + aggregate_names # If a field list has been specified, use it. Otherwise, use the # full list of fields, including extras and aggregates. if self._fields: - fields = list(self._fields) + filter(lambda f: f not in self._fields, aggregate_names) + fields = list(self._fields) + [f for f in aggregate_names if f not in self._fields] else: fields = names @@ -1296,7 +1315,7 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None, # Build the list of fields that *haven't* been requested for field, model in klass._meta.get_fields_with_model(): if field.name not in load_fields: - skip.add(field.name) + skip.add(field.attname) elif local_only and model is not None: continue else: @@ -1327,7 +1346,7 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None, related_fields = [] for f in klass._meta.fields: - if select_related_descend(f, restricted, requested): + if select_related_descend(f, restricted, requested, load_fields): if restricted: next = requested[f.name] else: @@ -1339,7 +1358,8 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None, reverse_related_fields = [] if restricted: for o in klass._meta.get_all_related_objects(): - if o.field.unique and select_related_descend(o.field, restricted, requested, reverse=True): + if o.field.unique and select_related_descend(o.field, restricted, requested, + only_load.get(o.model), reverse=True): next = requested[o.field.related_query_name()] klass_info = get_klass_info(o.model, max_depth=max_depth, cur_depth=cur_depth+1, requested=next, only_load=only_load, local_only=True) @@ -1508,7 +1528,7 @@ class RawQuerySet(object): # Associate fields to values if skip: model_init_kwargs = {} - for attname, pos in model_init_field_names.iteritems(): + for attname, pos in six.iteritems(model_init_field_names): model_init_kwargs[attname] = values[pos] instance = model_cls(**model_init_kwargs) else: diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index 5676fdce9a..c1a690a524 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -5,8 +5,10 @@ Factored out from django.db.models.query to avoid making the main module very large and/or so that they can be used by other modules without getting into circular import difficulties. """ +from __future__ import unicode_literals from django.db.backends import util +from django.utils import six from django.utils import tree @@ -39,7 +41,7 @@ class Q(tree.Node): default = AND def __init__(self, *args, **kwargs): - super(Q, self).__init__(children=list(args) + kwargs.items()) + super(Q, self).__init__(children=list(args) + list(six.iteritems(kwargs))) def _combine(self, other, conn): if not isinstance(other, Q): @@ -113,7 +115,7 @@ class DeferredAttribute(object): def _check_parent_chain(self, instance, name): """ - Check if the field value can be fetched from a parent field already + Check if the field value can be fetched from a parent field already loaded in the instance. This can be done if the to-be fetched field is a primary key field. """ @@ -125,18 +127,19 @@ class DeferredAttribute(object): return None -def select_related_descend(field, restricted, requested, reverse=False): +def select_related_descend(field, restricted, requested, load_fields, reverse=False): """ Returns True if this field should be used to descend deeper for select_related() purposes. Used by both the query construction code (sql.query.fill_related_selections()) and the model instance creation code - (query.get_cached_row()). + (query.get_klass_info()). Arguments: * field - the field to be checked * restricted - a boolean field, indicating if the field list has been manually restricted using a requested clause) * requested - The select_related() dictionary. + * load_fields - the set of fields to be loaded on this model * reverse - boolean, True if we are checking a reverse select related """ if not field.rel: @@ -150,6 +153,14 @@ def select_related_descend(field, restricted, requested, reverse=False): return False if not restricted and field.null: return False + if load_fields: + if field.name not in load_fields: + if restricted and field.name in requested: + raise InvalidQuery("Field %s.%s cannot be both deferred" + " and traversed using select_related" + " at the same time." % + (field.model._meta.object_name, field.name)) + return False return True # This function is needed because data descriptors must be defined on a class @@ -177,7 +188,7 @@ def deferred_class_factory(model, attrs): overrides["Meta"] = Meta overrides["__module__"] = model.__module__ overrides["_deferred"] = True - return type(name, (model,), overrides) + return type(str(name), (model,), overrides) # The above function is also used to unpickle model instances with deferred # fields. diff --git a/django/db/models/related.py b/django/db/models/related.py index 90995d749f..a0dcec7132 100644 --- a/django/db/models/related.py +++ b/django/db/models/related.py @@ -1,4 +1,4 @@ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.db.models.fields import BLANK_CHOICE_DASH class BoundRelatedObject(object): @@ -34,9 +34,9 @@ class RelatedObject(object): if limit_to_currently_related: queryset = queryset.complex_filter( {'%s__isnull' % self.parent_model._meta.module_name: False}) - lst = [(x._get_pk_val(), smart_unicode(x)) for x in queryset] + lst = [(x._get_pk_val(), smart_text(x)) for x in queryset] return first_choice + lst - + def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): # Defer to the actual field definition for db prep return self.field.get_db_prep_lookup(lookup_type, value, diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 5801b2f428..3dc0c6ece4 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -1,4 +1,4 @@ -from future_builtins import zip +from django.utils.six.moves import zip from django.core.exceptions import FieldError from django.db import transaction @@ -9,6 +9,7 @@ from django.db.models.sql.datastructures import EmptyResultSet from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.query import get_order_dir, Query from django.db.utils import DatabaseError +from django.utils import six class SQLCompiler(object): @@ -82,7 +83,7 @@ class SQLCompiler(object): where, w_params = self.query.where.as_sql(qn=qn, connection=self.connection) having, h_params = self.query.having.as_sql(qn=qn, connection=self.connection) params = [] - for val in self.query.extra_select.itervalues(): + for val in six.itervalues(self.query.extra_select): params.extend(val[1]) result = ['SELECT'] @@ -177,7 +178,7 @@ class SQLCompiler(object): """ qn = self.quote_name_unless_alias qn2 = self.connection.ops.quote_name - result = ['(%s) AS %s' % (col[0], qn2(alias)) for alias, col in self.query.extra_select.iteritems()] + result = ['(%s) AS %s' % (col[0], qn2(alias)) for alias, col in six.iteritems(self.query.extra_select)] aliases = set(self.query.extra_select.keys()) if with_aliases: col_aliases = aliases.copy() @@ -553,7 +554,7 @@ class SQLCompiler(object): group_by = self.query.group_by or [] extra_selects = [] - for extra_select, extra_params in self.query.extra_select.itervalues(): + for extra_select, extra_params in six.itervalues(self.query.extra_select): extra_selects.append(extra_select) params.extend(extra_params) cols = (group_by + self.query.select + @@ -596,6 +597,7 @@ class SQLCompiler(object): if avoid_set is None: avoid_set = set() orig_dupe_set = dupe_set + only_load = self.query.get_loaded_field_names() # Setup for the case when only particular related fields should be # included in the related selection. @@ -607,7 +609,8 @@ class SQLCompiler(object): restricted = False for f, model in opts.get_fields_with_model(): - if not select_related_descend(f, restricted, requested): + if not select_related_descend(f, restricted, requested, + only_load.get(model or self.query.model)): continue # The "avoid" set is aliases we want to avoid just for this # particular branch of the recursion. They aren't permanently @@ -680,7 +683,8 @@ class SQLCompiler(object): if o.field.unique ] for f, model in related_fields: - if not select_related_descend(f, restricted, requested, reverse=True): + if not select_related_descend(f, restricted, requested, + only_load.get(model), reverse=True): continue # The "avoid" set is aliases we want to avoid just for this # particular branch of the recursion. They aren't permanently @@ -782,7 +786,7 @@ class SQLCompiler(object): row = self.resolve_columns(row, fields) if has_aggregate_select: - aggregate_start = len(self.query.extra_select.keys()) + len(self.query.select) + aggregate_start = len(self.query.extra_select) + len(self.query.select) aggregate_end = aggregate_start + len(self.query.aggregate_select) row = tuple(row[:aggregate_start]) + tuple([ self.query.resolve_aggregate(value, aggregate, self.connection) diff --git a/django/db/models/sql/datastructures.py b/django/db/models/sql/datastructures.py index 92d64e15dd..b8e06daf01 100644 --- a/django/db/models/sql/datastructures.py +++ b/django/db/models/sql/datastructures.py @@ -6,9 +6,6 @@ the SQL domain. class EmptyResultSet(Exception): pass -class FullResultSet(Exception): - pass - class MultiJoin(Exception): """ Used by join construction code to indicate the point at which a diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 7f331bfe7f..c62c9ac23e 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -10,8 +10,9 @@ all about the internals of models in order to get the information it needs. import copy from django.utils.datastructures import SortedDict -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.tree import Node +from django.utils import six from django.db import connections, DEFAULT_DB_ALIAS from django.db.models import signals from django.db.models.expressions import ExpressionNode @@ -602,22 +603,22 @@ class Query(object): # slight complexity here is handling fields that exist on parent # models. workset = {} - for model, values in seen.iteritems(): + for model, values in six.iteritems(seen): for field, m in model._meta.get_fields_with_model(): if field in values: continue add_to_dict(workset, m or model, field) - for model, values in must_include.iteritems(): + for model, values in six.iteritems(must_include): # If we haven't included a model in workset, we don't add the # corresponding must_include fields for that model, since an # empty set means "include all fields". That's why there's no # "else" branch here. if model in workset: workset[model].update(values) - for model, values in workset.iteritems(): + for model, values in six.iteritems(workset): callback(target, model, values) else: - for model, values in must_include.iteritems(): + for model, values in six.iteritems(must_include): if model in seen: seen[model].update(values) else: @@ -631,7 +632,7 @@ class Query(object): for model in orig_opts.get_parent_list(): if model not in seen: seen[model] = set() - for model, values in seen.iteritems(): + for model, values in six.iteritems(seen): callback(target, model, values) @@ -770,7 +771,7 @@ class Query(object): for k, aliases in self.join_map.items(): aliases = tuple([change_map.get(a, a) for a in aliases]) self.join_map[k] = aliases - for old_alias, new_alias in change_map.iteritems(): + for old_alias, new_alias in six.iteritems(change_map): alias_data = self.alias_map[old_alias] alias_data = alias_data._replace(rhs_alias=new_alias) self.alias_refcount[new_alias] = self.alias_refcount[old_alias] @@ -792,7 +793,7 @@ class Query(object): self.included_inherited_models[key] = change_map[alias] # 3. Update any joins that refer to the old alias. - for alias, data in self.alias_map.iteritems(): + for alias, data in six.iteritems(self.alias_map): lhs = data.lhs_alias if lhs in change_map: data = data._replace(lhs_alias=change_map[lhs]) @@ -842,7 +843,7 @@ class Query(object): count. Note that after execution, the reference counts are zeroed, so tables added in compiler will not be seen by this method. """ - return len([1 for count in self.alias_refcount.itervalues() if count]) + return len([1 for count in six.itervalues(self.alias_refcount) if count]) def join(self, connection, always_create=False, exclusions=(), promote=False, outer_if_first=False, nullable=False, reuse=None): @@ -1302,7 +1303,7 @@ class Query(object): field, model, direct, m2m = opts.get_field_by_name(f.name) break else: - names = opts.get_all_field_names() + self.aggregate_select.keys() + names = opts.get_all_field_names() + list(self.aggregate_select) raise FieldError("Cannot resolve keyword %r into field. " "Choices are: %s" % (name, ", ".join(names))) @@ -1549,7 +1550,7 @@ class Query(object): N-to-many relation field. """ query = Query(self.model) - query.add_filter(filter_expr, can_reuse=can_reuse) + query.add_filter(filter_expr) query.bump_prefix() query.clear_ordering(True) query.set_start(prefix) @@ -1571,7 +1572,7 @@ class Query(object): # Tag.objects.exclude(parent__parent__name='t1'), a tag with no parent # would otherwise be overlooked). active_positions = [pos for (pos, count) in - enumerate(query.alias_refcount.itervalues()) if count] + enumerate(six.itervalues(query.alias_refcount)) if count] if active_positions[-1] > 1: self.add_filter(('%s__isnull' % prefix, False), negate=True, trim=True, can_reuse=can_reuse) @@ -1655,10 +1656,15 @@ class Query(object): except MultiJoin: raise FieldError("Invalid field name: '%s'" % name) except FieldError: - names = opts.get_all_field_names() + self.extra.keys() + self.aggregate_select.keys() - names.sort() - raise FieldError("Cannot resolve keyword %r into field. " - "Choices are: %s" % (name, ", ".join(names))) + if LOOKUP_SEP in name: + # For lookups spanning over relationships, show the error + # from the model on which the lookup failed. + raise + else: + names = sorted(opts.get_all_field_names() + list(self.extra) + + list(self.aggregate_select)) + raise FieldError("Cannot resolve keyword %r into field. " + "Choices are: %s" % (name, ", ".join(names))) self.remove_inherited_models() def add_ordering(self, *ordering): @@ -1770,7 +1776,7 @@ class Query(object): else: param_iter = iter([]) for name, entry in select.items(): - entry = force_unicode(entry) + entry = force_text(entry) entry_params = [] pos = entry.find("%s") while pos != -1: @@ -1845,9 +1851,15 @@ class Query(object): If no fields are marked for deferral, returns an empty dictionary. """ - collection = {} - self.deferred_to_data(collection, self.get_loaded_field_names_cb) - return collection + # We cache this because we call this function multiple times + # (compiler.fill_related_selections, query.iterator) + try: + return self._loaded_field_names_cache + except AttributeError: + collection = {} + self.deferred_to_data(collection, self.get_loaded_field_names_cb) + self._loaded_field_names_cache = collection + return collection def get_loaded_field_names_cb(self, target, model, fields): """ diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 5cfc984770..937505b9b0 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -8,8 +8,10 @@ from django.db.models.sql.constants import * from django.db.models.sql.datastructures import Date from django.db.models.sql.query import Query from django.db.models.sql.where import AND, Constraint +from django.utils.datastructures import SortedDict from django.utils.functional import Promise -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text +from django.utils import six __all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'DateQuery', @@ -86,7 +88,7 @@ class UpdateQuery(Query): querysets. """ values_seq = [] - for name, val in values.iteritems(): + for name, val in six.iteritems(values): field, model, direct, m2m = self.model._meta.get_field_by_name(name) if not direct or m2m: raise FieldError('Cannot update model field %r (only non-relations and foreign keys permitted).' % field) @@ -103,7 +105,7 @@ class UpdateQuery(Query): saving models. """ # Check that no Promise object passes to the query. Refs #10498. - values_seq = [(value[0], value[1], force_unicode(value[2])) + values_seq = [(value[0], value[1], force_text(value[2])) if isinstance(value[2], Promise) else value for value in values_seq] self.values.extend(values_seq) @@ -128,7 +130,7 @@ class UpdateQuery(Query): if not self.related_updates: return [] result = [] - for model, values in self.related_updates.iteritems(): + for model, values in six.iteritems(self.related_updates): query = UpdateQuery(model) query.values = values if self.related_ids is not None: @@ -169,7 +171,7 @@ class InsertQuery(Query): for obj in objs: value = getattr(obj, field.attname) if isinstance(value, Promise): - setattr(obj, field.attname, force_unicode(value)) + setattr(obj, field.attname, force_text(value)) self.objs = objs self.raw = raw @@ -205,6 +207,7 @@ class DateQuery(Query): self.select = [select] self.select_fields = [None] self.select_related = False # See #7097. + self.aggregates = SortedDict() # See 18056. self.set_extra_mask([]) self.distinct = True self.order_by = order == 'ASC' and [1] or [-1] diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 5515bc4f83..47f4ffaba9 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -10,8 +10,9 @@ from itertools import repeat from django.utils import tree from django.db.models.fields import Field -from django.db.models.sql.datastructures import EmptyResultSet, FullResultSet +from django.db.models.sql.datastructures import EmptyResultSet from django.db.models.sql.aggregates import Aggregate +from django.utils.six.moves import xrange # Connection types AND = 'AND' @@ -75,17 +76,21 @@ class WhereNode(tree.Node): def as_sql(self, qn, connection): """ Returns the SQL version of the where clause and the value to be - substituted in. Returns None, None if this node is empty. - - If 'node' is provided, that is the root of the SQL generation - (generally not needed except by the internal implementation for - recursion). + substituted in. Returns '', [] if this node matches everything, + None, [] if this node is empty, and raises EmptyResultSet if this + node can't match anything. """ - if not self.children: - return None, [] + # Note that the logic here is made slightly more complex than + # necessary because there are two kind of empty nodes: Nodes + # containing 0 children, and nodes that are known to match everything. + # A match-everything node is different than empty node (which also + # technically matches everything) for backwards compatibility reasons. + # Refs #5261. result = [] result_params = [] - empty = True + everything_childs, nothing_childs = 0, 0 + non_empty_childs = len(self.children) + for child in self.children: try: if hasattr(child, 'as_sql'): @@ -93,38 +98,50 @@ class WhereNode(tree.Node): else: # A leaf node in the tree. sql, params = self.make_atom(child, qn, connection) - except EmptyResultSet: - if self.connector == AND and not self.negated: - # We can bail out early in this particular case (only). - raise - elif self.negated: - empty = False - continue - except FullResultSet: - if self.connector == OR: - if self.negated: - empty = True - break - # We match everything. No need for any constraints. - return '', [] + nothing_childs += 1 + else: + if sql: + result.append(sql) + result_params.extend(params) + else: + if sql is None: + # Skip empty childs totally. + non_empty_childs -= 1 + continue + everything_childs += 1 + # Check if this node matches nothing or everything. + # First check the amount of full nodes and empty nodes + # to make this node empty/full. + if self.connector == AND: + full_needed, empty_needed = non_empty_childs, 1 + else: + full_needed, empty_needed = 1, non_empty_childs + # Now, check if this node is full/empty using the + # counts. + if empty_needed - nothing_childs <= 0: if self.negated: - empty = True - continue - - empty = False - if sql: - result.append(sql) - result_params.extend(params) - if empty: - raise EmptyResultSet + return '', [] + else: + raise EmptyResultSet + if full_needed - everything_childs <= 0: + if self.negated: + raise EmptyResultSet + else: + return '', [] + if non_empty_childs == 0: + # All the child nodes were empty, so this one is empty, too. + return None, [] conn = ' %s ' % self.connector sql_string = conn.join(result) if sql_string: if self.negated: + # Some backends (Oracle at least) need parentheses + # around the inner SQL in the negated case, even if the + # inner SQL contains just a single expression. sql_string = 'NOT (%s)' % sql_string - elif len(self.children) != 1: + elif len(result) > 1: sql_string = '(%s)' % sql_string return sql_string, result_params @@ -261,7 +278,7 @@ class EverythingNode(object): """ def as_sql(self, qn=None, connection=None): - raise FullResultSet + return '', [] def relabel_aliases(self, change_map, node=None): return diff --git a/django/db/utils.py b/django/db/utils.py index 2b6ae2cf2e..0ce09bab70 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -4,6 +4,7 @@ from threading import local from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.utils.importlib import import_module +from django.utils import six DEFAULT_DB_ALIAS = 'default' @@ -108,7 +109,7 @@ class ConnectionRouter(object): def __init__(self, routers): self.routers = [] for r in routers: - if isinstance(r, basestring): + if isinstance(r, six.string_types): try: module_name, klass_name = r.rsplit('.', 1) module = import_module(module_name) diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index 54e71c01cc..ad7302176e 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -2,6 +2,7 @@ import weakref import threading from django.dispatch import saferef +from django.utils.six.moves import xrange WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) @@ -13,17 +14,17 @@ def _make_id(target): class Signal(object): """ Base class for all signals - + Internal attributes: - + receivers { receriverkey (id) : weakref(receiver) } """ - + def __init__(self, providing_args=None): """ Create a new signal. - + providing_args A list of the arguments this signal can pass along in a send() call. """ @@ -36,9 +37,9 @@ class Signal(object): def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): """ Connect receiver to sender for signal. - + Arguments: - + receiver A function or an instance method which is to receive signals. Receivers must be hashable objects. @@ -46,7 +47,7 @@ class Signal(object): If weak is True, then receiver must be weak-referencable (more precisely saferef.safeRef() must be able to create a reference to the receiver). - + Receivers must be able to accept keyword arguments. If receivers have a dispatch_uid attribute, the receiver will @@ -62,19 +63,19 @@ class Signal(object): module will attempt to use weak references to the receiver objects. If this parameter is false, then strong references will be used. - + dispatch_uid An identifier used to uniquely identify a particular instance of a receiver. This will usually be a string, though it may be anything hashable. """ from django.conf import settings - + # If DEBUG is on, check that we got a good receiver if settings.DEBUG: import inspect assert callable(receiver), "Signal receivers must be callable." - + # Check for **kwargs # Not all callables are inspectable with getargspec, so we'll # try a couple different ways but in the end fall back on assuming @@ -90,7 +91,7 @@ class Signal(object): if argspec: assert argspec[2] is not None, \ "Signal receivers must accept keyword arguments (**kwargs)." - + if dispatch_uid: lookup_key = (dispatch_uid, _make_id(sender)) else: @@ -99,15 +100,12 @@ class Signal(object): if weak: receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver) - self.lock.acquire() - try: + with self.lock: for r_key, _ in self.receivers: if r_key == lookup_key: break else: self.receivers.append((lookup_key, receiver)) - finally: - self.lock.release() def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None): """ @@ -115,19 +113,19 @@ class Signal(object): If weak references are used, disconnect need not be called. The receiver will be remove from dispatch automatically. - + Arguments: - + receiver The registered receiver to disconnect. May be none if dispatch_uid is specified. - + sender The registered sender to disconnect - + weak The weakref state to disconnect - + dispatch_uid the unique identifier of the receiver to disconnect """ @@ -135,16 +133,13 @@ class Signal(object): lookup_key = (dispatch_uid, _make_id(sender)) else: lookup_key = (_make_id(receiver), _make_id(sender)) - - self.lock.acquire() - try: + + with self.lock: for index in xrange(len(self.receivers)): (r_key, _) = self.receivers[index] if r_key == lookup_key: del self.receivers[index] break - finally: - self.lock.release() def send(self, sender, **named): """ @@ -155,10 +150,10 @@ class Signal(object): receivers called if a raises an error. Arguments: - + sender The sender of the signal Either a specific object or None. - + named Named arguments which will be passed to receivers. @@ -178,7 +173,7 @@ class Signal(object): Send signal from sender to all connected receivers catching errors. Arguments: - + sender The sender of the signal. Can be any python object (normally one registered with a connect if you actually want something to @@ -237,8 +232,7 @@ class Signal(object): Remove dead receivers from connections. """ - self.lock.acquire() - try: + with self.lock: to_remove = [] for key, connected_receiver in self.receivers: if connected_receiver == receiver: @@ -250,21 +244,27 @@ class Signal(object): for idx, (r_key, _) in enumerate(reversed(self.receivers)): if r_key == key: del self.receivers[last_idx-idx] - finally: - self.lock.release() def receiver(signal, **kwargs): """ A decorator for connecting receivers to signals. Used by passing in the - signal and keyword arguments to connect:: + signal (or list of signals) and keyword arguments to connect:: @receiver(post_save, sender=MyModel) def signal_receiver(sender, **kwargs): ... + @receiver([post_save, post_delete], sender=MyModel) + def signals_receiver(sender, **kwargs): + ... + """ def _decorator(func): - signal.connect(func, **kwargs) + if isinstance(signal, (list, tuple)): + for s in signal: + s.connect(func, **kwargs) + else: + signal.connect(func, **kwargs) return func return _decorator diff --git a/django/dispatch/saferef.py b/django/dispatch/saferef.py index 2a2c8739fb..84d1b2183c 100644 --- a/django/dispatch/saferef.py +++ b/django/dispatch/saferef.py @@ -149,12 +149,15 @@ class BoundMethodWeakref(object): self.selfName, self.funcName, ) - + __repr__ = __str__ - - def __nonzero__( self ): + + __hash__ = object.__hash__ + + def __bool__( self ): """Whether we are still a valid reference""" return self() is not None + __nonzero__ = __bool__ # Python 2 def __eq__(self, other): """Compare with another reference""" diff --git a/django/forms/extras/widgets.py b/django/forms/extras/widgets.py index 0a2223bc3f..c5ca1424c8 100644 --- a/django/forms/extras/widgets.py +++ b/django/forms/extras/widgets.py @@ -1,6 +1,7 @@ """ Extra HTML Widget classes """ +from __future__ import unicode_literals import datetime import re @@ -10,6 +11,7 @@ from django.utils import datetime_safe from django.utils.dates import MONTHS from django.utils.safestring import mark_safe from django.utils.formats import get_format +from django.utils import six from django.conf import settings __all__ = ('SelectDateWidget',) @@ -63,7 +65,7 @@ class SelectDateWidget(Widget): year_val, month_val, day_val = value.year, value.month, value.day except AttributeError: year_val = month_val = day_val = None - if isinstance(value, basestring): + if isinstance(value, six.string_types): if settings.USE_L10N: try: input_format = get_format('DATE_INPUT_FORMATS')[0] @@ -77,7 +79,7 @@ class SelectDateWidget(Widget): year_val, month_val, day_val = [int(v) for v in match.groups()] choices = [(i, i) for i in self.years] year_html = self.create_select(name, self.year_field, value, year_val, choices) - choices = MONTHS.items() + choices = list(six.iteritems(MONTHS)) month_html = self.create_select(name, self.month_field, value, month_val, choices) choices = [(i, i) for i in range(1, 32)] day_html = self.create_select(name, self.day_field, value, day_val, choices) @@ -90,7 +92,7 @@ class SelectDateWidget(Widget): output.append(month_html) elif field == 'day': output.append(day_html) - return mark_safe(u'\n'.join(output)) + return mark_safe('\n'.join(output)) def id_for_label(self, id_): first_select = None diff --git a/django/forms/fields.py b/django/forms/fields.py index 3811510326..7f0d26d1aa 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -2,13 +2,16 @@ Field classes. """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import copy import datetime import os import re -import urlparse +try: + from urllib.parse import urlsplit, urlunsplit +except ImportError: # Python 2 + from urlparse import urlsplit, urlunsplit from decimal import Decimal, DecimalException from io import BytesIO @@ -20,8 +23,9 @@ from django.forms.widgets import (TextInput, PasswordInput, HiddenInput, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget, FILE_INPUT_CONTRADICTION) from django.utils import formats -from django.utils.encoding import smart_unicode, smart_str, force_unicode +from django.utils.encoding import smart_text, force_text from django.utils.ipv6 import clean_ipv6_address +from django.utils import six from django.utils.translation import ugettext_lazy as _ # Provide this import for backwards compatibility. @@ -44,8 +48,8 @@ class Field(object): hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". default_validators = [] # Default set of validators default_error_messages = { - 'required': _(u'This field is required.'), - 'invalid': _(u'Enter a valid value.'), + 'required': _('This field is required.'), + 'invalid': _('Enter a valid value.'), } # Tracks each time a Field instance is created. Used to retain order. @@ -74,13 +78,13 @@ class Field(object): # validators -- List of addtional validators to use # localize -- Boolean that specifies if the field should be localized. if label is not None: - label = smart_unicode(label) + label = smart_text(label) self.required, self.label, self.initial = required, label, initial self.show_hidden_initial = show_hidden_initial if help_text is None: - self.help_text = u'' + self.help_text = '' else: - self.help_text = smart_unicode(help_text) + self.help_text = smart_text(help_text) widget = widget or self.widget if isinstance(widget, type): widget = widget() @@ -190,8 +194,8 @@ class CharField(Field): def to_python(self, value): "Returns a Unicode object." if value in validators.EMPTY_VALUES: - return u'' - return smart_unicode(value) + return '' + return smart_text(value) def widget_attrs(self, widget): attrs = super(CharField, self).widget_attrs(widget) @@ -202,9 +206,9 @@ class CharField(Field): class IntegerField(Field): default_error_messages = { - 'invalid': _(u'Enter a whole number.'), - 'max_value': _(u'Ensure this value is less than or equal to %(limit_value)s.'), - 'min_value': _(u'Ensure this value is greater than or equal to %(limit_value)s.'), + 'invalid': _('Enter a whole number.'), + 'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), + 'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), } def __init__(self, max_value=None, min_value=None, *args, **kwargs): @@ -234,7 +238,7 @@ class IntegerField(Field): class FloatField(IntegerField): default_error_messages = { - 'invalid': _(u'Enter a number.'), + 'invalid': _('Enter a number.'), } def to_python(self, value): @@ -255,9 +259,9 @@ class FloatField(IntegerField): class DecimalField(Field): default_error_messages = { - 'invalid': _(u'Enter a number.'), - 'max_value': _(u'Ensure this value is less than or equal to %(limit_value)s.'), - 'min_value': _(u'Ensure this value is greater than or equal to %(limit_value)s.'), + 'invalid': _('Enter a number.'), + 'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), + 'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 'max_digits': _('Ensure that there are no more than %s digits in total.'), 'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), 'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') @@ -284,7 +288,7 @@ class DecimalField(Field): return None if self.localize: value = formats.sanitize_separators(value) - value = smart_str(value).strip() + value = smart_text(value).strip() try: value = Decimal(value) except DecimalException: @@ -329,11 +333,11 @@ class BaseTemporalField(Field): def to_python(self, value): # Try to coerce the value to unicode. - unicode_value = force_unicode(value, strings_only=True) - if isinstance(unicode_value, unicode): + unicode_value = force_text(value, strings_only=True) + if isinstance(unicode_value, six.text_type): value = unicode_value.strip() # If unicode, try to strptime against each input format. - if isinstance(value, unicode): + if isinstance(value, six.text_type): for format in self.input_formats: try: return self.strptime(value, format) @@ -348,7 +352,7 @@ class DateField(BaseTemporalField): widget = DateInput input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS') default_error_messages = { - 'invalid': _(u'Enter a valid date.'), + 'invalid': _('Enter a valid date.'), } def to_python(self, value): @@ -371,7 +375,7 @@ class TimeField(BaseTemporalField): widget = TimeInput input_formats = formats.get_format_lazy('TIME_INPUT_FORMATS') default_error_messages = { - 'invalid': _(u'Enter a valid time.') + 'invalid': _('Enter a valid time.') } def to_python(self, value): @@ -392,7 +396,7 @@ class DateTimeField(BaseTemporalField): widget = DateTimeInput input_formats = formats.get_format_lazy('DATETIME_INPUT_FORMATS') default_error_messages = { - 'invalid': _(u'Enter a valid date/time.'), + 'invalid': _('Enter a valid date/time.'), } def prepare_value(self, value): @@ -445,7 +449,7 @@ class RegexField(CharField): return self._regex def _set_regex(self, regex): - if isinstance(regex, basestring): + if isinstance(regex, six.string_types): regex = re.compile(regex, re.UNICODE) self._regex = regex if hasattr(self, '_regex_validator') and self._regex_validator in self.validators: @@ -457,7 +461,7 @@ class RegexField(CharField): class EmailField(CharField): default_error_messages = { - 'invalid': _(u'Enter a valid e-mail address.'), + 'invalid': _('Enter a valid e-mail address.'), } default_validators = [validators.validate_email] @@ -468,11 +472,11 @@ class EmailField(CharField): class FileField(Field): widget = ClearableFileInput default_error_messages = { - 'invalid': _(u"No file was submitted. Check the encoding type on the form."), - 'missing': _(u"No file was submitted."), - 'empty': _(u"The submitted file is empty."), - 'max_length': _(u'Ensure this filename has at most %(max)d characters (it has %(length)d).'), - 'contradiction': _(u'Please either submit a file or check the clear checkbox, not both.') + 'invalid': _("No file was submitted. Check the encoding type on the form."), + 'missing': _("No file was submitted."), + 'empty': _("The submitted file is empty."), + 'max_length': _('Ensure this filename has at most %(max)d characters (it has %(length)d).'), + 'contradiction': _('Please either submit a file or check the clear checkbox, not both.') } def __init__(self, *args, **kwargs): @@ -527,7 +531,7 @@ class FileField(Field): class ImageField(FileField): default_error_messages = { - 'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."), + 'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."), } def to_python(self, data): @@ -556,20 +560,10 @@ class ImageField(FileField): file = BytesIO(data['content']) try: - # load() is the only method that can spot a truncated JPEG, - # but it cannot be called sanely after verify() - trial_image = Image.open(file) - trial_image.load() - - # Since we're about to use the file again we have to reset the - # file object if possible. - if hasattr(file, 'seek') and callable(file.seek): - file.seek(0) - - # verify() is the only method that can spot a corrupt PNG, - # but it must be called immediately after the constructor - trial_image = Image.open(file) - trial_image.verify() + # load() could spot a truncated JPEG, but it loads the entire + # image in memory, which is a DoS vector. See #3848 and #18520. + # verify() must be called immediately after the constructor. + Image.open(file).verify() except ImportError: # Under PyPy, it is possible to import PIL. However, the underlying # _imaging C module isn't available, so an ImportError will be @@ -583,7 +577,7 @@ class ImageField(FileField): class URLField(CharField): default_error_messages = { - 'invalid': _(u'Enter a valid URL.'), + 'invalid': _('Enter a valid URL.'), } def __init__(self, max_length=None, min_length=None, *args, **kwargs): @@ -598,7 +592,7 @@ class URLField(CharField): ``ValidationError`` exception for certain). """ try: - return list(urlparse.urlsplit(url)) + return list(urlsplit(url)) except ValueError: # urlparse.urlsplit can raise a ValueError with some # misformatted URLs. @@ -617,11 +611,11 @@ class URLField(CharField): url_fields[2] = '' # Rebuild the url_fields list, since the domain segment may now # contain the path too. - url_fields = split_url(urlparse.urlunsplit(url_fields)) + url_fields = split_url(urlunsplit(url_fields)) if not url_fields[2]: # the path portion may need to be added before query params url_fields[2] = '/' - value = urlparse.urlunsplit(url_fields) + value = urlunsplit(url_fields) return value class BooleanField(Field): @@ -633,7 +627,7 @@ class BooleanField(Field): # will submit for False. Also check for '0', since this is what # RadioSelect will provide. Because bool("True") == bool('1') == True, # we don't need to handle that explicitly. - if isinstance(value, basestring) and value.lower() in ('false', '0'): + if isinstance(value, six.string_types) and value.lower() in ('false', '0'): value = False else: value = bool(value) @@ -669,7 +663,7 @@ class NullBooleanField(BooleanField): class ChoiceField(Field): widget = Select default_error_messages = { - 'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'), + 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'), } def __init__(self, choices=(), required=True, widget=None, label=None, @@ -697,8 +691,8 @@ class ChoiceField(Field): def to_python(self, value): "Returns a Unicode object." if value in validators.EMPTY_VALUES: - return u'' - return smart_unicode(value) + return '' + return smart_text(value) def validate(self, value): """ @@ -714,10 +708,10 @@ class ChoiceField(Field): if isinstance(v, (list, tuple)): # This is an optgroup, so look inside the group for options for k2, v2 in v: - if value == smart_unicode(k2): + if value == smart_text(k2): return True else: - if value == smart_unicode(k): + if value == smart_text(k): return True return False @@ -749,8 +743,8 @@ class MultipleChoiceField(ChoiceField): hidden_widget = MultipleHiddenInput widget = SelectMultiple default_error_messages = { - 'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'), - 'invalid_list': _(u'Enter a list of values.'), + 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'), + 'invalid_list': _('Enter a list of values.'), } def to_python(self, value): @@ -758,7 +752,7 @@ class MultipleChoiceField(ChoiceField): return [] elif not isinstance(value, (list, tuple)): raise ValidationError(self.error_messages['invalid_list']) - return [smart_unicode(val) for val in value] + return [smart_text(val) for val in value] def validate(self, value): """ @@ -838,7 +832,7 @@ class MultiValueField(Field): You'll probably want to use this with MultiWidget. """ default_error_messages = { - 'invalid': _(u'Enter a list of values.'), + 'invalid': _('Enter a list of values.'), } def __init__(self, fields=(), *args, **kwargs): @@ -956,8 +950,8 @@ class SplitDateTimeField(MultiValueField): widget = SplitDateTimeWidget hidden_widget = SplitHiddenDateTimeWidget default_error_messages = { - 'invalid_date': _(u'Enter a valid date.'), - 'invalid_time': _(u'Enter a valid time.'), + 'invalid_date': _('Enter a valid date.'), + 'invalid_time': _('Enter a valid time.'), } def __init__(self, input_date_formats=None, input_time_formats=None, *args, **kwargs): @@ -990,7 +984,7 @@ class SplitDateTimeField(MultiValueField): class IPAddressField(CharField): default_error_messages = { - 'invalid': _(u'Enter a valid IPv4 address.'), + 'invalid': _('Enter a valid IPv4 address.'), } default_validators = [validators.validate_ipv4_address] @@ -1007,7 +1001,7 @@ class GenericIPAddressField(CharField): def to_python(self, value): if value in validators.EMPTY_VALUES: - return u'' + return '' if value and ':' in value: return clean_ipv6_address(value, self.unpack_ipv4, self.error_messages['invalid']) @@ -1016,7 +1010,7 @@ class GenericIPAddressField(CharField): class SlugField(CharField): default_error_messages = { - 'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers," - u" underscores or hyphens."), + 'invalid': _("Enter a valid 'slug' consisting of letters, numbers," + " underscores or hyphens."), } default_validators = [validators.validate_slug] diff --git a/django/forms/forms.py b/django/forms/forms.py index 09663d173c..3299c2becc 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -2,7 +2,7 @@ Form classes """ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import copy @@ -11,9 +11,10 @@ from django.forms.fields import Field, FileField from django.forms.util import flatatt, ErrorDict, ErrorList from django.forms.widgets import Media, media_property, TextInput, Textarea from django.utils.datastructures import SortedDict -from django.utils.html import conditional_escape -from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode +from django.utils.html import conditional_escape, format_html +from django.utils.encoding import smart_text, force_text, python_2_unicode_compatible from django.utils.safestring import mark_safe +from django.utils import six __all__ = ('BaseForm', 'Form') @@ -23,7 +24,7 @@ NON_FIELD_ERRORS = '__all__' def pretty_name(name): """Converts 'first_name' to 'First name'""" if not name: - return u'' + return '' return name.replace('_', ' ').capitalize() def get_declared_fields(bases, attrs, with_base_fields=True): @@ -37,7 +38,7 @@ def get_declared_fields(bases, attrs, with_base_fields=True): used. The distinction is useful in ModelForm subclassing. Also integrates any additional media definitions """ - fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] + fields = [(field_name, attrs.pop(field_name)) for field_name, obj in list(six.iteritems(attrs)) if isinstance(obj, Field)] fields.sort(key=lambda x: x[1].creation_counter) # If this class is subclassing another Form, add that Form's fields. @@ -46,11 +47,11 @@ def get_declared_fields(bases, attrs, with_base_fields=True): if with_base_fields: for base in bases[::-1]: if hasattr(base, 'base_fields'): - fields = base.base_fields.items() + fields + fields = list(six.iteritems(base.base_fields)) + fields else: for base in bases[::-1]: if hasattr(base, 'declared_fields'): - fields = base.declared_fields.items() + fields + fields = list(six.iteritems(base.declared_fields)) + fields return SortedDict(fields) @@ -67,7 +68,8 @@ class DeclarativeFieldsMetaclass(type): new_class.media = media_property(new_class) return new_class -class BaseForm(StrAndUnicode): +@python_2_unicode_compatible +class BaseForm(object): # This is the main implementation of all the Form logic. Note that this # class is different than Form. See the comments by the Form class for more # information. Any improvements to the form API should be made to *this* @@ -94,7 +96,7 @@ class BaseForm(StrAndUnicode): # self.base_fields. self.fields = copy.deepcopy(self.base_fields) - def __unicode__(self): + def __str__(self): return self.as_table() def __iter__(self): @@ -136,7 +138,7 @@ class BaseForm(StrAndUnicode): """ Add a 'initial' prefix for checking dynamic initial values """ - return u'initial-%s' % self.add_prefix(field_name) + return 'initial-%s' % self.add_prefix(field_name) def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." @@ -149,8 +151,8 @@ class BaseForm(StrAndUnicode): bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. if bf.is_hidden: if bf_errors: - top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) - hidden_fields.append(unicode(bf)) + top_errors.extend(['(Hidden field %s) %s' % (name, force_text(e)) for e in bf_errors]) + hidden_fields.append(six.text_type(bf)) else: # Create a 'class="..."' atribute if the row should have any # CSS classes applied. @@ -159,37 +161,37 @@ class BaseForm(StrAndUnicode): html_class_attr = ' class="%s"' % css_classes if errors_on_separate_row and bf_errors: - output.append(error_row % force_unicode(bf_errors)) + output.append(error_row % force_text(bf_errors)) if bf.label: - label = conditional_escape(force_unicode(bf.label)) + label = conditional_escape(force_text(bf.label)) # Only add the suffix if the label does not end in # punctuation. if self.label_suffix: if label[-1] not in ':?.!': - label += self.label_suffix + label = format_html('{0}{1}', label, self.label_suffix) label = bf.label_tag(label) or '' else: label = '' if field.help_text: - help_text = help_text_html % force_unicode(field.help_text) + help_text = help_text_html % force_text(field.help_text) else: - help_text = u'' + help_text = '' output.append(normal_row % { - 'errors': force_unicode(bf_errors), - 'label': force_unicode(label), - 'field': unicode(bf), + 'errors': force_text(bf_errors), + 'label': force_text(label), + 'field': six.text_type(bf), 'help_text': help_text, 'html_class_attr': html_class_attr }) if top_errors: - output.insert(0, error_row % force_unicode(top_errors)) + output.insert(0, error_row % force_text(top_errors)) if hidden_fields: # Insert any hidden fields in the last row. - str_hidden = u''.join(hidden_fields) + str_hidden = ''.join(hidden_fields) if output: last_row = output[-1] # Chop off the trailing row_ender (e.g. '') and @@ -208,33 +210,33 @@ class BaseForm(StrAndUnicode): # If there aren't any rows in the output, just append the # hidden fields. output.append(str_hidden) - return mark_safe(u'\n'.join(output)) + return mark_safe('\n'.join(output)) def as_table(self): "Returns this form rendered as HTML s -- excluding the
    ." return self._html_output( - normal_row = u'%(label)s%(errors)s%(field)s%(help_text)s', - error_row = u'%s', - row_ender = u'', - help_text_html = u'
    %s', + normal_row = '%(label)s%(errors)s%(field)s%(help_text)s', + error_row = '%s', + row_ender = '', + help_text_html = '
    %s', errors_on_separate_row = False) def as_ul(self): "Returns this form rendered as HTML
  • s -- excluding the
      ." return self._html_output( - normal_row = u'%(errors)s%(label)s %(field)s%(help_text)s
    • ', - error_row = u'
    • %s
    • ', + normal_row = '%(errors)s%(label)s %(field)s%(help_text)s', + error_row = '
    • %s
    • ', row_ender = '', - help_text_html = u' %s', + help_text_html = ' %s', errors_on_separate_row = False) def as_p(self): "Returns this form rendered as HTML

      s." return self._html_output( - normal_row = u'%(label)s %(field)s%(help_text)s

      ', - error_row = u'%s', + normal_row = '%(label)s %(field)s%(help_text)s

      ', + error_row = '%s', row_ender = '

      ', - help_text_html = u' %s', + help_text_html = ' %s', errors_on_separate_row = True) def non_field_errors(self): @@ -270,8 +272,6 @@ class BaseForm(StrAndUnicode): self._clean_fields() self._clean_form() self._post_clean() - if self._errors: - del self.cleaned_data def _clean_fields(self): for name, field in self.fields.items(): @@ -380,16 +380,16 @@ class BaseForm(StrAndUnicode): """ return [field for field in self if not field.is_hidden] -class Form(BaseForm): +class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)): "A collection of Fields, plus their associated data." # This is a separate class from BaseForm in order to abstract the way # self.fields is specified. This class (Form) is the one that does the # fancy metaclass stuff purely for the semantic sugar -- it allows one # to define a form using declarative syntax. # BaseForm itself has no way of designating self.fields. - __metaclass__ = DeclarativeFieldsMetaclass -class BoundField(StrAndUnicode): +@python_2_unicode_compatible +class BoundField(object): "A Field plus data" def __init__(self, form, field, name): self.form = form @@ -404,7 +404,7 @@ class BoundField(StrAndUnicode): self.label = self.field.label self.help_text = field.help_text or '' - def __unicode__(self): + def __str__(self): """Renders this field as an HTML widget.""" if self.field.show_hidden_initial: return self.as_widget() + self.as_hidden(only_initial=True) @@ -498,8 +498,8 @@ class BoundField(StrAndUnicode): def label_tag(self, contents=None, attrs=None): """ Wraps the given contents in a ', label_for, self.tag(), choice_label) def is_checked(self): return self.value == self.choice_value @@ -677,9 +684,10 @@ class RadioInput(SubWidget): final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value) if self.is_checked(): final_attrs['checked'] = 'checked' - return mark_safe(u'' % flatatt(final_attrs)) + return format_html('', flatatt(final_attrs)) -class RadioFieldRenderer(StrAndUnicode): +@python_2_unicode_compatible +class RadioFieldRenderer(object): """ An object used by RadioSelect to enable customization of radio widgets. """ @@ -696,13 +704,15 @@ class RadioFieldRenderer(StrAndUnicode): choice = self.choices[idx] # Let the IndexError propogate return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx) - def __unicode__(self): + def __str__(self): return self.render() def render(self): """Outputs a - Vote again? + Vote again? Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a results page that gets updated each time you vote. If you submit the form @@ -238,11 +238,13 @@ Change it like so:: ListView.as_view( queryset=Poll.objects.order_by('-pub_date')[:5], context_object_name='latest_poll_list', - template_name='polls/index.html')), + template_name='polls/index.html'), + name='poll_index'), url(r'^(?P\d+)/$', DetailView.as_view( model=Poll, - template_name='polls/detail.html')), + template_name='polls/detail.html'), + name='poll_detail'), url(r'^(?P\d+)/results/$', DetailView.as_view( model=Poll, @@ -265,8 +267,8 @@ two views abstract the concepts of "display a list of objects" and ``"pk"``, so we've changed ``poll_id`` to ``pk`` for the generic views. -* We've added a name, ``poll_results``, to the results view so - that we have a way to refer to its URL later on (see the +* We've added the ``name`` argument to the views (e.g. ``name='poll_results'``) + so that we have a way to refer to their URL later on (see the documentation about :ref:`naming URL patterns ` for information). We're also using the :func:`~django.conf.urls.url` function from @@ -317,10 +319,17 @@ function anymore -- generic views can be (and are) used multiple times return HttpResponseRedirect(reverse('poll_results', args=(p.id,))) +The same rule apply for the :ttag:`url` template tag. For example in the +``results.html`` template: + +.. code-block:: html+django + + Vote again? + Run the server, and use your new polling app based on generic views. For full details on generic views, see the :doc:`generic views documentation -`. +`. Coming soon =========== diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt index d89f7883fa..cc793c8129 100644 --- a/docs/intro/whatsnext.txt +++ b/docs/intro/whatsnext.txt @@ -108,7 +108,7 @@ On the Web ---------- The most recent version of the Django documentation lives at -http://docs.djangoproject.com/en/dev/. These HTML pages are generated +https://docs.djangoproject.com/en/dev/. These HTML pages are generated automatically from the text files in source control. That means they reflect the "latest and greatest" in Django -- they include the very latest corrections and additions, and they discuss the latest Django features, which may only be @@ -124,7 +124,7 @@ rather than asking broad tech-support questions. If you need help with your particular Django setup, try the `django-users mailing list`_ or the `#django IRC channel`_ instead. -.. _ticket system: https://code.djangoproject.com/simpleticket?component=Documentation +.. _ticket system: https://code.djangoproject.com/newticket?component=Documentation .. _django-users mailing list: http://groups.google.com/group/django-users .. _#django IRC channel: irc://irc.freenode.net/django @@ -191,6 +191,8 @@ You can get a local copy of the HTML documentation following a few easy steps: __ http://sphinx.pocoo.org/ __ http://www.gnu.org/software/make/ +.. _differences-between-doc-versions: + Differences between versions ============================ @@ -226,4 +228,4 @@ We follow this policy: * The `main documentation Web page`_ includes links to documentation for all previous versions. -.. _main documentation Web page: http://docs.djangoproject.com/en/dev/ +.. _main documentation Web page: https://docs.djangoproject.com/en/dev/ diff --git a/docs/misc/api-stability.txt b/docs/misc/api-stability.txt index 55170262ec..2839ee3594 100644 --- a/docs/misc/api-stability.txt +++ b/docs/misc/api-stability.txt @@ -54,7 +54,7 @@ of 1.0. This includes these APIs: - :doc:`HTTP request/response handling `, including file uploads, middleware, sessions, URL resolution, view, and shortcut APIs. -- :doc:`Generic views `. +- :doc:`Generic views `. - :doc:`Internationalization `. diff --git a/docs/ref/class-based-views.txt b/docs/ref/class-based-views.txt deleted file mode 100644 index acd9db2d66..0000000000 --- a/docs/ref/class-based-views.txt +++ /dev/null @@ -1,1367 +0,0 @@ -========================= -Class-based generic views -========================= - -.. versionadded:: 1.3 - -.. note:: - Prior to Django 1.3, generic views were implemented as functions. The - function-based implementation has been removed in favor of the - class-based approach described here. - -Writing Web applications can be monotonous, because we repeat certain patterns -again and again. Django tries to take away some of that monotony at the model -and template layers, but Web developers also experience this boredom at the view -level. - -A general introduction to class-based generic views can be found in the -:doc:`topic guide `. - -This reference contains details of Django's built-in generic views, along with -a list of the keyword arguments that each generic view expects. Remember that -arguments may either come from the URL pattern or from the ``extra_context`` -additional-information dictionary. - -Most generic views require the ``queryset`` key, which is a ``QuerySet`` -instance; see :doc:`/topics/db/queries` for more information about ``QuerySet`` -objects. - -Mixins -====== - -A mixin class is a way of using the inheritance capabilities of -classes to compose a class out of smaller pieces of behavior. Django's -class-based generic views are constructed by composing mixins into -usable generic views. - -For example, the :class:`~django.views.generic.base.detail.DetailView` -is composed from: - -* :class:`~django.db.views.generic.base.View`, which provides the - basic class-based behavior -* :class:`~django.db.views.generic.detail.SingleObjectMixin`, which - provides the utilities for retrieving and displaying a single object -* :class:`~django.db.views.generic.detail.SingleObjectTemplateResponseMixin`, - which provides the tools for rendering a single object into a - template-based response. - -When combined, these mixins provide all the pieces necessary to -provide a view over a single object that renders a template to produce -a response. - -Django provides a range of mixins. If you want to write your own -generic views, you can build classes that compose these mixins in -interesting ways. Alternatively, you can just use the pre-mixed -`Generic views`_ that Django provides. - -.. note:: - - When the documentation for a view gives the list of mixins, that view - inherits all the properties and methods of that mixin. - -Simple mixins -------------- - -.. currentmodule:: django.views.generic.base - -TemplateResponseMixin -~~~~~~~~~~~~~~~~~~~~~ -.. class:: TemplateResponseMixin() - - .. attribute:: template_name - - The path to the template to use when rendering the view. - - .. attribute:: response_class - - The response class to be returned by ``render_to_response`` method. - Default is - :class:`TemplateResponse `. - The template and context of ``TemplateResponse`` instances can be - altered later (e.g. in - :ref:`template response middleware `). - - If you need custom template loading or custom context object - instantiation, create a ``TemplateResponse`` subclass and assign it to - ``response_class``. - - .. method:: render_to_response(context, **response_kwargs) - - Returns a ``self.response_class`` instance. - - If any keyword arguments are provided, they will be - passed to the constructor of the response class. - - Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the - list of template names that will be searched looking for an existent - template. - - .. method:: get_template_names() - - Returns a list of template names to search for when rendering the - template. - - If :attr:`TemplateResponseMixin.template_name` is specified, the - default implementation will return a list containing - :attr:`TemplateResponseMixin.template_name` (if it is specified). - - -Single object mixins --------------------- - -.. currentmodule:: django.views.generic.detail - -SingleObjectMixin -~~~~~~~~~~~~~~~~~ -.. class:: SingleObjectMixin() - - .. attribute:: model - - The model that this view will display data for. Specifying ``model - = Foo`` is effectively the same as specifying ``queryset = - Foo.objects.all()``. - - .. attribute:: queryset - - A ``QuerySet`` that represents the objects. If provided, the value of - :attr:`SingleObjectMixin.queryset` supersedes the value provided for - :attr:`SingleObjectMixin.model`. - - .. attribute:: slug_field - - The name of the field on the model that contains the slug. By default, - ``slug_field`` is ``'slug'``. - - .. attribute:: slug_url_kwarg - - .. versionadded:: 1.4 - - The name of the URLConf keyword argument that contains the slug. By - default, ``slug_url_kwarg`` is ``'slug'``. - - .. attribute:: pk_url_kwarg - - .. versionadded:: 1.4 - - The name of the URLConf keyword argument that contains the primary key. - By default, ``pk_url_kwarg`` is ``'pk'``. - - .. attribute:: context_object_name - - Designates the name of the variable to use in the context. - - .. method:: get_object(queryset=None) - - Returns the single object that this view will display. If - ``queryset`` is provided, that queryset will be used as the - source of objects; otherwise, - :meth:`~SingleObjectMixin.get_queryset` will be used. - ``get_object()`` looks for a - :attr:`SingleObjectMixin.pk_url_kwarg` argument in the arguments - to the view; if this argument is found, this method performs a - primary-key based lookup using that value. If this argument is not - found, it looks for a :attr:`SingleObjectMixin.slug_url_kwarg` - argument, and performs a slug lookup using the - :attr:`SingleObjectMixin.slug_field`. - - .. method:: get_queryset() - - Returns the queryset that will be used to retrieve the object that - this view will display. By default, - :meth:`~SingleObjectMixin.get_queryset` returns the value of the - :attr:`~SingleObjectMixin.queryset` attribute if it is set, otherwise - it constructs a :class:`QuerySet` by calling the `all()` method on the - :attr:`~SingleObjectMixin.model` attribute's default manager. - - .. method:: get_context_object_name(obj) - - Return the context variable name that will be used to contain the - data that this view is manipulating. If - :attr:`~SingleObjectMixin.context_object_name` is not set, the context - name will be constructed from the ``object_name`` of the model that - the queryset is composed from. For example, the model ``Article`` - would have context object named ``'article'``. - - .. method:: get_context_data(**kwargs) - - Returns context data for displaying the list of objects. - - **Context** - - * ``object``: The object that this view is displaying. If - ``context_object_name`` is specified, that variable will also be - set in the context, with the same value as ``object``. - -SingleObjectTemplateResponseMixin -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. class:: SingleObjectTemplateResponseMixin() - - A mixin class that performs template-based response rendering for views - that operate upon a single object instance. Requires that the view it is - mixed with provides ``self.object``, the object instance that the view is - operating on. ``self.object`` will usually be, but is not required to be, - an instance of a Django model. It may be ``None`` if the view is in the - process of constructing a new instance. - - **Extends** - - * :class:`~django.views.generic.base.TemplateResponseMixin` - - .. attribute:: template_name_field - - The field on the current object instance that can be used to determine - the name of a candidate template. If either ``template_name_field`` or - the value of the ``template_name_field`` on the current object instance - is ``None``, the object will not be interrogated for a candidate - template name. - - .. attribute:: template_name_suffix - - The suffix to append to the auto-generated candidate template name. - Default suffix is ``_detail``. - - .. method:: get_template_names() - - Returns a list of candidate template names. Returns the following list: - - * the value of ``template_name`` on the view (if provided) - * the contents of the ``template_name_field`` field on the - object instance that the view is operating upon (if available) - * ``/.html`` - -Multiple object mixins ----------------------- - -.. currentmodule:: django.views.generic.list - -MultipleObjectMixin -~~~~~~~~~~~~~~~~~~~ -.. class:: MultipleObjectMixin() - - A mixin that can be used to display a list of objects. - - If ``paginate_by`` is specified, Django will paginate the results returned - by this. You can specify the page number in the URL in one of two ways: - - * Use the ``page`` parameter in the URLconf. For example, this is what - your URLconf might look like:: - - (r'^objects/page(?P[0-9]+)/$', PaginatedView.as_view()) - - * Pass the page number via the ``page`` query-string parameter. For - example, a URL would look like this:: - - /objects/?page=3 - - These values and lists are 1-based, not 0-based, so the first page would be - represented as page ``1``. - - For more on pagination, read the :doc:`pagination documentation - `. - - As a special case, you are also permitted to use ``last`` as a value for - ``page``:: - - /objects/?page=last - - This allows you to access the final page of results without first having to - determine how many pages there are. - - Note that ``page`` *must* be either a valid page number or the value - ``last``; any other value for ``page`` will result in a 404 error. - - .. attribute:: allow_empty - - A boolean specifying whether to display the page if no objects are - available. If this is ``False`` and no objects are available, the view - will raise a 404 instead of displaying an empty page. By default, this - is ``True``. - - .. attribute:: model - - The model that this view will display data for. Specifying ``model - = Foo`` is effectively the same as specifying ``queryset = - Foo.objects.all()``. - - .. attribute:: queryset - - A ``QuerySet`` that represents the objects. If provided, the value of - :attr:`MultipleObjectMixin.queryset` supersedes the value provided for - :attr:`MultipleObjectMixin.model`. - - .. attribute:: paginate_by - - An integer specifying how many objects should be displayed per page. If - this is given, the view will paginate objects with - :attr:`MultipleObjectMixin.paginate_by` objects per page. The view will - expect either a ``page`` query string parameter (via ``GET``) or a - ``page`` variable specified in the URLconf. - - .. attribute:: paginator_class - - The paginator class to be used for pagination. By default, - :class:`django.core.paginator.Paginator` is used. If the custom paginator - class doesn't have the same constructor interface as - :class:`django.core.paginator.Paginator`, you will also need to - provide an implementation for :meth:`MultipleObjectMixin.get_paginator`. - - .. attribute:: context_object_name - - Designates the name of the variable to use in the context. - - .. method:: get_queryset() - - Returns the queryset that represents the data this view will display. - - .. method:: paginate_queryset(queryset, page_size) - - Returns a 4-tuple containing (``paginator``, ``page``, ``object_list``, - ``is_paginated``). - - Constructed by paginating ``queryset`` into pages of size ``page_size``. - If the request contains a ``page`` argument, either as a captured URL - argument or as a GET argument, ``object_list`` will correspond to the - objects from that page. - - .. method:: get_paginate_by(queryset) - - Returns the number of items to paginate by, or ``None`` for no - pagination. By default this simply returns the value of - :attr:`MultipleObjectMixin.paginate_by`. - - .. method:: get_paginator(queryset, per_page, orphans=0, allow_empty_first_page=True) - - Returns an instance of the paginator to use for this view. By default, - instantiates an instance of :attr:`paginator_class`. - - .. method:: get_allow_empty() - - Return a boolean specifying whether to display the page if no objects - are available. If this method returns ``False`` and no objects are - available, the view will raise a 404 instead of displaying an empty - page. By default, this is ``True``. - - .. method:: get_context_object_name(object_list) - - Return the context variable name that will be used to contain - the list of data that this view is manipulating. If - ``object_list`` is a queryset of Django objects and - :attr:`~MultipleObjectMixin.context_object_name` is not set, - the context name will be the ``object_name`` of the model that - the queryset is composed from, with postfix ``'_list'`` - appended. For example, the model ``Article`` would have a - context object named ``article_list``. - - .. method:: get_context_data(**kwargs) - - Returns context data for displaying the list of objects. - - **Context** - - * ``object_list``: The list of objects that this view is displaying. If - ``context_object_name`` is specified, that variable will also be set - in the context, with the same value as ``object_list``. - - * ``is_paginated``: A boolean representing whether the results are - paginated. Specifically, this is set to ``False`` if no page size has - been specified, or if the available objects do not span multiple - pages. - - * ``paginator``: An instance of - :class:`django.core.paginator.Paginator`. If the page is not - paginated, this context variable will be ``None``. - - * ``page_obj``: An instance of - :class:`django.core.paginator.Page`. If the page is not paginated, - this context variable will be ``None``. - -MultipleObjectTemplateResponseMixin -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. class:: MultipleObjectTemplateResponseMixin() - - A mixin class that performs template-based response rendering for views - that operate upon a list of object instances. Requires that the view it is - mixed with provides ``self.object_list``, the list of object instances that - the view is operating on. ``self.object_list`` may be, but is not required - to be, a :class:`~django.db.models.query.QuerySet`. - - **Extends** - - * :class:`~django.views.generic.base.TemplateResponseMixin` - - .. attribute:: template_name_suffix - - The suffix to append to the auto-generated candidate template name. - Default suffix is ``_list``. - - .. method:: get_template_names() - - Returns a list of candidate template names. Returns the following list: - - * the value of ``template_name`` on the view (if provided) - * ``/.html`` - -Editing mixins --------------- - -.. currentmodule:: django.views.generic.edit - -FormMixin -~~~~~~~~~ -.. class:: FormMixin() - - A mixin class that provides facilities for creating and displaying forms. - - .. attribute:: initial - - A dictionary containing initial data for the form. - - .. attribute:: form_class - - The form class to instantiate. - - .. attribute:: success_url - - The URL to redirect to when the form is successfully processed. - - .. method:: get_initial() - - Retrieve initial data for the form. By default, returns a copy of - :attr:`.initial`. - - .. admonition:: Changed in 1.4 - - In Django 1.3, this method was returning the :attr:`initial` class - variable itself. - - .. method:: get_form_class() - - Retrieve the form class to instantiate. By default - :attr:`.form_class`. - - .. method:: get_form(form_class) - - Instantiate an instance of ``form_class`` using - :meth:`.get_form_kwargs`. - - .. method:: get_form_kwargs() - - Build the keyword arguments required to instantiate the form. - - The ``initial`` argument is set to :meth:`.get_initial`. If the - request is a ``POST`` or ``PUT``, the request data (``request.POST`` - and ``request.FILES``) will also be provided. - - .. method:: get_success_url() - - Determine the URL to redirect to when the form is successfully - validated. Returns :attr:`.success_url` by default. - - .. method:: form_valid(form) - - Redirects to :meth:`.get_success_url`. - - .. method:: form_invalid(form) - - Renders a response, providing the invalid form as context. - - .. method:: get_context_data(**kwargs) - - Populates a context containing the contents of ``kwargs``. - - **Context** - - * ``form``: The form instance that was generated for the view. - - .. note:: - - Views mixing :class:`FormMixin` must - provide an implementation of :meth:`.form_valid` and - :meth:`.form_invalid`. - -ModelFormMixin -~~~~~~~~~~~~~~ -.. class:: ModelFormMixin() - - A form mixin that works on ModelForms, rather than a standalone form. - - Since this is a subclass of - :class:`~django.views.generic.detail.SingleObjectMixin`, instances of this - mixin have access to the :attr:`~SingleObjectMixin.model` and - :attr:`~SingleObjectMixin.queryset` attributes, describing the type of - object that the ModelForm is manipulating. The view also provides - ``self.object``, the instance being manipulated. If the instance is being - created, ``self.object`` will be ``None``. - - **Mixins** - - * :class:`django.views.generic.edit.FormMixin` - * :class:`django.views.generic.detail.SingleObjectMixin` - - .. attribute:: success_url - - The URL to redirect to when the form is successfully processed. - - ``success_url`` may contain dictionary string formatting, which - will be interpolated against the object's field attributes. For - example, you could use ``success_url="/polls/%(slug)s/"`` to - redirect to a URL composed out of the ``slug`` field on a model. - - .. method:: get_form_class() - - Retrieve the form class to instantiate. If - :attr:`FormMixin.form_class` is provided, that class will be used. - Otherwise, a ModelForm will be instantiated using the model associated - with the :attr:`~SingleObjectMixin.queryset`, or with the - :attr:`~SingleObjectMixin.model`, depending on which attribute is - provided. - - .. method:: get_form_kwargs() - - Add the current instance (``self.object``) to the standard - :meth:`FormMixin.get_form_kwargs`. - - .. method:: get_success_url() - - Determine the URL to redirect to when the form is successfully - validated. Returns :attr:`FormMixin.success_url` if it is provided; - otherwise, attempts to use the ``get_absolute_url()`` of the object. - - .. method:: form_valid(form) - - Saves the form instance, sets the current object for the view, and - redirects to :meth:`.get_success_url`. - - .. method:: form_invalid() - - Renders a response, providing the invalid form as context. - -ProcessFormView -~~~~~~~~~~~~~~~ -.. class:: ProcessFormView() - - A mixin that provides basic HTTP GET and POST workflow. - - .. method:: get(request, *args, **kwargs) - - Constructs a form, then renders a response using a context that - contains that form. - - .. method:: post(request, *args, **kwargs) - - Constructs a form, checks the form for validity, and handles it - accordingly. - - The PUT action is also handled, as an analog of POST. - -DeletionMixin -~~~~~~~~~~~~~ -.. class:: DeletionMixin() - - Enables handling of the ``DELETE`` http action. - - .. attribute:: success_url - - The url to redirect to when the nominated object has been - successfully deleted. - - .. method:: get_success_url(obj) - - Returns the url to redirect to when the nominated object has been - successfully deleted. Returns - :attr:`~django.views.generic.edit.DeletionMixin.success_url` by - default. - -Date-based mixins ------------------ - -.. currentmodule:: django.views.generic.dates - -YearMixin -~~~~~~~~~ -.. class:: YearMixin() - - A mixin that can be used to retrieve and provide parsing information for a - year component of a date. - - .. attribute:: year_format - - The :func:`~time.strftime` format to use when parsing the year. - By default, this is ``'%Y'``. - - .. attribute:: year - - **Optional** The value for the year (as a string). By default, set to - ``None``, which means the year will be determined using other means. - - .. method:: get_year_format() - - Returns the :func:`~time.strftime` format to use when parsing the year. Returns - :attr:`YearMixin.year_format` by default. - - .. method:: get_year() - - Returns the year for which this view will display data. Tries the - following sources, in order: - - * The value of the :attr:`YearMixin.year` attribute. - * The value of the `year` argument captured in the URL pattern - * The value of the `year` GET query argument. - - Raises a 404 if no valid year specification can be found. - -MonthMixin -~~~~~~~~~~ -.. class:: MonthMixin() - - A mixin that can be used to retrieve and provide parsing information for a - month component of a date. - - .. attribute:: month_format - - The :func:`~time.strftime` format to use when parsing the month. By default, this is - ``'%b'``. - - .. attribute:: month - - **Optional** The value for the month (as a string). By default, set to - ``None``, which means the month will be determined using other means. - - .. method:: get_month_format() - - Returns the :func:`~time.strftime` format to use when parsing the month. Returns - :attr:`MonthMixin.month_format` by default. - - .. method:: get_month() - - Returns the month for which this view will display data. Tries the - following sources, in order: - - * The value of the :attr:`MonthMixin.month` attribute. - * The value of the `month` argument captured in the URL pattern - * The value of the `month` GET query argument. - - Raises a 404 if no valid month specification can be found. - - .. method:: get_next_month(date) - - Returns a date object containing the first day of the month after the - date provided. Returns ``None`` if mixed with a view that sets - ``allow_future = False``, and the next month is in the future. If - ``allow_empty = False``, returns the next month that contains data. - - .. method:: get_prev_month(date) - - Returns a date object containing the first day of the month before the - date provided. If ``allow_empty = False``, returns the previous month - that contained data. - -DayMixin -~~~~~~~~~ -.. class:: DayMixin() - - A mixin that can be used to retrieve and provide parsing information for a - day component of a date. - - .. attribute:: day_format - - The :func:`~time.strftime` format to use when parsing the day. By default, this is - ``'%d'``. - - .. attribute:: day - - **Optional** The value for the day (as a string). By default, set to - ``None``, which means the day will be determined using other means. - - .. method:: get_day_format() - - Returns the :func:`~time.strftime` format to use when parsing the day. Returns - :attr:`DayMixin.day_format` by default. - - .. method:: get_day() - - Returns the day for which this view will display data. Tries the - following sources, in order: - - * The value of the :attr:`DayMixin.day` attribute. - * The value of the `day` argument captured in the URL pattern - * The value of the `day` GET query argument. - - Raises a 404 if no valid day specification can be found. - - .. method:: get_next_day(date) - - Returns a date object containing the next day after the date provided. - Returns ``None`` if mixed with a view that sets ``allow_future = False``, - and the next day is in the future. If ``allow_empty = False``, returns - the next day that contains data. - - .. method:: get_prev_day(date) - - Returns a date object containing the previous day. If - ``allow_empty = False``, returns the previous day that contained data. - -WeekMixin -~~~~~~~~~ -.. class:: WeekMixin() - - A mixin that can be used to retrieve and provide parsing information for a - week component of a date. - - .. attribute:: week_format - - The :func:`~time.strftime` format to use when parsing the week. By default, this is - ``'%U'``. - - .. attribute:: week - - **Optional** The value for the week (as a string). By default, set to - ``None``, which means the week will be determined using other means. - - .. method:: get_week_format() - - Returns the :func:`~time.strftime` format to use when parsing the week. Returns - :attr:`WeekMixin.week_format` by default. - - .. method:: get_week() - - Returns the week for which this view will display data. Tries the - following sources, in order: - - * The value of the :attr:`WeekMixin.week` attribute. - * The value of the `week` argument captured in the URL pattern - * The value of the `week` GET query argument. - - Raises a 404 if no valid week specification can be found. - - -DateMixin -~~~~~~~~~ -.. class:: DateMixin() - - A mixin class providing common behavior for all date-based views. - - .. attribute:: date_field - - The name of the ``DateField`` or ``DateTimeField`` in the - ``QuerySet``'s model that the date-based archive should use to - determine the objects on the page. - - When :doc:`time zone support ` is enabled and - ``date_field`` is a ``DateTimeField``, dates are assumed to be in the - current time zone. Otherwise, the queryset could include objects from - the previous or the next day in the end user's time zone. - - .. warning:: - - In this situation, if you have implemented per-user time zone - selection, the same URL may show a different set of objects, - depending on the end user's time zone. To avoid this, you should - use a ``DateField`` as the ``date_field`` attribute. - - .. attribute:: allow_future - - A boolean specifying whether to include "future" objects on this page, - where "future" means objects in which the field specified in - ``date_field`` is greater than the current date/time. By default, this - is ``False``. - - .. method:: get_date_field() - - Returns the name of the field that contains the date data that this - view will operate on. Returns :attr:`DateMixin.date_field` by default. - - .. method:: get_allow_future() - - Determine whether to include "future" objects on this page, where - "future" means objects in which the field specified in ``date_field`` - is greater than the current date/time. Returns - :attr:`DateMixin.allow_future` by default. - -BaseDateListView -~~~~~~~~~~~~~~~~ -.. class:: BaseDateListView() - - A base class that provides common behavior for all date-based views. There - won't normally be a reason to instantiate - :class:`~django.views.generic.dates.BaseDateListView`; instantiate one of - the subclasses instead. - - While this view (and it's subclasses) are executing, ``self.object_list`` - will contain the list of objects that the view is operating upon, and - ``self.date_list`` will contain the list of dates for which data is - available. - - **Mixins** - - * :class:`~django.views.generic.dates.DateMixin` - * :class:`~django.views.generic.list.MultipleObjectMixin` - - .. attribute:: allow_empty - - A boolean specifying whether to display the page if no objects are - available. If this is ``True`` and no objects are available, the view - will display an empty page instead of raising a 404. By default, this - is ``False``. - - .. method:: get_dated_items(): - - Returns a 3-tuple containing (``date_list``, ``object_list``, - ``extra_context``). - - ``date_list`` is the list of dates for which data is available. - ``object_list`` is the list of objects. ``extra_context`` is a - dictionary of context data that will be added to any context data - provided by the - :class:`~django.views.generic.list.MultipleObjectMixin`. - - .. method:: get_dated_queryset(**lookup) - - Returns a queryset, filtered using the query arguments defined by - ``lookup``. Enforces any restrictions on the queryset, such as - ``allow_empty`` and ``allow_future``. - - .. method:: get_date_list(queryset, date_type) - - Returns the list of dates of type ``date_type`` for which - ``queryset`` contains entries. For example, ``get_date_list(qs, - 'year')`` will return the list of years for which ``qs`` has entries. - See :meth:`~django.db.models.query.QuerySet.dates()` for the - ways that the ``date_type`` argument can be used. - - -Generic views -============= - -Simple generic views --------------------- - -.. currentmodule:: django.views.generic.base - -View -~~~~ -.. class:: View() - - The master class-based base view. All other generic class-based views - inherit from this base class. - - Each request served by a :class:`~django.views.generic.base.View` has an - independent state; therefore, it is safe to store state variables on the - instance (i.e., ``self.foo = 3`` is a thread-safe operation). - - A class-based view is deployed into a URL pattern using the - :meth:`~View.as_view()` classmethod:: - - urlpatterns = patterns('', - (r'^view/$', MyView.as_view(size=42)), - ) - - Any argument passed into :meth:`~View.as_view()` will be assigned onto the - instance that is used to service a request. Using the previous example, - this means that every request on ``MyView`` is able to interrogate - ``self.size``. - - .. admonition:: Thread safety with view arguments - - Arguments passed to a view are shared between every instance of a view. - This means that you shoudn't use a list, dictionary, or any other - variable object as an argument to a view. If you did, the actions of - one user visiting your view could have an effect on subsequent users - visiting the same view. - - .. method:: dispatch(request, *args, **kwargs) - - The ``view`` part of the view -- the method that accepts a ``request`` - argument plus arguments, and returns a HTTP response. - - The default implementation will inspect the HTTP method and attempt to - delegate to a method that matches the HTTP method; a ``GET`` will be - delegated to :meth:`~View.get()`, a ``POST`` to :meth:`~View.post()`, - and so on. - - The default implementation also sets ``request``, ``args`` and - ``kwargs`` as instance variables, so any method on the view can know - the full details of the request that was made to invoke the view. - - .. method:: http_method_not_allowed(request, *args, **kwargs) - - If the view was called with HTTP method it doesn't support, this method - is called instead. - - The default implementation returns ``HttpResponseNotAllowed`` with list - of allowed methods in plain text. - -TemplateView -~~~~~~~~~~~~ -.. class:: TemplateView() - - Renders a given template, passing it a ``{{ params }}`` template variable, - which is a dictionary of the parameters captured in the URL. - - **Mixins** - - * :class:`django.views.generic.base.TemplateResponseMixin` - - .. attribute:: template_name - - The full name of a template to use. - - .. method:: get_context_data(**kwargs) - - Return a context data dictionary consisting of the contents of - ``kwargs`` stored in the context variable ``params``. - - **Context** - - * ``params``: The dictionary of keyword arguments captured from the URL - pattern that served the view. - -RedirectView -~~~~~~~~~~~~ -.. class:: RedirectView() - - Redirects to a given URL. - - The given URL may contain dictionary-style string formatting, which will be - interpolated against the parameters captured in the URL. Because keyword - interpolation is *always* done (even if no arguments are passed in), any - ``"%"`` characters in the URL must be written as ``"%%"`` so that Python - will convert them to a single percent sign on output. - - If the given URL is ``None``, Django will return an ``HttpResponseGone`` - (410). - - .. attribute:: url - - The URL to redirect to, as a string. Or ``None`` to raise a 410 (Gone) - HTTP error. - - .. attribute:: permanent - - Whether the redirect should be permanent. The only difference here is - the HTTP status code returned. If ``True``, then the redirect will use - status code 301. If ``False``, then the redirect will use status code - 302. By default, ``permanent`` is ``True``. - - .. attribute:: query_string - - Whether to pass along the GET query string to the new location. If - ``True``, then the query string is appended to the URL. If ``False``, - then the query string is discarded. By default, ``query_string`` is - ``False``. - - .. method:: get_redirect_url(**kwargs) - - Constructs the target URL for redirection. - - The default implementation uses :attr:`~RedirectView.url` as a starting - string, performs expansion of ``%`` parameters in that string, as well - as the appending of query string if requested by - :attr:`~RedirectView.query_string`. Subclasses may implement any - behavior they wish, as long as the method returns a redirect-ready URL - string. - -Detail views ------------- - -.. currentmodule:: django.views.generic.detail - -DetailView -~~~~~~~~~~ -.. class:: BaseDetailView() -.. class:: DetailView() - - A page representing an individual object. - - While this view is executing, ``self.object`` will contain the object that - the view is operating upon. - - :class:`~django.views.generic.base.BaseDetailView` implements the same - behavior as :class:`~django.views.generic.base.DetailView`, but doesn't - include the - :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.detail.SingleObjectMixin` - * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` - -List views ----------- - -.. currentmodule:: django.views.generic.list - -ListView -~~~~~~~~ -.. class:: BaseListView() -.. class:: ListView() - - A page representing a list of objects. - - While this view is executing, ``self.object_list`` will contain the list of - objects (usually, but not necessarily a queryset) that the view is - operating upon. - - :class:`~django.views.generic.list.BaseListView` implements the same - behavior as :class:`~django.views.generic.list.ListView`, but doesn't - include the - :class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.list.MultipleObjectMixin` - * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` - - -Editing views -------------- - -.. currentmodule:: django.views.generic.edit - -FormView -~~~~~~~~ -.. class:: BaseFormView() -.. class:: FormView() - - A view that displays a form. On error, redisplays the form with validation - errors; on success, redirects to a new URL. - - :class:`~django.views.generic.edit.BaseFormView` implements the same - behavior as :class:`~django.views.generic.edit.FormView`, but doesn't - include the :class:`~django.views.generic.base.TemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.edit.FormMixin` - * :class:`django.views.generic.edit.ProcessFormView` - -CreateView -~~~~~~~~~~ -.. class:: BaseCreateView() -.. class:: CreateView() - - A view that displays a form for creating an object, redisplaying the form - with validation errors (if there are any) and saving the object. - - :class:`~django.views.generic.edit.BaseCreateView` implements the same - behavior as :class:`~django.views.generic.edit.CreateView`, but doesn't - include the :class:`~django.views.generic.base.TemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.edit.ModelFormMixin` - * :class:`django.views.generic.edit.ProcessFormView` - -UpdateView -~~~~~~~~~~ -.. class:: BaseUpdateView() -.. class:: UpdateView() - - A view that displays a form for editing an existing object, redisplaying - the form with validation errors (if there are any) and saving changes to - the object. This uses a form automatically generated from the object's - model class (unless a form class is manually specified). - - :class:`~django.views.generic.edit.BaseUpdateView` implements the same - behavior as :class:`~django.views.generic.edit.UpdateView`, but doesn't - include the :class:`~django.views.generic.base.TemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.edit.ModelFormMixin` - * :class:`django.views.generic.edit.ProcessFormView` - -DeleteView -~~~~~~~~~~ -.. class:: BaseDeleteView() -.. class:: DeleteView() - - A view that displays a confirmation page and deletes an existing object. - The given object will only be deleted if the request method is ``POST``. If - this view is fetched via ``GET``, it will display a confirmation page that - should contain a form that POSTs to the same URL. - - :class:`~django.views.generic.edit.BaseDeleteView` implements the same - behavior as :class:`~django.views.generic.edit.DeleteView`, but doesn't - include the :class:`~django.views.generic.base.TemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.edit.DeletionMixin` - * :class:`django.views.generic.detail.BaseDetailView` - - **Notes** - - * The delete confirmation page displayed to a GET request uses a - ``template_name_suffix`` of ``'_confirm_delete'``. - -Date-based views ----------------- - -Date-based generic views (in the module :mod:`django.views.generic.dates`) -are views for displaying drilldown pages for date-based data. - -.. currentmodule:: django.views.generic.dates - -ArchiveIndexView -~~~~~~~~~~~~~~~~ -.. class:: BaseArchiveIndexView() -.. class:: ArchiveIndexView() - - A top-level index page showing the "latest" objects, by date. Objects with - a date in the *future* are not included unless you set ``allow_future`` to - ``True``. - - :class:`~django.views.generic.dates.BaseArchiveIndexView` implements the - same behavior as :class:`~django.views.generic.dates.ArchiveIndexView`, but - doesn't include the - :class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.dates.BaseDateListView` - * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` - - **Notes** - - * Uses a default ``context_object_name`` of ``latest``. - * Uses a default ``template_name_suffix`` of ``_archive``. - -YearArchiveView -~~~~~~~~~~~~~~~ -.. class:: BaseYearArchiveView() -.. class:: YearArchiveView() - - A yearly archive page showing all available months in a given year. Objects - with a date in the *future* are not displayed unless you set - ``allow_future`` to ``True``. - - :class:`~django.views.generic.dates.BaseYearArchiveView` implements the - same behavior as :class:`~django.views.generic.dates.YearArchiveView`, but - doesn't include the - :class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` - * :class:`django.views.generic.dates.YearMixin` - * :class:`django.views.generic.dates.BaseDateListView` - - .. attribute:: make_object_list - - A boolean specifying whether to retrieve the full list of objects for - this year and pass those to the template. If ``True``, the list of - objects will be made available to the context. By default, this is - ``False``. - - .. method:: get_make_object_list() - - Determine if an object list will be returned as part of the context. If - ``False``, the ``None`` queryset will be used as the object list. - - **Context** - - In addition to the context provided by - :class:`django.views.generic.list.MultipleObjectMixin` (via - :class:`django.views.generic.dates.BaseDateListView`), the template's - context will be: - - * ``date_list``: A ``DateQuerySet`` object containing all months that - have objects available according to ``queryset``, represented as - ``datetime.datetime`` objects, in ascending order. - - * ``year``: A ``datetime.date`` object representing the given year. - - * ``next_year``: A ``datetime.date`` object representing the first day - of the next year. If the next year is in the future, this will be - ``None``. - - * ``previous_year``: A ``datetime.date`` object representing the first - day of the previous year. Unlike ``next_year``, this will never be - ``None``. - - **Notes** - - * Uses a default ``template_name_suffix`` of ``_archive_year``. - -MonthArchiveView -~~~~~~~~~~~~~~~~ -.. class:: BaseMonthArchiveView() -.. class:: MonthArchiveView() - - A monthly archive page showing all objects in a given month. Objects with a - date in the *future* are not displayed unless you set ``allow_future`` to - ``True``. - - :class:`~django.views.generic.dates.BaseMonthArchiveView` implements - the same behavior as - :class:`~django.views.generic.dates.MonthArchiveView`, but doesn't - include the - :class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` - * :class:`django.views.generic.dates.YearMixin` - * :class:`django.views.generic.dates.MonthMixin` - * :class:`django.views.generic.dates.BaseDateListView` - - **Context** - - In addition to the context provided by - :class:`~django.views.generic.list.MultipleObjectMixin` (via - :class:`~django.views.generic.dates.BaseDateListView`), the template's - context will be: - - * ``date_list``: A ``DateQuerySet`` object containing all days that - have objects available in the given month, according to ``queryset``, - represented as ``datetime.datetime`` objects, in ascending order. - - * ``month``: A ``datetime.date`` object representing the given month. - - * ``next_month``: A ``datetime.date`` object representing the first day - of the next month. If the next month is in the future, this will be - ``None``. - - * ``previous_month``: A ``datetime.date`` object representing the first - day of the previous month. Unlike ``next_month``, this will never be - ``None``. - - **Notes** - - * Uses a default ``template_name_suffix`` of ``_archive_month``. - -WeekArchiveView -~~~~~~~~~~~~~~~ -.. class:: BaseWeekArchiveView() -.. class:: WeekArchiveView() - - A weekly archive page showing all objects in a given week. Objects with a - date in the *future* are not displayed unless you set ``allow_future`` to - ``True``. - - :class:`~django.views.generic.dates.BaseWeekArchiveView` implements the - same behavior as :class:`~django.views.generic.dates.WeekArchiveView`, but - doesn't include the - :class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` - * :class:`django.views.generic.dates.YearMixin` - * :class:`django.views.generic.dates.MonthMixin` - * :class:`django.views.generic.dates.BaseDateListView` - - **Context** - - In addition to the context provided by - :class:`~django.views.generic.list.MultipleObjectMixin` (via - :class:`~django.views.generic.dates.BaseDateListView`), the template's - context will be: - - * ``week``: A ``datetime.date`` object representing the first day of - the given week. - - * ``next_week``: A ``datetime.date`` object representing the first day - of the next week. If the next week is in the future, this will be - ``None``. - - * ``previous_week``: A ``datetime.date`` object representing the first - day of the previous week. Unlike ``next_week``, this will never be - ``None``. - - **Notes** - - * Uses a default ``template_name_suffix`` of ``_archive_week``. - -DayArchiveView -~~~~~~~~~~~~~~ -.. class:: BaseDayArchiveView() -.. class:: DayArchiveView() - - A day archive page showing all objects in a given day. Days in the future - throw a 404 error, regardless of whether any objects exist for future days, - unless you set ``allow_future`` to ``True``. - - :class:`~django.views.generic.dates.BaseDayArchiveView` implements the same - behavior as :class:`~django.views.generic.dates.DayArchiveView`, but - doesn't include the - :class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` - * :class:`django.views.generic.dates.YearMixin` - * :class:`django.views.generic.dates.MonthMixin` - * :class:`django.views.generic.dates.DayMixin` - * :class:`django.views.generic.dates.BaseDateListView` - - **Context** - - In addition to the context provided by - :class:`~django.views.generic.list.MultipleObjectMixin` (via - :class:`~django.views.generic.dates.BaseDateListView`), the template's - context will be: - - * ``day``: A ``datetime.date`` object representing the given day. - - * ``next_day``: A ``datetime.date`` object representing the next day. - If the next day is in the future, this will be ``None``. - - * ``previous_day``: A ``datetime.date`` object representing the - previous day. Unlike ``next_day``, this will never be ``None``. - - * ``next_month``: A ``datetime.date`` object representing the first day - of the next month. If the next month is in the future, this will be - ``None``. - - * ``previous_month``: A ``datetime.date`` object representing the first - day of the previous month. Unlike ``next_month``, this will never be - ``None``. - - **Notes** - - * Uses a default ``template_name_suffix`` of ``_archive_day``. - -TodayArchiveView -~~~~~~~~~~~~~~~~ -.. class:: BaseTodayArchiveView() -.. class:: TodayArchiveView() - - A day archive page showing all objects for *today*. This is exactly the - same as :class:`django.views.generic.dates.DayArchiveView`, except today's - date is used instead of the ``year``/``month``/``day`` arguments. - - :class:`~django.views.generic.dates.BaseTodayArchiveView` implements the - same behavior as :class:`~django.views.generic.dates.TodayArchiveView`, but - doesn't include the - :class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` - * :class:`django.views.generic.dates.BaseDayArchiveView` - -DateDetailView -~~~~~~~~~~~~~~ -.. class:: BaseDateDetailView() -.. class:: DateDetailView() - - A page representing an individual object. If the object has a date value in - the future, the view will throw a 404 error by default, unless you set - ``allow_future`` to ``True``. - - :class:`~django.views.generic.dates.BaseDateDetailView` implements the same - behavior as :class:`~django.views.generic.dates.DateDetailView`, but - doesn't include the - :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`. - - **Mixins** - - * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` - * :class:`django.views.generic.detail.BaseDetailView` - * :class:`django.views.generic.dates.DateMixin` - * :class:`django.views.generic.dates.YearMixin` - * :class:`django.views.generic.dates.MonthMixin` - * :class:`django.views.generic.dates.DayMixin` diff --git a/docs/ref/class-based-views/base.txt b/docs/ref/class-based-views/base.txt new file mode 100644 index 0000000000..3f82b44f46 --- /dev/null +++ b/docs/ref/class-based-views/base.txt @@ -0,0 +1,233 @@ +========== +Base views +========== + +The following three classes provide much of the functionality needed to create +Django views. You may think of them as *parent* views, which can be used by +themselves or inherited from. They may not provide all the capabilities +required for projects, in which case there are Mixins and Generic class-based +views. + +View +---- + +.. class:: django.views.generic.base.View + + The master class-based base view. All other class-based views inherit from + this base class. + + **Method Flowchart** + + 1. :meth:`dispatch()` + 2. :meth:`http_method_not_allowed()` + + **Example views.py**:: + + from django.http import HttpResponse + from django.views.generic import View + + class MyView(View): + + def get(self, request, *args, **kwargs): + return HttpResponse('Hello, World!') + + **Example urls.py**:: + + from django.conf.urls import patterns, url + + from myapp.views import MyView + + urlpatterns = patterns('', + url(r'^mine/$', MyView.as_view(), name='my-view'), + ) + + **Methods** + + .. method:: dispatch(request, *args, **kwargs) + + The ``view`` part of the view -- the method that accepts a ``request`` + argument plus arguments, and returns a HTTP response. + + The default implementation will inspect the HTTP method and attempt to + delegate to a method that matches the HTTP method; a ``GET`` will be + delegated to :meth:`~View.get()`, a ``POST`` to :meth:`~View.post()`, + and so on. + + The default implementation also sets ``request``, ``args`` and + ``kwargs`` as instance variables, so any method on the view can know + the full details of the request that was made to invoke the view. + + .. method:: http_method_not_allowed(request, *args, **kwargs) + + If the view was called with a HTTP method it doesn't support, this + method is called instead. + + The default implementation returns ``HttpResponseNotAllowed`` with list + of allowed methods in plain text. + + .. note:: + + Documentation on class-based views is a work in progress. As yet, only the + methods defined directly on the class are documented here, not methods + defined on superclasses. + +TemplateView +------------ + +.. class:: django.views.generic.base.TemplateView + + Renders a given template, passing it a ``{{ params }}`` template variable, + which is a dictionary of the parameters captured in the URL. + + **Ancestors (MRO)** + + * :class:`django.views.generic.base.TemplateView` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.base.View` + + **Method Flowchart** + + 1. :meth:`dispatch()` + 2. :meth:`http_method_not_allowed()` + 3. :meth:`get_context_data()` + + **Example views.py**:: + + from django.views.generic.base import TemplateView + + from articles.models import Article + + class HomePageView(TemplateView): + + template_name = "home.html" + + def get_context_data(self, **kwargs): + context = super(HomePageView, self).get_context_data(**kwargs) + context['latest_articles'] = Article.objects.all()[:5] + return context + + **Example urls.py**:: + + from django.conf.urls import patterns, url + + from myapp.views import HomePageView + + urlpatterns = patterns('', + url(r'^$', HomePageView.as_view(), name='home'), + ) + + **Methods and Attributes** + + .. attribute:: template_name + + The full name of a template to use. + + .. method:: get_context_data(**kwargs) + + Return a context data dictionary consisting of the contents of + ``kwargs`` stored in the context variable ``params``. + + **Context** + + * ``params``: The dictionary of keyword arguments captured from the URL + pattern that served the view. + + .. note:: + + Documentation on class-based views is a work in progress. As yet, only the + methods defined directly on the class are documented here, not methods + defined on superclasses. + +RedirectView +------------ + +.. class:: django.views.generic.base.RedirectView + + Redirects to a given URL. + + The given URL may contain dictionary-style string formatting, which will be + interpolated against the parameters captured in the URL. Because keyword + interpolation is *always* done (even if no arguments are passed in), any + ``"%"`` characters in the URL must be written as ``"%%"`` so that Python + will convert them to a single percent sign on output. + + If the given URL is ``None``, Django will return an ``HttpResponseGone`` + (410). + + **Ancestors (MRO)** + + * :class:`django.views.generic.base.View` + + **Method Flowchart** + + 1. :meth:`dispatch()` + 2. :meth:`http_method_not_allowed()` + 3. :meth:`get_redirect_url()` + + **Example views.py**:: + + from django.shortcuts import get_object_or_404 + from django.views.generic.base import RedirectView + + from articles.models import Article + + class ArticleCounterRedirectView(RedirectView): + + permanent = False + query_string = True + + def get_redirect_url(self, pk): + article = get_object_or_404(Article, pk=pk) + article.update_counter() + return reverse('product_detail', args=(pk,)) + + **Example urls.py**:: + + from django.conf.urls import patterns, url + from django.views.generic.base import RedirectView + + from article.views import ArticleCounterRedirectView + + urlpatterns = patterns('', + + url(r'r^(?P\d+)/$', ArticleCounterRedirectView.as_view(), name='article-counter'), + url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'), + ) + + **Methods and Attributes** + + .. attribute:: url + + The URL to redirect to, as a string. Or ``None`` to raise a 410 (Gone) + HTTP error. + + .. attribute:: permanent + + Whether the redirect should be permanent. The only difference here is + the HTTP status code returned. If ``True``, then the redirect will use + status code 301. If ``False``, then the redirect will use status code + 302. By default, ``permanent`` is ``True``. + + .. attribute:: query_string + + Whether to pass along the GET query string to the new location. If + ``True``, then the query string is appended to the URL. If ``False``, + then the query string is discarded. By default, ``query_string`` is + ``False``. + + .. method:: get_redirect_url(**kwargs) + + Constructs the target URL for redirection. + + The default implementation uses :attr:`~RedirectView.url` as a starting + string, performs expansion of ``%`` parameters in that string, as well + as the appending of query string if requested by + :attr:`~RedirectView.query_string`. Subclasses may implement any + behavior they wish, as long as the method returns a redirect-ready URL + string. + + .. note:: + + Documentation on class-based views is a work in progress. As yet, only the + methods defined directly on the class are documented here, not methods + defined on superclasses. diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt new file mode 100644 index 0000000000..12776cbb94 --- /dev/null +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -0,0 +1,298 @@ +================== +Generic date views +================== + +Date-based generic views (in the module :mod:`django.views.generic.dates`) +are views for displaying drilldown pages for date-based data. + +ArchiveIndexView +---------------- + +.. class:: django.views.generic.dates.ArchiveIndexView + + A top-level index page showing the "latest" objects, by date. Objects with + a date in the *future* are not included unless you set ``allow_future`` to + ``True``. + + **Ancestors (MRO)** + + * :class:`django.views.generic.dates.ArchiveIndexView` + * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.dates.BaseArchiveIndexView` + * :class:`django.views.generic.dates.BaseDateListView` + * :class:`django.views.generic.list.MultipleObjectMixin` + * :class:`django.views.generic.dates.DateMixin` + * :class:`django.views.generic.base.View` + + **Notes** + + * Uses a default ``context_object_name`` of ``latest``. + * Uses a default ``template_name_suffix`` of ``_archive``. + * Defaults to providing ``date_list`` by year, but this can be altered to + month or day using the attribute ``date_list_period``. This also applies + to all subclass views. + +YearArchiveView +--------------- + +.. class:: django.views.generic.dates.YearArchiveView + + A yearly archive page showing all available months in a given year. Objects + with a date in the *future* are not displayed unless you set + ``allow_future`` to ``True``. + + **Ancestors (MRO)** + + * :class:`django.views.generic.dates.YearArchiveView` + * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.dates.BaseYearArchiveView` + * :class:`django.views.generic.dates.YearMixin` + * :class:`django.views.generic.dates.BaseDateListView` + * :class:`django.views.generic.list.MultipleObjectMixin` + * :class:`django.views.generic.dates.DateMixin` + * :class:`django.views.generic.base.View` + + .. attribute:: make_object_list + + A boolean specifying whether to retrieve the full list of objects for + this year and pass those to the template. If ``True``, the list of + objects will be made available to the context. By default, this is + ``False``. + + .. method:: get_make_object_list() + + Determine if an object list will be returned as part of the context. If + ``False``, the ``None`` queryset will be used as the object list. + + **Context** + + In addition to the context provided by + :class:`django.views.generic.list.MultipleObjectMixin` (via + :class:`django.views.generic.dates.BaseDateListView`), the template's + context will be: + + * ``date_list``: A + :meth:`DateQuerySet` object object + containing all months that have objects available according to + ``queryset``, represented as + :class:`datetime.datetime` objects, in + ascending order. + + * ``year``: A :class:`datetime.date` object + representing the given year. + + * ``next_year``: A :class:`datetime.date` object + representing the first day of the next year. If the next year is in the + future, this will be ``None``. + + * ``previous_year``: A :class:`datetime.date` object + representing the first day of the previous year. Unlike ``next_year``, + this will never be ``None``. + + **Notes** + + * Uses a default ``template_name_suffix`` of ``_archive_year``. + +MonthArchiveView +---------------- + +.. class:: django.views.generic.dates.MonthArchiveView + + A monthly archive page showing all objects in a given month. Objects with a + date in the *future* are not displayed unless you set ``allow_future`` to + ``True``. + + **Ancestors (MRO)** + + * :class:`django.views.generic.dates.MonthArchiveView` + * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.dates.BaseMonthArchiveView` + * :class:`django.views.generic.dates.YearMixin` + * :class:`django.views.generic.dates.MonthMixin` + * :class:`django.views.generic.dates.BaseDateListView` + * :class:`django.views.generic.list.MultipleObjectMixin` + * :class:`django.views.generic.dates.DateMixin` + * :class:`django.views.generic.base.View` + + **Context** + + In addition to the context provided by + :class:`~django.views.generic.list.MultipleObjectMixin` (via + :class:`~django.views.generic.dates.BaseDateListView`), the template's + context will be: + + * ``date_list``: A + :meth:`DateQuerySet` object + containing all days that have objects available in the given month, + according to ``queryset``, represented as + :class:`datetime.datetime` objects, in + ascending order. + + * ``month``: A :class:`datetime.date` object + representing the given month. + + * ``next_month``: A :class:`datetime.date` object + representing the first day of the next month. If the next month is in the + future, this will be ``None``. + + * ``previous_month``: A :class:`datetime.date` object + representing the first day of the previous month. Unlike ``next_month``, + this will never be ``None``. + + **Notes** + + * Uses a default ``template_name_suffix`` of ``_archive_month``. + +WeekArchiveView +--------------- + +.. class:: django.views.generic.dates.WeekArchiveView + + A weekly archive page showing all objects in a given week. Objects with a + date in the *future* are not displayed unless you set ``allow_future`` to + ``True``. + + **Ancestors (MRO)** + + * :class:`django.views.generic.dates.WeekArchiveView` + * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.dates.BaseWeekArchiveView` + * :class:`django.views.generic.dates.YearMixin` + * :class:`django.views.generic.dates.WeekMixin` + * :class:`django.views.generic.dates.BaseDateListView` + * :class:`django.views.generic.list.MultipleObjectMixin` + * :class:`django.views.generic.dates.DateMixin` + * :class:`django.views.generic.base.View` + + **Context** + + In addition to the context provided by + :class:`~django.views.generic.list.MultipleObjectMixin` (via + :class:`~django.views.generic.dates.BaseDateListView`), the template's + context will be: + + * ``week``: A :class:`datetime.date` object + representing the first day of the given week. + + * ``next_week``: A :class:`datetime.date` object + representing the first day of the next week. If the next week is in the + future, this will be ``None``. + + * ``previous_week``: A :class:`datetime.date` object + representing the first day of the previous week. Unlike ``next_week``, + this will never be ``None``. + + **Notes** + + * Uses a default ``template_name_suffix`` of ``_archive_week``. + +DayArchiveView +-------------- + +.. class:: django.views.generic.dates.DayArchiveView + + A day archive page showing all objects in a given day. Days in the future + throw a 404 error, regardless of whether any objects exist for future days, + unless you set ``allow_future`` to ``True``. + + **Ancestors (MRO)** + + * :class:`django.views.generic.dates.DayArchiveView` + * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.dates.BaseDayArchiveView` + * :class:`django.views.generic.dates.YearMixin` + * :class:`django.views.generic.dates.MonthMixin` + * :class:`django.views.generic.dates.DayMixin` + * :class:`django.views.generic.dates.BaseDateListView` + * :class:`django.views.generic.list.MultipleObjectMixin` + * :class:`django.views.generic.dates.DateMixin` + * :class:`django.views.generic.base.View` + + **Context** + + In addition to the context provided by + :class:`~django.views.generic.list.MultipleObjectMixin` (via + :class:`~django.views.generic.dates.BaseDateListView`), the template's + context will be: + + * ``day``: A :class:`datetime.date` object + representing the given day. + + * ``next_day``: A :class:`datetime.date` object + representing the next day. If the next day is in the future, this will be + ``None``. + + * ``previous_day``: A :class:`datetime.date` object + representing the previous day. Unlike ``next_day``, this will never be + ``None``. + + * ``next_month``: A :class:`datetime.date` object + representing the first day of the next month. If the next month is in the + future, this will be ``None``. + + * ``previous_month``: A :class:`datetime.date` object + representing the first day of the previous month. Unlike ``next_month``, + this will never be ``None``. + + **Notes** + + * Uses a default ``template_name_suffix`` of ``_archive_day``. + +TodayArchiveView +---------------- + +.. class:: django.views.generic.dates.TodayArchiveView + + A day archive page showing all objects for *today*. This is exactly the + same as :class:`django.views.generic.dates.DayArchiveView`, except today's + date is used instead of the ``year``/``month``/``day`` arguments. + + **Ancestors (MRO)** + + * :class:`django.views.generic.dates.TodayArchiveView` + * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.dates.BaseTodayArchiveView` + * :class:`django.views.generic.dates.BaseDayArchiveView` + * :class:`django.views.generic.dates.YearMixin` + * :class:`django.views.generic.dates.MonthMixin` + * :class:`django.views.generic.dates.DayMixin` + * :class:`django.views.generic.dates.BaseDateListView` + * :class:`django.views.generic.list.MultipleObjectMixin` + * :class:`django.views.generic.dates.DateMixin` + * :class:`django.views.generic.base.View` + + +DateDetailView +-------------- + +.. class:: django.views.generic.dates.DateDetailView + + A page representing an individual object. If the object has a date value in + the future, the view will throw a 404 error by default, unless you set + ``allow_future`` to ``True``. + + **Ancestors (MRO)** + + * :class:`django.views.generic.dates.DateDetailView` + * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.dates.BaseDateDetailView` + * :class:`django.views.generic.dates.YearMixin` + * :class:`django.views.generic.dates.MonthMixin` + * :class:`django.views.generic.dates.DayMixin` + * :class:`django.views.generic.dates.DateMixin` + * :class:`django.views.generic.detail.BaseDetailView` + * :class:`django.views.generic.detail.SingleObjectMixin` + * :class:`django.views.generic.base.View` + +.. note:: + + All of the generic views listed above have matching Base* views that only + differ in that the they do not include the + :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`. diff --git a/docs/ref/class-based-views/generic-display.txt b/docs/ref/class-based-views/generic-display.txt new file mode 100644 index 0000000000..ef3bc179ee --- /dev/null +++ b/docs/ref/class-based-views/generic-display.txt @@ -0,0 +1,92 @@ +===================== +Generic display views +===================== + +The two following generic class-based views are designed to display data. On +many projects they are typically the most commonly used views. + +DetailView +---------- + +.. class:: django.views.generic.detail.DetailView + + While this view is executing, ``self.object`` will contain the object that + the view is operating upon. + + **Ancestors (MRO)** + + * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.detail.BaseDetailView` + * :class:`django.views.generic.detail.SingleObjectMixin` + * :class:`django.views.generic.base.View` + + **Method Flowchart** + + 1. :meth:`dispatch()` + 2. :meth:`http_method_not_allowed()` + 3. :meth:`get_template_names()` + 4. :meth:`get_slug_field()` + 5. :meth:`get_queryset()` + 6. :meth:`get_object()` + 7. :meth:`get_context_object_name()` + 8. :meth:`get_context_data()` + 9. :meth:`get()` + 10. :meth:`render_to_response()` + + **Example views.py**:: + + from django.views.generic.detail import DetailView + from django.utils import timezone + + from articles.models import Article + + class ArticleDetailView(DetailView): + + model = Article + + def get_context_data(self, **kwargs): + context = super(ArticleDetailView, self).get_context_data(**kwargs) + context['now'] = timezone.now() + return context + + **Example urls.py**:: + + from django.conf.urls import patterns, url + + from article.views import ArticleDetailView + + urlpatterns = patterns('', + url(r'^(?P[-_\w]+)/$', ArticleDetailView.as_view(), name='article-detail'), + ) + +ListView +-------- + +.. class:: django.views.generic.list.ListView + + A page representing a list of objects. + + While this view is executing, ``self.object_list`` will contain the list of + objects (usually, but not necessarily a queryset) that the view is + operating upon. + + **Mixins** + + * :class:`django.views.generic.list.ListView` + * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.list.BaseListView` + * :class:`django.views.generic.list.MultipleObjectMixin` + * :class:`django.views.generic.base.View` + + **Method Flowchart** + + 1. :meth:`dispatch()` + 2. :meth:`http_method_not_allowed()` + 3. :meth:`get_template_names()` + 4. :meth:`get_queryset()` + 5. :meth:`get_objects()` + 6. :meth:`get_context_data()` + 7. :meth:`get()` + 8. :meth:`render_to_response()` diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt new file mode 100644 index 0000000000..2fac06ee02 --- /dev/null +++ b/docs/ref/class-based-views/generic-editing.txt @@ -0,0 +1,199 @@ +===================== +Generic editing views +===================== + +The following views are described on this page and provide a foundation for +editing content: + +* :class:`django.views.generic.edit.FormView` +* :class:`django.views.generic.edit.CreateView` +* :class:`django.views.generic.edit.UpdateView` +* :class:`django.views.generic.edit.DeleteView` + +.. note:: + + Some of the examples on this page assume that a model titled 'Author' + has been defined. For these cases we assume the following has been defined + in `myapp/models.py`:: + + from django import models + from django.core.urlresolvers import reverse + + class Author(models.Model): + name = models.CharField(max_length=200) + + def get_absolute_url(self): + return reverse('author-detail', kwargs={'pk': self.pk}) + +FormView +-------- + +.. class:: django.views.generic.edit.FormView + + A view that displays a form. On error, redisplays the form with validation + errors; on success, redirects to a new URL. + + **Ancestors (MRO)** + + This view inherits methods and attributes from the following views: + + * :class:`django.views.generic.edit.FormView` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.edit.BaseFormView` + * :class:`django.views.generic.edit.FormMixin` + * :class:`django.views.generic.edit.ProcessFormView` + * :class:`django.views.generic.base.View` + + **Example forms.py**:: + + from django import forms + + class ContactForm(forms.Form): + name = forms.CharField() + message = forms.CharField(widget=forms.Textarea) + + def send_email(self): + # send email using the self.cleaned_data dictionary + pass + + **Example views.py**:: + + from myapp.forms import ContactForm + from django.views.generic.edit import FormView + + class ContactView(FormView): + template_name = 'contact.html' + form_class = ContactForm + success_url = '/thanks/' + + def form_valid(self, form): + # This method is called when valid form data has been POSTed. + # It should return an HttpResponse. + form.send_email() + return super(ContactView, self).form_valid(form) + +CreateView +---------- + +.. class:: django.views.generic.edit.CreateView + + A view that displays a form for creating an object, redisplaying the form + with validation errors (if there are any) and saving the object. + + **Ancestors (MRO)** + + This view inherits methods and attributes from the following views: + + * :class:`django.views.generic.edit.CreateView` + * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.edit.BaseCreateView` + * :class:`django.views.generic.edit.ModelFormMixin` + * :class:`django.views.generic.edit.FormMixin` + * :class:`django.views.generic.detail.SingleObjectMixin` + * :class:`django.views.generic.edit.ProcessFormView` + * :class:`django.views.generic.base.View` + + **Attributes** + + .. attribute:: template_name_suffix + + The CreateView page displayed to a GET request uses a + ``template_name_suffix`` of ``'_form.html'``. For + example, changing this attribute to ``'_create_form.html'`` for a view + creating objects for the the example `Author` model would cause the the + default `template_name` to be ``'myapp/author_create_form.html'``. + + **Example views.py**:: + + from django.views.generic.edit import CreateView + from myapp.models import Author + + class AuthorCreate(CreateView): + model = Author + +UpdateView +---------- + +.. class:: django.views.generic.edit.UpdateView + + A view that displays a form for editing an existing object, redisplaying + the form with validation errors (if there are any) and saving changes to + the object. This uses a form automatically generated from the object's + model class (unless a form class is manually specified). + + **Ancestors (MRO)** + + This view inherits methods and attributes from the following views: + + * :class:`django.views.generic.edit.UpdateView` + * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.edit.BaseUpdateView` + * :class:`django.views.generic.edit.ModelFormMixin` + * :class:`django.views.generic.edit.FormMixin` + * :class:`django.views.generic.detail.SingleObjectMixin` + * :class:`django.views.generic.edit.ProcessFormView` + * :class:`django.views.generic.base.View` + + **Attributes** + + .. attribute:: template_name_suffix + + The UpdateView page displayed to a GET request uses a + ``template_name_suffix`` of ``'_form.html'``. For + example, changing this attribute to ``'_update_form.html'`` for a view + updating objects for the the example `Author` model would cause the the + default `template_name` to be ``'myapp/author_update_form.html'``. + + **Example views.py**:: + + from django.views.generic.edit import UpdateView + from myapp.models import Author + + class AuthorUpdate(UpdateView): + model = Author + +DeleteView +---------- + +.. class:: django.views.generic.edit.DeleteView + + A view that displays a confirmation page and deletes an existing object. + The given object will only be deleted if the request method is ``POST``. If + this view is fetched via ``GET``, it will display a confirmation page that + should contain a form that POSTs to the same URL. + + **Ancestors (MRO)** + + This view inherits methods and attributes from the following views: + + * :class:`django.views.generic.edit.DeleteView` + * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` + * :class:`django.views.generic.base.TemplateResponseMixin` + * :class:`django.views.generic.edit.BaseDeleteView` + * :class:`django.views.generic.edit.DeletionMixin` + * :class:`django.views.generic.detail.BaseDetailView` + * :class:`django.views.generic.detail.SingleObjectMixin` + * :class:`django.views.generic.base.View` + + **Attributes** + + .. attribute:: template_name_suffix + + The DeleteView page displayed to a GET request uses a + ``template_name_suffix`` of ``'_confirm_delete.html'``. For + example, changing this attribute to ``'_check_delete.html'`` for a view + deleting objects for the the example `Author` model would cause the the + default `template_name` to be ``'myapp/author_check_delete.html'``. + + + **Example views.py**:: + + from django.views.generic.edit import DeleteView + from django.core.urlresolvers import reverse_lazy + from myapp.models import Author + + class AuthorDelete(DeleteView): + model = Author + success_url = reverse_lazy('author-list') diff --git a/docs/ref/class-based-views/index.txt b/docs/ref/class-based-views/index.txt new file mode 100644 index 0000000000..9ed0762533 --- /dev/null +++ b/docs/ref/class-based-views/index.txt @@ -0,0 +1,59 @@ +================= +Class-based views +================= + +Class-based views API reference. For introductory material, see +:doc:`/topics/class-based-views/index`. + +.. toctree:: + :maxdepth: 3 + + base + generic-display + generic-editing + generic-date-based + mixins + +Specification +------------- + +Each request served by a class-based view has an independent state; therefore, +it is safe to store state variables on the instance (i.e., ``self.foo = 3`` is +a thread-safe operation). + +A class-based view is deployed into a URL pattern using the +:meth:`~View.as_view()` classmethod:: + + urlpatterns = patterns('', + (r'^view/$', MyView.as_view(size=42)), + ) + +.. admonition:: Thread safety with view arguments + + Arguments passed to a view are shared between every instance of a view. + This means that you shoudn't use a list, dictionary, or any other + mutable object as an argument to a view. If you do and the shared object + is modified, the actions of one user visiting your view could have an + effect on subsequent users visiting the same view. + +Any argument passed into :meth:`~View.as_view()` will be assigned onto the +instance that is used to service a request. Using the previous example, +this means that every request on ``MyView`` is able to use ``self.size``. + +Base vs Generic views +--------------------- + +Base class-based views can be thought of as *parent* views, which can be +used by themselves or inherited from. They may not provide all the +capabilities required for projects, in which case there are Mixins which +extend what base views can do. + +Django’s generic views are built off of those base views, and were developed +as a shortcut for common usage patterns such as displaying the details of an +object. They take certain common idioms and patterns found in view +development and abstract them so that you can quickly write common views of +data without having to repeat yourself. + +Most generic views require the ``queryset`` key, which is a ``QuerySet`` +instance; see :doc:`/topics/db/queries` for more information about ``QuerySet`` +objects. diff --git a/docs/ref/class-based-views/mixins-date-based.txt b/docs/ref/class-based-views/mixins-date-based.txt new file mode 100644 index 0000000000..6bf6f10b5d --- /dev/null +++ b/docs/ref/class-based-views/mixins-date-based.txt @@ -0,0 +1,274 @@ +================= +Date-based mixins +================= + + +YearMixin +--------- + +.. class:: django.views.generic.dates.YearMixin + + A mixin that can be used to retrieve and provide parsing information for a + year component of a date. + + **Methods and Attributes** + + .. attribute:: year_format + + The :func:`~time.strftime` format to use when parsing the year. + By default, this is ``'%Y'``. + + .. attribute:: year + + **Optional** The value for the year (as a string). By default, set to + ``None``, which means the year will be determined using other means. + + .. method:: get_year_format() + + Returns the :func:`~time.strftime` format to use when parsing the year. Returns + :attr:`YearMixin.year_format` by default. + + .. method:: get_year() + + Returns the year for which this view will display data. Tries the + following sources, in order: + + * The value of the :attr:`YearMixin.year` attribute. + * The value of the `year` argument captured in the URL pattern + * The value of the `year` GET query argument. + + Raises a 404 if no valid year specification can be found. + +MonthMixin +---------- + +.. class:: django.views.generic.dates.MonthMixin + + A mixin that can be used to retrieve and provide parsing information for a + month component of a date. + + **Methods and Attributes** + + .. attribute:: month_format + + The :func:`~time.strftime` format to use when parsing the month. By default, this is + ``'%b'``. + + .. attribute:: month + + **Optional** The value for the month (as a string). By default, set to + ``None``, which means the month will be determined using other means. + + .. method:: get_month_format() + + Returns the :func:`~time.strftime` format to use when parsing the month. Returns + :attr:`MonthMixin.month_format` by default. + + .. method:: get_month() + + Returns the month for which this view will display data. Tries the + following sources, in order: + + * The value of the :attr:`MonthMixin.month` attribute. + * The value of the `month` argument captured in the URL pattern + * The value of the `month` GET query argument. + + Raises a 404 if no valid month specification can be found. + + .. method:: get_next_month(date) + + Returns a date object containing the first day of the month after the + date provided. Returns ``None`` if mixed with a view that sets + ``allow_future = False``, and the next month is in the future. If + ``allow_empty = False``, returns the next month that contains data. + + .. method:: get_prev_month(date) + + Returns a date object containing the first day of the month before the + date provided. If ``allow_empty = False``, returns the previous month + that contained data. + +DayMixin +-------- + +.. class:: django.views.generic.dates.DayMixin + + A mixin that can be used to retrieve and provide parsing information for a + day component of a date. + + **Methods and Attributes** + + .. attribute:: day_format + + The :func:`~time.strftime` format to use when parsing the day. By default, this is + ``'%d'``. + + .. attribute:: day + + **Optional** The value for the day (as a string). By default, set to + ``None``, which means the day will be determined using other means. + + .. method:: get_day_format() + + Returns the :func:`~time.strftime` format to use when parsing the day. Returns + :attr:`DayMixin.day_format` by default. + + .. method:: get_day() + + Returns the day for which this view will display data. Tries the + following sources, in order: + + * The value of the :attr:`DayMixin.day` attribute. + * The value of the `day` argument captured in the URL pattern + * The value of the `day` GET query argument. + + Raises a 404 if no valid day specification can be found. + + .. method:: get_next_day(date) + + Returns a date object containing the next day after the date provided. + Returns ``None`` if mixed with a view that sets ``allow_future = False``, + and the next day is in the future. If ``allow_empty = False``, returns + the next day that contains data. + + .. method:: get_prev_day(date) + + Returns a date object containing the previous day. If + ``allow_empty = False``, returns the previous day that contained data. + +WeekMixin +--------- + +.. class:: django.views.generic.dates.WeekMixin + + A mixin that can be used to retrieve and provide parsing information for a + week component of a date. + + **Methods and Attributes** + + .. attribute:: week_format + + The :func:`~time.strftime` format to use when parsing the week. By default, this is + ``'%U'``. + + .. attribute:: week + + **Optional** The value for the week (as a string). By default, set to + ``None``, which means the week will be determined using other means. + + .. method:: get_week_format() + + Returns the :func:`~time.strftime` format to use when parsing the week. Returns + :attr:`WeekMixin.week_format` by default. + + .. method:: get_week() + + Returns the week for which this view will display data. Tries the + following sources, in order: + + * The value of the :attr:`WeekMixin.week` attribute. + * The value of the `week` argument captured in the URL pattern + * The value of the `week` GET query argument. + + Raises a 404 if no valid week specification can be found. + + +DateMixin +--------- + +.. class:: django.views.generic.dates.DateMixin + + A mixin class providing common behavior for all date-based views. + + **Methods and Attributes** + + .. attribute:: date_field + + The name of the ``DateField`` or ``DateTimeField`` in the + ``QuerySet``'s model that the date-based archive should use to + determine the objects on the page. + + When :doc:`time zone support ` is enabled and + ``date_field`` is a ``DateTimeField``, dates are assumed to be in the + current time zone. Otherwise, the queryset could include objects from + the previous or the next day in the end user's time zone. + + .. warning:: + + In this situation, if you have implemented per-user time zone + selection, the same URL may show a different set of objects, + depending on the end user's time zone. To avoid this, you should + use a ``DateField`` as the ``date_field`` attribute. + + .. attribute:: allow_future + + A boolean specifying whether to include "future" objects on this page, + where "future" means objects in which the field specified in + ``date_field`` is greater than the current date/time. By default, this + is ``False``. + + .. method:: get_date_field() + + Returns the name of the field that contains the date data that this + view will operate on. Returns :attr:`DateMixin.date_field` by default. + + .. method:: get_allow_future() + + Determine whether to include "future" objects on this page, where + "future" means objects in which the field specified in ``date_field`` + is greater than the current date/time. Returns + :attr:`DateMixin.allow_future` by default. + +BaseDateListView +---------------- + +.. class:: django.views.generic.dates.BaseDateListView + + A base class that provides common behavior for all date-based views. There + won't normally be a reason to instantiate + :class:`~django.views.generic.dates.BaseDateListView`; instantiate one of + the subclasses instead. + + While this view (and it's subclasses) are executing, ``self.object_list`` + will contain the list of objects that the view is operating upon, and + ``self.date_list`` will contain the list of dates for which data is + available. + + **Mixins** + + * :class:`~django.views.generic.dates.DateMixin` + * :class:`~django.views.generic.list.MultipleObjectMixin` + + **Methods and Attributes** + + .. attribute:: allow_empty + + A boolean specifying whether to display the page if no objects are + available. If this is ``True`` and no objects are available, the view + will display an empty page instead of raising a 404. By default, this + is ``False``. + + .. method:: get_dated_items(): + + Returns a 3-tuple containing (``date_list``, ``object_list``, + ``extra_context``). + + ``date_list`` is the list of dates for which data is available. + ``object_list`` is the list of objects. ``extra_context`` is a + dictionary of context data that will be added to any context data + provided by the + :class:`~django.views.generic.list.MultipleObjectMixin`. + + .. method:: get_dated_queryset(**lookup) + + Returns a queryset, filtered using the query arguments defined by + ``lookup``. Enforces any restrictions on the queryset, such as + ``allow_empty`` and ``allow_future``. + + .. method:: get_date_list(queryset, date_type) + + Returns the list of dates of type ``date_type`` for which + ``queryset`` contains entries. For example, ``get_date_list(qs, + 'year')`` will return the list of years for which ``qs`` has entries. + See :meth:`~django.db.models.query.QuerySet.dates()` for the + ways that the ``date_type`` argument can be used. diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt new file mode 100644 index 0000000000..95dd24f442 --- /dev/null +++ b/docs/ref/class-based-views/mixins-editing.txt @@ -0,0 +1,205 @@ +============== +Editing mixins +============== + +The following mixins are used to construct Django's editing views: + +* :class:`django.views.generic.edit.FormMixin` +* :class:`django.views.generic.edit.ModelFormMixin` +* :class:`django.views.generic.edit.ProcessFormView` +* :class:`django.views.generic.edit.DeletionMixin` + +.. note:: + + Examples of how these are combined into editing views can be found at + the documentation on ``Generic editing views``. + +FormMixin +--------- + +.. class:: django.views.generic.edit.FormMixin + + A mixin class that provides facilities for creating and displaying forms. + + **Methods and Attributes** + + .. attribute:: initial + + A dictionary containing initial data for the form. + + .. attribute:: form_class + + The form class to instantiate. + + .. attribute:: success_url + + The URL to redirect to when the form is successfully processed. + + .. method:: get_initial() + + Retrieve initial data for the form. By default, returns a copy of + :attr:`~django.views.generic.edit.FormMixin.initial`. + + .. versionchanged:: 1.4 + In Django 1.3, this method was returning the + :attr:`~django.views.generic.edit.FormMixin.initial` class variable + itself. + + .. method:: get_form_class() + + Retrieve the form class to instantiate. By default + :attr:`.form_class`. + + .. method:: get_form(form_class) + + Instantiate an instance of ``form_class`` using + :meth:`~django.views.generic.edit.FormMixin.get_form_kwargs`. + + .. method:: get_form_kwargs() + + Build the keyword arguments required to instantiate the form. + + The ``initial`` argument is set to :meth:`.get_initial`. If the + request is a ``POST`` or ``PUT``, the request data (``request.POST`` + and ``request.FILES``) will also be provided. + + .. method:: get_success_url() + + Determine the URL to redirect to when the form is successfully + validated. Returns + :attr:`~django.views.generic.edit.FormMixin.success_url` by default. + + .. method:: form_valid(form) + + Redirects to + :meth:`~django.views.generic.edit.FormMixin.get_success_url`. + + .. method:: form_invalid(form) + + Renders a response, providing the invalid form as context. + + .. method:: get_context_data(**kwargs) + + Populates a context containing the contents of ``kwargs``. + + **Context** + + * ``form``: The form instance that was generated for the view. + + .. note:: + + Views mixing :class:`FormMixin` must provide an implementation of + :meth:`~django.views.generic.FormMixin.form_valid` and + :meth:`~django.views.generic.FormMixin.form_invalid`. + + +ModelFormMixin +-------------- + +.. class:: django.views.generic.edit.ModelFormMixin + + A form mixin that works on ModelForms, rather than a standalone form. + + Since this is a subclass of + :class:`~django.views.generic.detail.SingleObjectMixin`, instances of this + mixin have access to the :attr:`~SingleObjectMixin.model` and + :attr:`~SingleObjectMixin.queryset` attributes, describing the type of + object that the ModelForm is manipulating. The view also provides + ``self.object``, the instance being manipulated. If the instance is being + created, ``self.object`` will be ``None``. + + **Mixins** + + * :class:`django.views.generic.edit.FormMixin` + * :class:`django.views.generic.detail.SingleObjectMixin` + + **Methods and Attributes** + + .. attribute:: success_url + + The URL to redirect to when the form is successfully processed. + + ``success_url`` may contain dictionary string formatting, which + will be interpolated against the object's field attributes. For + example, you could use ``success_url="/polls/%(slug)s/"`` to + redirect to a URL composed out of the ``slug`` field on a model. + + .. method:: get_form_class() + + Retrieve the form class to instantiate. If + :attr:`FormMixin.form_class` is provided, that class will be used. + Otherwise, a ModelForm will be instantiated using the model associated + with the :attr:`~SingleObjectMixin.queryset`, or with the + :attr:`~SingleObjectMixin.model`, depending on which attribute is + provided. + + .. method:: get_form_kwargs() + + Add the current instance (``self.object``) to the standard + :meth:`FormMixin.get_form_kwargs`. + + .. method:: get_success_url() + + Determine the URL to redirect to when the form is successfully + validated. Returns :attr:`ModelFormMixin.success_url` if it is provided; + otherwise, attempts to use the ``get_absolute_url()`` of the object. + + .. method:: form_valid(form) + + Saves the form instance, sets the current object for the view, and + redirects to + :meth:`~django.views.generic.edit.FormMixin.get_success_url`. + + .. method:: form_invalid() + + Renders a response, providing the invalid form as context. + + +ProcessFormView +--------------- + +.. class:: django.views.generic.edit.ProcessFormView + + A mixin that provides basic HTTP GET and POST workflow. + + .. note:: + + This is named 'ProcessFormView' and inherits directly from + :class:`django.views.generic.base.View`, but breaks if used + independently, so it is more of a mixin. + + **Extends** + + * :class:`django.views.generic.base.View` + + **Methods and Attributes** + + .. method:: get(request, *args, **kwargs) + + Constructs a form, then renders a response using a context that + contains that form. + + .. method:: post(request, *args, **kwargs) + + Constructs a form, checks the form for validity, and handles it + accordingly. + + The PUT action is also handled, as an analog of POST. + +.. class:: django.views.generic.edit.DeletionMixin + + Enables handling of the ``DELETE`` http action. + + **Methods and Attributes** + + .. attribute:: success_url + + The url to redirect to when the nominated object has been + successfully deleted. + + .. method:: get_success_url(obj) + + Returns the url to redirect to when the nominated object has been + successfully deleted. Returns + :attr:`~django.views.generic.edit.DeletionMixin.success_url` by + default. diff --git a/docs/ref/class-based-views/mixins-multiple-object.txt b/docs/ref/class-based-views/mixins-multiple-object.txt new file mode 100644 index 0000000000..8bc613b887 --- /dev/null +++ b/docs/ref/class-based-views/mixins-multiple-object.txt @@ -0,0 +1,181 @@ +====================== +Multiple object mixins +====================== + +MultipleObjectMixin +------------------- + +.. class:: django.views.generic.list.MultipleObjectMixin + + A mixin that can be used to display a list of objects. + + If ``paginate_by`` is specified, Django will paginate the results returned + by this. You can specify the page number in the URL in one of two ways: + + * Use the ``page`` parameter in the URLconf. For example, this is what + your URLconf might look like:: + + (r'^objects/page(?P[0-9]+)/$', PaginatedView.as_view()) + + * Pass the page number via the ``page`` query-string parameter. For + example, a URL would look like this:: + + /objects/?page=3 + + These values and lists are 1-based, not 0-based, so the first page would be + represented as page ``1``. + + For more on pagination, read the :doc:`pagination documentation + `. + + As a special case, you are also permitted to use ``last`` as a value for + ``page``:: + + /objects/?page=last + + This allows you to access the final page of results without first having to + determine how many pages there are. + + Note that ``page`` *must* be either a valid page number or the value + ``last``; any other value for ``page`` will result in a 404 error. + + **Extends** + + * :class:`django.views.generic.base.ContextMixin` + + **Methods and Attributes** + + .. attribute:: allow_empty + + A boolean specifying whether to display the page if no objects are + available. If this is ``False`` and no objects are available, the view + will raise a 404 instead of displaying an empty page. By default, this + is ``True``. + + .. attribute:: model + + The model that this view will display data for. Specifying ``model + = Foo`` is effectively the same as specifying ``queryset = + Foo.objects.all()``. + + .. attribute:: queryset + + A ``QuerySet`` that represents the objects. If provided, the value of + :attr:`MultipleObjectMixin.queryset` supersedes the value provided for + :attr:`MultipleObjectMixin.model`. + + .. attribute:: paginate_by + + An integer specifying how many objects should be displayed per page. If + this is given, the view will paginate objects with + :attr:`MultipleObjectMixin.paginate_by` objects per page. The view will + expect either a ``page`` query string parameter (via ``GET``) or a + ``page`` variable specified in the URLconf. + + .. attribute:: paginator_class + + The paginator class to be used for pagination. By default, + :class:`django.core.paginator.Paginator` is used. If the custom paginator + class doesn't have the same constructor interface as + :class:`django.core.paginator.Paginator`, you will also need to + provide an implementation for :meth:`MultipleObjectMixin.get_paginator`. + + .. attribute:: context_object_name + + Designates the name of the variable to use in the context. + + .. method:: get_queryset() + + Returns the queryset that represents the data this view will display. + + .. method:: paginate_queryset(queryset, page_size) + + Returns a 4-tuple containing (``paginator``, ``page``, ``object_list``, + ``is_paginated``). + + Constructed by paginating ``queryset`` into pages of size ``page_size``. + If the request contains a ``page`` argument, either as a captured URL + argument or as a GET argument, ``object_list`` will correspond to the + objects from that page. + + .. method:: get_paginate_by(queryset) + + Returns the number of items to paginate by, or ``None`` for no + pagination. By default this simply returns the value of + :attr:`MultipleObjectMixin.paginate_by`. + + .. method:: get_paginator(queryset, per_page, orphans=0, allow_empty_first_page=True) + + Returns an instance of the paginator to use for this view. By default, + instantiates an instance of :attr:`paginator_class`. + + .. method:: get_allow_empty() + + Return a boolean specifying whether to display the page if no objects + are available. If this method returns ``False`` and no objects are + available, the view will raise a 404 instead of displaying an empty + page. By default, this is ``True``. + + .. method:: get_context_object_name(object_list) + + Return the context variable name that will be used to contain + the list of data that this view is manipulating. If + ``object_list`` is a queryset of Django objects and + :attr:`~MultipleObjectMixin.context_object_name` is not set, + the context name will be the ``object_name`` of the model that + the queryset is composed from, with postfix ``'_list'`` + appended. For example, the model ``Article`` would have a + context object named ``article_list``. + + .. method:: get_context_data(**kwargs) + + Returns context data for displaying the list of objects. + + **Context** + + * ``object_list``: The list of objects that this view is displaying. If + ``context_object_name`` is specified, that variable will also be set + in the context, with the same value as ``object_list``. + + * ``is_paginated``: A boolean representing whether the results are + paginated. Specifically, this is set to ``False`` if no page size has + been specified, or if the available objects do not span multiple + pages. + + * ``paginator``: An instance of + :class:`django.core.paginator.Paginator`. If the page is not + paginated, this context variable will be ``None``. + + * ``page_obj``: An instance of + :class:`django.core.paginator.Page`. If the page is not paginated, + this context variable will be ``None``. + + +MultipleObjectTemplateResponseMixin +----------------------------------- + +.. class:: django.views.generic.list.MultipleObjectTemplateResponseMixin + + A mixin class that performs template-based response rendering for views + that operate upon a list of object instances. Requires that the view it is + mixed with provides ``self.object_list``, the list of object instances that + the view is operating on. ``self.object_list`` may be, but is not required + to be, a :class:`~django.db.models.query.QuerySet`. + + **Extends** + + * :class:`~django.views.generic.base.TemplateResponseMixin` + + **Methods and Attributes** + + .. attribute:: template_name_suffix + + The suffix to append to the auto-generated candidate template name. + Default suffix is ``_list``. + + .. method:: get_template_names() + + Returns a list of candidate template names. Returns the following list: + + * the value of ``template_name`` on the view (if provided) + * ``/.html`` diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt new file mode 100644 index 0000000000..61fc945cd3 --- /dev/null +++ b/docs/ref/class-based-views/mixins-simple.txt @@ -0,0 +1,78 @@ +============= +Simple mixins +============= + +ContextMixin +------------ + +.. class:: django.views.generic.base.ContextMixin + + .. versionadded:: 1.5 + + **classpath** + + ``django.views.generic.base.ContextMixin`` + + **Methods** + + .. method:: get_context_data(**kwargs) + + Returns a dictionary representing the template context. The keyword + arguments provided will make up the returned context. + + The template context of all class-based generic views include a + ``view`` variable that points to the ``View`` instance. + + .. admonition:: Use ``alters_data`` where appropriate + + Note that having the view instance in the template context may + expose potentially hazardous methods to template authors. To + prevent methods like this from being called in the template, set + ``alters_data=True`` on those methods. For more information, read + the documentation on :ref:`rendering a template context + `. + +TemplateResponseMixin +--------------------- + +.. class:: django.views.generic.base.TemplateResponseMixin + + Provides a mechanism to construct a + :class:`~django.template.response.TemplateResponse`, given + suitable context. The template to use is configurable and can be + further customized by subclasses. + + **Methods and Attributes** + + .. attribute:: response_class + + The response class to be returned by ``render_to_response`` method. + Default is + :class:`TemplateResponse `. + The template and context of ``TemplateResponse`` instances can be + altered later (e.g. in + :ref:`template response middleware `). + + If you need custom template loading or custom context object + instantiation, create a ``TemplateResponse`` subclass and assign it to + ``response_class``. + + .. method:: render_to_response(context, **response_kwargs) + + Returns a ``self.response_class`` instance. + + If any keyword arguments are provided, they will be + passed to the constructor of the response class. + + Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the + list of template names that will be searched looking for an existent + template. + + .. method:: get_template_names() + + Returns a list of template names to search for when rendering the + template. + + If :attr:`TemplateResponseMixin.template_name` is specified, the + default implementation will return a list containing + :attr:`TemplateResponseMixin.template_name` (if it is specified). diff --git a/docs/ref/class-based-views/mixins-single-object.txt b/docs/ref/class-based-views/mixins-single-object.txt new file mode 100644 index 0000000000..77f52b96c6 --- /dev/null +++ b/docs/ref/class-based-views/mixins-single-object.txt @@ -0,0 +1,130 @@ +==================== +Single object mixins +==================== + +SingleObjectMixin +----------------- + +.. class:: django.views.generic.detail.SingleObjectMixin + + Provides a mechanism for looking up an object associated with the + current HTTP request. + + **Methods and Attributes** + + .. attribute:: model + + The model that this view will display data for. Specifying ``model + = Foo`` is effectively the same as specifying ``queryset = + Foo.objects.all()``. + + .. attribute:: queryset + + A ``QuerySet`` that represents the objects. If provided, the value of + :attr:`SingleObjectMixin.queryset` supersedes the value provided for + :attr:`SingleObjectMixin.model`. + + .. attribute:: slug_field + + The name of the field on the model that contains the slug. By default, + ``slug_field`` is ``'slug'``. + + .. attribute:: slug_url_kwarg + + .. versionadded:: 1.4 + + The name of the URLConf keyword argument that contains the slug. By + default, ``slug_url_kwarg`` is ``'slug'``. + + .. attribute:: pk_url_kwarg + + .. versionadded:: 1.4 + + The name of the URLConf keyword argument that contains the primary key. + By default, ``pk_url_kwarg`` is ``'pk'``. + + .. attribute:: context_object_name + + Designates the name of the variable to use in the context. + + .. method:: get_object(queryset=None) + + Returns the single object that this view will display. If + ``queryset`` is provided, that queryset will be used as the + source of objects; otherwise, + :meth:`~SingleObjectMixin.get_queryset` will be used. + ``get_object()`` looks for a + :attr:`SingleObjectMixin.pk_url_kwarg` argument in the arguments + to the view; if this argument is found, this method performs a + primary-key based lookup using that value. If this argument is not + found, it looks for a :attr:`SingleObjectMixin.slug_url_kwarg` + argument, and performs a slug lookup using the + :attr:`SingleObjectMixin.slug_field`. + + .. method:: get_queryset() + + Returns the queryset that will be used to retrieve the object that + this view will display. By default, + :meth:`~SingleObjectMixin.get_queryset` returns the value of the + :attr:`~SingleObjectMixin.queryset` attribute if it is set, otherwise + it constructs a :class:`QuerySet` by calling the `all()` method on the + :attr:`~SingleObjectMixin.model` attribute's default manager. + + .. method:: get_context_object_name(obj) + + Return the context variable name that will be used to contain the + data that this view is manipulating. If + :attr:`~SingleObjectMixin.context_object_name` is not set, the context + name will be constructed from the ``object_name`` of the model that + the queryset is composed from. For example, the model ``Article`` + would have context object named ``'article'``. + + .. method:: get_context_data(**kwargs) + + Returns context data for displaying the list of objects. + + **Context** + + * ``object``: The object that this view is displaying. If + ``context_object_name`` is specified, that variable will also be + set in the context, with the same value as ``object``. + +SingleObjectTemplateResponseMixin +--------------------------------- + +.. class:: django.views.generic.detail.SingleObjectTemplateResponseMixin + + A mixin class that performs template-based response rendering for views + that operate upon a single object instance. Requires that the view it is + mixed with provides ``self.object``, the object instance that the view is + operating on. ``self.object`` will usually be, but is not required to be, + an instance of a Django model. It may be ``None`` if the view is in the + process of constructing a new instance. + + **Extends** + + * :class:`~django.views.generic.base.TemplateResponseMixin` + + **Methods and Attributes** + + .. attribute:: template_name_field + + The field on the current object instance that can be used to determine + the name of a candidate template. If either ``template_name_field`` + itself or the value of the ``template_name_field`` on the current + object instance is ``None``, the object will not be used for a + candidate template name. + + .. attribute:: template_name_suffix + + The suffix to append to the auto-generated candidate template name. + Default suffix is ``_detail``. + + .. method:: get_template_names() + + Returns a list of candidate template names. Returns the following list: + + * the value of ``template_name`` on the view (if provided) + * the contents of the ``template_name_field`` field on the + object instance that the view is operating upon (if available) + * ``/.html`` diff --git a/docs/ref/class-based-views/mixins.txt b/docs/ref/class-based-views/mixins.txt new file mode 100644 index 0000000000..661454f74d --- /dev/null +++ b/docs/ref/class-based-views/mixins.txt @@ -0,0 +1,14 @@ +======================== +Class-based views mixins +======================== + +Class-based views API reference. For introductory material, see :doc:`/topics/class-based-views/mixins`. + +.. toctree:: + :maxdepth: 1 + + mixins-simple + mixins-single-object + mixins-multiple-object + mixins-editing + mixins-date-based diff --git a/docs/ref/contrib/admin/_images/article_actions.png b/docs/ref/contrib/admin/_images/article_actions.png index 78a78ae494..1d35e60e5d 100644 Binary files a/docs/ref/contrib/admin/_images/article_actions.png and b/docs/ref/contrib/admin/_images/article_actions.png differ diff --git a/docs/ref/contrib/admin/_images/article_actions_message.png b/docs/ref/contrib/admin/_images/article_actions_message.png index 6ea9439b8e..16a2d0e197 100644 Binary files a/docs/ref/contrib/admin/_images/article_actions_message.png and b/docs/ref/contrib/admin/_images/article_actions_message.png differ diff --git a/docs/ref/contrib/admin/_images/user_actions.png b/docs/ref/contrib/admin/_images/user_actions.png index fdbe2ad897..22d40e0181 100644 Binary files a/docs/ref/contrib/admin/_images/user_actions.png and b/docs/ref/contrib/admin/_images/user_actions.png differ diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index f3e39b9c40..7ca102c529 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -115,9 +115,7 @@ subclass:: .. attribute:: ModelAdmin.actions_selection_counter - .. versionadded:: 1.2 - - Controls whether a selection counter is display next to the action dropdown. + Controls whether a selection counter is displayed next to the action dropdown. By default, the admin changelist will display it (``actions_selection_counter = True``). @@ -177,12 +175,9 @@ subclass:: fields = ('url', 'title', 'content') In the above example, only the fields ``url``, ``title`` and ``content`` - will be displayed, sequentially, in the form. - - .. versionadded:: 1.2 - - ``fields`` can contain values defined in :attr:`ModelAdmin.readonly_fields` - to be displayed as read-only. + will be displayed, sequentially, in the form. ``fields`` can contain + values defined in :attr:`ModelAdmin.readonly_fields` to be displayed as + read-only. .. versionadded:: 1.4 @@ -262,8 +257,6 @@ subclass:: 'fields': (('first_name', 'last_name'), 'address', 'city', 'state'), } - .. versionadded:: 1.2 - ``fields`` can contain values defined in :attr:`~ModelAdmin.readonly_fields` to be displayed as read-only. @@ -378,14 +371,6 @@ subclass:: because ``raw_id_fields`` and ``radio_fields`` imply custom widgets of their own. -.. attribute:: ModelAdmin.get_changelist - - .. versionchanged:: 1.2 - - Returns the Changelist class to be used for listing. By default, - ``django.contrib.admin.views.main.ChangeList`` is used. By inheriting this - class you can change the behavior of the listing. - .. attribute:: ModelAdmin.inlines See :class:`InlineModelAdmin` objects below. @@ -638,7 +623,7 @@ subclass:: provided in the query string and retrievable via `self.value()`. """ - # Compare the requested value (either '80s' or 'other') + # Compare the requested value (either '80s' or '90s') # to decide how to filter the queryset. if self.value() == '80s': return queryset.filter(birthday__gte=date(1980, 1, 1), @@ -819,8 +804,6 @@ subclass:: .. attribute:: ModelAdmin.readonly_fields - .. versionadded:: 1.2 - By default the admin shows all fields as editable. Any fields in this option (which should be a ``list`` or ``tuple``) will display its data as-is and non-editable. This option behaves nearly identical to @@ -928,8 +911,6 @@ templates used by the :class:`ModelAdmin` views: .. attribute:: ModelAdmin.add_form_template - .. versionadded:: 1.2 - Path to a custom template, used by :meth:`add_view`. .. attribute:: ModelAdmin.change_form_template @@ -947,8 +928,6 @@ templates used by the :class:`ModelAdmin` views: .. attribute:: ModelAdmin.delete_selected_confirmation_template - .. versionadded:: 1.2 - Path to a custom template, used by the :meth:`delete_selected` action method for displaying a confirmation page when deleting one or more objects. See the :doc:`actions @@ -1035,8 +1014,6 @@ templates used by the :class:`ModelAdmin` views: .. method:: ModelAdmin.get_readonly_fields(self, request, obj=None) - .. versionadded:: 1.2 - The ``get_readonly_fields`` method is given the ``HttpRequest`` and the ``obj`` being edited (or ``None`` on an add form) and is expected to return a ``list`` or ``tuple`` of field names that will be displayed as read-only, @@ -1185,6 +1162,12 @@ templates used by the :class:`ModelAdmin` views: kwargs['choices'] += (('ready', 'Ready for deployment'),) return super(MyModelAdmin, self).formfield_for_choice_field(db_field, request, **kwargs) +.. method:: ModelAdmin.get_changelist(self, request, **kwargs) + + Returns the Changelist class to be used for listing. By default, + ``django.contrib.admin.views.main.ChangeList`` is used. By inheriting this + class you can change the behavior of the listing. + .. method:: ModelAdmin.has_add_permission(self, request) Should return ``True`` if adding an object is permitted, ``False`` @@ -1283,11 +1266,11 @@ provided some extra mapping data that would not otherwise be available:: # ... pass - def change_view(self, request, object_id, extra_context=None): + def change_view(self, request, object_id, form_url='', extra_context=None): extra_context = extra_context or {} extra_context['osm_data'] = self.get_osm_info() return super(MyModelAdmin, self).change_view(request, object_id, - extra_context=extra_context) + form_url, extra_context=extra_context) .. versionadded:: 1.4 @@ -1405,20 +1388,17 @@ adds some of its own (the shared features are actually defined in the - :attr:`~InlineModelAdmin.form` - :attr:`~ModelAdmin.fieldsets` - :attr:`~ModelAdmin.fields` +- :attr:`~ModelAdmin.formfield_overrides` - :attr:`~ModelAdmin.exclude` - :attr:`~ModelAdmin.filter_horizontal` - :attr:`~ModelAdmin.filter_vertical` - :attr:`~ModelAdmin.prepopulated_fields` - :attr:`~ModelAdmin.radio_fields` +- :attr:`~ModelAdmin.readonly_fields` - :attr:`~InlineModelAdmin.raw_id_fields` - :meth:`~ModelAdmin.formfield_for_foreignkey` - :meth:`~ModelAdmin.formfield_for_manytomany` -.. versionadded:: 1.2 - -- :attr:`~ModelAdmin.readonly_fields` -- :attr:`~ModelAdmin.formfield_overrides` - .. versionadded:: 1.3 - :attr:`~ModelAdmin.ordering` @@ -1463,8 +1443,6 @@ The ``InlineModelAdmin`` class adds: :doc:`formsets documentation ` for more information. - .. versionadded:: 1.2 - For users with JavaScript-enabled browsers, an "Add another" link is provided to enable any number of additional inlines to be added in addition to those provided as a result of the ``extra`` argument. @@ -1541,8 +1519,6 @@ automatically:: Working with many-to-many models -------------------------------- -.. versionadded:: 1.2 - By default, admin widgets for many-to-many relations will be displayed on whichever model contains the actual reference to the :class:`~django.db.models.ManyToManyField`. Depending on your ``ModelAdmin`` @@ -1842,21 +1818,15 @@ Templates can override or extend base admin templates as described in .. attribute:: AdminSite.logout_template - .. versionadded:: 1.2 - Path to a custom template that will be used by the admin site logout view. .. attribute:: AdminSite.password_change_template - .. versionadded:: 1.2 - Path to a custom template that will be used by the admin site password change view. .. attribute:: AdminSite.password_change_done_template - .. versionadded:: 1.2 - Path to a custom template that will be used by the admin site password change done view. @@ -1978,16 +1948,17 @@ accessible using Django's :ref:`URL reversing system `. The :class:`AdminSite` provides the following named URL patterns: -====================== ======================== ============= -Page URL name Parameters -====================== ======================== ============= -Index ``index`` -Logout ``logout`` -Password change ``password_change`` -Password change done ``password_change_done`` -i18n javascript ``jsi18n`` -Application index page ``app_list`` ``app_label`` -====================== ======================== ============= +========================= ======================== ================================== +Page URL name Parameters +========================= ======================== ================================== +Index ``index`` +Logout ``logout`` +Password change ``password_change`` +Password change done ``password_change_done`` +i18n javascript ``jsi18n`` +Application index page ``app_list`` ``app_label`` +Redirect to object's page ``view_on_site`` ``content_type_id``, ``object_id`` +========================= ======================== ================================== Each :class:`ModelAdmin` instance provides an additional set of named URLs: diff --git a/docs/ref/contrib/comments/custom.txt b/docs/ref/contrib/comments/custom.txt index 5007ddff69..0ef37a9a0b 100644 --- a/docs/ref/contrib/comments/custom.txt +++ b/docs/ref/contrib/comments/custom.txt @@ -51,9 +51,9 @@ To make this kind of customization, we'll need to do three things: custom :setting:`COMMENTS_APP`. So, carrying on the example above, we're dealing with a typical app structure in -the ``my_custom_app`` directory:: +the ``my_comment_app`` directory:: - my_custom_app/ + my_comment_app/ __init__.py models.py forms.py @@ -98,11 +98,11 @@ Django provides a couple of "helper" classes to make writing certain types of custom comment forms easier; see :mod:`django.contrib.comments.forms` for more. -Finally, we'll define a couple of methods in ``my_custom_app/__init__.py`` to +Finally, we'll define a couple of methods in ``my_comment_app/__init__.py`` to point Django at these classes we've created:: - from my_comments_app.models import CommentWithTitle - from my_comments_app.forms import CommentFormWithTitle + from my_comment_app.models import CommentWithTitle + from my_comment_app.forms import CommentFormWithTitle def get_model(): return CommentWithTitle diff --git a/docs/ref/contrib/comments/example.txt b/docs/ref/contrib/comments/example.txt index f6c118a02d..e78d83c35d 100644 --- a/docs/ref/contrib/comments/example.txt +++ b/docs/ref/contrib/comments/example.txt @@ -37,8 +37,6 @@ available in the context, then you can refer to it directly:: {% get_comment_count for entry as comment_count %}

      {{ comment_count }} comments have been posted.

      -.. versionadded:: 1.2 - Next, we can use the :ttag:`render_comment_list` tag, to render all comments to the given instance (``entry``) by using the ``comments/list.html`` template:: diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt index 61e7bd9f46..af937e036e 100644 --- a/docs/ref/contrib/comments/index.txt +++ b/docs/ref/contrib/comments/index.txt @@ -130,8 +130,6 @@ details. Linking to comments ------------------- -.. versionadded:: 1.2 - To provide a permalink to a specific comment, use :ttag:`get_comment_permalink`:: {% get_comment_permalink comment_obj [format_string] %} @@ -252,7 +250,7 @@ Redirecting after the comment post To specify the URL you want to redirect to after the comment has been posted, you can include a hidden form input called ``next`` in your comment form. For example:: - + .. _notes-on-the-comment-form: diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index 1181e4b44f..0226435159 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -187,13 +187,13 @@ The ``ContentTypeManager`` probably won't ever need to call this method yourself; Django will call it automatically when it's needed. - .. method:: get_for_model(model) + .. method:: get_for_model(model[, for_concrete_model=True]) Takes either a model class or an instance of a model, and returns the :class:`~django.contrib.contenttypes.models.ContentType` instance representing that model. - .. method:: get_for_models(*models) + .. method:: get_for_models(*models[, for_concrete_models=True]) Takes a variadic number of model classes, and returns a dictionary mapping the model classes to the @@ -224,6 +224,19 @@ lookup:: .. _generic-relations: +.. versionadded:: 1.5 + +Prior to Django 1.5 :meth:`~ContentTypeManager.get_for_model()` and +:meth:`~ContentTypeManager.get_for_models()` always returned the +:class:`~django.contrib.contenttypes.models.ContentType` associated with the +concrete model of the specified one(s). That means there was no way to retreive +the :class:`~django.contrib.contenttypes.models.ContentType` of a proxy model +using those methods. As of Django 1.5 you can now pass a boolean flag – +respectively ``for_concrete_model`` and ``for_concrete_models`` – to specify +wether or not you want to retreive the +:class:`~django.contrib.contenttypes.models.ContentType` for the concrete or +direct model. + Generic relations ================= diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index 0ff9bd1a51..f25cb31e4b 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -434,14 +434,12 @@ A number of settings can be used to control Django's CSRF behavior. CSRF_COOKIE_DOMAIN ------------------ -.. versionadded:: 1.2 - Default: ``None`` The domain to be used when setting the CSRF cookie. This can be useful for easily allowing cross-subdomain requests to be excluded from the normal cross site request forgery protection. It should be set to a string such as -``".lawrence.com"`` to allow a POST request from a form on one subdomain to be +``".example.com"`` to allow a POST request from a form on one subdomain to be accepted by a view served from another subdomain. Please note that, with or without use of this setting, this CSRF protection @@ -450,8 +448,6 @@ mechanism is not safe against cross-subdomain attacks -- see `Limitations`_. CSRF_COOKIE_NAME ---------------- -.. versionadded:: 1.2 - Default: ``'csrftoken'`` The name of the cookie to use for the CSRF authentication token. This can be @@ -485,8 +481,6 @@ cookie is only sent under an HTTPS connection. CSRF_FAILURE_VIEW ----------------- -.. versionadded:: 1.2 - Default: ``'django.views.csrf.csrf_failure'`` A dotted path to the view function to be used when an incoming request diff --git a/docs/ref/contrib/formtools/form-preview.txt b/docs/ref/contrib/formtools/form-preview.txt index f9a1feb262..784213ecba 100644 --- a/docs/ref/contrib/formtools/form-preview.txt +++ b/docs/ref/contrib/formtools/form-preview.txt @@ -110,8 +110,6 @@ default templates. Advanced ``FormPreview`` methods ================================ -.. versionadded:: 1.2 - .. method:: FormPreview.process_preview Given a validated form, performs any extra processing before displaying the diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index 7aafbe89f3..b8e585a4d2 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -86,8 +86,8 @@ the message itself. Here's what the :file:`forms.py` might look like:: section :ref:`Handling files ` below to learn more about what to do. -Creating a ``WizardView`` class -------------------------------- +Creating a ``WizardView`` subclass +---------------------------------- The next step is to create a :class:`django.contrib.formtools.wizard.views.WizardView` subclass. You can @@ -528,7 +528,7 @@ We define our wizard in a ``views.py``:: We need to add the ``ContactWizard`` to our ``urls.py`` file:: - from django.conf.urls import pattern + from django.conf.urls import patterns from myapp.forms import ContactForm1, ContactForm2 from myapp.views import ContactWizard, show_message_form_condition @@ -554,9 +554,8 @@ How to work with ModelForm and ModelFormSet WizardView supports :doc:`ModelForms ` and :ref:`ModelFormSets `. Additionally to :attr:`~WizardView.initial_dict`, the :meth:`~WizardView.as_view` method takes -an ``instance_dict`` argument that should contain instances of ``ModelForm`` and -``ModelFormSet``. Similarly to :attr:`~WizardView.initial_dict`, these -dictionary key values should be equal to the step number in the form list. +an ``instance_dict`` argument that should contain model instances for steps +based on ``ModelForm`` and querysets for steps based on ``ModelFormSet``. Usage of ``NamedUrlWizardView`` =============================== diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index 9c6eb033eb..318110ef04 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -12,11 +12,7 @@ GeoDjango Database API Spatial Backends ================ -.. versionadded:: 1.2 - -In Django 1.2, support for :doc:`multiple databases ` was -introduced. In order to support multiple databases, GeoDjango has segregated -its functionality into full-fledged spatial database backends: +GeoDjango currently provides the following spatial database backends: * :mod:`django.contrib.gis.db.backends.postgis` * :mod:`django.contrib.gis.db.backends.mysql` diff --git a/docs/ref/contrib/gis/deployment.txt b/docs/ref/contrib/gis/deployment.txt index d98fc51837..f90c9c2e91 100644 --- a/docs/ref/contrib/gis/deployment.txt +++ b/docs/ref/contrib/gis/deployment.txt @@ -2,6 +2,10 @@ Deploying GeoDjango =================== +Basically, the deployment of a GeoDjango application is not different from +the deployment of a normal Django application. Please consult Django's +:doc:`deployment documentation `. + .. warning:: GeoDjango uses the GDAL geospatial library which is @@ -10,58 +14,7 @@ Deploying GeoDjango appropriate configuration of Apache or the prefork method when using FastCGI through another Web server. -Apache -====== -In this section there are some example ``VirtualHost`` directives for -when deploying using ``mod_wsgi``, which is now officially the recommended -way to deploy Django applications with Apache. -As long as ``mod_wsgi`` is configured correctly, it does not -matter whether the version of Apache is prefork or worker. - -.. note:: - - The ``Alias`` and ``Directory`` configurations in the examples - below use an example path to a system-wide installation folder of Django. - Substitute in an appropriate location, if necessary, as it may be - different than the path on your system. - -``mod_wsgi`` ------------- - -Example:: - - - WSGIDaemonProcess geodjango user=geo group=geo processes=5 threads=1 - WSGIProcessGroup geodjango - WSGIScriptAlias / /home/geo/geodjango/world.wsgi - - Alias /media/ "/usr/lib/python2.6/site-packages/django/contrib/admin/media/" - - Order allow,deny - Options Indexes - Allow from all - IndexOptions FancyIndexing - - - - -.. warning:: - - If the ``WSGIDaemonProcess`` attribute ``threads`` is not set to ``1``, then + For example, when configuring your application with ``mod_wsgi``, + set the ``WSGIDaemonProcess`` attribute ``threads`` to ``1``, unless Apache may crash when running your GeoDjango application. Increase the number of ``processes`` instead. - -For more information, please consult Django's -:doc:`mod_wsgi documentation `. - -Lighttpd -======== - -FastCGI -------- - -Nginx -===== - -FastCGI -------- diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index 5cd6187c6c..c4b29bead7 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -212,8 +212,6 @@ __ http://www.gdal.org/ogr/ogr_formats.html .. attribute:: spatial_filter - .. versionadded:: 1.2 - Property that may be used to retrieve or set a spatial filter for this layer. A spatial filter can only be set with an :class:`OGRGeometry` instance, a 4-tuple extent, or ``None``. When set with something @@ -449,7 +447,7 @@ systems and coordinate transformation:: This object is a wrapper for the `OGR Geometry`__ class. These objects are instantiated directly from the given ``geom_input`` - parameter, which may be a string containing WKT or HEX, a ``buffer`` + parameter, which may be a string containing WKT, HEX, GeoJSON, a ``buffer`` containing WKB data, or an :class:`OGRGeomType` object. These objects are also returned from the :class:`Feature.geom` attribute, when reading vector data from :class:`Layer` (which is in turn a part of @@ -490,15 +488,9 @@ systems and coordinate transformation:: .. attribute:: coord_dim - .. versionchanged:: 1.2 - Returns or sets the coordinate dimension of this geometry. For example, the value would be 2 for two-dimensional geometries. - .. note:: - - Setting this property is only available in versions 1.2 and above. - .. attribute:: geom_count Returns the number of elements in this geometry:: @@ -619,8 +611,6 @@ systems and coordinate transformation:: .. attribute:: ewkt - .. versionadded:: 1.2 - Returns the EWKT representation of this geometry. .. method:: clone() diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index b9e3a7acd3..eeec2e2133 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -117,8 +117,6 @@ SpatiaLite ``Contains(poly, geom)`` contains_properly ----------------- -.. versionadded:: 1.2 - *Availability*: PostGIS Returns true if the lookup geometry intersects the interior of the @@ -803,8 +801,6 @@ Geometry Editors .. method:: GeoQuerySet.force_rhr(**kwargs) -.. versionadded:: 1.2 - *Availability*: PostGIS Returns a modified version of the polygon/multipolygon in which all @@ -816,8 +812,6 @@ of the vertices follow the Right-Hand-Rule, and attaches as a .. method:: GeoQuerySet.reverse_geom(**kwargs) -.. versionadded:: 1.2 - *Availability*: PostGIS, Oracle Reverse the coordinate order of the geometry field, and attaches as a @@ -943,8 +937,6 @@ of the geometry field in each model converted to the requested output format. .. method:: GeoQuerySet.geohash(precision=20, **kwargs) -.. versionadded:: 1.2 - Attaches a ``geohash`` attribute to every model the queryset containing the `GeoHash`__ representation of the geometry. @@ -1034,7 +1026,7 @@ Keyword Argument Description representation -- the default value is 8. ===================== ===================================================== -__ http://code.google.com/apis/kml/documentation/ +__ https://developers.google.com/kml/documentation/ ``svg`` ~~~~~~~ @@ -1136,8 +1128,6 @@ Example:: .. method:: GeoQuerySet.extent3d(**kwargs) -.. versionadded:: 1.2 - *Availability*: PostGIS Returns the 3D extent of the ``GeoQuerySet`` as a six-tuple, comprising @@ -1195,7 +1185,7 @@ Keyword Argument Description details. ===================== ===================================================== -__ http://download.oracle.com/docs/html/B14255_01/sdo_intro.htm#sthref150 +__ http://docs.oracle.com/html/B14255_01/sdo_intro.htm#sthref150 Aggregate Functions ------------------- @@ -1224,8 +1214,6 @@ Returns the same as the :meth:`GeoQuerySet.extent` aggregate method. .. class:: Extent3D(geo_field) -.. versionadded:: 1.2 - Returns the same as the :meth:`GeoQuerySet.extent3d` aggregate method. ``MakeLine`` @@ -1244,6 +1232,6 @@ Returns the same as the :meth:`GeoQuerySet.union` aggregate method. .. rubric:: Footnotes .. [#fnde9im] *See* `OpenGIS Simple Feature Specification For SQL `_, at Ch. 2.1.13.2, p. 2-13 (The Dimensionally Extended Nine-Intersection Model). -.. [#fnsdorelate] *See* `SDO_RELATE documentation `_, from Ch. 11 of the Oracle Spatial User's Guide and Manual. +.. [#fnsdorelate] *See* `SDO_RELATE documentation `_, from Ch. 11 of the Oracle Spatial User's Guide and Manual. .. [#fncovers] For an explanation of this routine, read `Quirks of the "Contains" Spatial Predicate `_ by Martin Davis (a PostGIS developer). .. [#fncontainsproperly] Refer to the PostGIS ``ST_ContainsProperly`` `documentation `_ for more details. diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index d3d9fe4442..f4e706d275 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -75,6 +75,17 @@ return a :class:`GEOSGeometry` object from an input string or a file:: >>> pnt = fromfile('/path/to/pnt.wkt') >>> pnt = fromfile(open('/path/to/pnt.wkt')) +.. _geos-exceptions-in-logfile: + +.. admonition:: My logs are filled with GEOS-related errors + + You find many ``TypeError`` or ``AttributeError`` exceptions filling your + Web server's log files. This generally means that you are creating GEOS + objects at the top level of some of your Python modules. Then, due to a race + condition in the garbage collector, your module is garbage collected before + the GEOS object. To prevent this, create :class:`GEOSGeometry` objects + inside the local scope of your functions/methods. + Geometries are Pythonic ----------------------- :class:`GEOSGeometry` objects are 'Pythonic', in other words components may @@ -265,8 +276,6 @@ because it is not a part of the OGC specification (use the .. attribute:: GEOSGeometry.hexewkb -.. versionadded:: 1.2 - Returns the EWKB of this Geometry in hexadecimal form. This is an extension of the WKB specification that includes SRID and Z values that are a part of this geometry. @@ -314,8 +323,6 @@ as a Python buffer. SRID and Z values are not included, use the .. attribute:: GEOSGeometry.ewkb -.. versionadded:: 1.2 - Return the EWKB representation of this Geometry as a Python buffer. This is an extension of the WKB specification that includes any SRID and Z values that are a part of this geometry. @@ -328,7 +335,7 @@ and Z values that are a part of this geometry. Returns the Well-Known Text of the geometry (an OGC standard). -__ http://code.google.com/apis/kml/documentation/ +__ https://developers.google.com/kml/documentation/ Spatial Predicate Methods ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -895,7 +902,7 @@ Returns the WKT of the given geometry. Example:: .. rubric:: Footnotes -.. [#fnogc] *See* `PostGIS EWKB, EWKT and Canonical Forms `_, PostGIS documentation at Ch. 4.1.2. +.. [#fnogc] *See* `PostGIS EWKB, EWKT and Canonical Forms `_, PostGIS documentation at Ch. 4.1.2. .. [#fncascadedunion] For more information, read Paul Ramsey's blog post about `(Much) Faster Unions in PostGIS 1.4 `_ and Martin Davis' blog post on `Fast polygon merging in JTS using Cascaded Union `_. Settings diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 9cb945f76a..3cd790212c 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -60,14 +60,14 @@ The geospatial libraries required for a GeoDjango installation depends on the spatial database used. The following lists the library requirements, supported versions, and any notes for each of the supported database backends: -================== ============================== ================== ========================================================== +================== ============================== ================== ========================================= Database Library Requirements Supported Versions Notes -================== ============================== ================== ========================================================== +================== ============================== ================== ========================================= PostgreSQL GEOS, PROJ.4, PostGIS 8.1+ Requires PostGIS. MySQL GEOS 5.x Not OGC-compliant; limited functionality. Oracle GEOS 10.2, 11 XE not supported; not tested with 9. -SQLite GEOS, GDAL, PROJ.4, SpatiaLite 3.6.+ Requires SpatiaLite 2.3+, pysqlite2 2.5+, and Django 1.1. -================== ============================== ================== ========================================================== +SQLite GEOS, GDAL, PROJ.4, SpatiaLite 3.6.+ Requires SpatiaLite 2.3+, pysqlite2 2.5+ +================== ============================== ================== ========================================= .. _geospatial_libs: @@ -81,7 +81,7 @@ Program Description Required ======================== ==================================== ================================ ========================== :ref:`GEOS ` Geometry Engine Open Source Yes 3.3, 3.2, 3.1, 3.0 `PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 4.7, 4.6, 4.5, 4.4 -:ref:`GDAL ` Geospatial Data Abstraction Library No (but, required for SQLite) 1.8, 1.7, 1.6, 1.5, 1.4 +:ref:`GDAL ` Geospatial Data Abstraction Library No (but, required for SQLite) 1.9, 1.8, 1.7, 1.6, 1.5 :ref:`GeoIP ` IP-based geolocation library No 1.4 `PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 1.5, 1.4, 1.3 `SpatiaLite`__ Spatial extensions for SQLite Yes (SQLite only) 3.0, 2.4, 2.3 @@ -102,7 +102,7 @@ Program Description Required .. _PROJ.4: http://trac.osgeo.org/proj/ __ http://postgis.refractions.net/ -__ http://www.gaia-gis.it/spatialite/index.html +__ http://www.gaia-gis.it/gaia-sins/ .. _build_from_source: @@ -140,7 +140,7 @@ internal geometry representation used by GeoDjango (it's behind the "lazy" geometries). Specifically, the C API library is called (e.g., ``libgeos_c.so``) directly from Python using ctypes. -First, download GEOS 3.2 from the refractions Web site and untar the source +First, download GEOS 3.3.0 from the refractions Web site and untar the source archive:: $ wget http://download.osgeo.org/geos/geos-3.3.0.tar.bz2 @@ -191,6 +191,8 @@ GEOS C library. For example: The setting must be the *full* path to the **C** shared library; in other words you want to use ``libgeos_c.so``, not ``libgeos.so``. +See also :ref:`My logs are filled with GEOS-related errors `. + .. _proj4: PROJ.4 @@ -233,7 +235,7 @@ installed prior to building PostGIS. The `psycopg2`_ module is required for use as the database adaptor when using GeoDjango with PostGIS. -.. _psycopg2: http://initd.org/projects/psycopg2 +.. _psycopg2: http://initd.org/psycopg/ First download the source archive, and extract:: @@ -270,9 +272,9 @@ supports :ref:`GDAL's vector data ` capabilities [#]_. First download the latest GDAL release version and untar the archive:: - $ wget http://download.osgeo.org/gdal/gdal-1.8.1.tar.gz - $ tar xzf gdal-1.8.1.tar.gz - $ cd gdal-1.8.1 + $ wget http://download.osgeo.org/gdal/gdal-1.9.1.tar.gz + $ tar xzf gdal-1.9.1.tar.gz + $ cd gdal-1.9.1 Configure, make and install:: @@ -376,7 +378,7 @@ SpatiaLite. After installation is complete, don't forget to read the post-installation docs on :ref:`create_spatialite_db`. -__ http://www.gaia-gis.it/spatialite/index.html +__ http://www.gaia-gis.it/gaia-sins/ .. _sqlite: @@ -418,8 +420,8 @@ SpatiaLite library (``libspatialite``) and tools (``spatialite``) After SQLite has been built with the R*Tree module enabled, get the latest SpatiaLite library source and tools bundle from the `download page`__:: - $ wget http://www.gaia-gis.it/spatialite/libspatialite-amalgamation-2.3.1.tar.gz - $ wget http://www.gaia-gis.it/spatialite/spatialite-tools-2.3.1.tar.gz + $ wget http://www.gaia-gis.it/gaia-sins/libspatialite-sources/libspatialite-amalgamation-2.3.1.tar.gz + $ wget http://www.gaia-gis.it/gaia-sins/spatialite-tools-sources/spatialite-tools-2.3.1.tar.gz $ tar xzf libspatialite-amalgamation-2.3.1.tar.gz $ tar xzf spatialite-tools-2.3.1.tar.gz @@ -457,7 +459,7 @@ Finally, do the same for the SpatiaLite tools:: $ ./configure --target=macosx -__ http://www.gaia-gis.it/spatialite/sources.html +__ http://www.gaia-gis.it/gaia-sins/libspatialite-sources/ .. _pysqlite2: @@ -465,8 +467,8 @@ pysqlite2 ^^^^^^^^^ Because SpatiaLite must be loaded as an external extension, it requires the -``enable_load_extension`` method, which is only available in versions 2.5+. -Thus, download pysqlite2 2.6, and untar:: +``enable_load_extension`` method, which is only available in versions 2.5+ of +pysqlite2. Thus, download pysqlite2 2.6, and untar:: $ wget http://pysqlite.googlecode.com/files/pysqlite-2.6.0.tar.gz $ tar xzf pysqlite-2.6.0.tar.gz @@ -581,7 +583,7 @@ After you've installed SpatiaLite, you'll need to create a number of spatial metadata tables in your database in order to perform spatial queries. If you're using SpatiaLite 3.0 or newer, use the ``spatialite`` utility to -call the ``InitSpatiaMetaData()`` function, like this:: +call the ``InitSpatialMetaData()`` function, like this:: $ spatialite geodjango.db "SELECT InitSpatialMetaData();" the SPATIAL_REF_SYS table already contains some row(s) @@ -603,7 +605,7 @@ http://www.gaia-gis.it/spatialite-2.4.0/ for 2.4):: Then, use the ``spatialite`` command to initialize a spatial database:: - $ spatialite geodjango.db < init_spatialite-2.X.sql + $ spatialite geodjango.db < init_spatialite-2.3.sql .. note:: @@ -624,8 +626,6 @@ features such as the geographic admin or KML sitemaps will not function properly Add Google projection to ``spatial_ref_sys`` table -------------------------------------------------- -.. versionchanged:: 1.2 - .. note:: If you're running PostGIS 1.4 or above, you can skip this step. The entry @@ -643,10 +643,6 @@ Invoke the Django shell from your project and execute the >>> from django.contrib.gis.utils import add_srs_entry >>> add_srs_entry(900913) -.. note:: - - In Django 1.1 the name of this function is ``add_postgis_srs``. - This adds an entry for the 900913 SRID to the ``spatial_ref_sys`` (or equivalent) table, making it possible for the spatial database to transform coordinates in this projection. You only need to execute this command *once* per spatial database. @@ -666,7 +662,7 @@ community! You can: and specify the component as "GIS". __ http://groups.google.com/group/geodjango -__ https://code.djangoproject.com/simpleticket +__ https://code.djangoproject.com/newticket .. _libsettings: @@ -764,13 +760,13 @@ Python ^^^^^^ Although OS X comes with Python installed, users can use framework -installers (`2.5`__ and `2.6`__ are available) provided by +installers (`2.6`__ and `2.7`__ are available) provided by the Python Software Foundation. An advantage to using the installer is that OS X's Python will remain "pristine" for internal operating system use. -__ http://python.org/ftp/python/2.5.4/python-2.5.4-macosx.dmg -__ http://python.org/ftp/python/2.6.2/python-2.6.2-macosx2009-04-16.dmg +__ http://python.org/ftp/python/2.6.6/python-2.6.6-macosx10.3.dmg +__ http://python.org/ftp/python/2.7.3/ .. note:: @@ -840,17 +836,6 @@ your ``.profile`` to be able to run the package programs from the command-line:: __ http://www.kyngchaos.com/software/frameworks __ http://www.kyngchaos.com/software/postgres -.. note:: - - Use of these binaries requires Django 1.0.3 and above. If you are - using a previous version of Django (like 1.0.2), then you will have - to add the following in your settings: - - .. code-block:: python - - GEOS_LIBRARY_PATH='/Library/Frameworks/GEOS.framework/GEOS' - GDAL_LIBRARY_PATH='/Library/Frameworks/GDAL.framework/GDAL' - .. _psycopg2_kyngchaos: psycopg2 @@ -905,7 +890,7 @@ add the following to your ``settings.py``: SPATIALITE_LIBRARY_PATH='/Library/Frameworks/SQLite3.framework/SQLite3' -__ http://www.gaia-gis.it/spatialite/binaries.html +__ http://www.gaia-gis.it/spatialite-2.3.1/binaries.html .. _fink: @@ -1047,61 +1032,11 @@ Optional packages to consider: do not plan on doing any database transformation of geometries to the Google projection (900913). -.. _heron: - -8.04 and lower -~~~~~~~~~~~~~~ - -The 8.04 (and lower) versions of Ubuntu use GEOS v2.2.3 in their binary packages, -which is incompatible with GeoDjango. Thus, do *not* use the binary packages -for GEOS or PostGIS and build some prerequisites from source, per the instructions -in this document; however, it is okay to use the PostgreSQL binary packages. - -For more details, please see the Debian instructions for :ref:`etch` below. - .. _debian: Debian ------ -.. _etch: - -4.0 (Etch) -^^^^^^^^^^ - -The situation here is the same as that of Ubuntu :ref:`heron` -- in other words, -some packages must be built from source to work properly with GeoDjango. - -Binary packages -~~~~~~~~~~~~~~~ -The following command will install acceptable binary packages, as well as -the development tools necessary to build the rest of the requirements: - -.. code-block:: bash - - $ sudo apt-get install binutils bzip2 gcc g++ flex make postgresql-8.1 \ - postgresql-server-dev-8.1 python-ctypes python-psycopg2 python-setuptools - -Required package information: - -* ``binutils``: for ctypes to find libraries -* ``bzip2``: for decompressing the source packages -* ``gcc``, ``g++``, ``make``: GNU developer tools used to compile the libraries -* ``flex``: required to build PostGIS -* ``postgresql-8.1`` -* ``postgresql-server-dev-8.1``: for ``pg_config`` -* ``python-psycopg2`` - -Optional packages: - -* ``libgeoip``: for :ref:`GeoIP ` support - -Source packages -~~~~~~~~~~~~~~~ -You will still have to install :ref:`geosbuild`, :ref:`proj4`, -:ref:`postgis`, and :ref:`gdalbuild` from source. Please follow the -directions carefully. - .. _lenny: 5.0 (Lenny) @@ -1316,8 +1251,8 @@ may be executed from the SQL Shell as the ``postgres`` user:: .. rubric:: Footnotes .. [#] The datum shifting files are needed for converting data to and from certain projections. - For example, the PROJ.4 string for the `Google projection (900913) - `_ requires the + For example, the PROJ.4 string for the `Google projection (900913 or 3857) + `_ requires the ``null`` grid file only included in the extra datum shifting files. It is easier to install the shifting files now, then to have debug a problem caused by their absence later. diff --git a/docs/ref/contrib/gis/model-api.txt b/docs/ref/contrib/gis/model-api.txt index bbbf148106..8c5274e6d3 100644 --- a/docs/ref/contrib/gis/model-api.txt +++ b/docs/ref/contrib/gis/model-api.txt @@ -142,7 +142,7 @@ __ http://en.wikipedia.org/wiki/Geodesy __ http://en.wikipedia.org/wiki/Great_circle __ http://www.spatialreference.org/ref/epsg/2796/ __ http://spatialreference.org/ -__ http://welcome.warnercnr.colostate.edu/class_info/nr502/lg3/datums_coordinates/spcs.html +__ http://web.archive.org/web/20080302095452/http://welcome.warnercnr.colostate.edu/class_info/nr502/lg3/datums_coordinates/spcs.html ``spatial_index`` ----------------- @@ -163,8 +163,6 @@ field. ``dim`` ------- -.. versionadded:: 1.2 - .. attribute:: GeometryField.dim This option may be used for customizing the coordinate dimension of the @@ -180,8 +178,6 @@ three-dimensonal support. ``geography`` ------------- -.. versionadded:: 1.2 - .. attribute:: GeometryField.geography If set to ``True``, this option will create a database column of @@ -256,7 +252,7 @@ for example:: qs = Address.objects.filter(zipcode__poly__contains='POINT(-104.590948 38.319914)') .. rubric:: Footnotes -.. [#fnogc] OpenGIS Consortium, Inc., `Simple Feature Specification For SQL `_, Document 99-049 (May 5, 1999). +.. [#fnogc] OpenGIS Consortium, Inc., `Simple Feature Specification For SQL `_. .. [#fnogcsrid] *See id.* at Ch. 2.3.8, p. 39 (Geometry Values and Spatial Reference Systems). .. [#fnsrid] Typically, SRID integer corresponds to an EPSG (`European Petroleum Survey Group `_) identifier. However, it may also be associated with custom projections defined in spatial database's spatial reference systems table. .. [#fnharvard] Harvard Graduate School of Design, `An Overview of Geodesy and Geographic Referencing Systems `_. This is an excellent resource for an overview of principles relating to geographic and Cartesian coordinate systems. diff --git a/docs/ref/contrib/gis/sitemaps.txt b/docs/ref/contrib/gis/sitemaps.txt index 75bddd3b86..0ab8f75825 100644 --- a/docs/ref/contrib/gis/sitemaps.txt +++ b/docs/ref/contrib/gis/sitemaps.txt @@ -2,10 +2,10 @@ Geographic Sitemaps =================== -Google's sitemap protocol has been recently extended to support geospatial -content. [#]_ This includes the addition of the ```` child element +Google's sitemap protocol used to include geospatial content support. [#]_ +This included the addition of the ```` child element ````, which tells Google that the content located at the URL is -geographic in nature. [#]_ +geographic in nature. This is now obsolete. Example ======= @@ -23,5 +23,4 @@ Reference ----------------- .. rubric:: Footnotes -.. [#] Google, Inc., `What is a Geo Sitemap? `_. -.. [#] Google, Inc., `Submit Your Geo Content to Google `_. +.. [#] Google, Inc., `What is a Geo Sitemap? `_. diff --git a/docs/ref/contrib/gis/testing.txt b/docs/ref/contrib/gis/testing.txt index b21ba7b407..18ffbdd7d8 100644 --- a/docs/ref/contrib/gis/testing.txt +++ b/docs/ref/contrib/gis/testing.txt @@ -2,12 +2,6 @@ Testing GeoDjango apps ====================== -.. versionchanged:: 1.2 - -In Django 1.2, the addition of :ref:`spatial-backends` simplified the -process of testing GeoDjango applications. The process is now the -same as :doc:`/topics/testing`. - Included in this documentation are some additional notes and settings for :ref:`testing-postgis` and :ref:`testing-spatialite` users. @@ -28,11 +22,9 @@ Settings ``POSTGIS_TEMPLATE`` ^^^^^^^^^^^^^^^^^^^^ -.. versionchanged:: 1.2 - This setting may be used to customize the name of the PostGIS template -database to use. In Django versions 1.2 and above, it automatically -defaults to ``'template_postgis'`` (the same name used in the +database to use. It automatically defaults to ``'template_postgis'`` +(the same name used in the :ref:`installation documentation `). .. setting:: POSTGIS_VERSION @@ -129,7 +121,8 @@ Settings Only relevant when using a SpatiaLite version older than 3.0. -By default, the GeoDjango test runner looks for the SpatiaLite SQL in the +By default, the GeoDjango test runner looks for the :ref:`file containing the +SpatiaLite dababase-initialization SQL code ` in the same directory where it was invoked (by default the same directory where ``manage.py`` is located). To use a different location, add the following to your settings:: diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index 395eac1821..3a63493137 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -784,4 +784,4 @@ option class in your ``admin.py`` file:: .. [#] Special thanks to Bjørn Sandvik of `thematicmapping.org `_ for providing and maintaining this data set. .. [#] GeoDjango basic apps was written by Dane Springmeyer, Josh Livni, and Christopher Schmidt. .. [#] Here the point is for the `University of Houston Law Center `_. -.. [#] Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification For SQL `_, Document 99-049. +.. [#] Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification For SQL `_. diff --git a/docs/ref/contrib/humanize.txt b/docs/ref/contrib/humanize.txt index cdc3009a51..57978288b1 100644 --- a/docs/ref/contrib/humanize.txt +++ b/docs/ref/contrib/humanize.txt @@ -103,9 +103,9 @@ naturaltime .. versionadded:: 1.4 For datetime values, returns a string representing how many seconds, -minutes or hours ago it was -- falling back to a longer date format if the -value is more than a day old. In case the datetime value is in the future -the return value will automatically use an appropriate phrase. +minutes or hours ago it was -- falling back to the :tfilter:`timesince` +format if the value is more than a day old. In case the datetime value is in +the future the return value will automatically use an appropriate phrase. Examples (when 'now' is 17 Feb 2007 16:30:00): @@ -115,13 +115,14 @@ Examples (when 'now' is 17 Feb 2007 16:30:00): * ``17 Feb 2007 16:25:35`` becomes ``4 minutes ago``. * ``17 Feb 2007 15:30:29`` becomes ``an hour ago``. * ``17 Feb 2007 13:31:29`` becomes ``2 hours ago``. -* ``16 Feb 2007 13:31:29`` becomes ``1 day ago``. +* ``16 Feb 2007 13:31:29`` becomes ``1 day, 3 hours ago``. * ``17 Feb 2007 16:30:30`` becomes ``29 seconds from now``. * ``17 Feb 2007 16:31:00`` becomes ``a minute from now``. * ``17 Feb 2007 16:34:35`` becomes ``4 minutes from now``. * ``17 Feb 2007 16:30:29`` becomes ``an hour from now``. * ``17 Feb 2007 18:31:29`` becomes ``2 hours from now``. * ``18 Feb 2007 16:31:29`` becomes ``1 day from now``. +* ``26 Feb 2007 18:31:29`` becomes ``1 week, 2 days from now``. .. templatefilter:: ordinal diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt index 37216468b9..efe4393f64 100644 --- a/docs/ref/contrib/index.txt +++ b/docs/ref/contrib/index.txt @@ -142,9 +142,6 @@ See the :doc:`markup documentation `. messages ======== -.. versionchanged:: 1.2 - The messages framework was added. - A framework for storing and retrieving temporary cookie- or session-based messages diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index 61c8c7ae47..4595f51d9e 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -93,7 +93,7 @@ Here's an example of how to use them:: class MyForm(forms.Form): my_date_field = generic.forms.DateField() -.. _ISO 3166 country codes: http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm +.. _ISO 3166 country codes: http://www.iso.org/iso/country_codes.htm .. _Argentina: `Argentina (ar)`_ .. _Australia: `Australia (au)`_ .. _Austria: `Austria (at)`_ @@ -158,7 +158,7 @@ any code you'd like to contribute. One thing we ask is that you please use Unicode objects (``u'mystring'``) for strings, rather than setting the encoding in the file. See any of the existing flavors for examples. -.. _create a ticket: https://code.djangoproject.com/simpleticket +.. _create a ticket: https://code.djangoproject.com/newticket Localflavor and backwards compatibility ======================================= @@ -713,7 +713,7 @@ Italy (``it``) A form field that validates input as an Italian social security number (`codice fiscale`_). -.. _codice fiscale: http://www.agenziaentrate.it/ilwwcm/connect/Nsi/Servizi/Codice+fiscale+-+tessera+sanitaria/NSI+Informazioni+sulla+codificazione+delle+persone+fisiche +.. _codice fiscale: http://www.agenziaentrate.gov.it/wps/content/Nsilib/Nsi/Home/CosaDeviFare/Richiedere/Codice+fiscale+e+tessera+sanitaria/Richiesta+TS_CF/SchedaI/Informazioni+codificazione+pf/ .. class:: it.forms.ITVatNumberField diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index 4ab9734aa4..4cf90ee381 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -5,15 +5,14 @@ The messages framework .. module:: django.contrib.messages :synopsis: Provides cookie- and session-based temporary message storage. -Django provides full support for cookie- and session-based messaging, for -both anonymous and authenticated clients. The messages framework allows you -to temporarily store messages in one request and retrieve them for display -in a subsequent request (usually the next one). Every message is tagged -with a specific ``level`` that determines its priority (e.g., ``info``, -``warning``, or ``error``). - -.. versionadded:: 1.2 - The messages framework was added. +Quite commonly in web applications, you may need to display a one-time +notification message (also know as "flash message") to the user after +processing a form or some other types of user input. For this, Django provides +full support for cookie- and session-based messaging, for both anonymous and +authenticated users. The messages framework allows you to temporarily store +messages in one request and retrieve them for display in a subsequent request +(usually the next one). Every message is tagged with a specific ``level`` that +determines its priority (e.g., ``info``, ``warning``, or ``error``). Enabling messages ================= @@ -57,7 +56,8 @@ Storage backends ---------------- The messages framework can use different backends to store temporary messages. -To change which backend is being used, add a `MESSAGE_STORAGE`_ to your +If the default FallbackStorage isn't suitable to your needs, you can change +which backend is being used by adding a `MESSAGE_STORAGE`_ to your settings, referencing the module and class of the storage class. For example:: @@ -65,7 +65,7 @@ example:: The value should be the full path of the desired storage class. -Four storage classes are included: +Three storage classes are available: ``'django.contrib.messages.storage.session.SessionStorage'`` This class stores all messages inside of the request's session. It @@ -77,6 +77,8 @@ Four storage classes are included: messages are dropped if the cookie data size would exceed 4096 bytes. ``'django.contrib.messages.storage.fallback.FallbackStorage'`` + This is the default storage class. + This class first uses CookieStorage for all messages, falling back to using SessionStorage for the messages that could not fit in a single cookie. diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index d775092eae..2393a4a9a3 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -410,7 +410,7 @@ generate a Google News compatible sitemap: {% endspaceless %} -.. _`Google news sitemaps`: http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=74288 +.. _`Google news sitemaps`: http://support.google.com/webmasters/bin/answer.py?hl=en&answer=74288 Pinging Google ============== diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index 2bdf825316..cbe8ad54b8 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -380,13 +380,24 @@ full URL for the given relative path, e.g.: .. code-block:: html+django {% load static from staticfiles %} - + Hi! The previous example is equal to calling the ``url`` method of an instance of -:setting:`STATICFILES_STORAGE` with ``"css/base.css"``. This is especially +:setting:`STATICFILES_STORAGE` with ``"images/hi.jpg"``. This is especially useful when using a non-local storage backend to deploy files as documented in :ref:`staticfiles-from-cdn`. +.. versionadded:: 1.5 + +If you'd like to retrieve a static URL without displaying it, you can use a +slightly different call: + +.. code-block:: html+django + + {% load static from staticfiles %} + {% static "images/hi.jpg" as myphoto %} + Hi! + Other Helpers ============= diff --git a/docs/ref/contrib/syndication.txt b/docs/ref/contrib/syndication.txt index 6f89ee187c..5653397748 100644 --- a/docs/ref/contrib/syndication.txt +++ b/docs/ref/contrib/syndication.txt @@ -22,10 +22,6 @@ lower-level way. The high-level framework ======================== -.. versionchanged:: 1.2 - The high-level feeds framework was refactored in Django 1.2. The - pre-1.2 interface has been removed in Django 1.4. - Overview -------- @@ -176,9 +172,6 @@ These can be matched with a :doc:`URLconf ` line such as:: Like a view, the arguments in the URL are passed to the ``get_object()`` method along with the request object. -.. versionchanged:: 1.2 - Prior to version 1.2, ``get_object()`` only accepted a ``bits`` argument. - Here's the code for these beat-specific feeds:: from django.contrib.syndication.views import FeedDoesNotExist diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 2dea337d2b..3e256e9d9e 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -31,7 +31,7 @@ attempt to use the ``StdDev(sample=False)`` or ``Variance(sample=False)`` aggregate with a database backend that falls within the affected release range. .. _known to be faulty: http://archives.postgresql.org/pgsql-bugs/2007-07/msg00046.php -.. _Release 8.2.5: http://developer.postgresql.org/pgdocs/postgres/release-8-2-5.html +.. _Release 8.2.5: http://www.postgresql.org/docs/devel/static/release-8-2-5.html Optimizing PostgreSQL's configuration ------------------------------------- @@ -56,10 +56,10 @@ will do some additional queries to set these parameters. Transaction handling --------------------- -:doc:`By default `, Django starts a transaction when a -database connection is first used and commits the result at the end of the -request/response handling. The PostgreSQL backends normally operate the same -as any other Django backend in this respect. +:doc:`By default `, Django runs with an open +transaction which it commits automatically when any built-in, data-altering +model function is called. The PostgreSQL backends normally operate the same as +any other Django backend in this respect. .. _postgresql-autocommit-mode: @@ -111,7 +111,7 @@ outputs a single ``CREATE INDEX`` statement. However, if the database type for the field is either ``varchar`` or ``text`` (e.g., used by ``CharField``, ``FileField``, and ``TextField``), then Django will create an additional index that uses an appropriate `PostgreSQL operator class`_ -for the column. The extra index is necessary to correctly perfrom +for the column. The extra index is necessary to correctly perform lookups that use the ``LIKE`` operator in their SQL, as is done with the ``contains`` and ``startswith`` lookup types. @@ -238,7 +238,7 @@ to you, the developer, to handle the fact that you will receive bytestrings if you configure your table(s) to use ``utf8_bin`` collation. Django itself should mostly work smoothly with such columns (except for the ``contrib.sessions`` ``Session`` and ``contrib.admin`` ``LogEntry`` tables described below), but -your code must be prepared to call ``django.utils.encoding.smart_unicode()`` at +your code must be prepared to call ``django.utils.encoding.smart_text()`` at times if it really wants to work with consistent data -- Django will not do this for you (the database backend layer and the model population layer are separated internally so the database layer doesn't know it needs to make this @@ -335,7 +335,9 @@ storage engine, you have a couple of options. } This sets the default storage engine upon connecting to the database. - After your tables have been created, you should remove this option. + After your tables have been created, you should remove this option as it + adds a query that is only needed during table creation to each database + connection. * Another method for changing the storage engine is described in AlterModelOnSyncDB_. @@ -373,15 +375,6 @@ these methods in no-op's based in the results of such detection. Notes on specific fields ------------------------ -Boolean fields -~~~~~~~~~~~~~~ - -.. versionchanged:: 1.2 - -In previous versions of Django when running under MySQL ``BooleanFields`` would -return their data as ``ints``, instead of true ``bools``. See the release -notes for a complete description of the change. - Character fields ~~~~~~~~~~~~~~~~ diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 0ea8252c71..5ff7ecba2c 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -109,7 +109,7 @@ If not provided, all locales are processed. Example usage:: - django-admin.py compilemessages --locale=br_PT + django-admin.py compilemessages --locale=pt_BR createcachetable ---------------- @@ -119,8 +119,6 @@ createcachetable Creates a cache table named ``tablename`` for use with the database cache backend. See :doc:`/topics/cache` for more information. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database onto which the cachetable will be installed. @@ -142,8 +140,6 @@ the program name (``psql``, ``mysql``, ``sqlite3``) will find the program in the right place. There's no way to specify the location of the program manually. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database onto which to open a shell. @@ -212,15 +208,11 @@ name to ``dumpdata``, the dumped output will be restricted to that model, rather than the entire application. You can also mix application names and model names. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database from which data will be dumped. .. django-admin-option:: --natural -.. versionadded:: 1.2 - Use :ref:`natural keys ` to represent any foreign key and many-to-many relationship with a model that provides a natural key definition. If you are dumping ``contrib.auth`` ``Permission`` @@ -232,19 +224,25 @@ flush .. django-admin:: flush -Returns the database to the state it was in immediately after syncdb was -executed. This means that all data will be removed from the database, any +Returns the database to the state it was in immediately after :djadmin:`syncdb` +was executed. This means that all data will be removed from the database, any post-synchronization handlers will be re-executed, and the ``initial_data`` fixture will be re-installed. The :djadminopt:`--noinput` option may be provided to suppress all user prompts. -.. versionadded:: 1.2 - The :djadminopt:`--database` option may be used to specify the database to flush. +--no-initial-data +~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.5 + +Use ``--no-initial-data`` to avoid loading the initial_data fixture. + + inspectdb --------- @@ -288,8 +286,6 @@ needed. ``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection only works in PostgreSQL and with certain types of MySQL tables. -.. versionadded:: 1.2 - The :djadminopt:`--database` option may be used to specify the database to introspect. @@ -300,8 +296,6 @@ loaddata Searches for and loads the contents of the named fixture into the database. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database onto which the data will be loaded. @@ -432,7 +426,7 @@ Use the :djadminopt:`--locale` option to specify the locale to process. Example usage:: - django-admin.py makemessages --locale=br_PT + django-admin.py makemessages --locale=pt_BR .. django-admin-option:: --domain @@ -444,8 +438,6 @@ Currently supported: .. django-admin-option:: --symlinks -.. versionadded:: 1.2 - Use the ``--symlinks`` or ``-s`` option to follow symlinks to directories when looking for new translation strings. @@ -676,6 +668,7 @@ Example usage:: .. versionadded:: 1.4 +Since version 1.4, the development server is multithreaded by default. Use the ``--nothreading`` option to disable the use of threading in the development server. @@ -750,6 +743,24 @@ use the ``--plain`` option, like so:: django-admin.py shell --plain +.. versionchanged:: 1.5 + +If you would like to specify either IPython or bpython as your interpreter if +you have both installed you can specify an alternative interpreter interface +with the ``-i`` or ``--interface`` options like so: + +IPython:: + + django-admin.py shell -i ipython + django-admin.py shell --interface ipython + + +bpython:: + + django-admin.py shell -i bpython + django-admin.py shell --interface bpython + + .. _IPython: http://ipython.scipy.org/ .. _bpython: http://bpython-interpreter.org/ @@ -760,8 +771,6 @@ sql Prints the CREATE TABLE SQL statements for the given app name(s). -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -775,8 +784,6 @@ Prints the CREATE TABLE and initial-data SQL statements for the given app name(s Refer to the description of ``sqlcustom`` for an explanation of how to specify initial data. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -787,8 +794,6 @@ sqlclear Prints the DROP TABLE SQL statements for the given app name(s). -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -813,8 +818,6 @@ table modifications, or insert any SQL functions into the database. Note that the order in which the SQL files are processed is undefined. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -826,8 +829,6 @@ sqlflush Prints the SQL statements that would be executed for the :djadmin:`flush` command. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -838,8 +839,6 @@ sqlindexes Prints the CREATE INDEX SQL statements for the given app name(s). -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -856,8 +855,6 @@ number for automatically incremented fields. Use this command to generate SQL which will fix cases where a sequence is out of sync with its automatically incremented field data. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database for which to print the SQL. @@ -909,7 +906,8 @@ through the template engine: the files whose extensions match the with the ``--name`` option. The :class:`template context ` used is: -- Any option passed to the startapp command +- Any option passed to the startapp command (among the command's supported + options) - ``app_name`` -- the app name as passed to the command - ``app_directory`` -- the full path of the newly created app @@ -1019,11 +1017,16 @@ data files. The :djadminopt:`--noinput` option may be provided to suppress all user prompts. -.. versionadded:: 1.2 - The :djadminopt:`--database` option can be used to specify the database to synchronize. +--no-initial-data +~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.5 + +Use ``--no-initial-data`` to avoid loading the initial_data fixture. + test ----------------------------- @@ -1032,7 +1035,6 @@ test Runs tests for all installed models. See :doc:`/topics/testing` for more information. -.. versionadded:: 1.2 .. django-admin-option:: --failfast The ``--failfast`` option can be used to stop running tests and report the @@ -1140,8 +1142,6 @@ changepassword .. django-admin:: changepassword -.. versionadded:: 1.2 - This command is only available if Django's :doc:`authentication system ` (``django.contrib.auth``) is installed. @@ -1302,8 +1302,6 @@ to a number of commands. .. django-admin-option:: --database -.. versionadded:: 1.2 - Used to specify the database on which a command will operate. If not specified, this option will default to an alias of ``default``. diff --git a/docs/ref/files/file.txt b/docs/ref/files/file.txt index 10108d1f4f..99547f1c9e 100644 --- a/docs/ref/files/file.txt +++ b/docs/ref/files/file.txt @@ -94,10 +94,11 @@ The ``ContentFile`` Class but unlike :class:`~django.core.files.File` it operates on string content, rather than an actual file. For example:: + from __future__ import unicode_literals from django.core.files.base import ContentFile f1 = ContentFile(b"my string content") - f2 = ContentFile(u"my unicode content encoded as UTF-8".encode('UTF-8')) + f2 = ContentFile("my unicode content encoded as UTF-8".encode('UTF-8')) .. currentmodule:: django.core.files.images diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index a866fc550c..777d73e015 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -199,8 +199,8 @@ Note that any text-based field -- such as ``CharField`` or ``EmailField`` -- always cleans the input into a Unicode string. We'll cover the encoding implications later in this document. -If your data does *not* validate, your ``Form`` instance will not have a -``cleaned_data`` attribute:: +If your data does *not* validate, the ``cleaned_data`` dictionary contains +only the valid fields:: >>> data = {'subject': '', ... 'message': 'Hi there', @@ -210,9 +210,12 @@ If your data does *not* validate, your ``Form`` instance will not have a >>> f.is_valid() False >>> f.cleaned_data - Traceback (most recent call last): - ... - AttributeError: 'ContactForm' object has no attribute 'cleaned_data' + {'cc_myself': True, 'message': u'Hi there'} + +.. versionchanged:: 1.5 + +Until Django 1.5, the ``cleaned_data`` attribute wasn't defined at all when +the ``Form`` didn't validate. ``cleaned_data`` will always *only* contain a key for fields defined in the ``Form``, even if you pass extra data when you define the ``Form``. In this @@ -232,9 +235,9 @@ but ``cleaned_data`` contains only the form's fields:: >>> f.cleaned_data # Doesn't contain extra_field_1, etc. {'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'} -``cleaned_data`` will include a key and value for *all* fields defined in the -``Form``, even if the data didn't include a value for fields that are not -required. In this example, the data dictionary doesn't include a value for the +When the ``Form`` is valid, ``cleaned_data`` will include a key and value for +*all* its fields, even if the data didn't include a value for some optional +fields. In this example, the data dictionary doesn't include a value for the ``nick_name`` field, but ``cleaned_data`` includes it, with an empty value:: >>> class OptionalPersonForm(Form): @@ -377,8 +380,6 @@ a form object, and each rendering method returns a Unicode object. Styling required or erroneous form rows ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.2 - It's pretty common to style form rows and fields that are required or have errors. For example, you might want to present required form rows in bold and highlight errors in red. @@ -585,7 +586,7 @@ lazy developers -- they're not the only way a form object can be displayed. Used to display HTML or access attributes for a single field of a :class:`Form` instance. - + The :meth:`__unicode__` and :meth:`__str__` methods of this object displays the HTML for this field. @@ -638,8 +639,6 @@ For a field's list of errors, access the field's ``errors`` attribute. .. method:: BoundField.css_classes() - .. versionadded:: 1.2 - When you use Django's rendering shortcuts, CSS classes are used to indicate required form fields or fields that contain errors. If you're manually rendering a form, you can access these CSS classes using the diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 6e7d85f586..082ec17a35 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -25,8 +25,6 @@ exception or returns the clean value:: >>> f = forms.EmailField() >>> f.clean('foo@example.com') u'foo@example.com' - >>> f.clean(u'foo@example.com') - u'foo@example.com' >>> f.clean('invalid email address') Traceback (most recent call last): ... @@ -256,8 +254,6 @@ error message keys it uses. ``validators`` ~~~~~~~~~~~~~~ -.. versionadded:: 1.2 - .. attribute:: Field.validators The ``validators`` argument lets you provide a list of validation functions @@ -268,8 +264,6 @@ See the :doc:`validators documentation ` for more information. ``localize`` ~~~~~~~~~~~~ -.. versionadded:: 1.2 - .. attribute:: Field.localize The ``localize`` argument enables the localization of form data, input as well @@ -492,11 +486,6 @@ For each field, we describe the default widget used if you don't specify If provided, these arguments ensure that the string is at most or at least the given length. -.. versionchanged:: 1.2 - The EmailField previously did not recognize email addresses as valid that - contained an IDN (Internationalized Domain Name; a domain containing - unicode characters) domain part. This has now been corrected. - ``FileField`` ~~~~~~~~~~~~~ @@ -602,7 +591,11 @@ For each field, we describe the default widget used if you don't specify * Error message keys: ``required``, ``invalid``, ``missing``, ``empty``, ``invalid_image`` - Using an ImageField requires that the `Python Imaging Library`_ is installed. + Using an ``ImageField`` requires that the `Python Imaging Library`_ (PIL) + is installed and supports the image formats you use. If you encounter a + ``corrupt image`` error when you upload an image, it usually means PIL + doesn't understand its format. To fix this, install the appropriate + library and reinstall PIL. When you use an ``ImageField`` on a form, you must also remember to :ref:`bind the file data to the form `. @@ -815,11 +808,6 @@ For each field, we describe the default widget used if you don't specify These are the same as ``CharField.max_length`` and ``CharField.min_length``. -.. versionchanged:: 1.2 - The URLField previously did not recognize URLs as valid that contained an IDN - (Internationalized Domain Name; a domain name containing unicode characters) - domain name. This has now been corrected. - Slightly complex built-in ``Field`` classes ------------------------------------------- diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index 42006bba90..95424d0cd0 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -1,8 +1,8 @@ +.. _form-and-field-validation: + Form and field validation ========================= -.. versionchanged:: 1.2 - Form validation happens when the data is cleaned. If you want to customize this process, there are various places you can change, each one serving a different purpose. Three types of cleaning methods are run during form @@ -175,7 +175,6 @@ previous features. Using validators ~~~~~~~~~~~~~~~~ -.. versionadded:: 1.2 Django's form (and model) fields support use of simple utility functions and classes known as validators. These can be passed to a field's constructor, via @@ -187,7 +186,7 @@ a look at Django's ``EmailField``:: class EmailField(CharField): default_error_messages = { - 'invalid': _(u'Enter a valid e-mail address.'), + 'invalid': _('Enter a valid e-mail address.'), } default_validators = [validators.validate_email] @@ -200,7 +199,7 @@ on field definition so:: is equivalent to:: email = forms.CharField(validators=[validators.validate_email], - error_messages={'invalid': _(u'Enter a valid e-mail address.')}) + error_messages={'invalid': _('Enter a valid e-mail address.')}) Form field default cleaning @@ -363,7 +362,9 @@ Secondly, once we have decided that the combined data in the two fields we are considering aren't valid, we must remember to remove them from the ``cleaned_data``. -In fact, Django will currently completely wipe out the ``cleaned_data`` -dictionary if there are any errors in the form. However, this behavior may -change in the future, so it's not a bad idea to clean up after yourself in the -first place. +.. versionchanged:: 1.5 + +Django used to remove the ``cleaned_data`` attribute entirely if there were +any errors in the form. Since version 1.5, ``cleaned_data`` is present even if +the form doesn't validate, but it contains only field values that did +validate. diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index ac110c2ee8..fb7657349a 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -47,14 +47,12 @@ widget on the field. In the following example, the from django.forms.extras.widgets import SelectDateWidget BIRTH_YEAR_CHOICES = ('1980', '1981', '1982') - GENDER_CHOICES = (('m', 'Male'), ('f', 'Female')) FAVORITE_COLORS_CHOICES = (('blue', 'Blue'), ('green', 'Green'), ('black', 'Black')) class SimpleForm(forms.Form): birth_year = DateField(widget=SelectDateWidget(years=BIRTH_YEAR_CHOICES)) - gender = ChoiceField(widget=RadioSelect, choices=GENDER_CHOICES) favorite_colors = forms.MultipleChoiceField(required=False, widget=CheckboxSelectMultiple, choices=FAVORITE_COLORS_CHOICES) @@ -77,7 +75,7 @@ changing :attr:`ChoiceField.choices` will update :attr:`Select.choices`. For example:: >>> from django import forms - >>> CHOICES = (('1', 'First',), ('2', 'Second',))) + >>> CHOICES = (('1', 'First',), ('2', 'Second',)) >>> choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES) >>> choice_field.choices [('1', 'First'), ('2', 'Second')] diff --git a/docs/ref/index.txt b/docs/ref/index.txt index 32b263199f..01a8ab22d1 100644 --- a/docs/ref/index.txt +++ b/docs/ref/index.txt @@ -6,6 +6,7 @@ API Reference :maxdepth: 1 authbackends + class-based-views/index clickjacking contrib/index databases @@ -13,7 +14,6 @@ API Reference exceptions files/index forms/index - class-based-views middleware models/index request-response diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index 99e2ae3838..a6ea9a6c41 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -146,7 +146,7 @@ Locale middleware Enables language selection based on data from the request. It customizes content for each user. See the :doc:`internationalization documentation -`. +`. Message middleware ------------------ @@ -156,9 +156,6 @@ Message middleware .. class:: MessageMiddleware -.. versionadded:: 1.2 - ``MessageMiddleware`` was added. - Enables cookie- and session-based message support. See the :doc:`messages documentation `. diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index eab7ad91e1..a43163c5e9 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -86,36 +86,43 @@ field. If this is given, Django's admin will use a select box instead of the standard text field and will limit choices to the choices given. -A choices list looks like this:: +A choices list is an iterable of 2-tuples; the first element in each +tuple is the actual value to be stored, and the second element is the +human-readable name. For example:: YEAR_IN_SCHOOL_CHOICES = ( ('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), - ('GR', 'Graduate'), ) -The first element in each tuple is the actual value to be stored. The second -element is the human-readable name for the option. +Generally, it's best to define choices inside a model class, and to +define a suitably-named constant for each value:: -The choices list can be defined either as part of your model class:: - - class Foo(models.Model): - GENDER_CHOICES = ( - ('M', 'Male'), - ('F', 'Female'), + class Student(models.Model): + FRESHMAN = 'FR' + SOPHOMORE = 'SO' + JUNIOR = 'JR' + SENIOR = 'SR' + YEAR_IN_SCHOOL_CHOICES = ( + (FRESHMAN, 'Freshman'), + (SOPHOMORE, 'Sophomore'), + (JUNIOR, 'Junior'), + (SENIOR, 'Senior'), ) - gender = models.CharField(max_length=1, choices=GENDER_CHOICES) + year_in_school = models.CharField(max_length=2, + choices=YEAR_IN_SCHOOL_CHOICES, + default=FRESHMAN) -or outside your model class altogether:: + def is_upperclass(self): + return self.year_in_school in (self.JUNIOR, self.SENIOR) - GENDER_CHOICES = ( - ('M', 'Male'), - ('F', 'Female'), - ) - class Foo(models.Model): - gender = models.CharField(max_length=1, choices=GENDER_CHOICES) +Though you can define a choices list outside of a model class and then +refer to it, defining the choices and names for each choice inside the +model class keeps all of that information with the class that uses it, +and makes the choices easy to reference (e.g, ``Student.SOPHOMORE`` +will work anywhere that the ``Student`` model has been imported). You can also collect your available choices into named groups that can be used for organizational purposes:: @@ -202,8 +209,6 @@ automatically generated from the model class. Default is ``True``. ``error_messages`` ------------------ -.. versionadded:: 1.2 - .. attribute:: Field.error_messages The ``error_messages`` argument lets you override the default messages that the @@ -303,8 +308,6 @@ underscores to spaces. See :ref:`Verbose field names `. ``validators`` ------------------- -.. versionadded:: 1.2 - .. attribute:: Field.validators A list of validators to run for this field. See the :doc:`validators @@ -330,8 +333,6 @@ otherwise. See :ref:`automatic-primary-key-fields`. ``BigIntegerField`` ------------------- -.. versionadded:: 1.2 - .. class:: BigIntegerField([**options]) A 64 bit integer, much like an :class:`IntegerField` except that it is @@ -351,11 +352,6 @@ The admin represents this as a checkbox. If you need to accept :attr:`~Field.null` values then use :class:`NullBooleanField` instead. -.. versionchanged:: 1.2 - In previous versions of Django when running under MySQL ``BooleanFields`` - would return their data as ``ints``, instead of true ``bools``. See the - release notes for a complete description of the change. - ``CharField`` ------------- @@ -683,7 +679,7 @@ directory on the filesystem. Has three special arguments, of which the first is Optional. A regular expression, as a string, that :class:`FilePathField` will use to filter filenames. Note that the regex will be applied to the base filename, not the full path. Example: ``"foo.*\.txt$"``, which will - match a file called ``foo23.txt`` but not ``bar.txt`` or ``foo23.gif``. + match a file called ``foo23.txt`` but not ``bar.txt`` or ``foo23.png``. .. attribute:: FilePathField.recursive @@ -714,9 +710,9 @@ base filename, not the full path. So, this example:: FilePathField(path="/home/images", match="foo.*", recursive=True) -...will match ``/home/images/foo.gif`` but not ``/home/images/foo/bar.gif`` +...will match ``/home/images/foo.png`` but not ``/home/images/foo/bar.png`` because the :attr:`~FilePathField.match` applies to the base filename -(``foo.gif`` and ``bar.gif``). +(``foo.png`` and ``bar.png``). By default, :class:`FilePathField` instances are created as ``varchar(100)`` columns in your database. As with other fields, you @@ -1007,9 +1003,10 @@ define the details of how the relation works. `; and when you do so :ref:`some special syntax ` is available. - If you'd prefer Django didn't create a backwards relation, set ``related_name`` - to ``'+'``. For example, this will ensure that the ``User`` model won't get a - backwards relation to this model:: + If you'd prefer Django not to create a backwards relation, set + ``related_name`` to ``'+'`` or end it with ``'+'``. For example, this will + ensure that the ``User`` model won't have a backwards relation to this + model:: user = models.ForeignKey(User, related_name='+') @@ -1079,15 +1076,14 @@ the model is related. This works exactly the same as it does for Database Representation ~~~~~~~~~~~~~~~~~~~~~~~ -Behind the scenes, Django creates an intermediary join table to -represent the many-to-many relationship. By default, this table name -is generated using the name of the many-to-many field and the model -that contains it. Since some databases don't support table names above -a certain length, these table names will be automatically truncated to -64 characters and a uniqueness hash will be used. This means you might -see table names like ``author_books_9cdf4``; this is perfectly normal. -You can manually provide the name of the join table using the -:attr:`~ManyToManyField.db_table` option. +Behind the scenes, Django creates an intermediary join table to represent the +many-to-many relationship. By default, this table name is generated using the +name of the many-to-many field and the name of the table for the model that +contains it. Since some databases don't support table names above a certain +length, these table names will be automatically truncated to 64 characters and a +uniqueness hash will be used. This means you might see table names like +``author_books_9cdf4``; this is perfectly normal. You can manually provide the +name of the join table using the :attr:`~ManyToManyField.db_table` option. .. _manytomany-arguments: @@ -1101,6 +1097,13 @@ that control how the relationship functions. Same as :attr:`ForeignKey.related_name`. + If you have more than one ``ManyToManyField`` pointing to the same model + and want to suppress the backwards relations, set each ``related_name`` + to a unique value ending with ``'+'``:: + + users = models.ManyToManyField(User, related_name='u+') + referents = models.ManyToManyField(User, related_name='ref+') + .. attribute:: ManyToManyField.limit_choices_to Same as :attr:`ForeignKey.limit_choices_to`. @@ -1143,8 +1146,9 @@ that control how the relationship functions. .. attribute:: ManyToManyField.db_table The name of the table to create for storing the many-to-many data. If this - is not provided, Django will assume a default name based upon the names of - the two tables being joined. + is not provided, Django will assume a default name based upon the names of: + the table for the model defining the relationship and the name of the field + itself. .. _ref-onetoone: diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index fb0fcc046d..b7ae3890cf 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -25,13 +25,46 @@ The keyword arguments are simply the names of the fields you've defined on your model. Note that instantiating a model in no way touches your database; for that, you need to :meth:`~Model.save()`. +.. note:: + + You may be tempted to customize the model by overriding the ``__init__`` + method. If you do so, however, take care not to change the calling + signature as any change may prevent the model instance from being saved. + Rather than overriding ``__init__``, try using one of these approaches: + + 1. Add a classmethod on the model class:: + + class Book(models.Model): + title = models.CharField(max_length=100) + + @classmethod + def create(cls, title): + book = cls(title=title) + # do something with the book + return book + + book = Book.create("Pride and Prejudice") + + 2. Add a method on a custom manager (usually preferred):: + + class BookManager(models.Manager): + def create_book(title): + book = self.create(title=title) + # do something with the book + return book + + class Book(models.Model): + title = models.CharField(max_length=100) + + objects = BookManager() + + book = Book.objects.create_book("Pride and Prejudice") + .. _validating-objects: Validating objects ================== -.. versionadded:: 1.2 - There are three steps involved in validating a model: 1. Validate the model fields @@ -137,9 +170,6 @@ To save an object back to the database, call ``save()``: .. method:: Model.save([force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None]) -.. versionadded:: 1.2 - The ``using`` argument was added. - If you want customized saving behavior, you can override this ``save()`` method. See :ref:`overriding-model-methods` for more details. @@ -356,14 +386,17 @@ perform an update on all fields. Specifying ``update_fields`` will force an update. +When saving a model fetched through deferred model loading +(:meth:`~Model.only()` or :meth:`~Model.defer()`) only the fields loaded from +the DB will get updated. In effect there is an automatic ``update_fields`` in +this case. If you assign or change any deferred field value, these fields will +be added to the updated fields. + Deleting objects ================ .. method:: Model.delete([using=DEFAULT_DB_ALIAS]) -.. versionadded:: 1.2 - The ``using`` argument was added. - Issues a SQL ``DELETE`` for the object. This only deletes the object in the database; the Python instance will still exist and will still have data in its fields. @@ -426,9 +459,9 @@ using ``__str__()`` like this:: last_name = models.CharField(max_length=50) def __str__(self): - # Note use of django.utils.encoding.smart_str() here because + # Note use of django.utils.encoding.smart_bytes() here because # first_name and last_name will be unicode strings. - return smart_str('%s %s' % (self.first_name, self.last_name)) + return smart_bytes('%s %s' % (self.first_name, self.last_name)) ``get_absolute_url`` -------------------- @@ -572,25 +605,29 @@ might have some of the following methods: For every field that has :attr:`~django.db.models.Field.choices` set, the object will have a ``get_FOO_display()`` method, where ``FOO`` is the name of -the field. This method returns the "human-readable" value of the field. For -example, in the following model:: +the field. This method returns the "human-readable" value of the field. - GENDER_CHOICES = ( - ('M', 'Male'), - ('F', 'Female'), - ) - class Person(models.Model): - name = models.CharField(max_length=20) - gender = models.CharField(max_length=1, choices=GENDER_CHOICES) +For example:: -...each ``Person`` instance will have a ``get_gender_display()`` method. Example:: + from django.db import models - >>> p = Person(name='John', gender='M') - >>> p.save() - >>> p.gender - 'M' - >>> p.get_gender_display() - 'Male' + class Person(models.Model): + SHIRT_SIZES = ( + (u'S', u'Small'), + (u'M', u'Medium'), + (u'L', u'Large'), + ) + name = models.CharField(max_length=60) + shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES) + + :: + + >>> p = Person(name="Fred Flintstone", shirt_size="L") + >>> p.save() + >>> p.shirt_size + u'L' + >>> p.get_shirt_size_display() + u'Large' .. method:: Model.get_next_by_FOO(\**kwargs) .. method:: Model.get_previous_by_FOO(\**kwargs) @@ -608,4 +645,3 @@ described in :ref:`Field lookups `. Note that in the case of identical date values, these methods will use the primary key as a tie-breaker. This guarantees that no records are skipped or duplicated. That also means you cannot use those methods on unsaved objects. - diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 6ca3d3b2d0..9d076f6274 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -244,12 +244,12 @@ Django quotes column and table names behind the scenes. unique_together = (("driver", "restaurant"),) - This is a list of lists of fields that must be unique when considered together. + This is a tuple of tuples that must be unique when considered together. It's used in the Django admin and is enforced at the database level (i.e., the appropriate ``UNIQUE`` statements are included in the ``CREATE TABLE`` statement). - For convenience, unique_together can be a single list when dealing with a single + For convenience, unique_together can be a single tuple when dealing with a single set of fields:: unique_together = ("driver", "restaurant") diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 050598a532..b59b2ece82 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -727,8 +727,6 @@ passed to ``select_related()``. This includes foreign keys that have It's an error to use both a list of fields and the ``depth`` parameter in the same ``select_related()`` call; they are conflicting options. -.. versionchanged:: 1.2 - You can also refer to the reverse direction of a :class:`~django.db.models.OneToOneField` in the list of fields passed to ``select_related`` — that is, you can traverse a @@ -1083,11 +1081,13 @@ to ``defer()``:: # Load all fields immediately. my_queryset.defer(None) +.. versionchanged:: 1.5 + Some fields in a model won't be deferred, even if you ask for them. You can never defer the loading of the primary key. If you are using :meth:`select_related()` to retrieve related models, you shouldn't defer the -loading of the field that connects from the primary model to the related one -(at the moment, that doesn't raise an error, but it will eventually). +loading of the field that connects from the primary model to the related +one, doing so will result in an error. .. note:: @@ -1110,6 +1110,14 @@ loading of the field that connects from the primary model to the related one reader, is slightly faster and consumes a little less memory in the Python process. +.. versionchanged:: 1.5 + +.. note:: + + When calling :meth:`~Model.save()` for instances with deferred fields, + only the loaded fields will be saved. See :meth:`~Model.save()` for more + details. + only ~~~~ @@ -1147,17 +1155,26 @@ logically:: # existing set of fields). Entry.objects.defer("body").only("headline", "body") +.. versionchanged:: 1.5 + All of the cautions in the note for the :meth:`defer` documentation apply to ``only()`` as well. Use it cautiously and only after exhausting your other -options. +options. Also note that using :meth:`only` and omitting a field requested +using :meth:`select_related` is an error as well. + +.. versionchanged:: 1.5 + +.. note:: + + When calling :meth:`~Model.save()` for instances with deferred fields, + only the loaded fields will be saved. See :meth:`~Model.save()` for more + details. using ~~~~~ .. method:: using(alias) -.. versionadded:: 1.2 - This method is for controlling which database the ``QuerySet`` will be evaluated against if you are using more than one database. The only argument this method takes is the alias of a database, as defined in @@ -1349,7 +1366,7 @@ has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec. bulk_create ~~~~~~~~~~~ -.. method:: bulk_create(objs) +.. method:: bulk_create(objs, batch_size=None) .. versionadded:: 1.4 @@ -1371,20 +1388,12 @@ This has a number of caveats though: * If the model's primary key is an :class:`~django.db.models.AutoField` it does not retrieve and set the primary key attribute, as ``save()`` does. -.. admonition:: Limits of SQLite +The ``batch_size`` parameter controls how many objects are created in single +query. The default is to create all objects in one batch, except for SQLite +where the default is such that at maximum 999 variables per query is used. - SQLite sets a limit on the number of parameters per SQL statement. The - maximum is defined by the SQLITE_MAX_VARIABLE_NUMBER_ compilation option, - which defaults to 999. For instance, if your model has 8 fields (including - the primary key), you cannot create more than 999 // 8 = 124 instances at - a time. If you exceed this limit, you'll get an exception:: - - django.db.utils.DatabaseError: too many SQL variables - - If your application's performance requirements exceed SQLite's limits, you - should switch to another database engine, such as PostgreSQL. - -.. _SQLITE_MAX_VARIABLE_NUMBER: http://sqlite.org/limits.html#max_variable_number +.. versionadded:: 1.5 + The ``batch_size`` parameter was added in version 1.5. count ~~~~~ @@ -1512,8 +1521,6 @@ exists .. method:: exists() -.. versionadded:: 1.2 - Returns ``True`` if the :class:`.QuerySet` contains any results, and ``False`` if not. This tries to perform the query in the simplest and fastest way possible, but it *does* execute nearly the same query. This means that calling @@ -1769,22 +1776,6 @@ This queryset will be evaluated as subselect statement:: SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%') -The above code fragment could also be written as follows:: - - inner_q = Blog.objects.filter(name__contains='Cheddar').values('pk').query - entries = Entry.objects.filter(blog__in=inner_q) - -.. warning:: - - This ``query`` attribute should be considered an opaque internal attribute. - It's fine to use it like above, but its API may change between Django - versions. - -This second form is a bit less readable and unnatural to write, since it -accesses the internal ``query`` attribute and requires a ``ValuesQuerySet``. -If your code doesn't require compatibility with Django 1.0, use the first -form, passing in a queryset directly. - If you pass in a ``ValuesQuerySet`` or ``ValuesListQuerySet`` (the result of calling ``values()`` or ``values_list()`` on a queryset) as the value to an ``__in`` lookup, you need to ensure you are only extracting one field in the diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index ae736b1c75..551ee00c6b 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -28,7 +28,8 @@ HttpRequest objects Attributes ---------- -All attributes except ``session`` should be considered read-only. +All attributes should be considered read-only, unless stated otherwise below. +``session`` is a notable exception. .. attribute:: HttpRequest.body @@ -605,11 +606,10 @@ Attributes Methods ------- -.. method:: HttpResponse.__init__(content='', mimetype=None, status=200, content_type=DEFAULT_CONTENT_TYPE) +.. method:: HttpResponse.__init__(content='', content_type=None, status=200) - Instantiates an ``HttpResponse`` object with the given page content (a - string) and MIME type. The :setting:`DEFAULT_CONTENT_TYPE` is - ``'text/html'``. + Instantiates an ``HttpResponse`` object with the given page content and + content type. ``content`` should be an iterator or a string. If it's an iterator, it should return strings, and those strings will be @@ -617,15 +617,15 @@ Methods an iterator or a string, it will be converted to a string when accessed. + ``content_type`` is the MIME type optionally completed by a character set + encoding and is used to fill the HTTP ``Content-Type`` header. If not + specified, it is formed by the :setting:`DEFAULT_CONTENT_TYPE` and + :setting:`DEFAULT_CHARSET` settings, by default: "`text/html; charset=utf-8`". + + Historically, this parameter was called ``mimetype`` (now deprecated). + ``status`` is the `HTTP Status code`_ for the response. - ``content_type`` is an alias for ``mimetype``. Historically, this parameter - was only called ``mimetype``, but since this is actually the value included - in the HTTP ``Content-Type`` header, it can also include the character set - encoding, which makes it more than just a MIME type specification. - If ``mimetype`` is specified (not ``None``), that value is used. - Otherwise, ``content_type`` is used. If neither is given, the - :setting:`DEFAULT_CONTENT_TYPE` setting is used. .. method:: HttpResponse.__setitem__(header, value) @@ -683,7 +683,7 @@ Methods risk of client side script accessing the protected cookie data. - .. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly + .. _HTTPOnly: https://www.owasp.org/index.php/HTTPOnly .. method:: HttpResponse.set_signed_cookie(key, value='', salt='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=True) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index b5bbf35c9b..9269a96e83 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -192,7 +192,7 @@ compose a prefix, version and key into a final cache key. The default implementation is equivalent to the function:: def make_key(key, key_prefix, version): - return ':'.join([key_prefix, str(version), smart_str(key)]) + return ':'.join([key_prefix, str(version), smart_bytes(key)]) You may use any key function you want, as long as it has the same argument signature. @@ -316,14 +316,12 @@ See :doc:`/topics/cache`. CSRF_COOKIE_DOMAIN ------------------ -.. versionadded:: 1.2 - Default: ``None`` The domain to be used when setting the CSRF cookie. This can be useful for easily allowing cross-subdomain requests to be excluded from the normal cross site request forgery protection. It should be set to a string such as -``".lawrence.com"`` to allow a POST request from a form on one subdomain to be +``".example.com"`` to allow a POST request from a form on one subdomain to be accepted by accepted by a view served from another subdomain. Please note that the presence of this setting does not imply that Django's CSRF @@ -335,8 +333,6 @@ protection is safe from cross-subdomain attacks by default - please see the CSRF_COOKIE_NAME ---------------- -.. versionadded:: 1.2 - Default: ``'csrftoken'`` The name of the cookie to use for the CSRF authentication token. This can be whatever you @@ -376,8 +372,6 @@ cookie is only sent under an HTTPS connection. CSRF_FAILURE_VIEW ----------------- -.. versionadded:: 1.2 - Default: ``'django.views.csrf.csrf_failure'`` A dotted path to the view function to be used when an incoming request @@ -395,8 +389,6 @@ end users) indicating the reason the request was rejected. See DATABASES --------- -.. versionadded:: 1.2 - Default: ``{}`` (Empty dictionary) A dictionary containing the settings for all databases to be used with @@ -439,12 +431,6 @@ You can use a database backend that doesn't ship with Django by setting scratch is left as an exercise to the reader; see the other backends for examples. -.. note:: - Prior to Django 1.2, you could use a short version of the backend name - to reference the built-in database backends (e.g., you could use - ``'sqlite3'`` to refer to the SQLite backend). This format has been - deprecated, and will be removed in Django 1.4. - .. setting:: HOST HOST @@ -673,8 +659,6 @@ not provided, Django will use ``'test_' + NAME + '_temp'``. DATABASE_ROUTERS ---------------- -.. versionadded:: 1.2 - Default: ``[]`` (Empty list) The list of routers that will be used to determine which database @@ -695,9 +679,6 @@ system. Note that if :setting:`USE_L10N` is set to ``True``, then the locale-dictated format has higher precedence and will be applied instead. See :tfilter:`allowed date format strings `. -.. versionchanged:: 1.2 - This setting can now be overriden by setting :setting:`USE_L10N` to ``True``. - See also :setting:`DATETIME_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_DATE_FORMAT`. .. setting:: DATE_INPUT_FORMATS @@ -705,8 +686,6 @@ See also :setting:`DATETIME_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_ DATE_INPUT_FORMATS ------------------ -.. versionadded:: 1.2 - Default:: ('%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', '%b %d %Y', @@ -737,9 +716,6 @@ system. Note that if :setting:`USE_L10N` is set to ``True``, then the locale-dictated format has higher precedence and will be applied instead. See :tfilter:`allowed date format strings `. -.. versionchanged:: 1.2 - This setting can now be overriden by setting :setting:`USE_L10N` to ``True``. - See also :setting:`DATE_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_DATETIME_FORMAT`. .. setting:: DATETIME_INPUT_FORMATS @@ -747,8 +723,6 @@ See also :setting:`DATE_FORMAT`, :setting:`TIME_FORMAT` and :setting:`SHORT_DATE DATETIME_INPUT_FORMATS ---------------------- -.. versionadded:: 1.2 - Default:: ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', '%Y-%m-%d', @@ -782,14 +756,15 @@ Did you catch that? NEVER deploy a site into production with :setting:`DEBUG` turned on. One of the main features of debug mode is the display of detailed error pages. -If your app raises an exception when ``DEBUG`` is ``True``, Django will display -a detailed traceback, including a lot of metadata about your environment, such -as all the currently defined Django settings (from ``settings.py``). +If your app raises an exception when :setting:`DEBUG` is ``True``, Django will +display a detailed traceback, including a lot of metadata about your +environment, such as all the currently defined Django settings (from +``settings.py``). As a security measure, Django will *not* include settings that might be -sensitive (or offensive), such as ``SECRET_KEY`` or ``PROFANITIES_LIST``. -Specifically, it will exclude any setting whose name includes any of the -following: +sensitive (or offensive), such as :setting:`SECRET_KEY` or +:setting:`PROFANITIES_LIST`. Specifically, it will exclude any setting whose +name includes any of the following: * API * KEY @@ -832,8 +807,6 @@ site. DECIMAL_SEPARATOR ----------------- -.. versionadded:: 1.2 - Default: ``'.'`` (Dot) Default decimal separator used when formatting decimal numbers. @@ -935,8 +908,6 @@ This is only used if ``CommonMiddleware`` is installed (see EMAIL_BACKEND ------------- -.. versionadded:: 1.2 - Default: ``'django.core.mail.backends.smtp.EmailBackend'`` The backend to use for sending emails. For the list of available backends see @@ -947,8 +918,6 @@ The backend to use for sending emails. For the list of available backends see EMAIL_FILE_PATH --------------- -.. versionadded:: 1.2 - Default: Not defined The directory used by the ``file`` email backend to store output files. @@ -1095,8 +1064,6 @@ See :doc:`/topics/files` for details. FIRST_DAY_OF_WEEK ----------------- -.. versionadded:: 1.2 - Default: ``0`` (Sunday) Number representing the first day of the week. This is especially useful @@ -1136,8 +1103,6 @@ of the preferred value or not supplied at all. FORMAT_MODULE_PATH ------------------ -.. versionadded:: 1.2 - Default: ``None`` A full Python path to a Python package that contains format definitions for @@ -1401,7 +1366,7 @@ MANAGERS Default: ``()`` (Empty tuple) A tuple in the same format as :setting:`ADMINS` that specifies who should get -broken-link notifications when ``SEND_BROKEN_LINK_EMAILS=True``. +broken-link notifications when :setting:`SEND_BROKEN_LINK_EMAILS` is ``True``. .. setting:: MEDIA_ROOT @@ -1413,7 +1378,7 @@ Default: ``''`` (Empty string) Absolute path to the directory that holds media for this installation, used for :doc:`managing stored files `. -Example: ``"/home/media/media.lawrence.com/"`` +Example: ``"/var/www/example.com/media/"`` See also :setting:`MEDIA_URL`. @@ -1427,7 +1392,7 @@ Default: ``''`` (Empty string) URL that handles the media served from :setting:`MEDIA_ROOT`, used for :doc:`managing stored files `. -Example: ``"http://media.lawrence.com/"`` +Example: ``"http://media.example.com/"`` .. versionchanged:: 1.3 It must end in a slash if set to a non-empty value. @@ -1435,8 +1400,6 @@ Example: ``"http://media.lawrence.com/"`` MESSAGE_LEVEL ------------- -.. versionadded:: 1.2 - Default: `messages.INFO` Sets the minimum message level that will be recorded by the messages @@ -1446,8 +1409,6 @@ more details. MESSAGE_STORAGE --------------- -.. versionadded:: 1.2 - Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` Controls where Django stores message data. See the @@ -1456,8 +1417,6 @@ Controls where Django stores message data. See the MESSAGE_TAGS ------------ -.. versionadded:: 1.2 - Default:: {messages.DEBUG: 'debug', @@ -1484,11 +1443,6 @@ Default:: A tuple of middleware classes to use. See :doc:`/topics/http/middleware`. -.. versionchanged:: 1.2 - ``'django.contrib.messages.middleware.MessageMiddleware'`` was added to the - default. For more information, see the :doc:`messages documentation - `. - .. setting:: MONTH_DAY_FORMAT MONTH_DAY_FORMAT @@ -1514,8 +1468,6 @@ See :tfilter:`allowed date format strings `. See also NUMBER_GROUPING ---------------- -.. versionadded:: 1.2 - Default: ``0`` Number of digits grouped together on the integer part of a number. @@ -1595,9 +1547,23 @@ SECRET_KEY Default: ``''`` (Empty string) -A secret key for this particular Django installation. Used to provide a seed in -secret-key hashing algorithms. Set this to a random string -- the longer, the -better. ``django-admin.py startproject`` creates one automatically. +A secret key for a particular Django installation. This is used to provide +:doc:`cryptographic signing `, and should be set to a unique, +unpredictable value. + +:djadmin:`django-admin.py startproject ` automatically adds a +randomly-generated ``SECRET_KEY`` to each new project. + +.. warning:: + + **Keep this value secret.** + + Running Django with a known :setting:`SECRET_KEY` defeats many of Django's + security protections, and can lead to privilege escalation and remote code + execution vulnerabilities. + +.. versionchanged:: 1.5 + Django will now refuse to start if :setting:`SECRET_KEY` is not set. .. setting:: SECURE_PROXY_SSL_HEADER @@ -1614,7 +1580,8 @@ method. This takes some explanation. By default, ``is_secure()`` is able to determine whether a request is secure by looking at whether the requested URL uses -"https://". +"https://". This is important for Django's CSRF protection, and may be used +by your own code or third-party apps. If your Django app is behind a proxy, though, the proxy may be "swallowing" the fact that a request is HTTPS, using a non-HTTPS connection between the proxy @@ -1644,7 +1611,7 @@ available in ``request.META``.) .. warning:: - **You will probably open security holes in your site if you set this without knowing what you're doing. Seriously.** + **You will probably open security holes in your site if you set this without knowing what you're doing. And if you fail to set it when you should. Seriously.** Make sure ALL of the following are true before setting this (assuming the values from the example above): @@ -1712,7 +1679,7 @@ SESSION_COOKIE_DOMAIN Default: ``None`` The domain to use for session cookies. Set this to a string such as -``".lawrence.com"`` for cross-domain cookies, or use ``None`` for a standard +``".example.com"`` for cross-domain cookies, or use ``None`` for a standard domain cookie. See the :doc:`/topics/http/sessions`. .. setting:: SESSION_COOKIE_HTTPONLY @@ -1732,7 +1699,7 @@ consistently by all browsers. However, when it is honored, it can be a useful way to mitigate the risk of client side script accessing the protected cookie data. -.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly +.. _HTTPOnly: https://www.owasp.org/index.php/HTTPOnly .. versionchanged:: 1.4 The default value of the setting was changed from ``False`` to ``True``. @@ -1828,8 +1795,6 @@ Whether to save the session data on every request. See SHORT_DATE_FORMAT ----------------- -.. versionadded:: 1.2 - Default: ``m/d/Y`` (e.g. ``12/31/2003``) An available formatting that can be used for displaying date fields on @@ -1844,8 +1809,6 @@ See also :setting:`DATE_FORMAT` and :setting:`SHORT_DATETIME_FORMAT`. SHORT_DATETIME_FORMAT --------------------- -.. versionadded:: 1.2 - Default: ``m/d/Y P`` (e.g. ``12/31/2003 4 p.m.``) An available formatting that can be used for displaying datetime fields on @@ -1893,7 +1856,7 @@ Default: ``''`` (Empty string) The absolute path to the directory where :djadmin:`collectstatic` will collect static files for deployment. -Example: ``"/home/example.com/static/"`` +Example: ``"/var/www/example.com/static/"`` If the :doc:`staticfiles` contrib app is enabled (default) the :djadmin:`collectstatic` management command will collect static @@ -1923,7 +1886,7 @@ Default: ``None`` URL to use when referring to static files located in :setting:`STATIC_ROOT`. -Example: ``"/site_media/static/"`` or ``"http://static.example.com/"`` +Example: ``"/static/"`` or ``"http://static.example.com/"`` If not ``None``, this will be used as the base path for :ref:`media definitions` and the @@ -1952,16 +1915,6 @@ A tuple of callables that are used to populate the context in ``RequestContext`` These callables take a request object as their argument and return a dictionary of items to be merged into the context. -.. versionchanged:: 1.2 - ``django.contrib.messages.context_processors.messages`` was added to the - default. For more information, see the :doc:`messages documentation - `. - -.. versionchanged:: 1.2 - The auth context processor was moved in this release from its old location - ``django.core.context_processors.auth`` to - ``django.contrib.auth.context_processors.auth``. - .. versionadded:: 1.3 The ``django.core.context_processors.static`` context processor was added in this release. @@ -2017,12 +1970,6 @@ used instead of a string. The first item in the tuple should be the ``Loader``'s module, subsequent items are passed to the ``Loader`` during initialization. See :doc:`/ref/templates/api`. -.. versionchanged:: 1.2 - The class-based API for template loaders was introduced in Django 1.2 - although the :setting:`TEMPLATE_LOADERS` setting will accept strings - that specify function-based loaders until compatibility with them is - completely removed in Django 1.4. - .. setting:: TEMPLATE_STRING_IF_INVALID TEMPLATE_STRING_IF_INVALID @@ -2040,9 +1987,6 @@ TEST_RUNNER Default: ``'django.test.simple.DjangoTestSuiteRunner'`` -.. versionchanged:: 1.2 - Prior to 1.2, test runners were a function, not a class. - The name of the class to use for starting the test suite. See :doc:`/topics/testing`. @@ -2053,8 +1997,6 @@ The name of the class to use for starting the test suite. See THOUSAND_SEPARATOR ------------------ -.. versionadded:: 1.2 - Default: ``,`` (Comma) Default thousand separator used when formatting numbers. This setting is @@ -2079,9 +2021,6 @@ system. Note that if :setting:`USE_L10N` is set to ``True``, then the locale-dictated format has higher precedence and will be applied instead. See :tfilter:`allowed date format strings `. -.. versionchanged:: 1.2 - This setting can now be overriden by setting :setting:`USE_L10N` to ``True``. - See also :setting:`DATE_FORMAT` and :setting:`DATETIME_FORMAT`. .. setting:: TIME_INPUT_FORMATS @@ -2089,8 +2028,6 @@ See also :setting:`DATE_FORMAT` and :setting:`DATETIME_FORMAT`. TIME_INPUT_FORMATS ------------------ -.. versionadded:: 1.2 - Default: ``('%H:%M:%S', '%H:%M')`` A tuple of formats that will be accepted when inputting data on a time field. @@ -2112,9 +2049,6 @@ TIME_ZONE Default: ``'America/Chicago'`` -.. versionchanged:: 1.2 - ``None`` was added as an allowed value. - .. versionchanged:: 1.4 The meaning of this setting now depends on the value of :setting:`USE_TZ`. @@ -2191,8 +2125,6 @@ See also :setting:`LANGUAGE_CODE`, :setting:`USE_L10N` and :setting:`USE_TZ`. USE_L10N -------- -.. versionadded:: 1.2 - Default: ``False`` A boolean that specifies if localized formatting of data will be enabled by @@ -2211,8 +2143,6 @@ See also :setting:`LANGUAGE_CODE`, :setting:`USE_I18N` and :setting:`USE_TZ`. USE_THOUSAND_SEPARATOR ---------------------- -.. versionadded:: 1.2 - Default: ``False`` A boolean that specifies whether to display numbers using a thousand separator. diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index 136eee935c..b2f2e85abc 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -220,8 +220,6 @@ m2m_changed .. data:: django.db.models.signals.m2m_changed :module: -.. versionadded:: 1.2 - Sent when a :class:`ManyToManyField` is changed on a model instance. Strictly speaking, this is not a model signal since it is sent by the :class:`ManyToManyField`, but since it complements the @@ -544,9 +542,6 @@ connection_created .. data:: django.db.backends.signals.connection_created :module: -.. versionchanged:: 1.2 - The connection argument was added - Sent when the database wrapper makes the initial connection to the database. This is particularly useful if you'd like to send any post connection commands to the SQL backend. diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index e945e0d4ca..c52194e6b7 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -198,6 +198,8 @@ straight lookups. Here are some things to keep in mind: * A variable can only be called if it has no required arguments. Otherwise, the system will return an empty string. +.. _alters-data-description: + * Obviously, there can be side effects when calling some variables, and it'd be either foolish or a security hole to allow the template system to access them. @@ -370,23 +372,14 @@ and return a dictionary of items to be merged into the context. By default, "django.core.context_processors.i18n", "django.core.context_processors.media", "django.core.context_processors.static", + "django.core.context_processors.tz", "django.contrib.messages.context_processors.messages") -.. versionadded:: 1.2 - In addition to these, ``RequestContext`` always uses - ``django.core.context_processors.csrf``. This is a security - related context processor required by the admin and other contrib apps, and, - in case of accidental misconfiguration, it is deliberately hardcoded in and - cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting. - -.. versionadded:: 1.2 - The ``'messages'`` context processor was added. For more information, see - the :doc:`messages documentation `. - -.. versionchanged:: 1.2 - The auth context processor was moved in this release from its old location - ``django.core.context_processors.auth`` to - ``django.contrib.auth.context_processors.auth``. +In addition to these, ``RequestContext`` always uses +``django.core.context_processors.csrf``. This is a security +related context processor required by the admin and other contrib apps, and, +in case of accidental misconfiguration, it is deliberately hardcoded in and +cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting. Each processor is applied in order. That means, if one processor adds a variable to the context and a second processor adds a variable with the same @@ -447,10 +440,6 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every ``django.contrib.auth.context_processors.PermWrapper``, representing the permissions that the currently logged-in user has. -.. versionchanged:: 1.2 - This context processor was moved in this release from - ``django.core.context_processors.auth`` to its current location. - .. versionchanged:: 1.3 Prior to version 1.3, ``PermWrapper`` was located in ``django.contrib.auth.context_processors``. @@ -503,8 +492,6 @@ value of the :setting:`STATIC_URL` setting. django.core.context_processors.csrf ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.2 - This processor adds a token that is needed by the :ttag:`csrf_token` template tag for protection against :doc:`Cross Site Request Forgeries `. @@ -527,15 +514,6 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every via the user model (using ``user.message_set.create``) or through the :doc:`messages framework `. -.. versionadded:: 1.2 - This template context variable was previously supplied by the ``'auth'`` - context processor. For backwards compatibility the ``'auth'`` context - processor will continue to supply the ``messages`` variable until Django - 1.4. If you use the ``messages`` variable, your project will work with - either (or both) context processors, but it is recommended to add - ``django.contrib.messages.context_processors.messages`` so your project - will be prepared for the future upgrade. - Writing your own context processors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -657,11 +635,6 @@ by editing your :setting:`TEMPLATE_LOADERS` setting. :setting:`TEMPLATE_LOADERS` should be a tuple of strings, where each string represents a template loader class. Here are the template loaders that come with Django: -.. versionchanged:: 1.2 - Template loaders were based on callables (usually functions) before Django - 1.2, starting with the 1.2 release there is a new class-based API, all the - loaders described below implement this new API. - ``django.template.loaders.filesystem.Loader`` Loads templates from the filesystem, according to :setting:`TEMPLATE_DIRS`. This loader is enabled by default. @@ -678,14 +651,24 @@ class. Here are the template loaders that come with Django: INSTALLED_APPS = ('myproject.polls', 'myproject.music') - ...then ``get_template('foo.html')`` will look for templates in these + ...then ``get_template('foo.html')`` will look for ``foo.html`` in these directories, in this order: - * ``/path/to/myproject/polls/templates/foo.html`` - * ``/path/to/myproject/music/templates/foo.html`` + * ``/path/to/myproject/polls/templates/`` + * ``/path/to/myproject/music/templates/`` - Note that the loader performs an optimization when it is first imported: It - caches a list of which :setting:`INSTALLED_APPS` packages have a + ... and will use the one it finds first. + + The order of :setting:`INSTALLED_APPS` is significant! For example, if you + want to customize the Django admin, you might choose to override the + standard ``admin/base_site.html`` template, from ``django.contrib.admin``, + with your own ``admin/base_site.html`` in ``myproject.polls``. You must + then make sure that your ``myproject.polls`` comes *before* + ``django.contrib.admin`` in :setting:`INSTALLED_APPS`, otherwise + ``django.contrib.admin``'s will be loaded first and yours will be ignored. + + Note that the loader performs an optimization when it is first imported: + it caches a list of which :setting:`INSTALLED_APPS` packages have a ``templates`` subdirectory. This loader is enabled by default. @@ -800,12 +783,10 @@ and any setting starting with ``TEMPLATE_`` is of obvious interest. Using an alternative template language ====================================== -.. versionadded:: 1.2 - The Django ``Template`` and ``Loader`` classes implement a simple API for loading and rendering templates. By providing some simple wrapper classes that implement this API we can use third party template systems like `Jinja2 -`_ or `Cheetah `_. This +`_ or `Cheetah `_. This allows us to use third-party template libraries without giving up useful Django features like the Django ``Context`` object and handy shortcuts like :func:`~django.shortcuts.render_to_response()`. diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 5019963f60..072eebf69f 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -58,10 +58,8 @@ Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. csrf_token ^^^^^^^^^^ -In the Django 1.1.X series, this is a no-op tag that returns an empty string -for future compatibility purposes. In Django 1.2 and later, it is used for -CSRF protection, as described in the documentation for :doc:`Cross Site Request -Forgeries `. +This tag is used for CSRF protection, as described in the documentation for +:doc:`Cross Site Request Forgeries `. .. templatetag:: cycle @@ -410,8 +408,6 @@ variables or to negate a given variable:: There are some athletes and absolutely no coaches. {% endif %} -.. versionchanged:: 1.2 - Use of both ``and`` and ``or`` clauses within the same tag is allowed, with ``and`` having higher precedence than ``or`` e.g.:: @@ -423,12 +419,9 @@ will be interpreted like: if (athlete_list and coach_list) or cheerleader_list -Use of actual brackets in the :ttag:`if` tag is invalid syntax. If you need +Use of actual parentheses in the :ttag:`if` tag is invalid syntax. If you need them to indicate precedence, you should use nested :ttag:`if` tags. -.. versionadded:: 1.2 - - :ttag:`if` tags may also use the operators ``==``, ``!=``, ``<``, ``>``, ``<=``, ``>=`` and ``in`` which work as follows: @@ -637,9 +630,8 @@ You cannot check for equality with Python objects such as ``True`` or ``False``. If you need to test if something is true or false, use the :ttag:`if` tag instead. -.. versionadded:: 1.2 - An alternative to the ``ifequal`` tag is to use the :ttag:`if` tag and the - ``==`` operator. +An alternative to the ``ifequal`` tag is to use the :ttag:`if` tag and the +``==`` operator. .. templatetag:: ifnotequal @@ -649,9 +641,8 @@ ifnotequal Just like :ttag:`ifequal`, except it tests that the two arguments are not equal. -.. versionadded:: 1.2 - An alternative to the ``ifnotequal`` tag is to use the :ttag:`if` tag and - the ``!=`` operator. +An alternative to the ``ifnotequal`` tag is to use the :ttag:`if` tag and +the ``!=`` operator. .. templatetag:: include @@ -771,48 +762,41 @@ regroup Regroups a list of alike objects by a common attribute. -This complex tag is best illustrated by use of an example: say that ``people`` -is a list of people represented by dictionaries with ``first_name``, -``last_name``, and ``gender`` keys: +This complex tag is best illustrated by way of an example: say that "places" is a list of cities represented by dictionaries containing ``"name"``, ``"population"``, and ``"country"`` keys: .. code-block:: python - people = [ - {'first_name': 'George', 'last_name': 'Bush', 'gender': 'Male'}, - {'first_name': 'Bill', 'last_name': 'Clinton', 'gender': 'Male'}, - {'first_name': 'Margaret', 'last_name': 'Thatcher', 'gender': 'Female'}, - {'first_name': 'Condoleezza', 'last_name': 'Rice', 'gender': 'Female'}, - {'first_name': 'Pat', 'last_name': 'Smith', 'gender': 'Unknown'}, + cities = [ + {'name': 'Mumbai', 'population': '19,000,000', 'country': 'India'}, + {'name': 'Calcutta', 'population': '15,000,000', 'country': 'India'}, + {'name': 'New York', 'population': '20,000,000', 'country': 'USA'}, + {'name': 'Chicago', 'population': '7,000,000', 'country': 'USA'}, + {'name': 'Tokyo', 'population': '33,000,000', 'country': 'Japan'}, ] -...and you'd like to display a hierarchical list that is ordered by gender, -like this: +...and you'd like to display a hierarchical list that is ordered by country, like this: -* Male: +* India + * Mumbai: 19,000,000 + * Calcutta: 15,000,000 +* USA + * New York: 20,000,000 + * Chicago: 7,000,000 +* Japan + * Tokyo: 33,000,000 - * George Bush - * Bill Clinton -* Female: - - * Margaret Thatcher - * Condoleezza Rice - -* Unknown: - - * Pat Smith - -You can use the ``{% regroup %}`` tag to group the list of people by gender. +You can use the ``{% regroup %}`` tag to group the list of cities by country. The following snippet of template code would accomplish this:: - {% regroup people by gender as gender_list %} + {% regroup cities by country as country_list %}
        - {% for gender in gender_list %} -
      • {{ gender.grouper }} + {% for country in country_list %} +
      • {{ country.grouper }}
          - {% for item in gender.list %} -
        • {{ item.first_name }} {{ item.last_name }}
        • + {% for item in country.list %} +
        • {{ item.name }}: {{ item.population }}
        • {% endfor %}
      • @@ -821,56 +805,45 @@ The following snippet of template code would accomplish this:: Let's walk through this example. ``{% regroup %}`` takes three arguments: the list you want to regroup, the attribute to group by, and the name of the -resulting list. Here, we're regrouping the ``people`` list by the ``gender`` -attribute and calling the result ``gender_list``. +resulting list. Here, we're regrouping the ``cities`` list by the ``country`` +attribute and calling the result ``country_list``. -``{% regroup %}`` produces a list (in this case, ``gender_list``) of +``{% regroup %}`` produces a list (in this case, ``country_list``) of **group objects**. Each group object has two attributes: -* ``grouper`` -- the item that was grouped by (e.g., the string "Male" or - "Female"). -* ``list`` -- a list of all items in this group (e.g., a list of all people - with gender='Male'). +* ``grouper`` -- the item that was grouped by (e.g., the string "India" or + "Japan"). +* ``list`` -- a list of all items in this group (e.g., a list of all cities + with country='India'). Note that ``{% regroup %}`` does not order its input! Our example relies on -the fact that the ``people`` list was ordered by ``gender`` in the first place. -If the ``people`` list did *not* order its members by ``gender``, the -regrouping would naively display more than one group for a single gender. For -example, say the ``people`` list was set to this (note that the males are not +the fact that the ``cities`` list was ordered by ``country`` in the first place. +If the ``cities`` list did *not* order its members by ``country``, the +regrouping would naively display more than one group for a single country. For +example, say the ``cities`` list was set to this (note that the countries are not grouped together): .. code-block:: python - people = [ - {'first_name': 'Bill', 'last_name': 'Clinton', 'gender': 'Male'}, - {'first_name': 'Pat', 'last_name': 'Smith', 'gender': 'Unknown'}, - {'first_name': 'Margaret', 'last_name': 'Thatcher', 'gender': 'Female'}, - {'first_name': 'George', 'last_name': 'Bush', 'gender': 'Male'}, - {'first_name': 'Condoleezza', 'last_name': 'Rice', 'gender': 'Female'}, + cities = [ + {'name': 'Mumbai', 'population': '19,000,000', 'country': 'India'}, + {'name': 'New York', 'population': '20,000,000', 'country': 'USA'}, + {'name': 'Calcutta', 'population': '15,000,000', 'country': 'India'}, + {'name': 'Chicago', 'population': '7,000,000', 'country': 'USA'}, + {'name': 'Tokyo', 'population': '33,000,000', 'country': 'Japan'}, ] -With this input for ``people``, the example ``{% regroup %}`` template code +With this input for ``cities``, the example ``{% regroup %}`` template code above would result in the following output: -* Male: - - * Bill Clinton - -* Unknown: - - * Pat Smith - -* Female: - - * Margaret Thatcher - -* Male: - - * George Bush - -* Female: - - * Condoleezza Rice +* India + * Mumbai: 19,000,000 +* USA + * New York: 20,000,000 +* India + * Calcutta: 15,000,000 +* Japan + * Tokyo: 33,000,000 The easiest solution to this gotcha is to make sure in your view code that the data is ordered according to how you want to display it. @@ -878,27 +851,26 @@ data is ordered according to how you want to display it. Another solution is to sort the data in the template using the :tfilter:`dictsort` filter, if your data is in a list of dictionaries:: - {% regroup people|dictsort:"gender" by gender as gender_list %} - + {% regroup cities|dictsort:"country" by country as country_list %} Grouping on other properties ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Any valid template lookup is a legal grouping attribute for the regroup tag, including methods, attributes, dictionary keys and list items. For -example, if the "gender" field is a foreign key to a class with +example, if the "country" field is a foreign key to a class with an attribute "description," you could use:: - {% regroup people by gender.description as gender_list %} + {% regroup cities by country.description as country_list %} -Or, if ``gender`` is a field with ``choices``, it will have a +Or, if ``country`` is a field with ``choices``, it will have a :meth:`^django.db.models.Model.get_FOO_display` method available as an attribute, allowing you to group on the display string rather than the ``choices`` key:: - {% regroup people by get_gender_display as gender_list %} + {% regroup cities by get_country_display as country_list %} -``{{ gender.grouper }}`` will now display the value fields from the +``{{ country.grouper }}`` will now display the value fields from the ``choices`` set rather than the keys. .. templatetag:: spaceless @@ -1059,6 +1031,29 @@ This will follow the normal :ref:`namespaced URL resolution strategy `, including using any hints provided by the context as to the current application. +.. templatetag:: verbatim + +verbatim +^^^^^^^^ + +.. versionadded:: 1.5 + +Stops the template engine from rendering the contents of this block tag. + +A common use is to allow a Javascript template layer that collides with +Django's syntax. For example:: + + {% verbatim %} + {{if dying}}Still alive.{{/if}} + {% endverbatim %} + +You can also designate a specific closing tag, allowing the use of +``{% endverbatim %}`` as part of the unrendered contents:: + + {% verbatim myblock %} + Avoid template rendering via the {% verbatim %}{% endverbatim %} block. + {% endverbatim myblock %} + .. templatetag:: widthratio widthratio @@ -1069,7 +1064,8 @@ value to a maximum value, and then applies that ratio to a constant. For example:: - + Bar Above, if ``this_value`` is 175 and ``max_value`` is 200, the image in the above example will be 88 pixels wide (because 175/200 = .875; .875 * 100 = 87.5 @@ -1122,9 +1118,6 @@ For example:: If ``value`` is ``4``, then the output will be ``6``. -.. versionchanged:: 1.2 - The following behavior didn't exist in previous Django versions. - This filter will first try to coerce both values to integers. If this fails, it'll attempt to add the values together anyway. This will work on some data types (strings, list, etc.) and fail on others. If it fails, the result will @@ -1191,7 +1184,7 @@ Removes all values of arg from the given string. For example:: - {{ value|cut:" "}} + {{ value|cut:" " }} If ``value`` is ``"String with spaces"``, the output will be ``"Stringwithspaces"``. @@ -1284,10 +1277,6 @@ Z Time zone offset in seconds. The ``-43200`` to ``4320 UTC is always positive. ================ ======================================== ===================== -.. versionadded:: 1.2 - -The ``c`` and ``u`` format specification characters were added in Django 1.2. - .. versionadded:: 1.4 The ``e`` and ``o`` format specification characters were added in Django 1.4. @@ -1321,9 +1310,6 @@ When used without a format string:: ...the formatting string defined in the :setting:`DATE_FORMAT` setting will be used, without applying any localization. -.. versionchanged:: 1.2 - Predefined formats can now be influenced by the current locale. - .. templatefilter:: default default @@ -1980,9 +1966,6 @@ When used without a format string:: ...the formatting string defined in the :setting:`TIME_FORMAT` setting will be used, without applying any localization. -.. versionchanged:: 1.2 - Predefined formats can now be influenced by the current locale. - .. templatefilter:: timesince timesince @@ -2361,7 +2344,7 @@ using :class:`~django.template.RequestContext` or not. .. code-block:: html+django {% load static %} - + Hi! It is also able to consume standard context variables, e.g. assuming a ``user_stylesheet`` variable is passed to the template: @@ -2371,6 +2354,17 @@ It is also able to consume standard context variables, e.g. assuming a {% load static %} +If you'd like to retrieve a static URL without displaying it, you can use a +slightly different call:: + +.. versionadded:: 1.5 + +.. code-block:: html+django + + {% load static %} + {% static "images/hi.jpg" as myphoto %} + + .. note:: The :mod:`staticfiles` contrib app also ships @@ -2380,7 +2374,7 @@ It is also able to consume standard context variables, e.g. assuming a :ref:`using a cloud service to serve static files`:: {% load static from staticfiles %} - + Hi! .. templatetag:: get_static_prefix @@ -2395,7 +2389,7 @@ into the template, you can use the :ttag:`get_static_prefix` template tag instead:: {% load static %} - + Hi! There's also a second form you can use to avoid extra processing if you need the value multiple times:: @@ -2403,8 +2397,8 @@ the value multiple times:: {% load static %} {% get_static_prefix as STATIC_PREFIX %} - - + Hi! + Hello! .. templatetag:: get_media_prefix diff --git a/docs/ref/unicode.txt b/docs/ref/unicode.txt index 46ce4138a4..ffab647379 100644 --- a/docs/ref/unicode.txt +++ b/docs/ref/unicode.txt @@ -45,6 +45,29 @@ rendering or anywhere else -- you have two choices for encoding those strings. You can use Unicode strings, or you can use normal strings (sometimes called "bytestrings") that are encoded using UTF-8. +.. versionchanged:: 1.5 + +In Python 3, the logic is reversed, that is normal strings are Unicode, and +when you want to specifically create a bytestring, you have to prefix the +string with a 'b'. As we are doing in Django code from version 1.5, +we recommend that you import ``unicode_literals`` from the __future__ library +in your code. Then, when you specifically want to create a bytestring literal, +prefix the string with 'b'. + +Python 2 legacy:: + + my_string = "This is a bytestring" + my_unicode = u"This is an Unicode string" + +Python 2 with unicode literals or Python 3:: + + from __future__ import unicode_literals + + my_string = b"This is a bytestring" + my_unicode = "This is an Unicode string" + +See also :doc:`Python 3 compatibility `. + .. admonition:: Warning A bytestring does not carry any information with it about its encoding. @@ -106,7 +129,7 @@ Conversion functions The ``django.utils.encoding`` module contains a few functions that are handy for converting back and forth between Unicode and bytestrings. -* ``smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict')`` +* ``smart_text(s, encoding='utf-8', strings_only=False, errors='strict')`` converts its input to a Unicode string. The ``encoding`` parameter specifies the input encoding. (For example, Django uses this internally when processing form input data, which might not be UTF-8 encoded.) The @@ -116,27 +139,27 @@ for converting back and forth between Unicode and bytestrings. that are accepted by Python's ``unicode()`` function for its error handling. - If you pass ``smart_unicode()`` an object that has a ``__unicode__`` + If you pass ``smart_text()`` an object that has a ``__unicode__`` method, it will use that method to do the conversion. -* ``force_unicode(s, encoding='utf-8', strings_only=False, - errors='strict')`` is identical to ``smart_unicode()`` in almost all +* ``force_text(s, encoding='utf-8', strings_only=False, + errors='strict')`` is identical to ``smart_text()`` in almost all cases. The difference is when the first argument is a :ref:`lazy - translation ` instance. While ``smart_unicode()`` - preserves lazy translations, ``force_unicode()`` forces those objects to a + translation ` instance. While ``smart_text()`` + preserves lazy translations, ``force_text()`` forces those objects to a Unicode string (causing the translation to occur). Normally, you'll want - to use ``smart_unicode()``. However, ``force_unicode()`` is useful in + to use ``smart_text()``. However, ``force_text()`` is useful in template tags and filters that absolutely *must* have a string to work with, not just something that can be converted to a string. -* ``smart_str(s, encoding='utf-8', strings_only=False, errors='strict')`` - is essentially the opposite of ``smart_unicode()``. It forces the first +* ``smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict')`` + is essentially the opposite of ``smart_text()``. It forces the first argument to a bytestring. The ``strings_only`` parameter has the same - behavior as for ``smart_unicode()`` and ``force_unicode()``. This is + behavior as for ``smart_text()`` and ``force_text()``. This is slightly different semantics from Python's builtin ``str()`` function, but the difference is needed in a few places within Django's internals. -Normally, you'll only need to use ``smart_unicode()``. Call it as early as +Normally, you'll only need to use ``smart_text()``. Call it as early as possible on any input data that might be either Unicode or a bytestring, and from then on, you can treat the result as always being Unicode. @@ -182,7 +205,7 @@ An example might clarify things here:: >>> urlquote(u'Paris & Orléans') u'Paris%20%26%20Orl%C3%A9ans' - >>> iri_to_uri(u'/favorites/François/%s' % urlquote(u'Paris & Orléans')) + >>> iri_to_uri(u'/favorites/François/%s' % urlquote('Paris & Orléans')) '/favorites/Fran%C3%A7ois/Paris%20%26%20Orl%C3%A9ans' If you look carefully, you can see that the portion that was generated by @@ -268,7 +291,9 @@ You can pass either Unicode strings or UTF-8 bytestrings as arguments to ``filter()`` methods and the like in the database API. The following two querysets are identical:: - qs = People.objects.filter(name__contains=u'Å') + from __future__ import unicode_literals + + qs = People.objects.filter(name__contains='Å') qs = People.objects.filter(name__contains=b'\xc3\x85') # UTF-8 encoding of Å Templates @@ -276,9 +301,10 @@ Templates You can use either Unicode or bytestrings when creating templates manually:: - from django.template import Template - t1 = Template(b'This is a bytestring template.') - t2 = Template(u'This is a Unicode template.') + from __future__ import unicode_literals + from django.template import Template + t1 = Template(b'This is a bytestring template.') + t2 = Template('This is a Unicode template.') But the common case is to read templates from the filesystem, and this creates a slight complication: not all filesystems store their data encoded as UTF-8. @@ -298,7 +324,7 @@ A couple of tips to remember when writing your own template tags and filters: * Always return Unicode strings from a template tag's ``render()`` method and from template filters. -* Use ``force_unicode()`` in preference to ``smart_unicode()`` in these +* Use ``force_text()`` in preference to ``smart_text()`` in these places. Tag rendering and filter calls occur as the template is being rendered, so there is no advantage to postponing the conversion of lazy translation objects into strings. It's easier to work solely with Unicode @@ -316,14 +342,15 @@ characters. The following code example demonstrates that everything except email addresses can be non-ASCII:: + from __future__ import unicode_literals from django.core.mail import EmailMessage - subject = u'My visit to Sør-Trøndelag' - sender = u'Arnbjörg Ráðormsdóttir ' + subject = 'My visit to Sør-Trøndelag' + sender = 'Arnbjörg Ráðormsdóttir ' recipients = ['Fred %s
        %s" % (some_html, + escape(some_text), + escape(some_other_text), + )) + + you should instead use: + + .. code-block:: python + + format_html(u"%{0} {1} {2}", + mark_safe(some_html), some_text, some_other_text) + + This has the advantage that you don't need to apply :func:`escape` to each + argument and risk a bug and an XSS vulnerability if you forget one. + + Note that although this function uses ``str.format`` to do the + interpolation, some of the formatting options provided by `str.format`_ + (e.g. number formatting) will not work, since all arguments are passed + through :func:`conditional_escape` which (ultimately) calls + :func:`~django.utils.encoding.force_text` on the values. + +.. function:: strip_tags(value) + + Removes anything that looks like an html tag from the string, that is + anything contained within ``<>``. + + For example:: + + strip_tags(value) + + If ``value`` is ``"Joel a slug"`` the + return value will be ``"Joel is a slug"``. + +.. function:: remove_tags(value, tags) + + Removes a list of [X]HTML tag names from the output. + + For example:: + + remove_tags(value, ["b", "span"]) + + If ``value`` is ``"Joel a slug"`` the + return value will be ``"Joel a slug"``. + + Note that this filter is case-sensitive. + + If ``value`` is ``"Joel a slug"`` the + return value will be ``"Joel a slug"``. + +.. _str.format: http://docs.python.org/library/stdtypes.html#str.format ``django.utils.http`` ===================== @@ -439,11 +582,13 @@ Atom1Feed .. function:: base36_to_int(s) - Converts a base 36 string to an integer. + Converts a base 36 string to an integer. On Python 2 the output is + guaranteed to be an :class:`int` and not a :class:`long`. .. function:: int_to_base36(i) - Converts a positive integer less than sys.maxint to a base 36 string. + Converts a positive integer to a base 36 string. On Python 2 ``i`` must be + smaller than :attr:`sys.maxint`. ``django.utils.safestring`` =========================== @@ -457,15 +602,29 @@ string" means that the producer of the string has already turned characters that should not be interpreted by the HTML engine (e.g. '<') into the appropriate entities. +.. class:: SafeBytes + + .. versionadded:: 1.5 + + A :class:`bytes` subclass that has been specifically marked as "safe" + (requires no further escaping) for HTML output purposes. + .. class:: SafeString - A string subclass that has been specifically marked as "safe" (requires no - further escaping) for HTML output purposes. + A :class:`str` subclass that has been specifically marked as "safe" + (requires no further escaping) for HTML output purposes. This is + :class:`SafeBytes` on Python 2 and :class:`SafeText` on Python 3. + +.. class:: SafeText + + .. versionadded:: 1.5 + + A :class:`str` (in Python 3) or :class:`unicode` (in Python 2) subclass + that has been specifically marked as "safe" for HTML output purposes. .. class:: SafeUnicode - A unicode subclass that has been specifically marked as "safe" for HTML - output purposes. + Historical name of :class:`SafeText`. Only available under Python 2. .. function:: mark_safe(s) @@ -482,6 +641,24 @@ appropriate entities. Can be called multiple times on a single string (the resulting escaping is only applied once). +``django.utils.text`` +===================== + +.. module:: django.utils.text + :synopsis: Text manipulation. + +.. function:: slugify + + Converts to lowercase, removes non-word characters (alphanumerics and + underscores) and converts spaces to hyphens. Also strips leading and trailing + whitespace. + + For example:: + + slugify(value) + + If ``value`` is ``"Joel is a slug"``, the output will be ``"joel-is-a-slug"``. + ``django.utils.translation`` ============================ diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index 5b83d26d87..b68d6f2772 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -2,7 +2,6 @@ Validators ========== -.. versionadded:: 1.2 .. module:: django.core.validators :synopsis: Validation utilities and base classes diff --git a/docs/releases/1.0-beta-2.txt b/docs/releases/1.0-beta-2.txt index 795b3c1f19..288ac8fbc1 100644 --- a/docs/releases/1.0-beta-2.txt +++ b/docs/releases/1.0-beta-2.txt @@ -46,7 +46,7 @@ Refactored documentation documentation files bundled with Django. .. _Sphinx: http://sphinx.pocoo.org/ -.. _online: http://docs.djangoproject.com/en/dev/ +.. _online: https://docs.djangoproject.com/ Along with these new features, the Django team has also been hard at work polishing Django's codebase for the final 1.0 release; this beta @@ -69,7 +69,7 @@ the remaining features to be implemented for Django 1.0, and on the bugs that need to be resolved before the final release. As of this beta release, Django is in its final "feature freeze" for 1.0; feature requests will be deferred to later releases, and the development -effort will be focused solely on bug-fixing and stability. Django is +effort will be focused solely on bugfixing and stability. Django is also now in a "string freeze"; translatable strings (labels, error messages, etc.) in Django's codebase will not be changed prior to the release, in order to allow our translators to produce the final 1.0 diff --git a/docs/releases/1.0.1.txt b/docs/releases/1.0.1.txt index 7901b8e219..d6d21afc01 100644 --- a/docs/releases/1.0.1.txt +++ b/docs/releases/1.0.1.txt @@ -18,7 +18,7 @@ Fixes and improvements in Django 1.0.1 Django 1.0.1 contains over two hundred fixes to the original Django 1.0 codebase; full details of every fix are available in `the -Subversion log of the 1.0.X branch`_, but here are some of the +history of the 1.0.X branch`_, but here are some of the highlights: * Several fixes in ``django.contrib.comments``, pertaining to RSS @@ -61,4 +61,4 @@ highlights: documentation, including both corrections to existing documents and expanded and new documentation. -.. _the Subversion log of the 1.0.X branch: https://code.djangoproject.com/log/django/branches/releases/1.0.X +.. _the history of the 1.0.X branch: https://github.com/django/django/commits/stable/1.0.x diff --git a/docs/releases/1.0.txt b/docs/releases/1.0.txt index e752b02e1b..1e44f57de8 100644 --- a/docs/releases/1.0.txt +++ b/docs/releases/1.0.txt @@ -57,7 +57,7 @@ there. In fact, new documentation is one of our favorite features of Django 1.0, so we might as well start there. First, there's a new documentation site: -* http://docs.djangoproject.com/ +* https://docs.djangoproject.com/ The documentation has been greatly improved, cleaned up, and generally made awesome. There's now dedicated search, indexes, and more. diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index 12940114ed..852644dee4 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -101,7 +101,7 @@ If you've been relying on this middleware, the easiest upgrade path is: * Introduce your modified version of ``SetRemoteAddrFromForwardedFor`` as a piece of middleware in your own project. -__ https://code.djangoproject.com/browser/django/trunk/django/middleware/http.py?rev=11000#L33 +__ https://github.com/django/django/blob/91f18400cc0fb37659e2dbaab5484ff2081f1f30/django/middleware/http.py#L33 Names of uploaded files are available later ------------------------------------------- @@ -366,7 +366,7 @@ features: For more details, see the `GeoDjango documentation`_. .. _geodjango: http://geodjango.org/ -.. _spatialite: http://www.gaia-gis.it/spatialite/ +.. _spatialite: http://www.gaia-gis.it/gaia-sins/ .. _geodjango documentation: http://geodjango.org/docs/ Other improvements diff --git a/docs/releases/1.3-alpha-1.txt b/docs/releases/1.3-alpha-1.txt index 25ca3ce31f..2f5124e52b 100644 --- a/docs/releases/1.3-alpha-1.txt +++ b/docs/releases/1.3-alpha-1.txt @@ -39,7 +39,7 @@ along with a completely generic view base class that can be used as the basis for reusable applications that can be easily extended. See :doc:`the documentation on Class-based Generic Views -` for more details. There is also a document to +` for more details. There is also a document to help you `convert your function-based generic views to class-based views `_. diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 3548a005bb..772dbdb2e7 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -80,7 +80,7 @@ provided, along with a completely generic view base class that can be used as the basis for reusable applications that can be easily extended. -See :doc:`the documentation on class-based generic views` +See :doc:`the documentation on class-based generic views` for more details. There is also a document to help you `convert your function-based generic views to class-based views `_. @@ -329,7 +329,7 @@ requests. These include: * Support for combining :ref:`F() expressions ` with timedelta values when retrieving or updating database values. -.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly +.. _HTTPOnly: https://www.owasp.org/index.php/HTTPOnly .. _backwards-incompatible-changes-1.3: @@ -830,7 +830,7 @@ now pending deprecation. ~~~~~~~~~~~~~~~~ Previously, ``django.http`` exposed an undocumented ``CompatCookie`` class, -which was a bug-fix wrapper around the standard library ``SimpleCookie``. As the +which was a bugfix wrapper around the standard library ``SimpleCookie``. As the fixes are moving upstream, this is now deprecated - you should use ``from django.http import SimpleCookie`` instead. diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 51766dc2f5..01532cc04c 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -37,7 +37,7 @@ Other notable new features in Django 1.4 include: the ability to `bulk insert <#model-objects-bulk-create-in-the-orm>`_ large datasets for improved performance, and `QuerySet.prefetch_related`_, a method to batch-load related objects - in areas where :meth:`~django.db.models.QuerySet.select_related` does't + in areas where :meth:`~django.db.models.QuerySet.select_related` doesn't work. * Some nice security additions, including `improved password hashing`_ @@ -1145,6 +1145,15 @@ field. This was something that should not have worked, and in 1.4 loading such incomplete fixtures will fail. Because fixtures are a raw import, they should explicitly specify all field values, regardless of field options on the model. +Development Server Multithreading +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The development server is now is multithreaded by default. Use the +:djadminopt:`--nothreading` option to disable the use of threading in the +development server:: + + django-admin.py runserver --nothreading + Attributes disabled in markdown when safe mode set ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1338,4 +1347,3 @@ Versions of Python-Markdown earlier than 2.1 do not support the option to disable attributes. As a security issue, earlier versions of this library will not be supported by the markup contrib app in 1.5 under an accelerated deprecation timeline. - diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 0d86a52670..4f25919d79 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -16,7 +16,7 @@ features`_. Python compatibility ==================== -Django 1.5 has dropped support for Python 2.5. Python 2.6 is now the minimum +Django 1.5 has dropped support for Python 2.5. Python 2.6.5 is now the minimum required Python version. Django is tested and supported on Python 2.6 and 2.7. @@ -27,8 +27,9 @@ Django 1.4 until you can upgrade your Python version. Per :doc:`our support poli `, Django 1.4 will continue to receive security support until the release of Django 1.6. -Django 1.5 does not run on Jython, because Jython doesn't currently offer any -version compatible with Python 2.6. +Django 1.5 does not run on a Jython final release, because Jython's latest release +doesn't currently support Python 2.6. However, Jython currently does offer an alpha +release featuring 2.7 support. What's new in Django 1.5 ======================== @@ -41,6 +42,10 @@ keyword argument ``update_fields``. By using this argument it is possible to save only a select list of model's fields. This can be useful for performance reasons or when trying to avoid overwriting concurrent changes. +Deferred instances (those loaded by .only() or .defer()) will automatically +save just the loaded fields. If any field is set manually after load, that +field will also get updated on save. + See the :meth:`Model.save() ` documentation for more details. @@ -62,6 +67,29 @@ For one-to-one relationships, both sides can be cached. For many-to-one relationships, only the single side of the relationship can be cached. This is particularly helpful in combination with ``prefetch_related``. +``{% verbatim %}`` template tag +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To make it easier to deal with javascript templates which collide with Django's +syntax, you can now use the :ttag:`verbatim` block tag to avoid parsing the +tag's content. + +Retrieval of ``ContentType`` instances associated with proxy models +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The methods :meth:`ContentTypeManager.get_for_model() ` +and :meth:`ContentTypeManager.get_for_models() ` +have a new keyword argument – respectively ``for_concrete_model`` and ``for_concrete_models``. +By passing ``False`` using this argument it is now possible to retreive the +:class:`ContentType ` +associated with proxy models. + +New ``view`` variable in class-based views context +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In all :doc:`generic class-based views ` +(or any class-based view inheriting from ``ContextMixin``), the context dictionary +contains a ``view`` variable that points to the ``View`` instance. + Minor features ~~~~~~~~~~~~~~ @@ -85,6 +113,14 @@ Django 1.5 also includes several smaller improvements worth noting: * In the localflavor for Canada, "pq" was added to the acceptable codes for Quebec. It's an old abbreviation. +* The :ref:`receiver ` decorator is now able to + connect to more than one signal by supplying a list of signals. + +* :meth:`QuerySet.bulk_create() + ` now has a batch_size + argument. By default the batch_size is unlimited except for SQLite where + single batch is limited so that 999 parameters per query isn't exceeded. + Backwards incompatible changes in 1.5 ===================================== @@ -108,6 +144,14 @@ year|date:"Y" }}``. ``next_year`` and ``previous_year`` were also added in the context. They are calculated according to ``allow_empty`` and ``allow_future``. +Context in TemplateView +~~~~~~~~~~~~~~~~~~~~~~~ + +For consistency with the design of the other generic views, +:class:`~django.views.generic.base.TemplateView` no longer passes a ``params`` +dictionary into the context, instead passing the variables from the URLconf +directly into the context. + OPTIONS, PUT and DELETE requests in the test client ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -128,6 +172,113 @@ If you were using the ``data`` parameter in a PUT request without a ``content_type``, you must encode your data before passing it to the test client and set the ``content_type`` argument. +String types of hasher method parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have written a :ref:`custom password hasher `, +your ``encode()``, ``verify()`` or ``safe_summary()`` methods should accept +Unicode parameters (``password``, ``salt`` or ``encoded``). If any of the +hashing methods need byte strings, you can use the +:func:`~django.utils.encoding.smart_bytes` utility to encode the strings. + +Validation of previous_page_number and next_page_number +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using :doc:`object pagination `, +the ``previous_page_number()`` and ``next_page_number()`` methods of the +:class:`~django.core.paginator.Page` object did not check if the returned +number was inside the existing page range. +It does check it now and raises an :exc:`InvalidPage` exception when the number +is either too low or too high. + +Behavior of autocommit database option on PostgreSQL changed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +PostgreSQL's autocommit option didn't work as advertised previously. It did +work for single transaction block, but after the first block was left the +autocommit behavior was never restored. This bug is now fixed in 1.5. While +this is only a bug fix, it is worth checking your applications behavior if +you are using PostgreSQL together with the autocommit option. + +Session not saved on 500 responses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Django's session middleware will skip saving the session data if the +response's status code is 500. + +Changes in tests execution +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some changes have been introduced in the execution of tests that might be +backward-incompatible for some testing setups: + +Database flushing in ``django.test.TransactionTestCase`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, the test database was truncated *before* each test run in a +:class:`~django.test.TransactionTestCase`. + +In order to be able to run unit tests in any order and to make sure they are +always isolated from each other, :class:`~django.test.TransactionTestCase` will +now reset the database *after* each test run instead. + +No more implict DB sequences reset +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:class:`~django.test.TransactionTestCase` tests used to reset primary key +sequences automatically together with the database flushing actions described +above. + +This has been changed so no sequences are implicitly reset. This can cause +:class:`~django.test.TransactionTestCase` tests that depend on hard-coded +primary key values to break. + +The new :attr:`~django.test.TransactionTestCase.reset_sequences` attribute can +be used to force the old behavior for :class:`~django.test.TransactionTestCase` +that might need it. + +Ordering of tests +^^^^^^^^^^^^^^^^^ + +In order to make sure all ``TestCase`` code starts with a clean database, +tests are now executed in the following order: + +* First, all unittests (including :class:`unittest.TestCase`, + :class:`~django.test.SimpleTestCase`, :class:`~django.test.TestCase` and + :class:`~django.test.TransactionTestCase`) are run with no particular ordering + guaranteed nor enforced among them. + +* Then any other tests (e.g. doctests) that may alter the database without + restoring it to its original state are run. + +This should not cause any problems unless you have existing doctests which +assume a :class:`~django.test.TransactionTestCase` executed earlier left some +database state behind or unit tests that rely on some form of state being +preserved after the execution of other tests. Such tests are already very +fragile, and must now be changed to be able to run independently. + +`cleaned_data` dictionary kept for invalid forms +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :attr:`~django.forms.Form.cleaned_data` dictionary is now always present +after form validation. When the form doesn't validate, it contains only the +fields that passed validation. You should test the success of the validation +with the :meth:`~django.forms.Form.is_valid()` method and not with the +presence or absence of the :attr:`~django.forms.Form.cleaned_data` attribute +on the form. + +Miscellaneous +~~~~~~~~~~~~~ + +* GeoDjango dropped support for GDAL < 1.5 + +* :func:`~django.utils.http.int_to_base36` properly raises a :exc:`TypeError` + instead of :exc:`ValueError` for non-integer inputs. + +* The ``slugify`` template filter is now available as a standard python + function at :func:`django.utils.text.slugify`. Similarly, ``remove_tags`` is + available at :func:`django.utils.html.remove_tags`. + Features deprecated in 1.5 ========================== @@ -144,3 +295,10 @@ our own copy of ``simplejson``. You can safely change any use of The :func:`~django.utils.itercompat.product` function has been deprecated. Use the built-in :func:`itertools.product` instead. + +``django.utils.encoding.StrAndUnicode`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :class:`~django.utils.encoding.StrAndUnicode` mix-in has been deprecated. +Define a ``__str__`` method and apply the +:func:`~django.utils.encoding.python_2_unicode_compatible` decorator instead. diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 24d0f4e571..99e36b34d7 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -64,11 +64,8 @@ Fields .. attribute:: models.User.username - Required. 30 characters or fewer. Alphanumeric characters only - (letters, digits and underscores). - - .. versionchanged:: 1.2 - Usernames may now contain ``@``, ``+``, ``.`` and ``-`` characters. + Required. 30 characters or fewer. Usernames may contain alphanumeric, + ``_``, ``@``, ``+``, ``.`` and ``-`` characters. .. attribute:: models.User.first_name @@ -101,12 +98,13 @@ Fields This doesn't necessarily control whether or not the user can log in. Authentication backends aren't required to check for the ``is_active`` - flag, so if you want to reject a login based on ``is_active`` being - ``False``, it's up to you to check that in your own login view. - However, the :class:`~django.contrib.auth.forms.AuthenticationForm` - used by the :func:`~django.contrib.auth.views.login` view *does* - perform this check, as do the permission-checking methods such as - :meth:`~models.User.has_perm` and the authentication in the Django + flag, and the default backends do not. If you want to reject a login + based on ``is_active`` being ``False``, it's up to you to check that in + your own login view or a custom authentication backend. However, the + :class:`~django.contrib.auth.forms.AuthenticationForm` used by the + :func:`~django.contrib.auth.views.login` view (which is the default) + *does* perform this check, as do the permission-checking methods such + as :meth:`~models.User.has_perm` and the authentication in the Django admin. All of those functions/methods will return ``False`` for inactive users. @@ -207,8 +205,6 @@ Methods Returns a set of permission strings that the user has, through his/her groups. - .. versionadded:: 1.2 - If ``obj`` is passed in, only returns the group permissions for this specific object. @@ -217,8 +213,6 @@ Methods Returns a set of permission strings that the user has, both through group and user permissions. - .. versionadded:: 1.2 - If ``obj`` is passed in, only returns the permissions for this specific object. @@ -229,8 +223,6 @@ Methods `permissions`_ section below). If the user is inactive, this method will always return ``False``. - .. versionadded:: 1.2 - If ``obj`` is passed in, this method won't check for a permission for the model, but for this specific object. @@ -241,8 +233,6 @@ Methods ``"."``. If the user is inactive, this method will always return ``False``. - .. versionadded:: 1.2 - If ``obj`` is passed in, this method won't check for permissions for the model, but for the specific object. @@ -349,9 +339,6 @@ Django requires add *and* change permissions as a slight security measure. Changing passwords ~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.2 - The ``manage.py changepassword`` command was added. - :djadmin:`manage.py changepassword *username* ` offers a method of changing a User's password from the command line. It prompts you to change the password of a given user which you must enter twice. If @@ -1046,8 +1033,6 @@ The login_required decorator {% endblock %} - .. versionadded:: 1.2 - If you are using alternate authentication (see :ref:`authentication-backends`) you can pass a custom authentication form to the login view via the ``authentication_form`` parameter. This form must @@ -1140,8 +1125,6 @@ includes a few other useful built-in views located in * ``post_change_redirect``: The URL to redirect to after a successful password change. - .. versionadded:: 1.2 - * ``password_change_form``: A custom "change password" form which must accept a ``user`` keyword argument. The form is responsible for actually changing the user's password. Defaults to @@ -1465,12 +1448,13 @@ The permission_required decorator .. currentmodule:: django.contrib.auth -Limiting access to generic views --------------------------------- +Applying permissions to generic views +------------------------------------- -To limit access to a :doc:`class-based generic view `, -decorate the :meth:`View.dispatch ` -method on the class. See :ref:`decorating-class-based-views` for details. +To apply a permission to a :doc:`class-based generic view +`, decorate the :meth:`View.dispatch +` method on the class. See +:ref:`decorating-class-based-views` for details. .. _permissions: @@ -1493,12 +1477,13 @@ The Django admin site uses permissions as follows: * Access to delete an object is limited to users with the "delete" permission for that type of object. -Permissions are set globally per type of object, not per specific object -instance. For example, it's possible to say "Mary may change news stories," but -it's not currently possible to say "Mary may change news stories, but only the -ones she created herself" or "Mary may only change news stories that have a -certain status, publication date or ID." The latter functionality is something -Django developers are currently discussing. +Permissions can be set not only per type of object, but also per specific +object instance. By using the +:meth:`~django.contrib.admin.ModelAdmin.has_add_permission`, +:meth:`~django.contrib.admin.ModelAdmin.has_change_permission` and +:meth:`~django.contrib.admin.ModelAdmin.has_delete_permission` methods provided +by the :class:`~django.contrib.admin.ModelAdmin` class, it is possible to +customize permissions for different object instances of the same type. Default permissions ------------------- @@ -1771,7 +1756,11 @@ By default, :setting:`AUTHENTICATION_BACKENDS` is set to:: ('django.contrib.auth.backends.ModelBackend',) -That's the basic authentication scheme that checks the Django users database. +That's the basic authentication backend that checks the Django users database +and queries the builtin permissions. It does not provide protection against +brute force attacks via any rate limiting mechanism. You may either implement +your own rate limiting mechanism in a custom auth backend, or use the +mechanisms provided by most Web servers. The order of :setting:`AUTHENTICATION_BACKENDS` matters, so if the same username and password is valid in multiple backends, Django will stop @@ -1791,8 +1780,9 @@ processing at the first positive match. Writing an authentication backend --------------------------------- -An authentication backend is a class that implements two methods: -``get_user(user_id)`` and ``authenticate(**credentials)``. +An authentication backend is a class that implements two required methods: +``get_user(user_id)`` and ``authenticate(**credentials)``, as well as a set of +optional permission related :ref:`authorization methods `. The ``get_user`` method takes a ``user_id`` -- which could be a username, database ID or whatever -- and returns a ``User`` object. @@ -1861,6 +1851,8 @@ object the first time a user authenticates:: except User.DoesNotExist: return None +.. _authorization_methods: + Handling authorization in custom backends ----------------------------------------- @@ -1891,13 +1883,16 @@ fairly simply:: return False This gives full permissions to the user granted access in the above example. -Notice that the backend auth functions all take the user object as an argument, -and they also accept the same arguments given to the associated -:class:`django.contrib.auth.models.User` functions. +Notice that in addition to the same arguments given to the associated +:class:`django.contrib.auth.models.User` functions, the backend auth functions +all take the user object, which may be an anonymous user, as an argument. -A full authorization implementation can be found in -`django/contrib/auth/backends.py`_, which is the default backend and queries -the ``auth_permission`` table most of the time. +A full authorization implementation can be found in the ``ModelBackend`` class +in `django/contrib/auth/backends.py`_, which is the default backend and queries +the ``auth_permission`` table most of the time. If you wish to provide +custom behavior for only part of the backend API, you can take advantage of +Python inheritence and subclass ``ModelBackend`` instead of implementing the +complete API in a custom backend. .. _django/contrib/auth/backends.py: https://github.com/django/django/blob/master/django/contrib/auth/backends.py @@ -1906,8 +1901,6 @@ the ``auth_permission`` table most of the time. Authorization for anonymous users ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionchanged:: 1.2 - An anonymous user is one that is not authenticated i.e. they have provided no valid authentication details. However, that does not necessarily mean they are not authorized to do anything. At the most basic level, most Web sites @@ -1915,25 +1908,27 @@ authorize anonymous users to browse most of the site, and many allow anonymous posting of comments etc. Django's permission framework does not have a place to store permissions for -anonymous users. However, it has a foundation that allows custom authentication -backends to specify authorization for anonymous users. This is especially useful -for the authors of re-usable apps, who can delegate all questions of authorization -to the auth backend, rather than needing settings, for example, to control -anonymous access. +anonymous users. However, the user object passed to an authentication backend +may be an :class:`django.contrib.auth.models.AnonymousUser` object, allowing +the backend to specify custom authorization behavior for anonymous users. This +is especially useful for the authors of re-usable apps, who can delegate all +questions of authorization to the auth backend, rather than needing settings, +for example, to control anonymous access. +.. _inactive_auth: Authorization for inactive users ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.3 +.. versionchanged:: 1.3 An inactive user is a one that is authenticated but has its attribute ``is_active`` set to ``False``. However this does not mean they are not authorized to do anything. For example they are allowed to activate their account. -The support for anonymous users in the permission system allows for -anonymous users to have permissions to do something while inactive +The support for anonymous users in the permission system allows for a scenario +where anonymous users have permissions to do something while inactive authenticated users do not. Do not forget to test for the ``is_active`` attribute of the user in your own @@ -1941,9 +1936,11 @@ backend permission methods. Handling object permissions ---------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~ Django's permission framework has a foundation for object permissions, though there is no implementation for it in the core. That means that checking for object permissions will always return ``False`` or an empty list (depending on -the check performed). +the check performed). An authentication backend will receive the keyword +parameters ``obj`` and ``user_obj`` for each object related authorization +method and can return the object level permission as appropriate. diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 99d764b60d..219b6c7795 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -83,12 +83,6 @@ two most common are `python-memcached`_ and `pylibmc`_. .. _`python-memcached`: ftp://ftp.tummy.com/pub/python-memcached/ .. _`pylibmc`: http://sendapatch.se/projects/pylibmc/ -.. versionchanged:: 1.2 - In Django 1.0 and 1.1, you could also use ``cmemcache`` as a binding. - However, support for this library was deprecated in 1.2 due to - a lack of maintenance on the ``cmemcache`` library itself. Support for - ``cmemcache`` will be removed completely in Django 1.4. - .. versionchanged:: 1.3 Support for ``pylibmc`` was added. @@ -493,8 +487,6 @@ more on these decorators. .. _i18n-cache-key: -.. versionadded:: 1.2 - If :setting:`USE_I18N` is set to ``True`` then the generated cache key will include the name of the active :term:`language` -- see also :ref:`how-django-discovers-language-preference`). This allows you to easily @@ -603,7 +595,8 @@ the ``cache`` template tag. To give your template access to this tag, put The ``{% cache %}`` template tag caches the contents of the block for a given amount of time. It takes at least two arguments: the cache timeout, in seconds, -and the name to give the cache fragment. For example: +and the name to give the cache fragment. The name will be taken as is, do not +use a variable. For example: .. code-block:: html+django @@ -737,8 +730,6 @@ actually exist in the cache (and haven't expired):: >>> cache.get_many(['a', 'b', 'c']) {'a': 1, 'b': 2, 'c': 3} -.. versionadded:: 1.2 - To set multiple values more efficiently, use ``set_many()`` to pass a dictionary of key-value pairs:: @@ -753,15 +744,11 @@ clearing the cache for a particular object:: >>> cache.delete('a') -.. versionadded:: 1.2 - If you want to clear a bunch of keys at once, ``delete_many()`` can take a list of keys to be cleared:: >>> cache.delete_many(['a', 'b', 'c']) -.. versionadded:: 1.2 - Finally, if you want to delete all the keys in the cache, use ``cache.clear()``. Be careful with this; ``clear()`` will remove *everything* from the cache, not just the keys set by your application. :: @@ -877,7 +864,7 @@ key version to provide a final cache key. By default, the three parts are joined using colons to produce a final string:: def make_key(key, key_prefix, version): - return ':'.join([key_prefix, str(version), smart_str(key)]) + return ':'.join([key_prefix, str(version), smart_bytes(key)]) If you want to combine the parts in different ways, or apply other processing to the final key (e.g., taking a hash digest of the key diff --git a/docs/topics/class-based-views.txt b/docs/topics/class-based-views.txt deleted file mode 100644 index 13c2b994e4..0000000000 --- a/docs/topics/class-based-views.txt +++ /dev/null @@ -1,624 +0,0 @@ -========================= -Class-based generic views -========================= - -.. versionadded:: 1.3 - -.. note:: - Prior to Django 1.3, generic views were implemented as functions. The - function-based implementation has been removed in favor of the - class-based approach described here. - -Writing Web applications can be monotonous, because we repeat certain patterns -again and again. Django tries to take away some of that monotony at the model -and template layers, but Web developers also experience this boredom at the view -level. - -Django's *generic views* were developed to ease that pain. They take certain -common idioms and patterns found in view development and abstract them so that -you can quickly write common views of data without having to write too much -code. - -We can recognize certain common tasks, like displaying a list of objects, and -write code that displays a list of *any* object. Then the model in question can -be passed as an extra argument to the URLconf. - -Django ships with generic views to do the following: - -* Perform common "simple" tasks: redirect to a different page and - render a given template. - -* Display list and detail pages for a single object. If we were creating an - application to manage conferences then a ``TalkListView`` and a - ``RegisteredUserListView`` would be examples of list views. A single - talk page is an example of what we call a "detail" view. - -* Present date-based objects in year/month/day archive pages, - associated detail, and "latest" pages. - -* Allow users to create, update, and delete objects -- with or - without authorization. - -Taken together, these views provide easy interfaces to perform the most common -tasks developers encounter. - - -Simple usage -============ - -Class-based generic views (and any class-based views that inherit from -the base classes Django provides) can be configured in two -ways: subclassing, or passing in arguments directly in the URLconf. - -When you subclass a class-based view, you can override attributes -(such as the ``template_name``) or methods (such as ``get_context_data``) -in your subclass to provide new values or methods. Consider, for example, -a view that just displays one template, ``about.html``. Django has a -generic view to do this - :class:`~django.views.generic.base.TemplateView` - -so we can just subclass it, and override the template name:: - - # some_app/views.py - from django.views.generic import TemplateView - - class AboutView(TemplateView): - template_name = "about.html" - -Then, we just need to add this new view into our URLconf. As the class-based -views themselves are classes, we point the URL to the ``as_view`` class method -instead, which is the entry point for class-based views:: - - # urls.py - from django.conf.urls import patterns, url, include - from some_app.views import AboutView - - urlpatterns = patterns('', - (r'^about/', AboutView.as_view()), - ) - -Alternatively, if you're only changing a few simple attributes on a -class-based view, you can simply pass the new attributes into the ``as_view`` -method call itself:: - - from django.conf.urls import patterns, url, include - from django.views.generic import TemplateView - - urlpatterns = patterns('', - (r'^about/', TemplateView.as_view(template_name="about.html")), - ) - -A similar overriding pattern can be used for the ``url`` attribute on -:class:`~django.views.generic.base.RedirectView`, another simple -generic view. - - -Generic views of objects -======================== - -:class:`~django.views.generic.base.TemplateView` certainly is useful, -but Django's generic views really shine when it comes to presenting -views of your database content. Because it's such a common task, -Django comes with a handful of built-in generic views that make -generating list and detail views of objects incredibly easy. - -Let's take a look at one of these generic views: the "object list" view. We'll -be using these models:: - - # models.py - from django.db import models - - class Publisher(models.Model): - name = models.CharField(max_length=30) - address = models.CharField(max_length=50) - city = models.CharField(max_length=60) - state_province = models.CharField(max_length=30) - country = models.CharField(max_length=50) - website = models.URLField() - - class Meta: - ordering = ["-name"] - - def __unicode__(self): - return self.name - - class Book(models.Model): - title = models.CharField(max_length=100) - authors = models.ManyToManyField('Author') - publisher = models.ForeignKey(Publisher) - publication_date = models.DateField() - -To build a list page of all publishers, we'd use a URLconf along these lines:: - - from django.conf.urls import patterns, url, include - from django.views.generic import ListView - from books.models import Publisher - - urlpatterns = patterns('', - (r'^publishers/$', ListView.as_view( - model=Publisher, - )), - ) - -That's all the Python code we need to write. We still need to write a template, -however. We could explicitly tell the view which template to use -by including a ``template_name`` key in the arguments to as_view, but in -the absence of an explicit template Django will infer one from the object's -name. In this case, the inferred template will be -``"books/publisher_list.html"`` -- the "books" part comes from the name of the -app that defines the model, while the "publisher" bit is just the lowercased -version of the model's name. - -.. note:: - Thus, when (for example) the :class:`django.template.loaders.app_directories.Loader` - template loader is enabled in :setting:`TEMPLATE_LOADERS`, the template - location would be:: - - /path/to/project/books/templates/books/publisher_list.html - -.. highlightlang:: html+django - -This template will be rendered against a context containing a variable called -``object_list`` that contains all the publisher objects. A very simple template -might look like the following:: - - {% extends "base.html" %} - - {% block content %} -

        Publishers

        -
          - {% for publisher in object_list %} -
        • {{ publisher.name }}
        • - {% endfor %} -
        - {% endblock %} - -That's really all there is to it. All the cool features of generic views come -from changing the "info" dictionary passed to the generic view. The -:doc:`generic views reference` documents all the generic -views and their options in detail; the rest of this document will consider -some of the common ways you might customize and extend generic views. - - -Extending generic views -======================= - -.. highlightlang:: python - -There's no question that using generic views can speed up development -substantially. In most projects, however, there comes a moment when the -generic views no longer suffice. Indeed, the most common question asked by new -Django developers is how to make generic views handle a wider array of -situations. - -This is one of the reasons generic views were redesigned for the 1.3 release - -previously, they were just view functions with a bewildering array of options; -now, rather than passing in a large amount of configuration in the URLconf, -the recommended way to extend generic views is to subclass them, and override -their attributes or methods. - - -Making "friendly" template contexts ------------------------------------ - -You might have noticed that our sample publisher list template stores -all the publishers in a variable named ``object_list``. While this -works just fine, it isn't all that "friendly" to template authors: -they have to "just know" that they're dealing with publishers here. - -Well, if you're dealing with a model object, this is already done for -you. When you are dealing with an object or queryset, Django is able -to populate the context using the verbose name (or the plural verbose -name, in the case of a list of objects) of the object being displayed. -This is provided in addition to the default ``object_list`` entry, but -contains exactly the same data. - -If the verbose name (or plural verbose name) still isn't a good match, -you can manually set the name of the context variable. The -``context_object_name`` attribute on a generic view specifies the -context variable to use. In this example, we'll override it in the -URLconf, since it's a simple change: - -.. parsed-literal:: - - urlpatterns = patterns('', - (r'^publishers/$', ListView.as_view( - model=Publisher, - **context_object_name="publisher_list",** - )), - ) - -Providing a useful ``context_object_name`` is always a good idea. Your -coworkers who design templates will thank you. - - -Adding extra context --------------------- - -Often you simply need to present some extra information beyond that -provided by the generic view. For example, think of showing a list of -all the books on each publisher detail page. The -:class:`~django.views.generic.detail.DetailView` generic view provides -the publisher to the context, but it seems there's no way to get -additional information in that template. - -However, there is; you can subclass -:class:`~django.views.generic.detail.DetailView` and provide your own -implementation of the ``get_context_data`` method. The default -implementation of this that comes with -:class:`~django.views.generic.detail.DetailView` simply adds in the -object being displayed to the template, but you can override it to show -more:: - - from django.views.generic import DetailView - from books.models import Publisher, Book - - class PublisherDetailView(DetailView): - - context_object_name = "publisher" - model = Publisher - - def get_context_data(self, **kwargs): - # Call the base implementation first to get a context - context = super(PublisherDetailView, self).get_context_data(**kwargs) - # Add in a QuerySet of all the books - context['book_list'] = Book.objects.all() - return context - -.. note:: - - Generally, get_context_data will merge the context data of all parent classes - with those of the current class. To preserve this behavior in your own classes - where you want to alter the context, you should be sure to call - get_context_data on the super class. When no two classes try to define the same - key, this will give the expected results. However if any class attempts to - override a key after parent classes have set it (after the call to super), any - children of that class will also need to explictly set it after super if they - want to be sure to override all parents. - -Viewing subsets of objects --------------------------- - -Now let's take a closer look at the ``model`` argument we've been -using all along. The ``model`` argument, which specifies the database -model that the view will operate upon, is available on all the -generic views that operate on a single object or a collection of -objects. However, the ``model`` argument is not the only way to -specify the objects that the view will operate upon -- you can also -specify the list of objects using the ``queryset`` argument:: - - from django.views.generic import DetailView - from books.models import Publisher, Book - - class PublisherDetailView(DetailView): - - context_object_name = "publisher" - queryset = Publisher.objects.all() - -Specifying ``model = Publisher`` is really just shorthand for saying -``queryset = Publisher.objects.all()``. However, by using ``queryset`` -to define a filtered list of objects you can be more specific about the -objects that will be visible in the view (see :doc:`/topics/db/queries` -for more information about :class:`QuerySet` objects, and see the -:doc:`class-based views reference ` for the complete -details). - -To pick a simple example, we might want to order a list of books by -publication date, with the most recent first:: - - urlpatterns = patterns('', - (r'^publishers/$', ListView.as_view( - queryset=Publisher.objects.all(), - context_object_name="publisher_list", - )), - (r'^books/$', ListView.as_view( - queryset=Book.objects.order_by("-publication_date"), - context_object_name="book_list", - )), - ) - - -That's a pretty simple example, but it illustrates the idea nicely. Of course, -you'll usually want to do more than just reorder objects. If you want to -present a list of books by a particular publisher, you can use the same -technique (here, illustrated using subclassing rather than by passing arguments -in the URLconf):: - - from django.views.generic import ListView - from books.models import Book - - class AcmeBookListView(ListView): - - context_object_name = "book_list" - queryset = Book.objects.filter(publisher__name="Acme Publishing") - template_name = "books/acme_list.html" - -Notice that along with a filtered ``queryset``, we're also using a custom -template name. If we didn't, the generic view would use the same template as the -"vanilla" object list, which might not be what we want. - -Also notice that this isn't a very elegant way of doing publisher-specific -books. If we want to add another publisher page, we'd need another handful of -lines in the URLconf, and more than a few publishers would get unreasonable. -We'll deal with this problem in the next section. - -.. note:: - - If you get a 404 when requesting ``/books/acme/``, check to ensure you - actually have a Publisher with the name 'ACME Publishing'. Generic - views have an ``allow_empty`` parameter for this case. See the - :doc:`class-based-views reference` for more details. - - -Dynamic filtering ------------------ - -Another common need is to filter down the objects given in a list page by some -key in the URL. Earlier we hard-coded the publisher's name in the URLconf, but -what if we wanted to write a view that displayed all the books by some arbitrary -publisher? - -Handily, the ``ListView`` has a -:meth:`~django.views.generic.detail.ListView.get_queryset` method we can -override. Previously, it has just been returning the value of the ``queryset`` -attribute, but now we can add more logic. - -The key part to making this work is that when class-based views are called, -various useful things are stored on ``self``; as well as the request -(``self.request``) this includes the positional (``self.args``) and name-based -(``self.kwargs``) arguments captured according to the URLconf. - -Here, we have a URLconf with a single captured group:: - - from books.views import PublisherBookListView - - urlpatterns = patterns('', - (r'^books/(\w+)/$', PublisherBookListView.as_view()), - ) - -Next, we'll write the ``PublisherBookListView`` view itself:: - - from django.shortcuts import get_object_or_404 - from django.views.generic import ListView - from books.models import Book, Publisher - - class PublisherBookListView(ListView): - - context_object_name = "book_list" - template_name = "books/books_by_publisher.html" - - def get_queryset(self): - publisher = get_object_or_404(Publisher, name__iexact=self.args[0]) - return Book.objects.filter(publisher=publisher) - -As you can see, it's quite easy to add more logic to the queryset selection; -if we wanted, we could use ``self.request.user`` to filter using the current -user, or other more complex logic. - -We can also add the publisher into the context at the same time, so we can -use it in the template:: - - class PublisherBookListView(ListView): - - context_object_name = "book_list" - template_name = "books/books_by_publisher.html" - - def get_queryset(self): - self.publisher = get_object_or_404(Publisher, name__iexact=self.args[0]) - return Book.objects.filter(publisher=self.publisher) - - def get_context_data(self, **kwargs): - # Call the base implementation first to get a context - context = super(PublisherBookListView, self).get_context_data(**kwargs) - # Add in the publisher - context['publisher'] = self.publisher - return context - -Performing extra work ---------------------- - -The last common pattern we'll look at involves doing some extra work before -or after calling the generic view. - -Imagine we had a ``last_accessed`` field on our ``Author`` object that we were -using to keep track of the last time anybody looked at that author:: - - # models.py - - class Author(models.Model): - salutation = models.CharField(max_length=10) - first_name = models.CharField(max_length=30) - last_name = models.CharField(max_length=40) - email = models.EmailField() - headshot = models.ImageField(upload_to='/tmp') - last_accessed = models.DateTimeField() - -The generic ``DetailView`` class, of course, wouldn't know anything about this -field, but once again we could easily write a custom view to keep that field -updated. - -First, we'd need to add an author detail bit in the URLconf to point to a -custom view: - -.. parsed-literal:: - - from books.views import AuthorDetailView - - urlpatterns = patterns('', - #... - **(r'^authors/(?P\\d+)/$', AuthorDetailView.as_view()),** - ) - -Then we'd write our new view -- ``get_object`` is the method that retrieves the -object -- so we simply override it and wrap the call:: - - import datetime - from books.models import Author - from django.views.generic import DetailView - from django.shortcuts import get_object_or_404 - - class AuthorDetailView(DetailView): - - queryset = Author.objects.all() - - def get_object(self): - # Call the superclass - object = super(AuthorDetailView, self).get_object() - # Record the last accessed date - object.last_accessed = datetime.datetime.now() - object.save() - # Return the object - return object - -.. note:: - - This code won't actually work unless you create a - ``books/author_detail.html`` template. - -.. note:: - - The URLconf here uses the named group ``pk`` - this name is the default - name that ``DetailView`` uses to find the value of the primary key used to - filter the queryset. - - If you want to change it, you'll need to do your own ``get()`` call - on ``self.queryset`` using the new named parameter from ``self.kwargs``. - -More than just HTML -------------------- - -So far, we've been focusing on rendering templates to generate -responses. However, that's not all generic views can do. - -Each generic view is composed out of a series of mixins, and each -mixin contributes a little piece of the entire view. Some of these -mixins -- such as -:class:`~django.views.generic.base.TemplateResponseMixin` -- are -specifically designed for rendering content to an HTML response using a -template. However, you can write your own mixins that perform -different rendering behavior. - -For example, a simple JSON mixin might look something like this:: - - import json - from django import http - - class JSONResponseMixin(object): - def render_to_response(self, context): - "Returns a JSON response containing 'context' as payload" - return self.get_json_response(self.convert_context_to_json(context)) - - def get_json_response(self, content, **httpresponse_kwargs): - "Construct an `HttpResponse` object." - return http.HttpResponse(content, - content_type='application/json', - **httpresponse_kwargs) - - def convert_context_to_json(self, context): - "Convert the context dictionary into a JSON object" - # Note: This is *EXTREMELY* naive; in reality, you'll need - # to do much more complex handling to ensure that arbitrary - # objects -- such as Django model instances or querysets - # -- can be serialized as JSON. - return json.dumps(context) - -Then, you could build a JSON-returning -:class:`~django.views.generic.detail.DetailView` by mixing your -:class:`JSONResponseMixin` with the -:class:`~django.views.generic.detail.BaseDetailView` -- (the -:class:`~django.views.generic.detail.DetailView` before template -rendering behavior has been mixed in):: - - class JSONDetailView(JSONResponseMixin, BaseDetailView): - pass - -This view can then be deployed in the same way as any other -:class:`~django.views.generic.detail.DetailView`, with exactly the -same behavior -- except for the format of the response. - -If you want to be really adventurous, you could even mix a -:class:`~django.views.generic.detail.DetailView` subclass that is able -to return *both* HTML and JSON content, depending on some property of -the HTTP request, such as a query argument or a HTTP header. Just mix -in both the :class:`JSONResponseMixin` and a -:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`, -and override the implementation of :func:`render_to_response()` to defer -to the appropriate subclass depending on the type of response that the user -requested:: - - class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView): - def render_to_response(self, context): - # Look for a 'format=json' GET argument - if self.request.GET.get('format','html') == 'json': - return JSONResponseMixin.render_to_response(self, context) - else: - return SingleObjectTemplateResponseMixin.render_to_response(self, context) - -Because of the way that Python resolves method overloading, the local -``render_to_response()`` implementation will override the versions provided by -:class:`JSONResponseMixin` and -:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`. - -Decorating class-based views -============================ - -.. highlightlang:: python - -The extension of class-based views isn't limited to using mixins. You -can use also use decorators. - -Decorating in URLconf ---------------------- - -The simplest way of decorating class-based views is to decorate the -result of the :meth:`~django.views.generic.base.View.as_view` method. -The easiest place to do this is in the URLconf where you deploy your -view:: - - from django.contrib.auth.decorators import login_required, permission_required - from django.views.generic import TemplateView - - from .views import VoteView - - urlpatterns = patterns('', - (r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))), - (r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())), - ) - -This approach applies the decorator on a per-instance basis. If you -want every instance of a view to be decorated, you need to take a -different approach. - -.. _decorating-class-based-views: - -Decorating the class --------------------- - -To decorate every instance of a class-based view, you need to decorate -the class definition itself. To do this you apply the decorator to the -:meth:`~django.views.generic.base.View.dispatch` method of the class. - -A method on a class isn't quite the same as a standalone function, so -you can't just apply a function decorator to the method -- you need to -transform it into a method decorator first. The ``method_decorator`` -decorator transforms a function decorator into a method decorator so -that it can be used on an instance method. For example:: - - from django.contrib.auth.decorators import login_required - from django.utils.decorators import method_decorator - from django.views.generic import TemplateView - - class ProtectedView(TemplateView): - template_name = 'secret.html' - - @method_decorator(login_required) - def dispatch(self, *args, **kwargs): - return super(ProtectedView, self).dispatch(*args, **kwargs) - -In this example, every instance of ``ProtectedView`` will have -login protection. - -.. note:: - - ``method_decorator`` passes ``*args`` and ``**kwargs`` - as parameters to the decorated method on the class. If your method - does not accept a compatible set of parameters it will raise a - ``TypeError`` exception. diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt new file mode 100644 index 0000000000..0d4cb6244d --- /dev/null +++ b/docs/topics/class-based-views/generic-display.txt @@ -0,0 +1,432 @@ +.. _Generic views: + +========================= +Class-based generic views +========================= + +.. note:: + Prior to Django 1.3, generic views were implemented as functions. The + function-based implementation has been removed in favor of the + class-based approach described here. + +Writing Web applications can be monotonous, because we repeat certain patterns +again and again. Django tries to take away some of that monotony at the model +and template layers, but Web developers also experience this boredom at the view +level. + +Django's *generic views* were developed to ease that pain. They take certain +common idioms and patterns found in view development and abstract them so that +you can quickly write common views of data without having to write too much +code. + +We can recognize certain common tasks, like displaying a list of objects, and +write code that displays a list of *any* object. Then the model in question can +be passed as an extra argument to the URLconf. + +Django ships with generic views to do the following: + +* Display list and detail pages for a single object. If we were creating an + application to manage conferences then a ``TalkListView`` and a + ``RegisteredUserListView`` would be examples of list views. A single + talk page is an example of what we call a "detail" view. + +* Present date-based objects in year/month/day archive pages, + associated detail, and "latest" pages. + +* Allow users to create, update, and delete objects -- with or + without authorization. + +Taken together, these views provide easy interfaces to perform the most common +tasks developers encounter. + + +Extending generic views +======================= + +There's no question that using generic views can speed up development +substantially. In most projects, however, there comes a moment when the +generic views no longer suffice. Indeed, the most common question asked by new +Django developers is how to make generic views handle a wider array of +situations. + +This is one of the reasons generic views were redesigned for the 1.3 release - +previously, they were just view functions with a bewildering array of options; +now, rather than passing in a large amount of configuration in the URLconf, +the recommended way to extend generic views is to subclass them, and override +their attributes or methods. + +That said, generic views will have a limit. If you find you're struggling to +implement your view as a subclass of a generic view, then you may find it more +effective to write just the code you need, using your own class-based or +functional views. + +More examples of generic views are available in some third party applications, +or you could write your own as needed. + + +Generic views of objects +======================== + +:class:`~django.views.generic.base.TemplateView` certainly is useful, but +Django's generic views really shine when it comes to presenting views of your +database content. Because it's such a common task, Django comes with a handful +of built-in generic views that make generating list and detail views of objects +incredibly easy. + +Let's start by looking at some examples of showing a list of objects or an +individual object. + +.. comment: link here to the other topic pages (form handling, date based, mixins) + +We'll be using these models:: + + # models.py + from django.db import models + + class Publisher(models.Model): + name = models.CharField(max_length=30) + address = models.CharField(max_length=50) + city = models.CharField(max_length=60) + state_province = models.CharField(max_length=30) + country = models.CharField(max_length=50) + website = models.URLField() + + class Meta: + ordering = ["-name"] + + def __unicode__(self): + return self.name + + class Book(models.Model): + title = models.CharField(max_length=100) + authors = models.ManyToManyField('Author') + publisher = models.ForeignKey(Publisher) + publication_date = models.DateField() + +Now we need to define a view:: + + # views.py + from django.views.generic import ListView + from books.models import Publisher + + class PublisherList(ListView): + model = Publisher + +Finally hook that view into your urls:: + + # urls.py + from django.conf.urls import patterns, url, include + from books.views import PublisherList + + urlpatterns = patterns('', + url(r'^publishers/$', PublisherList.as_view()), + ) + +That's all the Python code we need to write. We still need to write a template, +however. We could explicitly tell the view which template to use by adding a +``template_name`` attribute to the view, but in the absence of an explicit +template Django will infer one from the object's name. In this case, the +inferred template will be ``"books/publisher_list.html"`` -- the "books" part +comes from the name of the app that defines the model, while the "publisher" +bit is just the lowercased version of the model's name. + +.. note:: + + Thus, when (for example) the + :class:`django.template.loaders.app_directories.Loader` template loader is + enabled in :setting:`TEMPLATE_LOADERS`, a template location could be: + /path/to/project/books/templates/books/publisher_list.html + +.. highlightlang:: html+django + +This template will be rendered against a context containing a variable called +``object_list`` that contains all the publisher objects. A very simple template +might look like the following:: + + {% extends "base.html" %} + + {% block content %} +

        Publishers

        +
          + {% for publisher in object_list %} +
        • {{ publisher.name }}
        • + {% endfor %} +
        + {% endblock %} + +That's really all there is to it. All the cool features of generic views come +from changing the attributes set on the generic view. The +:doc:`generic views reference` documents all the +generic views and their options in detail; the rest of this document will +consider some of the common ways you might customize and extend generic views. + + +Making "friendly" template contexts +----------------------------------- + +.. highlightlang:: python + +You might have noticed that our sample publisher list template stores all the +publishers in a variable named ``object_list``. While this works just fine, it +isn't all that "friendly" to template authors: they have to "just know" that +they're dealing with publishers here. + +Well, if you're dealing with a model object, this is already done for you. When +you are dealing with an object or queryset, Django is able to populate the +context using the lower cased version of the model class' name. This is +provided in addition to the default ``object_list`` entry, but contains exactly +the same data, i.e. ``publisher_list``. + +If the this still isn't a good match, you can manually set the name of the +context variable. The ``context_object_name`` attribute on a generic view +specifies the context variable to use:: + + # views.py + from django.views.generic import ListView + from books.models import Publisher + + class PublisherList(ListView): + model = Publisher + context_object_name = 'my_favourite_publishers' + +Providing a useful ``context_object_name`` is always a good idea. Your +coworkers who design templates will thank you. + + +Adding extra context +-------------------- + +Often you simply need to present some extra information beyond that +provided by the generic view. For example, think of showing a list of +all the books on each publisher detail page. The +:class:`~django.views.generic.detail.DetailView` generic view provides +the publisher to the context, but how do we get additional information +in that template. + +However, there is; you can subclass +:class:`~django.views.generic.detail.DetailView` and provide your own +implementation of the ``get_context_data`` method. The default +implementation of this that comes with +:class:`~django.views.generic.detail.DetailView` simply adds in the +object being displayed to the template, but you can override it to send +more:: + + from django.views.generic import DetailView + from books.models import Publisher, Book + + class PublisherDetail(DetailView): + + model = Publisher + + def get_context_data(self, **kwargs): + # Call the base implementation first to get a context + context = super(PublisherDetail, self).get_context_data(**kwargs) + # Add in a QuerySet of all the books + context['book_list'] = Book.objects.all() + return context + +.. note:: + + Generally, get_context_data will merge the context data of all parent + classes with those of the current class. To preserve this behavior in your + own classes where you want to alter the context, you should be sure to call + get_context_data on the super class. When no two classes try to define the + same key, this will give the expected results. However if any class + attempts to override a key after parent classes have set it (after the call + to super), any children of that class will also need to explictly set it + after super if they want to be sure to override all parents. If you're + having trouble, review the method resolution order of your view. + +.. _generic-views-list-subsets: + +Viewing subsets of objects +-------------------------- + +Now let's take a closer look at the ``model`` argument we've been +using all along. The ``model`` argument, which specifies the database +model that the view will operate upon, is available on all the +generic views that operate on a single object or a collection of +objects. However, the ``model`` argument is not the only way to +specify the objects that the view will operate upon -- you can also +specify the list of objects using the ``queryset`` argument:: + + from django.views.generic import DetailView + from books.models import Publisher, Book + + class PublisherDetail(DetailView): + + context_object_name = 'publisher' + queryset = Publisher.objects.all() + +Specifying ``model = Publisher`` is really just shorthand for saying +``queryset = Publisher.objects.all()``. However, by using ``queryset`` +to define a filtered list of objects you can be more specific about the +objects that will be visible in the view (see :doc:`/topics/db/queries` +for more information about :class:`QuerySet` objects, and see the +:doc:`class-based views reference ` for the +complete details). + +To pick a simple example, we might want to order a list of books by +publication date, with the most recent first:: + + from django.views.generic import ListView + from books.models import Book + + class BookList(ListView): + queryset = Book.objects.order_by('-publication_date') + context_object_name = 'book_list' + +That's a pretty simple example, but it illustrates the idea nicely. Of course, +you'll usually want to do more than just reorder objects. If you want to +present a list of books by a particular publisher, you can use the same +technique:: + + from django.views.generic import ListView + from books.models import Book + + class AcmeBookList(ListView): + + context_object_name = 'book_list' + queryset = Book.objects.filter(publisher__name='Acme Publishing') + template_name = 'books/acme_list.html' + +Notice that along with a filtered ``queryset``, we're also using a custom +template name. If we didn't, the generic view would use the same template as the +"vanilla" object list, which might not be what we want. + +Also notice that this isn't a very elegant way of doing publisher-specific +books. If we want to add another publisher page, we'd need another handful of +lines in the URLconf, and more than a few publishers would get unreasonable. +We'll deal with this problem in the next section. + +.. note:: + + If you get a 404 when requesting ``/books/acme/``, check to ensure you + actually have a Publisher with the name 'ACME Publishing'. Generic + views have an ``allow_empty`` parameter for this case. See the + :doc:`class-based-views reference` for more + details. + + +Dynamic filtering +----------------- + +Another common need is to filter down the objects given in a list page by some +key in the URL. Earlier we hard-coded the publisher's name in the URLconf, but +what if we wanted to write a view that displayed all the books by some arbitrary +publisher? + +Handily, the ``ListView`` has a +:meth:`~django.views.generic.detail.ListView.get_queryset` method we can +override. Previously, it has just been returning the value of the ``queryset`` +attribute, but now we can add more logic. + +The key part to making this work is that when class-based views are called, +various useful things are stored on ``self``; as well as the request +(``self.request``) this includes the positional (``self.args``) and name-based +(``self.kwargs``) arguments captured according to the URLconf. + +Here, we have a URLconf with a single captured group:: + + # urls.py + from books.views import PublisherBookList + + urlpatterns = patterns('', + (r'^books/([\w-]+)/$', PublisherBookList.as_view()), + ) + +Next, we'll write the ``PublisherBookList`` view itself:: + + # views.py + from django.shortcuts import get_object_or_404 + from django.views.generic import ListView + from books.models import Book, Publisher + + class PublisherBookList(ListView): + + template_name = 'books/books_by_publisher.html' + + def get_queryset(self): + self.publisher = get_object_or_404(Publisher, name=self.args[0]) + return Book.objects.filter(publisher=self.publisher) + +As you can see, it's quite easy to add more logic to the queryset selection; +if we wanted, we could use ``self.request.user`` to filter using the current +user, or other more complex logic. + +We can also add the publisher into the context at the same time, so we can +use it in the template:: + + # ... + + def get_context_data(self, **kwargs): + # Call the base implementation first to get a context + context = super(PublisherBookList, self).get_context_data(**kwargs) + # Add in the publisher + context['publisher'] = self.publisher + return context + +.. _generic-views-extra-work: + +Performing extra work +--------------------- + +The last common pattern we'll look at involves doing some extra work before +or after calling the generic view. + +Imagine we had a ``last_accessed`` field on our ``Author`` object that we were +using to keep track of the last time anybody looked at that author:: + + # models.py + + class Author(models.Model): + salutation = models.CharField(max_length=10) + name = models.CharField(max_length=200) + email = models.EmailField() + headshot = models.ImageField(upload_to='/tmp') + last_accessed = models.DateTimeField() + +The generic ``DetailView`` class, of course, wouldn't know anything about this +field, but once again we could easily write a custom view to keep that field +updated. + +First, we'd need to add an author detail bit in the URLconf to point to a +custom view:: + + from books.views import AuthorDetailView + + urlpatterns = patterns('', + #... + url(r'^authors/(?P\d+)/$', AuthorDetailView.as_view(), name='author-detail'), + ) + +Then we'd write our new view -- ``get_object`` is the method that retrieves the +object -- so we simply override it and wrap the call:: + + from django.views.generic import DetailView + from django.shortcuts import get_object_or_404 + from django.utils import timezone + from books.models import Author + + class AuthorDetailView(DetailView): + + queryset = Author.objects.all() + + def get_object(self): + # Call the superclass + object = super(AuthorDetailView, self).get_object() + # Record the last accessed date + object.last_accessed = timezone.now() + object.save() + # Return the object + return object + +.. note:: + + The URLconf here uses the named group ``pk`` - this name is the default + name that ``DetailView`` uses to find the value of the primary key used to + filter the queryset. + + If you want to call the group something else, you can set ``pk_url_kwarg`` + on the view. More details can be found in the reference for + :class:`~django.views.generic.detail.DetailView` diff --git a/docs/topics/class-based-views/generic-editing.txt b/docs/topics/class-based-views/generic-editing.txt new file mode 100644 index 0000000000..23d346a32a --- /dev/null +++ b/docs/topics/class-based-views/generic-editing.txt @@ -0,0 +1,205 @@ +Form handling with class-based views +==================================== + +Form processing generally has 3 paths: + +* Initial GET (blank or prepopulated form) +* POST with invalid data (typically redisplay form with errors) +* POST with valid data (process the data and typically redirect) + +Implementing this yourself often results in a lot of repeated +boilerplate code (see :ref:`Using a form in a +view`). To help avoid this, Django provides a +collection of generic class-based views for form processing. + +Basic Forms +----------- + +Given a simple contact form:: + + # forms.py + from django import forms + + class ContactForm(forms.Form): + name = forms.CharField() + message = forms.CharField(widget=forms.Textarea) + + def send_email(self): + # send email using the self.cleaned_data dictionary + pass + +The view can be constructed using a FormView:: + + # views.py + from myapp.forms import ContactForm + from django.views.generic.edit import FormView + + class ContactView(FormView): + template_name = 'contact.html' + form_class = ContactForm + success_url = '/thanks/' + + def form_valid(self, form): + # This method is called when valid form data has been POSTed. + # It should return an HttpResponse. + form.send_email() + return super(ContactView, self).form_valid(form) + +Notes: + +* FormView inherits + :class:`~django.views.generic.base.TemplateResponseMixin` so + :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` + can be used here +* The default implementation for + :meth:`~django.views.generic.edit.FormView.form_valid` simply + redirects to the :attr:`success_url` + +Model Forms +----------- + +Generic views really shine when working with models. These generic +views will automatically create a :class:`ModelForm`, so long as they +can work out which model class to use: + +* If the :attr:`model` attribute is given, that model class will be used +* If :meth:`get_object()` returns an object, the class of that object + will be used +* If a :attr:`queryset` is given, the model for that queryset will be used + +Model form views provide a :meth:`form_valid()` implementation that +saves the model automatically. You can override this if you have any +special requirements; see below for examples. + +You don't even need to provide a attr:`success_url` for +:class:`~django.views.generic.edit.CreateView` or +:class:`~django.views.generic.edit.UpdateView` - they will use +:meth:`get_absolute_url()` on the model object if available. + +If you want to use a custom :class:`ModelForm` (for instance to add +extra validation) simply set +:attr:`~django.views.generic.edit.FormMixin.form_class` on your view. + +.. note:: + When specifying a custom form class, you must still specify the model, + even though the :attr:`form_class` may be a :class:`ModelForm`. + +First we need to add :meth:`get_absolute_url()` to our :class:`Author` +class: + +.. code-block:: python + + # models.py + from django import models + from django.core.urlresolvers import reverse + + class Author(models.Model): + name = models.CharField(max_length=200) + + def get_absolute_url(self): + return reverse('author-detail', kwargs={'pk': self.pk}) + +Then we can use :class:`CreateView` and friends to do the actual +work. Notice how we're just configuring the generic class-based views +here; we don't have to write any logic ourselves:: + + # views.py + from django.views.generic.edit import CreateView, UpdateView, DeleteView + from django.core.urlresolvers import reverse_lazy + from myapp.models import Author + + class AuthorCreate(CreateView): + model = Author + + class AuthorUpdate(UpdateView): + model = Author + + class AuthorDelete(DeleteView): + model = Author + success_url = reverse_lazy('author-list') + +.. note:: + We have to use :func:`~django.core.urlresolvers.reverse_lazy` here, not + just ``reverse`` as the urls are not loaded when the file is imported. + +Finally, we hook these new views into the URLconf:: + + # urls.py + from django.conf.urls import patterns, url + from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete + + urlpatterns = patterns('', + # ... + url(r'author/add/$', AuthorCreate.as_view(), name='author_add'), + url(r'author/(?P\d+)/$', AuthorUpdate.as_view(), name='author_update'), + url(r'author/(?P\d+)/delete/$', AuthorDelete.as_view(), name='author_delete'), + ) + +.. note:: + + These views inherit :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin` + which uses :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_prefix` + to construct the + :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` + based on the model. + + In this example: + + * :class:`CreateView` and :class:`UpdateView` use ``myapp/author_form.html`` + * :class:`DeleteView` uses ``myapp/author_confirm_delete.html`` + + If you wish to have separate templates for :class:`CreateView` and + :class:1UpdateView`, you can set either :attr:`template_name` or + :attr:`template_name_suffix` on your view class. + +Models and request.user +----------------------- + +To track the user that created an object using a :class:`CreateView`, +you can use a custom :class:`ModelForm` to do this. First, add the +foreign key relation to the model:: + + # models.py + from django import models + from django.contrib.auth import User + + class Author(models.Model): + name = models.CharField(max_length=200) + created_by = models.ForeignKey(User) + + # ... + +Create a custom :class:`ModelForm` in order to exclude the +``created_by`` field and prevent the user from editing it: + +.. code-block:: python + + # forms.py + from django import forms + from myapp.models import Author + + class AuthorForm(forms.ModelForm): + class Meta: + model = Author + exclude = ('created_by',) + +In the view, use the custom :attr:`form_class` and override +:meth:`form_valid()` to add the user:: + + # views.py + from django.views.generic.edit import CreateView + from myapp.models import Author + from myapp.forms import AuthorForm + + class AuthorCreate(CreateView): + form_class = AuthorForm + model = Author + + def form_valid(self, form): + form.instance.created_by = self.request.user + return super(AuthorCreate, self).form_valid(form) + +Note that you'll need to :ref:`decorate this +view` using +:func:`~django.contrib.auth.decorators.login_required`, or +alternatively handle unauthorised users in the :meth:`form_valid()`. diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt new file mode 100644 index 0000000000..c2900ecc01 --- /dev/null +++ b/docs/topics/class-based-views/index.txt @@ -0,0 +1,233 @@ +================= +Class-based views +================= + +.. versionadded:: 1.3 + +A view is a callable which takes a request and returns a +response. This can be more than just a function, and Django provides +an example of some classes which can be used as views. These allow you +to structure your views and reuse code by harnessing inheritance and +mixins. There are also some generic views for simple tasks which we'll +get to later, but you may want to design your own structure of +reusable views which suits your use case. For full details, see the +:doc:`class-based views reference +documentation`. + +.. toctree:: + :maxdepth: 1 + + generic-display + generic-editing + mixins + +Basic examples +============== + +Django provides base view classes which will suit a wide range of applications. +All views inherit from the :class:`~django.views.generic.base.View` class, which +handles linking the view in to the URLs, HTTP method dispatching and other +simple features. :class:`~django.views.generic.base.RedirectView` is for a simple HTTP +redirect, and :class:`~django.views.generic.base.TemplateView` extends the base class +to make it also render a template. + + +Simple usage +============ + +Class-based generic views (and any class-based views that inherit from +the base classes Django provides) can be configured in two +ways: subclassing, or passing in arguments directly in the URLconf. + +When you subclass a class-based view, you can override attributes +(such as the ``template_name``) or methods (such as ``get_context_data``) +in your subclass to provide new values or methods. Consider, for example, +a view that just displays one template, ``about.html``. Django has a +generic view to do this - :class:`~django.views.generic.base.TemplateView` - +so we can just subclass it, and override the template name:: + + # some_app/views.py + from django.views.generic import TemplateView + + class AboutView(TemplateView): + template_name = "about.html" + +Then, we just need to add this new view into our URLconf. As the class-based +views themselves are classes, we point the URL to the ``as_view`` class method +instead, which is the entry point for class-based views:: + + # urls.py + from django.conf.urls import patterns, url, include + from some_app.views import AboutView + + urlpatterns = patterns('', + (r'^about/', AboutView.as_view()), + ) + +Alternatively, if you're only changing a few simple attributes on a +class-based view, you can simply pass the new attributes into the ``as_view`` +method call itself:: + + from django.conf.urls import patterns, url, include + from django.views.generic import TemplateView + + urlpatterns = patterns('', + (r'^about/', TemplateView.as_view(template_name="about.html")), + ) + +A similar overriding pattern can be used for the ``url`` attribute on +:class:`~django.views.generic.base.RedirectView`. + +.. _jsonresponsemixin-example: + +More than just HTML +------------------- + +Where class based views shine is when you want to do the same thing many times. +Suppose you're writing an API, and every view should return JSON instead of +rendered HTML. + +We can create a mixin class to use in all of our views, handling the +conversion to JSON once. + +For example, a simple JSON mixin might look something like this:: + + import json + from django.http import HttpResponse + + class JSONResponseMixin(object): + """ + A mixin that can be used to render a JSON response. + """ + response_class = HttpResponse + + def render_to_response(self, context, **response_kwargs): + """ + Returns a JSON response, transforming 'context' to make the payload. + """ + response_kwargs['content_type'] = 'application/json' + return self.response_class( + self.convert_context_to_json(context), + **response_kwargs + ) + + def convert_context_to_json(self, context): + "Convert the context dictionary into a JSON object" + # Note: This is *EXTREMELY* naive; in reality, you'll need + # to do much more complex handling to ensure that arbitrary + # objects -- such as Django model instances or querysets + # -- can be serialized as JSON. + return json.dumps(context) + +Now we mix this into the base TemplateView:: + + from django.views.generic import TemplateView + + class JSONView(JSONResponseMixin, TemplateView): + pass + +Equally we could use our mixin with one of the generic views. We can make our +own version of :class:`~django.views.generic.detail.DetailView` by mixing +:class:`JSONResponseMixin` with the +:class:`~django.views.generic.detail.BaseDetailView` -- (the +:class:`~django.views.generic.detail.DetailView` before template +rendering behavior has been mixed in):: + + class JSONDetailView(JSONResponseMixin, BaseDetailView): + pass + +This view can then be deployed in the same way as any other +:class:`~django.views.generic.detail.DetailView`, with exactly the +same behavior -- except for the format of the response. + +If you want to be really adventurous, you could even mix a +:class:`~django.views.generic.detail.DetailView` subclass that is able +to return *both* HTML and JSON content, depending on some property of +the HTTP request, such as a query argument or a HTTP header. Just mix +in both the :class:`JSONResponseMixin` and a +:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`, +and override the implementation of :func:`render_to_response()` to defer +to the appropriate subclass depending on the type of response that the user +requested:: + + class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView): + def render_to_response(self, context): + # Look for a 'format=json' GET argument + if self.request.GET.get('format','html') == 'json': + return JSONResponseMixin.render_to_response(self, context) + else: + return SingleObjectTemplateResponseMixin.render_to_response(self, context) + +Because of the way that Python resolves method overloading, the local +``render_to_response()`` implementation will override the versions provided by +:class:`JSONResponseMixin` and +:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`. + +For more information on how to use the built in generic views, consult the next +topic on :doc:`generic class based views`. + +Decorating class-based views +============================ + +.. highlightlang:: python + +The extension of class-based views isn't limited to using mixins. You +can use also use decorators. + +Decorating in URLconf +--------------------- + +The simplest way of decorating class-based views is to decorate the +result of the :meth:`~django.views.generic.base.View.as_view` method. +The easiest place to do this is in the URLconf where you deploy your +view:: + + from django.contrib.auth.decorators import login_required, permission_required + from django.views.generic import TemplateView + + from .views import VoteView + + urlpatterns = patterns('', + (r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))), + (r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())), + ) + +This approach applies the decorator on a per-instance basis. If you +want every instance of a view to be decorated, you need to take a +different approach. + +.. _decorating-class-based-views: + +Decorating the class +-------------------- + +To decorate every instance of a class-based view, you need to decorate +the class definition itself. To do this you apply the decorator to the +:meth:`~django.views.generic.base.View.dispatch` method of the class. + +A method on a class isn't quite the same as a standalone function, so +you can't just apply a function decorator to the method -- you need to +transform it into a method decorator first. The ``method_decorator`` +decorator transforms a function decorator into a method decorator so +that it can be used on an instance method. For example:: + + from django.contrib.auth.decorators import login_required + from django.utils.decorators import method_decorator + from django.views.generic import TemplateView + + class ProtectedView(TemplateView): + template_name = 'secret.html' + + @method_decorator(login_required) + def dispatch(self, *args, **kwargs): + return super(ProtectedView, self).dispatch(*args, **kwargs) + +In this example, every instance of ``ProtectedView`` will have +login protection. + +.. note:: + + ``method_decorator`` passes ``*args`` and ``**kwargs`` + as parameters to the decorated method on the class. If your method + does not accept a compatible set of parameters it will raise a + ``TypeError`` exception. diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt new file mode 100644 index 0000000000..0c19d60e35 --- /dev/null +++ b/docs/topics/class-based-views/mixins.txt @@ -0,0 +1,605 @@ +=================================== +Using mixins with class-based views +=================================== + +.. versionadded:: 1.3 + +.. caution:: + + This is an advanced topic. A working knowledge of :doc:`Django's + class-based views` is advised before exploring these + techniques. + +Django's built-in class-based views provide a lot of functionality, +but some of it you may want to use separately. For instance, you may +want to write a view that renders a template to make the HTTP +response, but you can't use +:class:`~django.views.generic.base.TemplateView`; perhaps you need to +render a template only on `POST`, with `GET` doing something else +entirely. While you could use +:class:`~django.template.response.TemplateResponse` directly, this +will likely result in duplicate code. + +For this reason, Django also provides a number of mixins that provide +more discrete functionality. Template rendering, for instance, is +encapsulated in the +:class:`~django.views.generic.base.TemplateResponseMixin`. The Django +reference documentation contains :doc:`full documentation of all the +mixins`. + +Context and template responses +============================== + +Two central mixins are provided that help in providing a consistent +interface to working with templates in class-based views. + +:class:`~django.views.generic.base.TemplateResponseMixin` + Every built in view which returns a + :class:`~django.template.response.TemplateResponse` will call the + :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + method that :class:`TemplateResponseMixin` provides. Most of the time this + will be called for you (for instance, it is called by the ``get()`` method + implemented by both :class:`~django.views.generic.base.TemplateView` and + :class:`~django.views.generic.base.DetailView`); similarly, it's unlikely + that you'll need to override it, although if you want your response to + return something not rendered via a Django template then you'll want to do + it. For an example of this, see the :ref:`JSONResponseMixin example + `. + + ``render_to_response`` itself calls + :meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`, + which by default will just look up + :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` on + the class-based view; two other mixins + (:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin` + and + :class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`) + override this to provide more flexible defaults when dealing with actual + objects. + +.. versionadded:: 1.5 + +:class:`~django.views.generic.base.ContextMixin` + Every built in view which needs context data, such as for rendering a + template (including :class:`TemplateResponseMixin` above), should call + :meth:`~django.views.generic.base.ContextMixin.get_context_data` passing + any data they want to ensure is in there as keyword arguments. + ``get_context_data`` returns a dictionary; in :class:`ContextMixin` it + simply returns its keyword arguments, but it is common to override this to + add more members to the dictionary. + +Building up Django's generic class-based views +=============================================== + +Let's look at how two of Django's generic class-based views are built +out of mixins providing discrete functionality. We'll consider +:class:`~django.views.generic.detail.DetailView`, which renders a +"detail" view of an object, and +:class:`~django.views.generic.list.ListView`, which will render a list +of objects, typically from a queryset, and optionally paginate +them. This will introduce us to four mixins which between them provide +useful functionality when working with either a single Django object, +or multiple objects. + +There are also mixins involved in the generic edit views +(:class:`~django.views.generic.edit.FormView`, and the model-specific +views :class:`~django.views.generic.edit.CreateView`, +:class:`~django.views.generic.edit.UpdateView` and +:class:`~django.views.generic.edit.DeleteView`), and in the +date-based generic views. These are +covered in the :doc:`mixin reference +documentation`. + +DetailView: working with a single Django object +----------------------------------------------- + +To show the detail of an object, we basically need to do two things: +we need to look up the object and then we need to make a +:class:`TemplateResponse` with a suitable template, and that object as +context. + +To get the object, :class:`~django.views.generic.detail.DetailView` +relies on :class:`~django.views.generic.detail.SingleObjectMixin`, +which provides a +:meth:`~django.views.generic.detail.SingleObjectMixin.get_object` +method that figures out the object based on the URL of the request (it +looks for ``pk`` and ``slug`` keyword arguments as declared in the +URLConf, and looks the object up either from the +:attr:`~django.views.generic.detail.SingleObjectMixin.model` attribute +on the view, or the +:attr:`~django.views.generic.detail.SingleObjectMixin.queryset` +attribute if that's provided). :class:`SingleObjectMixin` also overrides +:meth:`~django.views.generic.base.ContextMixin.get_context_data`, +which is used across all Django's built in class-based views to supply +context data for template renders. + +To then make a :class:`TemplateResponse`, :class:`DetailView` uses +:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`, +which extends +:class:`~django.views.generic.base.TemplateResponseMixin`, overriding +:meth:`get_template_names()` as discussed above. It actually provides +a fairly sophisticated set of options, but the main one that most +people are going to use is +``/_detail.html``. The ``_detail`` part can be +changed by setting +:attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` +on a subclass to something else. (For instance, the :doc:`generic edit +views` use ``_form`` for create and update views, and +``_confirm_delete`` for delete views.) + +ListView: working with many Django objects +------------------------------------------ + +Lists of objects follow roughly the same pattern: we need a (possibly +paginated) list of objects, typically a :class:`QuerySet`, and then we need +to make a :class:`TemplateResponse` with a suitable template using +that list of objects. + +To get the objects, :class:`~django.views.generic.list.ListView` uses +:class:`~django.views.generic.list.MultipleObjectMixin`, which +provides both +:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset` +and +:meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset`. Unlike +with :class:`SingleObjectMixin`, there's no need to key off parts of +the URL to figure out the queryset to work with, so the default just +uses the +:attr:`~django.views.generic.list.MultipleObjectMixin.queryset` or +:attr:`~django.views.generic.list.MultipleObjectMixin.model` attribute +on the view class. A common reason to override +:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset` +here would be to dynamically vary the objects, such as depending on +the current user or to exclude posts in the future for a blog. + +:class:`MultipleObjectMixin` also overrides +:meth:`~django.views.generic.base.ContextMixin.get_context_data` to +include appropriate context variables for pagination (providing +dummies if pagination is disabled). It relies on ``object_list`` being +passed in as a keyword argument, which :class:`ListView` arranges for +it. + +To make a :class:`TemplateResponse`, :class:`ListView` then uses +:class:`~django.views.generic.list.MultipleObjectTemplateResponseMixin`; +as with :class:`SingleObjectTemplateResponseMixin` above, this +overrides :meth:`get_template_names()` to provide :meth:`a range of +options +<~django.views.generic.list.MultipleObjectTempalteResponseMixin>`, +with the most commonly-used being +``/_list.html``, with the ``_list`` part again +being taken from the +:attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` +attribute. (The date based generic views use suffixes such as ``_archive``, +``_archive_year`` and so on to use different templates for the various +specialised date-based list views.) + +Using Django's class-based view mixins +====================================== + +Now we've seen how Django's generic class-based views use the provided +mixins, let's look at other ways we can combine them. Of course we're +still going to be combining them with either built-in class-based +views, or other generic class-based views, but there are a range of +rarer problems you can solve than are provided for by Django out of +the box. + +.. warning:: + + Not all mixins can be used together, and not all generic class + based views can be used with all other mixins. Here we present a + few examples that do work; if you want to bring together other + functionality then you'll have to consider interactions between + attributes and methods that overlap between the different classes + you're using, and how `method resolution order`_ will affect which + versions of the methods will be called in what order. + + The reference documentation for Django's :doc:`class-based + views` and :doc:`class-based view + mixins` will help you in + understanding which attributes and methods are likely to cause + conflict between different classes and mixins. + + If in doubt, it's often better to back off and base your work on + :class:`View` or :class:`TemplateView`, perhaps with + :class:`SimpleObjectMixin` and + :class:`MultipleObjectMixin`. Although you will probably end up + writing more code, it is more likely to be clearly understandable + to someone else coming to it later, and with fewer interactions to + worry about you will save yourself some thinking. (Of course, you + can always dip into Django's implementation of the generic class + based views for inspiration on how to tackle problems.) + +.. _method resolution order: http://www.python.org/download/releases/2.3/mro/ + + +Using SingleObjectMixin with View +--------------------------------- + +If we want to write a simple class-based view that responds only to +``POST``, we'll subclass :class:`~django.views.generic.base.View` and +write a ``post()`` method in the subclass. However if we want our +processing to work on a particular object, identified from the URL, +we'll want the functionality provided by +:class:`~django.views.generic.detail.SingleObjectMixin`. + +We'll demonstrate this with the publisher modelling we used in the +:doc:`generic class-based views +introduction`. + +.. code-block:: python + + # views.py + from django.http import HttpResponseForbidden, HttpResponseRedirect + from django.core.urlresolvers import reverse + from django.views.generic import View + from django.views.generic.detail import SingleObjectMixin + from books.models import Author + + class RecordInterest(View, SingleObjectMixin): + """Records the current user's interest in an author.""" + model = Author + + def post(self, request, *args, **kwargs): + if not request.user.is_authenticated(): + return HttpResponseForbidden() + + # Look up the author we're interested in. + self.object = self.get_object() + # Actually record interest somehow here! + + return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk})) + +In practice you'd probably want to record the interest in a key-value +store rather than in a relational database, so we've left that bit +out. The only bit of the view that needs to worry about using +:class:`SingleObjectMixin` is where we want to look up the author +we're interested in, which it just does with a simple call to +``self.get_object()``. Everything else is taken care of for us by the +mixin. + +We can hook this into our URLs easily enough: + +.. code-block:: python + + # urls.py + from books.views import RecordInterest + + urlpatterns = patterns('', + #... + url(r'^author/(?P\d+)/interest/$', RecordInterest.as_view(), name='author-interest'), + ) + +Note the ``pk`` named group, which +:meth:`~django.views.generic.detail.SingleObjectMixin.get_object` uses +to look up the :class:`Author` instance. You could also use a slug, or +any of the other features of :class:`SingleObjectMixin`. + +Using SingleObjectMixin with ListView +------------------------------------- + +:class:`~django.views.generic.list.ListView` provides built-in +pagination, but you might want to paginate a list of objects that are +all linked (by a foreign key) to another object. In our publishing +example, you might want to paginate through all the books by a +particular publisher. + +One way to do this is to combine :class:`ListView` with +:class:`SingleObjectMixin`, so that the queryset for the paginated +list of books can hang off the publisher found as the single +object. In order to do this, we need to have two different querysets: + +**Publisher queryset for use in get_object** + We'll set that up directly when we call :meth:`get_object()`. + +**Book queryset for use by ListView** + We'll figure that out ourselves in :meth:`get_queryset()` so we + can take into account the Publisher we're looking at. + +.. highlightlang:: python + +.. note:: + + We have to think carefully about :meth:`get_context_data()`. + Since both :class:`SingleObjectMixin` and :class:`ListView` will + put things in the context data under the value of + :attr:`context_object_name` if it's set, we'll instead explictly + ensure the Publisher is in the context data. :class:`ListView` + will add in the suitable ``page_obj`` and ``paginator`` for us + providing we remember to call ``super()``. + +Now we can write a new :class:`PublisherDetail`:: + + from django.views.generic import ListView + from django.views.generic.detail import SingleObjectMixin + from books.models import Publisher + + class PublisherDetail(SingleObjectMixin, ListView): + paginate_by = 2 + template_name = "books/publisher_detail.html" + + def get_context_data(self, **kwargs): + kwargs['publisher'] = self.object + return super(PublisherDetail, self).get_context_data(**kwargs) + + def get_queryset(self): + self.object = self.get_object(Publisher.objects.all()) + return self.object.book_set.all() + +Notice how we set ``self.object`` within :meth:`get_queryset` so we +can use it again later in :meth:`get_context_data`. If you don't set +:attr:`template_name`, the template will default to the normal +:class:`ListView` choice, which in this case would be +``"books/book_list.html"`` because it's a list of books; +:class:`ListView` knows nothing about :class:`SingleObjectMixin`, so +it doesn't have any clue this view is anything to do with a Publisher. + +.. highlightlang:: html+django + +The ``paginate_by`` is deliberately small in the example so you don't +have to create lots of books to see the pagination working! Here's the +template you'd want to use:: + + {% extends "base.html" %} + + {% block content %} +

        Publisher {{ publisher.name }}

        + +
          + {% for book in page_obj %} +
        1. {{ book.title }}
        2. + {% endfor %} +
        + + + {% endblock %} + +Avoid anything more complex +=========================== + +Generally you can use +:class:`~django.views.generic.base.TemplateResponseMixin` and +:class:`~django.views.generic.detail.SingleObjectMixin` when you need +their functionality. As shown above, with a bit of care you can even +combine :class:`SingleObjectMixin` with +:class:`~django.views.generic.list.ListView`. However things get +increasingly complex as you try to do so, and a good rule of thumb is: + +.. hint:: + + Each of your views should use only mixins or views from one of the + groups of generic class-based views: :doc:`detail, + list`, :doc:`editing` and + date. For example it's fine to combine + :class:`TemplateView` (built in view) with + :class:`MultipleObjectMixin` (generic list), but you're likely to + have problems combining :class:`SingleObjectMixin` (generic + detail) with :class:`MultipleObjectMixin` (generic list). + +To show what happens when you try to get more sophisticated, we show +an example that sacrifices readability and maintainability when there +is a simpler solution. First, let's look at a naive attempt to combine +:class:`~django.views.generic.detail.DetailView` with +:class:`~django.views.generic.edit.FormMixin` to enable use to +``POST`` a Django :class:`Form` to the same URL as we're displaying an +object using :class:`DetailView`. + +Using FormMixin with DetailView +------------------------------- + +Think back to our earlier example of using :class:`View` and +:class:`SingleObjectMixin` together. We were recording a user's +interest in a particular author; say now that we want to let them +leave a message saying why they like them. Again, let's assume we're +not going to store this in a relational database but instead in +something more esoteric that we won't worry about here. + +At this point it's natural to reach for a :class:`Form` to encapsulate +the information sent from the user's browser to Django. Say also that +we're heavily invested in `REST`_, so we want to use the same URL for +displaying the author as for capturing the message from the +user. Let's rewrite our :class:`AuthorDetailView` to do that. + +.. _REST: http://en.wikipedia.org/wiki/Representational_state_transfer + +We'll keep the ``GET`` handling from :class:`DetailView`, although +we'll have to add a :class:`Form` into the context data so we can +render it in the template. We'll also want to pull in form processing +from :class:`~django.views.generic.edit.FormMixin`, and write a bit of +code so that on ``POST`` the form gets called appropriately. + +.. note:: + + We use :class:`FormMixin` and implement :meth:`post()` ourselves + rather than try to mix :class:`DetailView` with :class:`FormView` + (which provides a suitable :meth:`post()` already) because both of + the views implement :meth:`get()`, and things would get much more + confusing. + +Our new :class:`AuthorDetail` looks like this: + +.. code-block:: python + + # CAUTION: you almost certainly do not want to do this. + # It is provided as part of a discussion of problems you can + # run into when combining different generic class-based view + # functionality that is not designed to be used together. + + from django import forms + from django.http import HttpResponseForbidden + from django.core.urlresolvers import reverse + from django.views.generic import DetailView + from django.views.generic.edit import FormMixin + + class AuthorInterestForm(forms.Form): + message = forms.CharField() + + class AuthorDetail(DetailView, FormMixin): + model = Author + form_class = AuthorInterestForm + + def get_success_url(self): + return reverse( + 'author-detail', + kwargs = {'pk': self.object.pk}, + ) + + def get_context_data(self, **kwargs): + form_class = self.get_form_class() + form = self.get_form(form_class) + context = { + 'form': form + } + context.update(kwargs) + return super(AuthorDetail, self).get_context_data(**context) + + def post(self, request, *args, **kwargs): + form_class = self.get_form_class() + form = self.get_form(form_class) + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + + def form_valid(self, form): + if not self.request.user.is_authenticated(): + return HttpResponseForbidden() + self.object = self.get_object() + # record the interest using the message in form.cleaned_data + return super(AuthorDetail, self).form_valid(form) + +:meth:`get_success_url()` is just providing somewhere to redirect to, +which gets used in the default implementation of +:meth:`form_valid()`. We have to provide our own :meth:`post()` as +noted earlier, and override :meth:`get_context_data()` to make the +:class:`Form` available in the context data. + +A better solution +----------------- + +It should be obvious that the number of subtle interactions between +:class:`FormMixin` and :class:`DetailView` is already testing our +ability to manage things. It's unlikely you'd want to write this kind +of class yourself. + +In this case, it would be fairly easy to just write the :meth:`post()` +method yourself, keeping :class:`DetailView` as the only generic +functionality, although writing :class:`Form` handling code involves a +lot of duplication. + +Alternatively, it would still be easier than the above approach to +have a separate view for processing the form, which could use +:class:`~django.views.generic.edit.FormView` distinct from +:class:`DetailView` without concerns. + +An alternative better solution +------------------------------ + +What we're really trying to do here is to use two different class +based views from the same URL. So why not do just that? We have a very +clear division here: ``GET`` requests should get the +:class:`DetailView` (with the :class:`Form` added to the context +data), and ``POST`` requests should get the :class:`FormView`. Let's +set up those views first. + +The :class:`AuthorDisplay` view is almost the same as :ref:`when we +first introduced AuthorDetail`; we have to +write our own :meth:`get_context_data()` to make the +:class:`AuthorInterestForm` available to the template. We'll skip the +:meth:`get_object()` override from before for clarity. + +.. code-block:: python + + from django.views.generic import DetailView + from django import forms + from books.models import Author + + class AuthorInterestForm(forms.Form): + message = forms.CharField() + + class AuthorDisplay(DetailView): + + queryset = Author.objects.all() + + def get_context_data(self, **kwargs): + context = { + 'form': AuthorInterestForm(), + } + context.update(kwargs) + return super(AuthorDisplay, self).get_context_data(**context) + +Then the :class:`AuthorInterest` is a simple :class:`FormView`, but we +have to bring in :class:`SingleObjectMixin` so we can find the author +we're talking about, and we have to remember to set +:attr:`template_name` to ensure that form errors will render the same +template as :class:`AuthorDisplay` is using on ``GET``. + +.. code-block:: python + + from django.views.generic import FormView + from django.views.generic.detail import SingleObjectMixin + + class AuthorInterest(FormView, SingleObjectMixin): + template_name = 'books/author_detail.html' + form_class = AuthorInterestForm + model = Author + + def get_context_data(self, **kwargs): + context = { + 'object': self.get_object(), + } + return super(AuthorInterest, self).get_context_data(**context) + + def get_success_url(self): + return reverse( + 'author-detail', + kwargs = {'pk': self.object.pk}, + ) + + def form_valid(self, form): + if not self.request.user.is_authenticated(): + return HttpResponseForbidden() + self.object = self.get_object() + # record the interest using the message in form.cleaned_data + return super(AuthorInterest, self).form_valid(form) + +Finally we bring this together in a new :class:`AuthorDetail` view. We +already know that calling :meth:`as_view()` on a class-based view +gives us something that behaves exactly like a function based view, so +we can do that at the point we choose between the two subviews. + +You can of course pass through keyword arguments to :meth:`as_view()` +in the same way you would in your URLconf, such as if you wanted the +:class:`AuthorInterest` behaviour to also appear at another URL but +using a different template. + +.. code-block:: python + + from django.views.generic import View + + class AuthorDetail(View): + + def get(self, request, *args, **kwargs): + view = AuthorDisplay.as_view() + return view(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + view = AuthorInterest.as_view() + return view(request, *args, **kwargs) + +This approach can also be used with any other generic class-based +views or your own class-based views inheriting directly from +:class:`View` or :class:`TemplateView`, as it keeps the different +views as separate as possible. diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 9d2ba82c34..ce15dc9535 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -156,11 +156,11 @@ ones: A choices list looks like this:: YEAR_IN_SCHOOL_CHOICES = ( - (u'FR', u'Freshman'), - (u'SO', u'Sophomore'), - (u'JR', u'Junior'), - (u'SR', u'Senior'), - (u'GR', u'Graduate'), + ('FR', 'Freshman'), + ('SO', 'Sophomore'), + ('JR', 'Junior'), + ('SR', 'Senior'), + ('GR', 'Graduate'), ) The first element in each tuple is the value that will be stored in the @@ -172,21 +172,22 @@ ones: from django.db import models class Person(models.Model): - GENDER_CHOICES = ( - (u'M', u'Male'), - (u'F', u'Female'), + SHIRT_SIZES = ( + ('S', 'Small'), + ('M', 'Medium'), + ('L', 'Large'), ) name = models.CharField(max_length=60) - gender = models.CharField(max_length=2, choices=GENDER_CHOICES) + shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES) :: - >>> p = Person(name="Fred Flintstone", gender="M") + >>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() - >>> p.gender - u'M' - >>> p.get_gender_display() - u'Male' + >>> p.shirt_size + u'L' + >>> p.get_shirt_size_display() + u'Large' :attr:`~Field.default` The default value for the field. This can be a value or a callable @@ -383,7 +384,8 @@ Extra fields on many-to-many relationships ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When you're only dealing with simple many-to-many relationships such as -mixing and matching pizzas and toppings, a standard :class:`~django.db.models.ManyToManyField` is all you need. However, sometimes +mixing and matching pizzas and toppings, a standard +:class:`~django.db.models.ManyToManyField` is all you need. However, sometimes you may need to associate data with the relationship between two models. For example, consider the case of an application tracking the musical groups @@ -880,8 +882,6 @@ field. This would normally cause a problem in abstract base classes, since the fields on this class are included into each of the child classes, with exactly the same values for the attributes (including :attr:`~django.db.models.ForeignKey.related_name`) each time. -.. versionchanged:: 1.2 - To work around this problem, when you are using :attr:`~django.db.models.ForeignKey.related_name` in an abstract base class (only), part of the name should contain ``'%(app_label)s'`` and ``'%(class)s'``. diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt index ee542b9196..86e785a19d 100644 --- a/docs/topics/db/multi-db.txt +++ b/docs/topics/db/multi-db.txt @@ -2,8 +2,6 @@ Multiple databases ================== -.. versionadded:: 1.2 - This topic guide describes Django's support for interacting with multiple databases. Most of the rest of Django's documentation assumes you are interacting with a single database. If you want to interact diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 573e1fd0aa..772792d39d 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -230,7 +230,7 @@ It is optimal because: #. Use of :ttag:`with` means that we store ``user.emails.all`` in a variable for later use, allowing its cache to be re-used. -#. The line ``{% if emails %}`` causes ``QuerySet.__nonzero__()`` to be called, +#. The line ``{% if emails %}`` causes ``QuerySet.__bool__()`` to be called, which causes the ``user.emails.all()`` query to be run on the database, and at the least the first line to be turned into an ORM object. If there aren't any results, it will return False, otherwise True. diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index 2ac47170aa..19daffd464 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -18,8 +18,6 @@ __ `executing custom SQL directly`_ Performing raw queries ====================== -.. versionadded:: 1.2 - The ``raw()`` manager method can be used to perform raw SQL queries that return model instances: diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 76b65b99e0..9928354664 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -54,6 +54,13 @@ The various cache middlewares are an exception: Even when using database caching, Django's cache backend uses its own database cursor (which is mapped to its own database connection internally). +.. note:: + + The ``TransactionMiddleware`` only affects the database aliased + as "default" within your :setting:`DATABASES` setting. If you are using + multiple databases and want transaction control over databases other than + "default", you will need to write your own transaction middleware. + .. _transaction-management-functions: Controlling transaction management in views diff --git a/docs/topics/email.txt b/docs/topics/email.txt index 2069773f8a..0cc476e02c 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -192,7 +192,7 @@ from the request's POST data, sends that to admin@example.com and redirects to # to get proper validation errors. return HttpResponse('Make sure all fields are entered and valid.') -.. _Header injection: http://www.nyphp.org/phundamentals/email_header_injection.php +.. _Header injection: http://www.nyphp.org/phundamentals/8_Preventing-Email-Header-Injection .. _emailmessage-and-smtpconnection: @@ -365,8 +365,6 @@ subtype. For example:: Email backends ============== -.. versionadded:: 1.2 - The actual sending of an email is handled by the email backend. The email backend class has the following methods: diff --git a/docs/topics/files.txt b/docs/topics/files.txt index 9ab8d5c496..c9b4327941 100644 --- a/docs/topics/files.txt +++ b/docs/topics/files.txt @@ -75,6 +75,29 @@ using a Python built-in ``file`` object:: Now you can use any of the documented attributes and methods of the :class:`~django.core.files.File` class. +Be aware that files created in this way are not automatically closed. +The following approach may be used to close files automatically:: + + >>> from django.core.files import File + + # Create a Python file object using open() and the with statement + >>> with open('/tmp/hello.world', 'w') as f: + >>> myfile = File(f) + >>> for line in myfile: + >>> print line + >>> myfile.closed + True + >>> f.closed + True + +Closing files is especially important when accessing file fields in a loop +over a large number of objects:: If files are not manually closed after +accessing them, the risk of running out of file descriptors may arise. This +may lead to the following error: + + IOError: [Errno 24] Too many open files + + File storage ============ diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index 42b9d1f490..2a83172e17 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -102,15 +102,12 @@ limit the maximum number of empty forms the formset will display:: -.. versionchanged:: 1.2 - If the value of ``max_num`` is greater than the number of existing objects, up to ``extra`` additional blank forms will be added to the formset, so long as the total number of forms does not exceed ``max_num``. A ``max_num`` value of ``None`` (the default) puts no limit on the number of -forms displayed. Please note that the default value of ``max_num`` was changed -from ``0`` to ``None`` in version 1.2 to allow ``0`` as a valid value. +forms displayed. Formset validation ------------------ @@ -210,8 +207,6 @@ pre-filled, and is also used to determine how many forms are required. You will probably never need to override either of these methods, so please be sure you understand what they do before doing so. -.. versionadded:: 1.2 - ``empty_form`` ~~~~~~~~~~~~~~ diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index 18e55f5eb6..4693de6c7e 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -82,6 +82,8 @@ If your form is going to be used to directly add or edit a Django model, you can use a :doc:`ModelForm ` to avoid duplicating your model description. +.. _using-a-form-in-a-view: + Using a form in a view ---------------------- @@ -89,6 +91,9 @@ The standard pattern for processing a form in a view looks like this: .. code-block:: python + from django.shortcuts import render + from django.http import HttpResponseRedirect + def contact(request): if request.method == 'POST': # If the form has been submitted... form = ContactForm(request.POST) # A form bound to the POST data @@ -99,53 +104,63 @@ The standard pattern for processing a form in a view looks like this: else: form = ContactForm() # An unbound form - return render_to_response('contact.html', { + return render(request, 'contact.html', { 'form': form, }) -There are three code paths here: +There are three possible code paths here: -1. If the form has not been submitted, an unbound instance of ContactForm is - created and passed to the template. -2. If the form has been submitted, a bound instance of the form is created - using ``request.POST``. If the submitted data is valid, it is processed - and the user is re-directed to a "thanks" page. -3. If the form has been submitted but is invalid, the bound form instance is - passed on to the template. ++------------------+---------------+-----------------------------------------+ +| Form submitted? | Data? | What occurs | ++==================+===============+=========================================+ +| Unsubmitted | None yet | Template gets passed unbound instance | +| | | of ContactForm. | ++------------------+---------------+-----------------------------------------+ +| Submitted | Invalid data | Template gets passed bound instance | +| | | of ContactForm. | ++------------------+---------------+-----------------------------------------+ +| Submitted | Valid data | Valid data is processed. Redirect to a | +| | | "thanks" page. | ++------------------+---------------+-----------------------------------------+ -The distinction between **bound** and **unbound** forms is important. An unbound -form does not have any data associated with it; when rendered to the user, it -will be empty or will contain default values. A bound form does have submitted -data, and hence can be used to tell if that data is valid. If an invalid bound -form is rendered it can include inline error messages telling the user where -they went wrong. +The distinction between :ref:`ref-forms-api-bound-unbound` is important: -See :ref:`ref-forms-api-bound-unbound` for further information on the -differences between bound and unbound forms. +* An unbound form has no data associated with it. When rendered to the user, + it will be empty or will contain default values. + +* A bound form has submitted data, and hence can be used to tell if that data + is valid. If an invalid bound form is rendered, it can include inline error + messages telling the user what data to correct. Handling file uploads with a form --------------------------------- -To see how to handle file uploads with your form see -:ref:`binding-uploaded-files` for more information. +To see how to handle file uploads with your form, see +:ref:`binding-uploaded-files`. Processing the data from a form ------------------------------- -Once ``is_valid()`` returns ``True``, you can process the form submission safe -in the knowledge that it conforms to the validation rules defined by your form. -While you could access ``request.POST`` directly at this point, it is better to -access ``form.cleaned_data``. This data has not only been validated but will -also be converted in to the relevant Python types for you. In the above example, -``cc_myself`` will be a boolean value. Likewise, fields such as ``IntegerField`` -and ``FloatField`` convert values to a Python int and float respectively. Note -that read-only fields are not available in ``form.cleaned_data`` (and setting -a value in a custom ``clean()`` method won't have any effect) because these +Once ``is_valid()`` returns ``True``, the successfully validated form data +will be in the ``form.cleaned_data`` dictionary. This data will have been +converted nicely into Python types for you. + +.. note:: + + You can still access the unvalidated data directly from ``request.POST`` at + this point, but the validated data is better. + +In the above example, ``cc_myself`` will be a boolean value. Likewise, fields +such as ``IntegerField`` and ``FloatField`` convert values to a Python int and +float respectively. + +Read-only fields are not available in ``form.cleaned_data`` (and setting +a value in a custom ``clean()`` method won't have any effect). These fields are displayed as text rather than as input elements, and thus are not posted back to the server. -Extending the above example, here's how the form data could be processed: +Extending the earlier example, here's how the form data could be processed: .. code-block:: python @@ -163,7 +178,9 @@ Extending the above example, here's how the form data could be processed: send_mail(subject, message, sender, recipients) return HttpResponseRedirect('/thanks/') # Redirect after POST -For more on sending email from Django, see :doc:`/topics/email`. +.. tip:: + + For more on sending email from Django, see :doc:`/topics/email`. Displaying a form using a template ---------------------------------- @@ -294,7 +311,7 @@ templates: The field's label wrapped in the appropriate HTML ``