From 7ed1a919681b5b028c3f6eef1a862ff91b66feb2 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 3 Jul 2007 16:03:51 +0000 Subject: [PATCH] newforms-admin: Merged to [5595] git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@5596 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/contrib/auth/models.py | 2 +- django/contrib/sessions/middleware.py | 1 + django/contrib/sessions/tests.py | 16 ++++++++ django/core/mail.py | 19 ++++----- django/core/management.py | 6 +++ django/newforms/fields.py | 13 ++++--- django/newforms/widgets.py | 34 ++++++++++++----- django/views/defaults.py | 5 ++- tests/modeltests/lookup/models.py | 55 ++++++++++++++------------- 10 files changed, 99 insertions(+), 53 deletions(-) diff --git a/AUTHORS b/AUTHORS index 35ad3b7ef3..d78dabbb4b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -235,6 +235,7 @@ answer newbie questions, and generally made Django that much better: Joe Topjian torne-django@wolfpuppy.org.uk Karen Tracey + tstromberg@google.com Makoto Tsuyuki tt@gurgle.no Amit Upadhyay diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index da24575185..1b3305665a 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -267,7 +267,7 @@ class AnonymousUser(object): pass def __str__(self): - return _('AnonymousUser') + return 'AnonymousUser' def __eq__(self, other): return isinstance(other, self.__class__) diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 434997d616..82b217f829 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -37,6 +37,7 @@ class SessionWrapper(object): return self._session.get(key, default) def pop(self, key, *args): + self.modified = self.modified or key in self._session return self._session.pop(key, *args) def set_test_cookie(self): diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 5a28effa86..e83442123e 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -5,8 +5,24 @@ Inject data into the session cache. >>> s._session_cache = {} >>> s._session_cache['some key'] = 'exists' +>>> s.accessed +False +>>> s.modified +False + +>>> s.pop('non existant key', 'does not exist') +'does not exist' +>>> s.accessed +True +>>> s.modified +False + >>> s.pop('some key') 'exists' +>>> s.accessed +True +>>> s.modified +True >>> s.pop('some key', 'does not exist') 'does not exist' diff --git a/django/core/mail.py b/django/core/mail.py index e9d8a150a1..2f252df724 100644 --- a/django/core/mail.py +++ b/django/core/mail.py @@ -62,22 +62,23 @@ def make_msgid(idstring=None): class BadHeaderError(ValueError): pass -class SafeHeaderMixin(object): +class SafeMIMEText(MIMEText): def __setitem__(self, name, val): "Forbids multi-line headers, to prevent header injection." if '\n' in val or '\r' in val: raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name) if name == "Subject": val = Header(val, settings.DEFAULT_CHARSET) - # Note: using super() here is safe; any __setitem__ overrides must use - # the same argument signature. - super(SafeHeaderMixin, self).__setitem__(name, val) + MIMEText.__setitem__(self, name, val) -class SafeMIMEText(MIMEText, SafeHeaderMixin): - pass - -class SafeMIMEMultipart(MIMEMultipart, SafeHeaderMixin): - pass +class SafeMIMEMultipart(MIMEMultipart): + def __setitem__(self, name, val): + "Forbids multi-line headers, to prevent header injection." + if '\n' in val or '\r' in val: + raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name) + if name == "Subject": + val = Header(val, settings.DEFAULT_CHARSET) + MIMEMultipart.__setitem__(self, name, val) class SMTPConnection(object): """ diff --git a/django/core/management.py b/django/core/management.py index 1c049a790b..c6107e90e3 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -832,9 +832,15 @@ def startproject(project_name, directory): sys.stderr.write(style.ERROR("Error: '%r' conflicts with the name of an existing Python module and cannot be used as a project name. Please try another name.\n" % project_name)) sys.exit(1) _start_helper('project', project_name, directory) + # Create a random SECRET_KEY hash, and put it in the main settings. main_settings_file = os.path.join(directory, project_name, 'settings.py') settings_contents = open(main_settings_file, 'r').read() + + # If settings.py was copied from a read-only source, make it writeable. + if not os.access(main_settings_file, os.W_OK): + os.chmod(main_settings_file, 0600) + fp = open(main_settings_file, 'w') secret_key = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) settings_contents = re.sub(r"(?<=SECRET_KEY = ')'", secret_key + "'", settings_contents) diff --git a/django/newforms/fields.py b/django/newforms/fields.py index 167281a061..cec2ae8317 100644 --- a/django/newforms/fields.py +++ b/django/newforms/fields.py @@ -482,17 +482,18 @@ class ComboField(Field): class MultiValueField(Field): """ - A Field that is composed of multiple Fields. - - Its clean() method takes a "decompressed" list of values. Each value in + A Field that aggregates the logic of multiple Fields. + + Its clean() method takes a "decompressed" list of values, which are then + cleaned into a single value according to self.fields. Each value in this list is cleaned by the corresponding field -- the first value is cleaned by the first field, the second value is cleaned by the second field, etc. Once all fields are cleaned, the list of clean values is "compressed" into a single value. - Subclasses should implement compress(), which specifies how a list of - valid values should be converted to a single value. Subclasses should not - have to implement clean(). + Subclasses should not have to implement clean(). Instead, they must + implement compress(), which takes a list of valid values and returns a + "compressed" version of those values -- a single value. You'll probably want to use this with MultiWidget. """ diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py index d4b5f596d1..ecd6dcc9d3 100644 --- a/django/newforms/widgets.py +++ b/django/newforms/widgets.py @@ -304,19 +304,28 @@ class MultiWidget(Widget): """ A widget that is composed of multiple widgets. - Its render() method takes a "decompressed" list of values, not a single - value. Each value in this list is rendered in the corresponding widget -- - the first value is rendered in the first widget, the second value is - rendered in the second widget, etc. + Its render() method is different than other widgets', because it has to + figure out how to split a single value for display in multiple widgets. + The ``value`` argument can be one of two things: - Subclasses should implement decompress(), which specifies how a single - value should be converted to a list of values. Subclasses should not - have to implement clean(). + * A list. + * A normal value (e.g., a string) that has been "compressed" from + a list of values. + + In the second case -- i.e., if the value is NOT a list -- render() will + first "decompress" the value into a list before rendering it. It does so by + calling the decompress() method, which MultiWidget subclasses must + implement. This method takes a single "compressed" value and returns a + list. + + When render() does its HTML rendering, each value in the list is rendered + with the corresponding widget -- the first value is rendered in the first + widget, the second value is rendered in the second widget, etc. Subclasses may implement format_output(), which takes the list of rendered - widgets and returns HTML that formats them any way you'd like. + widgets and returns a string of HTML that formats them any way you'd like. - You'll probably want to use this with MultiValueField. + You'll probably want to use this class with MultiValueField. """ def __init__(self, widgets, attrs=None): self.widgets = [isinstance(w, type) and w() or w for w in widgets] @@ -351,6 +360,13 @@ class MultiWidget(Widget): return [widget.value_from_datadict(data, name + '_%s' % i) for i, widget in enumerate(self.widgets)] def format_output(self, rendered_widgets): + """ + Given a list of rendered widgets (as strings), returns a Unicode string + representing the HTML for the whole lot. + + This hook allows you to format the HTML design of the widgets, if + needed. + """ return u''.join(rendered_widgets) def decompress(self, value): diff --git a/django/views/defaults.py b/django/views/defaults.py index 701aebabd6..b4dfc1e1eb 100644 --- a/django/views/defaults.py +++ b/django/views/defaults.py @@ -21,7 +21,7 @@ def shortcut(request, content_type_id, object_id): # if necessary. # If the object actually defines a domain, we're done. - if absurl.startswith('http://'): + if absurl.startswith('http://') or absurl.startswith('https://'): return http.HttpResponseRedirect(absurl) object_domain = None @@ -61,7 +61,8 @@ def shortcut(request, content_type_id, object_id): # If all that malarkey found an object domain, use it; otherwise fall back # to whatever get_absolute_url() returned. if object_domain is not None: - return http.HttpResponseRedirect('http://%s%s' % (object_domain, absurl)) + protocol = request.is_secure() and 'https' or 'http' + return http.HttpResponseRedirect('%s://%s%s' % (protocol, object_domain, absurl)) else: return http.HttpResponseRedirect(absurl) diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index 60ed6f7685..9d3e8ca4b4 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -5,6 +5,7 @@ This demonstrates features of the database API. """ from django.db import models +from django.conf import settings class Article(models.Model): headline = models.CharField(maxlength=100) @@ -263,14 +264,14 @@ TypeError: Cannot resolve keyword 'headline__starts' into field. Choices are: id >>> a3.save() >>> a4 = Article(pub_date=now, headline='fooo') >>> a4.save() ->>> a5 = Article(pub_date=now, headline='Foo') +>>> a5 = Article(pub_date=now, headline='hey-Foo') >>> a5.save() # zero-or-more >>> Article.objects.filter(headline__regex=r'fo*') [, , , ] >>> Article.objects.filter(headline__iregex=r'fo*') -[, , , , ] +[, , , , ] # one-or-more >>> Article.objects.filter(headline__regex=r'fo+') @@ -283,39 +284,39 @@ TypeError: Cannot resolve keyword 'headline__starts' into field. Choices are: id # and some more: >>> a6 = Article(pub_date=now, headline='bar') >>> a6.save() ->>> a7 = Article(pub_date=now, headline='Bar') +>>> a7 = Article(pub_date=now, headline='AbBa') >>> a7.save() >>> a8 = Article(pub_date=now, headline='baz') >>> a8.save() ->>> a9 = Article(pub_date=now, headline='baZ') +>>> a9 = Article(pub_date=now, headline='baxZ') >>> a9.save() # leading anchor >>> Article.objects.filter(headline__regex=r'^b') -[, , ] ->>> Article.objects.filter(headline__iregex=r'^b') -[, , , ] +[, , ] +>>> Article.objects.filter(headline__iregex=r'^a') +[] # trailing anchor >>> Article.objects.filter(headline__regex=r'z$') [] >>> Article.objects.filter(headline__iregex=r'z$') -[, ] +[, ] # character sets >>> Article.objects.filter(headline__regex=r'ba[rz]') [, ] ->>> Article.objects.filter(headline__regex=r'ba[RZ]') -[] ->>> Article.objects.filter(headline__iregex=r'ba[RZ]') -[, , , ] +>>> Article.objects.filter(headline__regex=r'ba.[RxZ]') +[] +>>> Article.objects.filter(headline__iregex=r'ba[RxZ]') +[, , ] # and yet more: >>> a10 = Article(pub_date=now, headline='foobar') >>> a10.save() >>> a11 = Article(pub_date=now, headline='foobaz') >>> a11.save() ->>> a12 = Article(pub_date=now, headline='FooBarBaz') +>>> a12 = Article(pub_date=now, headline='ooF') >>> a12.save() >>> a13 = Article(pub_date=now, headline='foobarbaz') >>> a13.save() @@ -323,26 +324,28 @@ TypeError: Cannot resolve keyword 'headline__starts' into field. Choices are: id >>> a14.save() >>> a15 = Article(pub_date=now, headline='barfoobaz') >>> a15.save() ->>> a16 = Article(pub_date=now, headline='BAZBARFOO') +>>> a16 = Article(pub_date=now, headline='bazbaRFOO') >>> a16.save() # alternation ->>> Article.objects.filter(headline__regex=r'foo(bar|baz)') +>>> Article.objects.filter(headline__regex=r'oo(f|b)') [, , , ] ->>> Article.objects.filter(headline__iregex=r'foo(bar|baz)') -[, , , , ] ->>> Article.objects.filter(headline__regex=r'^foo(bar|baz)') +>>> Article.objects.filter(headline__iregex=r'oo(f|b)') +[, , , , ] +>>> Article.objects.filter(headline__regex=r'^foo(f|b)') [, , ] # greedy matching ->>> Article.objects.filter(headline__regex=r'f.*z') -[, , , ] ->>> Article.objects.filter(headline__iregex=r'f.*z') -[, , , , ] +>>> Article.objects.filter(headline__regex=r'b.*az') +[, , , , ] +>>> Article.objects.filter(headline__iregex=r'b.*ar') +[, , , , ] +"""} + +if settings.DATABASE_ENGINE not in ('mysql', 'mysql_old'): + __test__['API_TESTS'] += r""" # grouping and backreferences >>> Article.objects.filter(headline__regex=r'b(.).*b\1') -[, ] ->>> Article.objects.filter(headline__iregex=r'b(.).*b\1') -[, , , ] -"""} +[, , ] +"""