diff --git a/AUTHORS b/AUTHORS index a690115942..79ac9e1b42 100644 --- a/AUTHORS +++ b/AUTHORS @@ -90,6 +90,7 @@ answer newbie questions, and generally made Django that much better: Paul Collier Pete Crosier Matt Croydon + Leah Culver flavio.curella@gmail.com Jure Cuhalev John D'Agostino @@ -133,6 +134,7 @@ answer newbie questions, and generally made Django that much better: Jorge Gajon gandalf@owca.info Marc Garcia + Andy Gayton Baishampayan Ghose Dimitris Glezos glin@seznam.cz @@ -173,6 +175,7 @@ answer newbie questions, and generally made Django that much better: junzhang.jn@gmail.com Antti Kaihola Nagy Károly + Erik Karulf Ben Dean Kawamura Ian G. Kelly Thomas Kerpe @@ -192,6 +195,7 @@ answer newbie questions, and generally made Django that much better: Joseph Kocherhans konrad@gwu.edu knox + David Krauth kurtiss@meetro.com lakin.wecker@gmail.com Nick Lane @@ -207,6 +211,7 @@ answer newbie questions, and generally made Django that much better: limodou Philip Lindborg Simon Litchfield + Trey Long msaelices Matt McClanahan Martin Maney @@ -256,6 +261,7 @@ answer newbie questions, and generally made Django that much better: Gustavo Picon Luke Plant plisk + Mihai Preda Daniel Poelzleithner polpak@yahoo.com Matthias Pronk @@ -268,6 +274,7 @@ answer newbie questions, and generally made Django that much better: Massimiliano Ravelli Brian Ray remco@diji.biz + David Reynolds rhettg@gmail.com ricardojbarrios@gmail.com Matt Riggott @@ -287,6 +294,7 @@ answer newbie questions, and generally made Django that much better: Pete Shinners jason.sidabras@gmail.com Jozko Skrablin + Ben Slavin SmileyChris smurf@smurf.noris.de Vsevolod Solovyov diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 0177490df9..c24e87e6ed 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -52,7 +52,7 @@ class LazySettings(object): if not settings_module: # If it's set but is an empty string. raise KeyError except KeyError: - raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE + raise ImportError, "Environment variable %s is undefined so settings cannot be imported." % ENVIRONMENT_VARIABLE # NOTE: This is arguably an EnvironmentError, but that causes problems with Python's interactive help self._target = Settings(settings_module) @@ -63,7 +63,7 @@ class LazySettings(object): argument must support attribute access (__getattr__)). """ if self._target != None: - raise EnvironmentError, 'Settings already configured.' + raise RuntimeError, 'Settings already configured.' holder = UserSettingsHolder(default_settings) for name, value in options.items(): setattr(holder, name, value) @@ -82,7 +82,7 @@ class Settings(object): try: mod = __import__(self.SETTINGS_MODULE, {}, {}, ['']) except ImportError, e: - raise EnvironmentError, "Could not import settings '%s' (Is it on sys.path? Does it have syntax errors?): %s" % (self.SETTINGS_MODULE, e) + raise ImportError, "Could not import settings '%s' (Is it on sys.path? Does it have syntax errors?): %s" % (self.SETTINGS_MODULE, e) # Settings that should be converted into tuples if they're mistakenly entered # as strings. diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 2853d6c618..187eb0645e 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -253,6 +253,10 @@ TRANSACTIONS_MANAGED = False from django import get_version URL_VALIDATOR_USER_AGENT = "Django/%s (http://www.djangoproject.com)" % get_version() +# The tablespaces to use for each model when not specified otherwise. +DEFAULT_TABLESPACE = '' +DEFAULT_INDEX_TABLESPACE = '' + ############## # MIDDLEWARE # ############## @@ -289,7 +293,7 @@ SESSION_FILE_PATH = '/tmp/' # Directory to store ses # The cache backend to use. See the docstring in django.core.cache for the # possible values. -CACHE_BACKEND = 'simple://' +CACHE_BACKEND = 'locmem://' CACHE_MIDDLEWARE_KEY_PREFIX = '' CACHE_MIDDLEWARE_SECONDS = 600 diff --git a/django/conf/locale/ca/LC_MESSAGES/djangojs.mo b/django/conf/locale/ca/LC_MESSAGES/djangojs.mo index 581b176be4..ddd7859675 100644 Binary files a/django/conf/locale/ca/LC_MESSAGES/djangojs.mo and b/django/conf/locale/ca/LC_MESSAGES/djangojs.mo differ diff --git a/django/conf/locale/ca/LC_MESSAGES/djangojs.po b/django/conf/locale/ca/LC_MESSAGES/djangojs.po index 3ae0e9bad8..47f41acdda 100644 --- a/django/conf/locale/ca/LC_MESSAGES/djangojs.po +++ b/django/conf/locale/ca/LC_MESSAGES/djangojs.po @@ -1,19 +1,21 @@ +# translation of djangojs.po to español # translation of djangojs.po to # Catalan translation for the django-admin JS files. # This file is distributed under the same license as the Django package. # +# Antoni Aloy , 2007. msgid "" msgstr "" "Project-Id-Version: djangojs\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-20 18:25+0200\n" -"PO-Revision-Date: 2007-06-25 17:47+0200\n" -"Last-Translator: Marc Fargas \n" -"Language-Team: \n" +"PO-Revision-Date: 2007-12-01 12:06+0100\n" +"Last-Translator: Antoni Aloy \n" +"Language-Team: español \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: VIM 7.0\n" +"X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: contrib/admin/media/js/SelectFilter2.js:33 @@ -51,8 +53,7 @@ msgstr "Deseleccionar tots" msgid "" "January February March April May June July August September October November " "December" -msgstr "" -"Febrer Març Abril Maig Juny Juliol Agost Setembre Octubre Novembre Desembre" +msgstr "Gener Febrer Març Abril Maig Juny Juliol Agost Setembre Octubre Novembre Desembre" #: contrib/admin/media/js/dateparse.js:33 msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" @@ -117,3 +118,4 @@ msgstr "Mostrar" #: contrib/admin/media/js/admin/CollapsedFieldsets.js:63 msgid "Hide" msgstr "Ocultar" + diff --git a/django/contrib/localflavor/mx/__init__.py b/django/contrib/localflavor/mx/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/mx/forms.py b/django/contrib/localflavor/mx/forms.py new file mode 100644 index 0000000000..5e42cca7d7 --- /dev/null +++ b/django/contrib/localflavor/mx/forms.py @@ -0,0 +1,14 @@ +""" +Mexican-specific form helpers. +""" + +from django.newforms.fields import Select + +class MXStateSelect(Select): + """ + A Select widget that uses a list of Mexican states as its choices. + """ + def __init__(self, attrs=None): + from mx_states import STATE_CHOICES + super(MXStateSelect, self).__init__(attrs, choices=STATE_CHOICES) + diff --git a/django/contrib/localflavor/mx/mx_states.py b/django/contrib/localflavor/mx/mx_states.py new file mode 100644 index 0000000000..eed1700efb --- /dev/null +++ b/django/contrib/localflavor/mx/mx_states.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +""" +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 django.utils.translation import ugettext_lazy as _ + +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')), +) + diff --git a/django/contrib/localflavor/uk/forms.py b/django/contrib/localflavor/uk/forms.py index 84d6c0e157..2b162230de 100644 --- a/django/contrib/localflavor/uk/forms.py +++ b/django/contrib/localflavor/uk/forms.py @@ -2,7 +2,7 @@ UK-specific Form helpers """ -from django.newforms.fields import RegexField +from django.newforms.fields import RegexField, Select from django.utils.translation import ugettext class UKPostcodeField(RegexField): @@ -17,3 +17,19 @@ class UKPostcodeField(RegexField): max_length=None, min_length=None, error_message=ugettext(u'Enter a postcode. A space is required between the two postcode parts.'), *args, **kwargs) + +class UKCountySelect(Select): + """ + A Select widget that uses a list of UK Counties/Regions as its choices. + """ + def __init__(self, attrs=None): + from uk_regions import UK_REGION_CHOICES + super(UKCountySelect, self).__init__(attrs, choices=UK_REGION_CHOICES) + +class UKNationSelect(Select): + """ + A Select widget that uses a list of UK Nations as its choices. + """ + def __init__(self, attrs=None): + from uk_regions import UK_NATIONS_CHOICES + super(UKNationSelect, self).__init__(attrs, choices=UK_NATIONS_CHOICES) diff --git a/django/contrib/localflavor/uk/uk_regions.py b/django/contrib/localflavor/uk/uk_regions.py new file mode 100644 index 0000000000..3e2c16ef5d --- /dev/null +++ b/django/contrib/localflavor/uk/uk_regions.py @@ -0,0 +1,97 @@ +""" +Sources: + English regions: http://www.statistics.gov.uk/geography/downloads/31_10_01_REGION_names_and_codes_12_00.xls + Northern Ireland regions: http://en.wikipedia.org/wiki/List_of_Irish_counties_by_area + Welsh regions: http://en.wikipedia.org/wiki/Preserved_counties_of_Wales + Scottish regions: http://en.wikipedia.org/wiki/Regions_and_districts_of_Scotland +""" +from django.utils.translation import ugettext as _ + +ENGLAND_REGION_CHOICES = ( + ("Bedfordshire", _("Bedfordshire")), + ("Buckinghamshire", _("Buckinghamshire")), + ("Cambridgeshire", ("Cambridgeshire")), + ("Cheshire", _("Cheshire")), + ("Cornwall and Isles of Scilly", _("Cornwall and Isles of Scilly")), + ("Cumbria", _("Cumbria")), + ("Derbyshire", _("Derbyshire")), + ("Devon", _("Devon")), + ("Dorset", _("Dorset")), + ("Durham", _("Durham")), + ("East Sussex", _("East Sussex")), + ("Essex", _("Essex")), + ("Gloucestershire", _("Gloucestershire")), + ("Greater London", _("Greater London")), + ("Greater Manchester", _("Greater Manchester")), + ("Hampshire", _("Hampshire")), + ("Hertfordshire", _("Hertfordshire")), + ("Kent", _("Kent")), + ("Lancashire", _("Lancashire")), + ("Leicestershire", _("Leicestershire")), + ("Lincolnshire", _("Lincolnshire")), + ("Merseyside", _("Merseyside")), + ("Norfolk", _("Norfolk")), + ("North Yorkshire", _("North Yorkshire")), + ("Northamptonshire", _("Northamptonshire")), + ("Northumberland", _("Northumberland")), + ("Nottinghamshire", _("Nottinghamshire")), + ("Oxfordshire", _("Oxfordshire")), + ("Shropshire", _("Shropshire")), + ("Somerset", _("Somerset")), + ("South Yorkshire", _("South Yorkshire")), + ("Staffordshire", _("Staffordshire")), + ("Suffolk", _("Suffolk")), + ("Surrey", _("Surrey")), + ("Tyne and Wear", _("Tyne and Wear")), + ("Warwickshire", _("Warwickshire")), + ("West Midlands", _("West Midlands")), + ("West Sussex", _("West Sussex")), + ("West Yorkshire", _("West Yorkshire")), + ("Wiltshire", _("Wiltshire")), + ("Worcestershire", _("Worcestershire")), +) + +NORTHERN_IRELAND_REGION_CHOICES = ( + ("County Antrim", _("County Antrim")), + ("County Armagh", _("County Armagh")), + ("County Down", _("County Down")), + ("County Fermanagh", _("County Down")), + ("County Londonderry", _("County Londonderry")), + ("County Tyrone", _("County Tyrone")), +) + +WALES_REGION_CHOICES = ( + ("Clwyd", _("Clwyd")), + ("Dyfed", _("Dyfed")), + ("Gwent", _("Gwent")), + ("Gwynedd", _("Gwynedd")), + ("Mid Glamorgan", _("Mid Glamorgan")), + ("Powys", _("Powys")), + ("South Glamorgan", _("South Glamorgan")), + ("West Glamorgan", _("West Glamorgan")), +) + +SCOTTISH_REGION_CHOICES = ( + ("Borders", _("Borders")), + ("Central Scotland", _("Central Scotland")), + ("Dumfries and Galloway", _("Dumfries and Galloway")), + ("Fife", _("Fife")), + ("Grampian", _("Grampian")), + ("Highland", _("Highland")), + ("Lothian", _("Lothian")), + ("Orkney Islands", _("Orkney Islands")), + ("Shetland Islands", _("Shetland Islands")), + ("Strathclyde", _("Strathclyde")), + ("Tayside", _("Tayside")), + ("Western Isles", _("Western Isles")), +) + +UK_NATIONS_CHOICES = ( + ("England", _("England")), + ("Northern Ireland", _("Northern Ireland")), + ("Scotland", _("Scotland")), + ("Wales", _("Wales")), +) + +UK_REGION_CHOICES = ENGLAND_REGION_CHOICES + NORTHERN_IRELAND_REGION_CHOICES + WALES_REGION_CHOICES + SCOTTISH_REGION_CHOICES + diff --git a/django/contrib/localflavor/za/__init__.py b/django/contrib/localflavor/za/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/za/forms.py b/django/contrib/localflavor/za/forms.py new file mode 100644 index 0000000000..b04b7cf6ab --- /dev/null +++ b/django/contrib/localflavor/za/forms.py @@ -0,0 +1,57 @@ +""" +South Africa-specific Form helpers +""" + +from django.newforms import ValidationError +from django.newforms.fields import Field, RegexField, EMPTY_VALUES +from django.utils.checksums import luhn +from django.utils.translation import gettext as _ +import re +from datetime import date + +id_re = re.compile(r'^(?P\d\d)(?P\d\d)(?P
\d\d)(?P\d{4})(?P\d{3})') + +class ZAIDField(Field): + """A form field for South African ID numbers -- the checksum is validated + using the Luhn checksum, and uses a simlistic (read: not entirely accurate) + check for the birthdate + """ + + def __init__(self, *args, **kwargs): + super(ZAIDField, self).__init__() + self.error_message = _(u'Enter a valid South African ID number') + + def clean(self, value): + # strip spaces and dashes + value = value.strip().replace(' ', '').replace('-', '') + + super(ZAIDField, self).clean(value) + + if value in EMPTY_VALUES: + return u'' + + match = re.match(id_re, value) + + if not match: + raise ValidationError(self.error_message) + + g = match.groupdict() + + try: + # The year 2000 is conveniently a leapyear. + # This algorithm will break in xx00 years which aren't leap years + # There is no way to guess the century of a ZA ID number + d = date(int(g['yy']) + 2000, int(g['mm']), int(g['dd'])) + except ValueError: + raise ValidationError(self.error_message) + + if not luhn(value): + raise ValidationError(self.error_message) + + return value + +class ZAPostCodeField(RegexField): + def __init__(self, *args, **kwargs): + super(ZAPostCodeField, self).__init__(r'^\d{4}$', + max_length=None, min_length=None, + error_message=_(u'Enter a valid South African postal code')) diff --git a/django/contrib/localflavor/za/za_provinces.py b/django/contrib/localflavor/za/za_provinces.py new file mode 100644 index 0000000000..0bc6fe14b3 --- /dev/null +++ b/django/contrib/localflavor/za/za_provinces.py @@ -0,0 +1,13 @@ +from django.utils.translation import gettext_lazy as _ + +PROVINCE_CHOICES = ( + ('EC', _('Eastern Cape')), + ('FS', _('Free State')), + ('GP', _('Gauteng')), + ('KN', _('KwaZulu-Natal')), + ('LP', _('Limpopo')), + ('MP', _('Mpumalanga')), + ('NC', _('Northern Cape')), + ('NW', _('North West')), + ('WC', _('Western Cape')), +) diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index 13708fd26d..5d4f4786e1 100644 --- a/django/contrib/markup/templatetags/markup.py +++ b/django/contrib/markup/templatetags/markup.py @@ -32,7 +32,23 @@ def textile(value): return mark_safe(force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8'))) textile.is_safe = True -def markdown(value): +def markdown(value, arg=''): + """ + Runs Markdown over a given value, optionally using various + extensions python-markdown supports. + + Syntax:: + + {{ value|markdown:"extension1_name,extension2_name..." }} + + To enable safe mode, which strips raw HTML and only returns HTML + generated by actual Markdown syntax, pass "safe" as the first + extension in the list. + + If the version of Markdown in use does not support extensions, + they will be silently ignored. + + """ try: import markdown except ImportError: @@ -40,7 +56,18 @@ def markdown(value): raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed." return force_unicode(value) else: - return mark_safe(force_unicode(markdown.markdown(smart_str(value)))) + # markdown.version was first added in 1.6b. The only version of markdown + # to fully support extensions before 1.6b was the shortlived 1.6a. + if hasattr(markdown, 'version'): + extensions = [e for e in arg.split(",") if e] + if len(extensions) > 0 and extensions[0] == "safe": + extensions = extensions[1:] + safe_mode = True + else: + safe_mode = False + return mark_safe(force_unicode(markdown.markdown(smart_str(value), extensions, safe_mode=safe_mode))) + else: + return mark_safe(force_unicode(markdown.markdown(smart_str(value)))) markdown.is_safe = True def restructuredtext(value): diff --git a/django/contrib/markup/tests.py b/django/contrib/markup/tests.py index 6e7beaeb2f..9a96f8cda4 100644 --- a/django/contrib/markup/tests.py +++ b/django/contrib/markup/tests.py @@ -61,8 +61,15 @@ Paragraph 2 with a link_ t = Template("{{ rest_content|restructuredtext }}") rendered = t.render(Context(locals())).strip() if docutils: - self.assertEqual(rendered, """

Paragraph 1

+ # Different versions of docutils return slightly different HTML + try: + # Docutils v0.4 and earlier + self.assertEqual(rendered, """

Paragraph 1

Paragraph 2 with a link

""") + except AssertionError, e: + # Docutils from SVN (which will become 0.5) + self.assertEqual(rendered, """

Paragraph 1

+

Paragraph 2 with a link

""") else: self.assertEqual(rendered, rest_content) diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 192065a5f3..b8726fd2bd 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -51,6 +51,14 @@ class SessionBase(object): self.modified = self.modified or key in self._session return self._session.pop(key, *args) + def setdefault(self, key, value): + if key in self._session: + return self._session[key] + else: + self.modified = True + self._session[key] = value + return value + def set_test_cookie(self): self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py index 8fc5e17d69..dfa7bed226 100644 --- a/django/contrib/sessions/models.py +++ b/django/contrib/sessions/models.py @@ -18,40 +18,6 @@ class SessionManager(models.Manager): pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest() return base64.encodestring(pickled + pickled_md5) - def get_new_session_key(self): - "Returns session key that isn't being used." - # The random module is seeded when this Apache child is created. - # Use SECRET_KEY as added salt. - try: - pid = os.getpid() - except AttributeError: - # No getpid() in Jython, for example - pid = 1 - while 1: - session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), pid, time.time(), settings.SECRET_KEY)).hexdigest() - try: - self.get(session_key=session_key) - except self.model.DoesNotExist: - break - return session_key - - def get_new_session_object(self): - """ - Returns a new session object. - """ - # FIXME: There is a *small* chance of collision here, meaning we will - # return an existing object. That can be fixed when we add a way to - # validate (and guarantee) that non-auto primary keys are unique. For - # now, we save immediately in order to reduce the "window of - # misfortune" as much as possible. - created = False - while not created: - obj, created = self.get_or_create(session_key=self.get_new_session_key(), - expire_date = datetime.datetime.now()) - # Collision in key generation, so re-seed the generator - random.seed() - return obj - def save(self, session_key, session_dict, expire_date): s = self.model(session_key, self.encode(session_dict), expire_date) if session_dict: diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index ab3034f858..10bec9668b 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -66,6 +66,11 @@ False >>> s.accessed, s.modified (True, False) +>>> s.setdefault('foo', 'bar') +'bar' +>>> s.setdefault('foo', 'baz') +'bar' + >>> s.accessed = False # Reset the accessed flag >>> s.pop('some key') diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py index 6da8e883b9..495cc92822 100644 --- a/django/core/cache/__init__.py +++ b/django/core/cache/__init__.py @@ -22,19 +22,28 @@ from django.core.cache.backends.base import InvalidCacheBackendError BACKENDS = { # name for use in settings file --> name of module in "backends" directory 'memcached': 'memcached', - 'simple': 'simple', 'locmem': 'locmem', 'file': 'filebased', 'db': 'db', 'dummy': 'dummy', } +DEPRECATED_BACKENDS = { + # deprecated backend --> replacement module + 'simple': 'locmem', +} + def get_cache(backend_uri): if backend_uri.find(':') == -1: raise InvalidCacheBackendError, "Backend URI must start with scheme://" scheme, rest = backend_uri.split(':', 1) if not rest.startswith('//'): raise InvalidCacheBackendError, "Backend URI must start with scheme://" + if scheme in DEPRECATED_BACKENDS: + import warnings + warnings.warn("'%s' backend is deprecated. Use '%s' instead." % + (scheme, DEPRECATED_BACKENDS[scheme]), DeprecationWarning) + scheme = DEPRECATED_BACKENDS[scheme] if scheme not in BACKENDS: raise InvalidCacheBackendError, "%r is not a valid cache backend" % scheme diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py index 690193ac81..72cb877f6f 100644 --- a/django/core/cache/backends/filebased.py +++ b/django/core/cache/backends/filebased.py @@ -1,21 +1,32 @@ "File-based cache backend" -from django.core.cache.backends.simple import CacheClass as SimpleCacheClass -from django.utils.http import urlquote_plus import os, time try: import cPickle as pickle except ImportError: import pickle +from django.core.cache.backends.base import BaseCache +from django.utils.http import urlquote_plus -class CacheClass(SimpleCacheClass): +class CacheClass(BaseCache): def __init__(self, dir, params): + BaseCache.__init__(self, params) + + max_entries = params.get('max_entries', 300) + try: + self._max_entries = int(max_entries) + except (ValueError, TypeError): + self._max_entries = 300 + + cull_frequency = params.get('cull_frequency', 3) + try: + self._cull_frequency = int(cull_frequency) + except (ValueError, TypeError): + self._cull_frequency = 3 + self._dir = dir if not os.path.exists(self._dir): self._createdir() - SimpleCacheClass.__init__(self, dir, params) - del self._cache - del self._expire_info def add(self, key, value, timeout=None): fname = self._key_to_file(key) diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py index 2d74e2b132..2734cef86a 100644 --- a/django/core/cache/backends/locmem.py +++ b/django/core/cache/backends/locmem.py @@ -6,20 +6,44 @@ try: except ImportError: import pickle -from django.core.cache.backends.simple import CacheClass as SimpleCacheClass +from django.core.cache.backends.base import BaseCache from django.utils.synch import RWLock -class CacheClass(SimpleCacheClass): - def __init__(self, host, params): - SimpleCacheClass.__init__(self, host, params) +class CacheClass(BaseCache): + def __init__(self, _, params): + BaseCache.__init__(self, params) + self._cache = {} + self._expire_info = {} + + max_entries = params.get('max_entries', 300) + try: + self._max_entries = int(max_entries) + except (ValueError, TypeError): + self._max_entries = 300 + + cull_frequency = params.get('cull_frequency', 3) + try: + self._cull_frequency = int(cull_frequency) + except (ValueError, TypeError): + self._cull_frequency = 3 + self._lock = RWLock() + def _add(self, key, value, timeout=None): + if len(self._cache) >= self._max_entries: + self._cull() + if timeout is None: + timeout = self.default_timeout + if key not in self._cache.keys(): + self._cache[key] = value + self._expire_info[key] = time.time() + timeout + def add(self, key, value, timeout=None): self._lock.writer_enters() # Python 2.3 and 2.4 don't allow combined try-except-finally blocks. try: try: - super(CacheClass, self).add(key, pickle.dumps(value), timeout) + self._add(key, pickle.dumps(value), timeout) except pickle.PickleError: pass finally: @@ -51,20 +75,50 @@ class CacheClass(SimpleCacheClass): finally: self._lock.writer_leaves() + def _set(self, key, value, timeout=None): + if len(self._cache) >= self._max_entries: + self._cull() + if timeout is None: + timeout = self.default_timeout + self._cache[key] = value + self._expire_info[key] = time.time() + timeout + def set(self, key, value, timeout=None): self._lock.writer_enters() # Python 2.3 and 2.4 don't allow combined try-except-finally blocks. try: try: - super(CacheClass, self).set(key, pickle.dumps(value), timeout) + self._set(key, pickle.dumps(value), timeout) except pickle.PickleError: pass finally: self._lock.writer_leaves() + def has_key(self, key): + return key in self._cache + + def _cull(self): + if self._cull_frequency == 0: + self._cache.clear() + self._expire_info.clear() + else: + doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0] + for k in doomed: + self.delete(k) + + def _delete(self, key): + try: + del self._cache[key] + except KeyError: + pass + try: + del self._expire_info[key] + except KeyError: + pass + def delete(self, key): self._lock.writer_enters() try: - SimpleCacheClass.delete(self, key) + self._delete(key) finally: self._lock.writer_leaves() diff --git a/django/core/cache/backends/simple.py b/django/core/cache/backends/simple.py deleted file mode 100644 index ff60d49066..0000000000 --- a/django/core/cache/backends/simple.py +++ /dev/null @@ -1,73 +0,0 @@ -"Single-process in-memory cache backend." - -from django.core.cache.backends.base import BaseCache -import time - -class CacheClass(BaseCache): - def __init__(self, host, params): - BaseCache.__init__(self, params) - self._cache = {} - self._expire_info = {} - - max_entries = params.get('max_entries', 300) - try: - self._max_entries = int(max_entries) - except (ValueError, TypeError): - self._max_entries = 300 - - cull_frequency = params.get('cull_frequency', 3) - try: - self._cull_frequency = int(cull_frequency) - except (ValueError, TypeError): - self._cull_frequency = 3 - - def add(self, key, value, timeout=None): - if len(self._cache) >= self._max_entries: - self._cull() - if timeout is None: - timeout = self.default_timeout - if key not in self._cache.keys(): - self._cache[key] = value - self._expire_info[key] = time.time() + timeout - - def get(self, key, default=None): - now = time.time() - exp = self._expire_info.get(key) - if exp is None: - return default - elif exp < now: - del self._cache[key] - del self._expire_info[key] - return default - else: - return self._cache[key] - - def set(self, key, value, timeout=None): - if len(self._cache) >= self._max_entries: - self._cull() - if timeout is None: - timeout = self.default_timeout - self._cache[key] = value - self._expire_info[key] = time.time() + timeout - - def delete(self, key): - try: - del self._cache[key] - except KeyError: - pass - try: - del self._expire_info[key] - except KeyError: - pass - - def has_key(self, key): - return key in self._cache - - def _cull(self): - if self._cull_frequency == 0: - self._cache.clear() - self._expire_info.clear() - else: - doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0] - for k in doomed: - self.delete(k) diff --git a/django/core/exceptions.py b/django/core/exceptions.py index f22f67c261..d9fc326cf2 100644 --- a/django/core/exceptions.py +++ b/django/core/exceptions.py @@ -4,6 +4,10 @@ class ObjectDoesNotExist(Exception): "The requested object does not exist" silent_variable_failure = True +class MultipleObjectsReturned(Exception): + "The query returned multiple objects when only one was expected." + pass + class SuspiciousOperation(Exception): "The user did something suspicious" pass diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 1796cae8ea..17a24d6f60 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -1,7 +1,8 @@ +import sys + +from django import http from django.core import signals from django.dispatch import dispatcher -from django import http -import sys class BaseHandler(object): # Changes that are always applied to a response (in this order). diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py index e81a65be4d..ebf79295e0 100644 --- a/django/core/handlers/modpython.py +++ b/django/core/handlers/modpython.py @@ -1,11 +1,12 @@ -from django.core.handlers.base import BaseHandler +import os +from pprint import pformat + +from django import http from django.core import signals +from django.core.handlers.base import BaseHandler from django.dispatch import dispatcher from django.utils import datastructures from django.utils.encoding import force_unicode -from django import http -from pprint import pformat -import os # NOTE: do *not* import settings (or any module which eventually imports # settings) until after ModPythonHandler has been called; otherwise os.environ diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 94575ca369..df2ba19b65 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -5,12 +5,12 @@ try: except ImportError: from StringIO import StringIO -from django.core.handlers.base import BaseHandler +from django import http from django.core import signals +from django.core.handlers.base import BaseHandler from django.dispatch import dispatcher from django.utils import datastructures from django.utils.encoding import force_unicode -from django import http # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html STATUS_CODE_TEXT = { diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index fcbc9d1110..ee0eb6fda8 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -84,7 +84,7 @@ def get_commands(): try: from django.conf import settings apps = settings.INSTALLED_APPS - except (AttributeError, EnvironmentError): + except (AttributeError, ImportError): apps = [] for app_name in apps: @@ -99,7 +99,7 @@ def get_commands(): try: from django.conf import settings project_directory = setup_environ(__import__(settings.SETTINGS_MODULE)) - except (AttributeError, EnvironmentError, ImportError): + except (AttributeError, ImportError): project_directory = None if project_directory: @@ -254,6 +254,8 @@ def setup_environ(settings_mod): # way. For example, if this file (manage.py) lives in a directory # "myproject", this code would add "/path/to/myproject" to sys.path. project_directory, settings_filename = os.path.split(settings_mod.__file__) + if not project_directory: + project_directory = os.getcwd() project_name = os.path.basename(project_directory) settings_name = os.path.splitext(settings_filename)[0] sys.path.append(os.path.join(project_directory, os.pardir)) diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index 81938c777c..0f21130f7a 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -33,8 +33,9 @@ class Command(NoArgsCommand): for app_name in settings.INSTALLED_APPS: try: __import__(app_name + '.management', {}, {}, ['']) - except ImportError: - pass + except ImportError, exc: + if not exc.args[0].startswith('No module named management'): + raise cursor = connection.cursor() diff --git a/django/core/management/validation.py b/django/core/management/validation.py index b3515f2f6e..6e8e6d0d49 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -70,10 +70,14 @@ def get_validation_errors(outfile, app=None): # Check to see if the related field will clash with any # existing fields, m2m fields, m2m related objects or related objects if f.rel: - rel_opts = f.rel.to._meta if f.rel.to not in models.get_models(): - e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, rel_opts.object_name)) + e.add(opts, "'%s' has relation with model %s, which has not been installed" % (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)): + continue + rel_opts = f.rel.to._meta rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() rel_query_name = f.related_query_name() for r in rel_opts.fields: @@ -101,10 +105,14 @@ def get_validation_errors(outfile, app=None): for i, f in enumerate(opts.many_to_many): # Check to see if the related m2m field will clash with any # existing fields, m2m fields, m2m related objects or related objects - rel_opts = f.rel.to._meta if f.rel.to not in models.get_models(): - e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, rel_opts.object_name)) + e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (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)): + continue + rel_opts = f.rel.to._meta rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() rel_query_name = f.related_query_name() # If rel_name is none, there is no reverse accessor. diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 496fc26306..24fda78427 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -47,7 +47,8 @@ class DatabaseOperations(BaseDatabaseOperations): BEGIN SELECT %(sq_name)s.nextval INTO :new.%(col_name)s FROM dual; - END;/""" % locals() + END; + /""" % locals() return sequence_sql, trigger_sql def date_extract_sql(self, lookup_type, field_name): diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index d7b3558344..6b5233e8de 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -6,6 +6,7 @@ Requires psycopg 2: http://initd.org/projects/psycopg2 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures from django.db.backends.postgresql.operations import DatabaseOperations as PostgresqlDatabaseOperations +from django.utils.safestring import SafeUnicode try: import psycopg2 as Database import psycopg2.extensions @@ -17,6 +18,7 @@ DatabaseError = Database.DatabaseError IntegrityError = Database.IntegrityError psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) +psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedString) class DatabaseFeatures(BaseDatabaseFeatures): needs_datetime_string_cast = False diff --git a/django/db/models/base.py b/django/db/models/base.py index c84c574282..081ed2abfe 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1,7 +1,7 @@ import django.db.models.manipulators import django.db.models.manager from django.core import validators -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist from django.db.models.fields.related import OneToOneRel, ManyToOneRel from django.db.models.query import delete_objects @@ -35,6 +35,8 @@ class ModelBase(type): new_class = type.__new__(cls, name, bases, {'__module__': attrs.pop('__module__')}) new_class.add_to_class('_meta', Options(attrs.pop('Meta', None))) new_class.add_to_class('DoesNotExist', types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {})) + new_class.add_to_class('MultipleObjectsReturned', + types.ClassType('MultipleObjectsReturned', (MultipleObjectsReturned, ), {})) # Build complete list of parents for base in bases: diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 3e92e45d30..f8a3f18d25 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -102,7 +102,7 @@ class Field(object): self.radio_admin = radio_admin self.help_text = help_text self.db_column = db_column - self.db_tablespace = db_tablespace + self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE # Set db_index to True if the field has a relationship and doesn't explicitly set db_index. self.db_index = db_index diff --git a/django/db/models/options.py b/django/db/models/options.py index 71cb216cab..c0f36d9f40 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -28,7 +28,7 @@ class Options(object): self.object_name, self.app_label = None, None self.get_latest_by = None self.order_with_respect_to = None - self.db_tablespace = None + self.db_tablespace = settings.DEFAULT_TABLESPACE self.admin = None self.meta = meta self.pk = None @@ -151,7 +151,7 @@ class Options(object): rel_objs = [] for klass in get_models(): for f in klass._meta.fields: - if f.rel and self == f.rel.to._meta: + if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta: rel_objs.append(RelatedObject(f.rel.to, klass, f)) self._all_related_objects = rel_objs return rel_objs @@ -185,7 +185,7 @@ class Options(object): rel_objs = [] for klass in get_models(): for f in klass._meta.many_to_many: - if f.rel and self == f.rel.to._meta: + if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta: rel_objs.append(RelatedObject(f.rel.to, klass, f)) if app_cache_ready(): self._all_related_many_to_many_objects = rel_objs diff --git a/django/db/models/query.py b/django/db/models/query.py index 4d0d295e97..0bc36f425a 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -261,7 +261,8 @@ class _QuerySet(object): obj_list = list(clone) if len(obj_list) < 1: raise self.model.DoesNotExist, "%s matching query does not exist." % self.model._meta.object_name - assert len(obj_list) == 1, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.model._meta.object_name, len(obj_list), kwargs) + elif len(obj_list) > 1: + raise self.model.MultipleObjectsReturned, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.model._meta.object_name, len(obj_list), kwargs) return obj_list[0] def create(self, **kwargs): diff --git a/django/http/utils.py b/django/http/utils.py index d08a9e0237..f98ca93a37 100644 --- a/django/http/utils.py +++ b/django/http/utils.py @@ -5,7 +5,7 @@ Functions that modify an HTTP request or response in some way. # This group of functions are run as part of the response handling, after # everything else, including all response middleware. Think of them as # "compulsory response middleware". Be careful about what goes here, because -# it's a little fiddly to override this behaviour, so they should be truly +# it's a little fiddly to override this behavior, so they should be truly # universally applicable. def fix_location_header(request, response): @@ -13,7 +13,7 @@ def fix_location_header(request, response): Ensures that we always use an absolute URI in any location header in the response. This is required by RFC 2616, section 14.30. - Code constructing response objects is free to insert relative paths and + Code constructing response objects is free to insert relative paths, as this function converts them to absolute paths. """ if 'Location' in response and request.get_host(): @@ -31,4 +31,3 @@ def conditional_content_removal(request, response): if request.method == 'HEAD': response.content = '' return response - diff --git a/django/middleware/common.py b/django/middleware/common.py index 10a3a71b8d..3d57fa4367 100644 --- a/django/middleware/common.py +++ b/django/middleware/common.py @@ -5,6 +5,7 @@ from django.conf import settings from django import http from django.core.mail import mail_managers from django.utils.http import urlquote +from django.core import urlresolvers class CommonMiddleware(object): """ @@ -16,6 +17,12 @@ class CommonMiddleware(object): this middleware appends missing slashes and/or prepends missing "www."s. + - If APPEND_SLASH is set and the initial URL doesn't end with a + slash, and it is not found in urlpatterns, a new URL is formed by + appending a slash at the end. If this new URL is found in + urlpatterns, then an HTTP-redirect is returned to this new URL; + otherwise the initial URL is processed as usual. + - ETags: If the USE_ETAGS setting is set, ETags will be calculated from the entire page content and Not Modified responses will be returned appropriately. @@ -33,27 +40,48 @@ class CommonMiddleware(object): if user_agent_regex.search(request.META['HTTP_USER_AGENT']): return http.HttpResponseForbidden('

Forbidden

') - # Check for a redirect based on settings.APPEND_SLASH and settings.PREPEND_WWW + # Check for a redirect based on settings.APPEND_SLASH + # and settings.PREPEND_WWW host = request.get_host() old_url = [host, request.path] new_url = old_url[:] - if settings.PREPEND_WWW and old_url[0] and not old_url[0].startswith('www.'): + + if (settings.PREPEND_WWW and old_url[0] and + not old_url[0].startswith('www.')): new_url[0] = 'www.' + old_url[0] - # Append a slash if append_slash is set and the URL doesn't have a - # trailing slash or a file extension. - if settings.APPEND_SLASH and (not old_url[1].endswith('/')) and ('.' not in old_url[1].split('/')[-1]): - new_url[1] = new_url[1] + '/' - if settings.DEBUG and request.method == 'POST': - raise RuntimeError, "You called this URL via POST, but the URL doesn't end in a slash and you have APPEND_SLASH set. Django can't redirect to the slash URL while maintaining POST data. Change your form to point to %s%s (note the trailing slash), or set APPEND_SLASH=False in your Django settings." % (new_url[0], new_url[1]) + + # Append a slash if APPEND_SLASH is set and the URL doesn't have a + # trailing slash and there is no pattern for the current path + if settings.APPEND_SLASH and (not old_url[1].endswith('/')): + try: + urlresolvers.resolve(request.path) + except urlresolvers.Resolver404: + new_url[1] = new_url[1] + '/' + if settings.DEBUG and request.method == 'POST': + raise RuntimeError, ("" + "You called this URL via POST, but the URL doesn't end " + "in a slash and you have APPEND_SLASH set. Django can't " + "redirect to the slash URL while maintaining POST data. " + "Change your form to point to %s%s (note the trailing " + "slash), or set APPEND_SLASH=False in your Django " + "settings.") % (new_url[0], new_url[1]) + if new_url != old_url: - # Redirect - if new_url[0]: - newurl = "%s://%s%s" % (request.is_secure() and 'https' or 'http', new_url[0], urlquote(new_url[1])) + # Redirect if the target url exists + try: + urlresolvers.resolve(new_url[1]) + except urlresolvers.Resolver404: + pass else: - newurl = urlquote(new_url[1]) - if request.GET: - newurl += '?' + request.GET.urlencode() - return http.HttpResponsePermanentRedirect(newurl) + if new_url[0]: + newurl = "%s://%s%s" % ( + request.is_secure() and 'https' or 'http', + new_url[0], urlquote(new_url[1])) + else: + newurl = urlquote(new_url[1]) + if request.GET: + newurl += '?' + request.GET.urlencode() + return http.HttpResponsePermanentRedirect(newurl) return None diff --git a/django/newforms/fields.py b/django/newforms/fields.py index 4a54a98d61..58f65ffde5 100644 --- a/django/newforms/fields.py +++ b/django/newforms/fields.py @@ -83,21 +83,15 @@ class Field(object): self.creation_counter = Field.creation_counter Field.creation_counter += 1 - self.error_messages = self._build_error_messages(error_messages) - - def _build_error_messages(self, extra_error_messages): - error_messages = {} - - def get_default_error_messages(klass): + def set_class_error_messages(messages, klass): for base_class in klass.__bases__: - get_default_error_messages(base_class) - if hasattr(klass, 'default_error_messages'): - error_messages.update(klass.default_error_messages) + set_class_error_messages(messages, base_class) + messages.update(getattr(klass, 'default_error_messages', {})) - get_default_error_messages(self.__class__) - if extra_error_messages: - error_messages.update(extra_error_messages) - return error_messages + messages = {} + set_class_error_messages(messages, self.__class__) + messages.update(error_messages or {}) + self.error_messages = messages def clean(self, value): """ @@ -415,7 +409,7 @@ class EmailField(RegexField): try: from django.conf import settings URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT -except (ImportError, EnvironmentError): +except ImportError: # It's OK if Django settings aren't configured. URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' diff --git a/django/newforms/models.py b/django/newforms/models.py index fdb1285f6f..3c9b43da20 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -3,17 +3,21 @@ Helper functions for creating Form classes from Django models and database field objects. """ +from warnings import warn + from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_unicode from django.utils.datastructures import SortedDict +from django.core.exceptions import ImproperlyConfigured -from util import ValidationError +from util import ValidationError, ErrorList from forms import BaseForm from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES from formsets import BaseFormSet, formset_for_form, DELETION_FIELD_NAME from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput __all__ = ( + 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', 'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', 'formset_for_model', 'inline_formset', 'ModelChoiceField', 'ModelMultipleChoiceField', @@ -82,6 +86,9 @@ def form_for_model(model, form=BaseForm, fields=None, determining the formfield for a given database field. It's a callable that takes a database Field instance and returns a form Field instance. """ + warn("form_for_model is deprecated, use ModelForm instead.", + PendingDeprecationWarning, + stacklevel=3) opts = model._meta field_list = [] for f in opts.fields + opts.many_to_many: @@ -109,6 +116,9 @@ def form_for_instance(instance, form=BaseForm, fields=None, takes a database Field instance, plus **kwargs, and returns a form Field instance with the given kwargs (i.e. 'initial'). """ + warn("form_for_instance is deprecated, use ModelForm instead.", + PendingDeprecationWarning, + stacklevel=3) model = instance.__class__ opts = model._meta field_list = [] @@ -134,6 +144,155 @@ def form_for_fields(field_list): for f in field_list if f.editable]) return type('FormForFields', (BaseForm,), {'base_fields': fields}) + +# ModelForms ################################################################# + +def model_to_dict(instance, fields=None, exclude=None): + """ + Returns a dict containing the data in ``instance`` suitable for passing as + a Form's ``initial`` keyword argument. + + ``fields`` is an optional list of field names. If provided, only the named + fields will be included in the returned dict. + + ``exclude`` is an optional list of field names. If provided, the named + fields will be excluded from the returned dict, even if they are listed in + the ``fields`` argument. + """ + # avoid a circular import + from django.db.models.fields.related import ManyToManyField + opts = instance._meta + data = {} + for f in opts.fields + opts.many_to_many: + if not f.editable: + continue + if fields and not f.name in fields: + continue + if exclude and f.name in exclude: + continue + if isinstance(f, ManyToManyField): + # If the object doesn't have a primry key yet, just use an empty + # list for its m2m fields. Calling f.value_from_object will raise + # an exception. + if instance.pk is None: + data[f.name] = [] + else: + # MultipleChoiceWidget needs a list of pks, not object instances. + data[f.name] = [obj.pk for obj in f.value_from_object(instance)] + else: + data[f.name] = f.value_from_object(instance) + return data + +def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()): + """ + Returns a ``SortedDict`` containing form fields for the given model. + + ``fields`` is an optional list of field names. If provided, only the named + fields will be included in the returned fields. + + ``exclude`` is an optional list of field names. If provided, the named + fields will be excluded from the returned fields, even if they are listed + in the ``fields`` argument. + """ + # TODO: if fields is provided, it would be nice to return fields in that order + field_list = [] + opts = model._meta + for f in opts.fields + opts.many_to_many: + if not f.editable: + continue + if fields and not f.name in fields: + continue + if exclude and f.name in exclude: + continue + formfield = formfield_callback(f) + if formfield: + field_list.append((f.name, formfield)) + return SortedDict(field_list) + +class ModelFormOptions(object): + def __init__(self, options=None): + self.model = getattr(options, 'model', None) + self.fields = getattr(options, 'fields', None) + self.exclude = getattr(options, 'exclude', None) + +class ModelFormMetaclass(type): + def __new__(cls, name, bases, attrs): + # TODO: no way to specify formfield_callback yet, do we need one, or + # should it be a special case for the admin? + fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] + fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) + + # If this class is subclassing another Form, add that Form's fields. + # Note that we loop over the bases in *reverse*. This is necessary in + # order to preserve the correct order of fields. + for base in bases[::-1]: + if hasattr(base, 'base_fields'): + fields = base.base_fields.items() + fields + declared_fields = SortedDict(fields) + + opts = ModelFormOptions(attrs.get('Meta', None)) + attrs['_meta'] = opts + + # Don't allow more than one Meta model defenition in bases. The fields + # would be generated correctly, but the save method won't deal with + # more than one object. + base_models = [] + for base in bases: + base_opts = getattr(base, '_meta', None) + base_model = getattr(base_opts, 'model', None) + if base_model is not None: + base_models.append(base_model) + if len(base_models) > 1: + raise ImproperlyConfigured("%s's base classes define more than one model." % name) + + # If a model is defined, extract form fields from it and add them to base_fields + if attrs['_meta'].model is not None: + # Don't allow a subclass to define a Meta model if a parent class has. + # Technically the right fields would be generated, but the save + # method will not deal with more than one model. + for base in bases: + base_opts = getattr(base, '_meta', None) + base_model = getattr(base_opts, 'model', None) + if base_model is not None: + raise ImproperlyConfigured('%s defines more than one model.' % name) + model_fields = fields_for_model(opts.model, opts.fields, opts.exclude) + # fields declared in base classes override fields from the model + model_fields.update(declared_fields) + attrs['base_fields'] = model_fields + else: + attrs['base_fields'] = declared_fields + return type.__new__(cls, name, bases, attrs) + +class BaseModelForm(BaseForm): + def __init__(self, instance, data=None, files=None, auto_id='id_%s', prefix=None, + initial=None, error_class=ErrorList, label_suffix=':'): + self.instance = instance + opts = self._meta + object_data = model_to_dict(instance, opts.fields, opts.exclude) + # if initial was provided, it should override the values from instance + if initial is not None: + object_data.update(initial) + BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix) + + def save(self, commit=True): + """ + Saves this ``form``'s cleaned_data into model instance ``self.instance``. + + If commit=True, then the changes to ``instance`` will be saved to the + database. Returns ``instance``. + """ + if self.instance.pk is None: + fail_message = 'created' + else: + fail_message = 'changed' + return save_instance(self, self.instance, self._meta.fields, fail_message, commit) + +class ModelForm(BaseModelForm): + __metaclass__ = ModelFormMetaclass + + +# Fields ##################################################################### + class QuerySetIterator(object): def __init__(self, queryset, empty_label, cache_choices): self.queryset = queryset diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py index 611b997736..1cece7c7c0 100644 --- a/django/shortcuts/__init__.py +++ b/django/shortcuts/__init__.py @@ -38,7 +38,7 @@ def get_object_or_404(klass, *args, **kwargs): klass may be a Model, Manager, or QuerySet object. All other passed arguments and keyword arguments are used in the get() query. - Note: Like with get(), an AssertionError will be raised if more than one + Note: Like with get(), an MultipleObjectsReturned will be raised if more than one object is found. """ queryset = _get_queryset(klass) diff --git a/django/template/context.py b/django/template/context.py index 017d2d84b1..0e41a26618 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -23,12 +23,14 @@ class Context(object): yield d def push(self): - self.dicts = [{}] + self.dicts + d = {} + self.dicts = [d] + self.dicts + return d def pop(self): if len(self.dicts) == 1: raise ContextPopException - del self.dicts[0] + return self.dicts.pop(0) def __setitem__(self, key, value): "Set a variable in the current context" @@ -62,6 +64,7 @@ class Context(object): def update(self, other_dict): "Like dict.update(). Pushes an entire dictionary's keys and values onto the context." self.dicts = [other_dict] + self.dicts + return other_dict # This is a function rather than module-level procedural code because we only # want it to execute if somebody uses RequestContext. diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index f2be64ef1d..ac92bef6cf 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -696,7 +696,7 @@ filesizeformat.is_safe = True def pluralize(value, arg=u's'): """ - Returns a plural suffix if the value is not 1. By default, 's' is used as + Returns a plural suffix if the value is not 1. By default, 's' is used as the suffix: * If value is 0, vote{{ value|pluralize }} displays "0 votes". diff --git a/django/test/_doctest.py b/django/test/_doctest.py index 3589e16225..d1308b855e 100644 --- a/django/test/_doctest.py +++ b/django/test/_doctest.py @@ -354,8 +354,19 @@ class _OutputRedirectingPdb(pdb.Pdb): """ def __init__(self, out): self.__out = out + self.__debugger_used = False pdb.Pdb.__init__(self) + def set_trace(self): + self.__debugger_used = True + pdb.Pdb.set_trace(self) + + def set_continue(self): + # Calling set_continue unconditionally would break unit test coverage + # reporting, as Bdb.set_continue calls sys.settrace(None). + if self.__debugger_used: + pdb.Pdb.set_continue(self) + def trace_dispatch(self, *args): # Redirect stdout to the given stream. save_stdout = sys.stdout diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index ee156b11d0..25e9421575 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -1,6 +1,6 @@ class MergeDict(object): """ - A simple class for creating new "virtual" dictionaries that actualy look + A simple class for creating new "virtual" dictionaries that actually look up values in more than one dictionary, passed in the constructor. """ def __init__(self, *dicts): @@ -215,7 +215,7 @@ class MultiValueDict(dict): def get(self, key, default=None): """ - Returns the last data value for the passed key. If key doesn't exist + Returns the last data value for the passed key. If key doesn't exist or value is an empty list, then default is returned. """ try: @@ -228,7 +228,7 @@ class MultiValueDict(dict): def getlist(self, key): """ - Returns the list of values for the passed key. If key doesn't exist, + Returns the list of values for the passed key. If key doesn't exist, then an empty list is returned. """ try: diff --git a/django/utils/html.py b/django/utils/html.py index cb786db1e4..34bbf7357f 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -100,7 +100,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): if safe_input: middle = mark_safe(middle) if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \ - len(middle) > 0 and middle[0] in string.letters + string.digits and \ + len(middle) > 0 and middle[0] in string.ascii_letters + string.digits and \ (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))): middle = '%s' % ( urlquote(middle, safe='/&=:;#?+'), nofollow_attr, diff --git a/django/utils/maxlength.py b/django/utils/maxlength.py index 9216fe1c3a..1df6a3e93e 100644 --- a/django/utils/maxlength.py +++ b/django/utils/maxlength.py @@ -21,7 +21,7 @@ def legacy_maxlength(max_length, maxlength): """ if maxlength is not None: warn("maxlength is deprecated, use max_length instead.", - PendingDeprecationWarning, + DeprecationWarning, stacklevel=3) if max_length is not None: raise TypeError("field can not take both the max_length" diff --git a/django/utils/safestring.py b/django/utils/safestring.py index f6f4c1eb2f..2e31c23676 100644 --- a/django/utils/safestring.py +++ b/django/utils/safestring.py @@ -34,16 +34,13 @@ class SafeString(str, SafeData): Concatenating a safe string with another safe string or safe unicode object is safe. Otherwise, the result is no longer safe. """ + t = super(SafeString, self).__add__(rhs) if isinstance(rhs, SafeUnicode): - return SafeUnicode(self + rhs) + return SafeUnicode(t) elif isinstance(rhs, SafeString): - return SafeString(self + rhs) - else: - return super(SafeString, self).__add__(rhs) - - def __str__(self): - return self - + return SafeString(t) + return t + def _proxy_method(self, *args, **kwargs): """ Wrap a call to a normal unicode method up so that we return safe @@ -69,11 +66,11 @@ class SafeUnicode(unicode, SafeData): Concatenating a safe unicode object with another safe string or safe unicode object is safe. Otherwise, the result is no longer safe. """ + t = super(SafeUnicode, self).__add__(rhs) if isinstance(rhs, SafeData): - return SafeUnicode(self + rhs) - else: - return super(SafeUnicode, self).__add__(rhs) - + return SafeUnicode(t) + return t + def _proxy_method(self, *args, **kwargs): """ Wrap a call to a normal unicode method up so that we return safe diff --git a/django/views/debug.py b/django/views/debug.py index 3358d2f08e..fba43912bb 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -131,7 +131,7 @@ def technical_500_response(request, exc_type, exc_value, tb): if start is not None and end is not None: unicode_str = exc_value.args[1] unicode_hint = smart_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace') - + from django import get_version t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') c = Context({ 'exception_type': exc_type.__name__, @@ -144,6 +144,7 @@ def technical_500_response(request, exc_type, exc_value, tb): 'settings': get_safe_settings(), 'sys_executable' : sys.executable, 'sys_version_info' : '%d.%d.%d' % sys.version_info[0:3], + 'django_version_info' : get_version(), 'template_info': template_info, 'template_does_not_exist': template_does_not_exist, 'loader_debug_info': loader_debug_info, @@ -275,6 +276,8 @@ TECHNICAL_500_TEMPLATE = """ #requestinfo h3 { margin-bottom:-1em; } .error { background: #ffc; } .specific { color:#cc3300; font-weight:bold; } + h2 span.commands { font-size:.7em;} + span.commands a:link {color:#5E5694;}