Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[soc2009/multidb] Merged up to trunk r11240.

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11247 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 08ab082480f1beaf8dac5cddd0948a1c26f574d8 1 parent 94e002c
Alex Gaynor alex authored
1  AUTHORS
View
@@ -131,6 +131,7 @@ answer newbie questions, and generally made Django that much better:
Andrew Durdin <adurdin@gmail.com>
dusk@woofle.net
Andy Dustman <farcepest@gmail.com>
+ J. Clifford Dyer <jcd@unc.edu>
Clint Ecker
Nick Efford <nick@efford.org>
eibaan@gmail.com
26 django/contrib/admin/sites.py
View
@@ -114,20 +114,20 @@ def add_action(self, action, name=None):
name = name or action.__name__
self._actions[name] = action
self._global_actions[name] = action
-
+
def disable_action(self, name):
"""
Disable a globally-registered action. Raises KeyError for invalid names.
"""
del self._actions[name]
-
+
def get_action(self, name):
"""
Explicitally get a registered global action wheather it's enabled or
not. Raises KeyError for invalid names.
"""
return self._global_actions[name]
-
+
def actions(self):
"""
Get all the enabled actions as an iterable of (name, func).
@@ -159,9 +159,9 @@ def check_dependencies(self):
if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
- def admin_view(self, view):
+ def admin_view(self, view, cacheable=False):
"""
- Decorator to create an "admin view attached to this ``AdminSite``. This
+ Decorator to create an admin view attached to this ``AdminSite``. This
wraps the view and provides permission checking by calling
``self.has_permission``.
@@ -177,19 +177,25 @@ def get_urls(self):
url(r'^my_view/$', self.admin_view(some_view))
)
return urls
+
+ By default, admin_views are marked non-cacheable using the
+ ``never_cache`` decorator. If the view can be safely cached, set
+ cacheable=True.
"""
def inner(request, *args, **kwargs):
if not self.has_permission(request):
return self.login(request)
return view(request, *args, **kwargs)
+ if not cacheable:
+ inner = never_cache(inner)
return update_wrapper(inner, view)
def get_urls(self):
from django.conf.urls.defaults import patterns, url, include
- def wrap(view):
+ def wrap(view, cacheable=False):
def wrapper(*args, **kwargs):
- return self.admin_view(view)(*args, **kwargs)
+ return self.admin_view(view, cacheable)(*args, **kwargs)
return update_wrapper(wrapper, view)
# Admin-site-wide views.
@@ -201,13 +207,13 @@ def wrapper(*args, **kwargs):
wrap(self.logout),
name='%sadmin_logout'),
url(r'^password_change/$',
- wrap(self.password_change),
+ wrap(self.password_change, cacheable=True),
name='%sadmin_password_change' % self.name),
url(r'^password_change/done/$',
- wrap(self.password_change_done),
+ wrap(self.password_change_done, cacheable=True),
name='%sadmin_password_change_done' % self.name),
url(r'^jsi18n/$',
- wrap(self.i18n_javascript),
+ wrap(self.i18n_javascript, cacheable=True),
name='%sadmin_jsi18n' % self.name),
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
'django.views.defaults.shortcut'),
3  django/contrib/gis/db/models/manager.py
View
@@ -19,6 +19,9 @@ def area(self, *args, **kwargs):
def centroid(self, *args, **kwargs):
return self.get_query_set().centroid(*args, **kwargs)
+ def collect(self, *args, **kwargs):
+ return self.get_query_set().collect(*args, **kwargs)
+
def difference(self, *args, **kwargs):
return self.get_query_set().difference(*args, **kwargs)
8 django/contrib/gis/db/models/query.py
View
@@ -62,6 +62,14 @@ def centroid(self, **kwargs):
"""
return self._geom_attribute('centroid', **kwargs)
+ def collect(self, **kwargs):
+ """
+ Performs an aggregate collect operation on the given geometry field.
+ This is analagous to a union operation, but much faster because
+ boundaries are not dissolved.
+ """
+ return self._spatial_aggregate(aggregates.Collect, **kwargs)
+
def difference(self, geom, **kwargs):
"""
Returns the spatial difference of the geographic field in a `difference`
27 django/contrib/gis/tests/relatedapp/tests.py
View
@@ -1,7 +1,7 @@
import os, unittest
from django.contrib.gis.geos import *
from django.contrib.gis.db.backend import SpatialBackend
-from django.contrib.gis.db.models import Count, Extent, F, Union
+from django.contrib.gis.db.models import Collect, Count, Extent, F, Union
from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_spatialite
from django.conf import settings
from models import City, Location, DirectoryEntry, Parcel, Book, Author
@@ -237,7 +237,7 @@ def test12_count(self):
# as Dallas.
dallas = City.objects.get(name='Dallas')
ftworth = City.objects.create(name='Fort Worth', state='TX', location=dallas.location)
-
+
# Count annotation should be 2 for the Dallas location now.
loc = Location.objects.annotate(num_cities=Count('city')).get(id=dallas.location.id)
self.assertEqual(2, loc.num_cities)
@@ -250,7 +250,7 @@ def test12_count(self):
Book.objects.create(title='Blank Spots on the Map', author=tp)
wp = Author.objects.create(name='William Patry')
Book.objects.create(title='Patry on Copyright', author=wp)
-
+
# Should only be one author (Trevor Paglen) returned by this query, and
# the annotation should have 3 for the number of books.
qs = Author.objects.annotate(num_books=Count('books')).filter(num_books__gt=1)
@@ -264,6 +264,27 @@ def test13_select_related_null_fk(self):
# Should be `None`, and not a 'dummy' model.
self.assertEqual(None, b.author)
+ @no_mysql
+ @no_oracle
+ @no_spatialite
+ def test14_collect(self):
+ "Testing the `collect` GeoQuerySet method and `Collect` aggregate."
+ # Reference query:
+ # SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN
+ # "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
+ # WHERE "relatedapp_city"."state" = 'TX';
+ ref_geom = fromstr('MULTIPOINT(-97.516111 33.058333,-96.801611 32.782057,-95.363151 29.763374,-96.801611 32.782057)')
+
+ c1 = City.objects.filter(state='TX').collect(field_name='location__point')
+ c2 = City.objects.filter(state='TX').aggregate(Collect('location__point'))['location__point__collect']
+
+ for coll in (c1, c2):
+ # Even though Dallas and Ft. Worth share same point, Collect doesn't
+ # consolidate -- that's why 4 points in MultiPoint.
+ self.assertEqual(4, len(coll))
+ self.assertEqual(ref_geom, coll)
+
+
# TODO: Related tests for KML, GML, and distance lookups.
def suite():
13 django/db/backends/oracle/base.py
View
@@ -217,12 +217,13 @@ def sequence_reset_sql(self, style, model_list):
# continue to loop
break
for f in model._meta.many_to_many:
- table_name = self.quote_name(f.m2m_db_table())
- sequence_name = get_sequence_name(f.m2m_db_table())
- column_name = self.quote_name('id')
- output.append(query % {'sequence': sequence_name,
- 'table': table_name,
- 'column': column_name})
+ if not f.rel.through:
+ table_name = self.quote_name(f.m2m_db_table())
+ sequence_name = get_sequence_name(f.m2m_db_table())
+ column_name = self.quote_name('id')
+ output.append(query % {'sequence': sequence_name,
+ 'table': table_name,
+ 'column': column_name})
return output
def start_transaction_sql(self):
17 django/db/backends/postgresql/operations.py
View
@@ -121,14 +121,15 @@ def sequence_reset_sql(self, style, model_list):
style.SQL_TABLE(qn(model._meta.db_table))))
break # Only one AutoField is allowed per model, so don't bother continuing.
for f in model._meta.many_to_many:
- output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
- (style.SQL_KEYWORD('SELECT'),
- style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())),
- style.SQL_FIELD(qn('id')),
- style.SQL_FIELD(qn('id')),
- style.SQL_KEYWORD('IS NOT'),
- style.SQL_KEYWORD('FROM'),
- style.SQL_TABLE(qn(f.m2m_db_table()))))
+ if not f.rel.through:
+ output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
+ (style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())),
+ style.SQL_FIELD(qn('id')),
+ style.SQL_FIELD(qn('id')),
+ style.SQL_KEYWORD('IS NOT'),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_TABLE(qn(f.m2m_db_table()))))
return output
def savepoint_create_sql(self, sid):
2  docs/howto/custom-model-fields.txt
View
@@ -464,7 +464,7 @@ should raise either a ``ValueError`` if the ``value`` is of the wrong sort (a
list when you were expecting an object, for example) or a ``TypeError`` if
your field does not support that type of lookup. For many fields, you can get
by with handling the lookup types that need special handling for your field
-and pass the rest of the :meth:`get_db_prep_lookup` method of the parent class.
+and pass the rest to the :meth:`get_db_prep_lookup` method of the parent class.
If you needed to implement ``get_db_prep_save()``, you will usually need to
implement ``get_db_prep_lookup()``. If you don't, ``get_db_prep_value`` will be
10 docs/howto/error-reporting.txt
View
@@ -23,6 +23,10 @@ administrators immediate notification of any errors. The :setting:`ADMINS` will
get a description of the error, a complete Python traceback, and details about
the HTTP request that caused the error.
+By default, Django will send email from root@localhost. However, some mail
+providers reject all email from this address. To use a different sender
+address, modify the :setting:`SERVER_EMAIL` setting.
+
To disable this behavior, just remove all entries from the :setting:`ADMINS`
setting.
@@ -33,12 +37,12 @@ Django can also be configured to email errors about broken links (404 "page
not found" errors). Django sends emails about 404 errors when:
* :setting:`DEBUG` is ``False``
-
+
* :setting:`SEND_BROKEN_LINK_EMAILS` is ``True``
-
+
* Your :setting:`MIDDLEWARE_CLASSES` setting includes ``CommonMiddleware``
(which it does by default).
-
+
If those conditions are met, Django will e-mail the users listed in the
:setting:`MANAGERS` setting whenever your code raises a 404 and the request has
a referer. (It doesn't bother to e-mail for 404s that don't have a referer --
2  docs/intro/tutorial03.txt
View
@@ -365,7 +365,7 @@ That takes care of setting ``handler404`` in the current module. As you can see
in ``django/conf/urls/defaults.py``, ``handler404`` is set to
:func:`django.views.defaults.page_not_found` by default.
-Three more things to note about 404 views:
+Four more things to note about 404 views:
* If :setting:`DEBUG` is set to ``True`` (in your settings module) then your
404 view will never be used (and thus the ``404.html`` template will never
BIN  docs/ref/contrib/admin/_images/article_actions.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 docs/ref/contrib/admin/index.txt
View
@@ -762,12 +762,19 @@ documented in :ref:`topics-http-urls`::
anything, so you'll usually want to prepend your custom URLs to the built-in
ones.
-Note, however, that the ``self.my_view`` function registered above will *not*
-have any permission check done; it'll be accessible to the general public. Since
-this is usually not what you want, Django provides a convience wrapper to check
-permissions. This wrapper is :meth:`AdminSite.admin_view` (i.e.
-``self.admin_site.admin_view`` inside a ``ModelAdmin`` instance); use it like
-so::
+However, the ``self.my_view`` function registered above suffers from two
+problems:
+
+ * It will *not* perform and permission checks, so it will be accessible to
+ the general public.
+ * It will *not* provide any header details to prevent caching. This means if
+ the page retrieves data from the database, and caching middleware is
+ active, the page could show outdated information.
+
+Since this is usually not what you want, Django provides a convenience wrapper
+to check permissions and mark the view as non-cacheable. This wrapper is
+:meth:`AdminSite.admin_view` (i.e. ``self.admin_site.admin_view`` inside a
+``ModelAdmin`` instance); use it like so::
class MyModelAdmin(admin.ModelAdmin):
def get_urls(self):
@@ -781,7 +788,14 @@ Notice the wrapped view in the fifth line above::
(r'^my_view/$', self.admin_site.admin_view(self.my_view))
-This wrapping will protect ``self.my_view`` from unauthorized access.
+This wrapping will protect ``self.my_view`` from unauthorized access and will
+apply the ``django.views.decorators.cache.never_cache`` decorator to make sure
+it is not cached if the cache middleware is active.
+
+If the page is cacheable, but you still want the permission check to be performed,
+you can pass a ``cacheable=True`` argument to :meth:`AdminSite.admin_view`::
+
+ (r'^my_view/$', self.admin_site.admin_view(self.my_view, cacheable=True))
.. method:: ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)
@@ -849,7 +863,7 @@ provided some extra mapping data that would not otherwise be available::
'osm_data': self.get_osm_info(),
}
return super(MyModelAdmin, self).change_view(request, object_id,
- extra_context=my_context))
+ extra_context=my_context)
``ModelAdmin`` media definitions
--------------------------------
4 docs/ref/contrib/contenttypes.txt
View
@@ -177,9 +177,9 @@ The ``ContentTypeManager``
.. method:: models.ContentTypeManager.clear_cache()
Clears an internal cache used by
- :class:`~django.contrib.contenttypes.models.ContentType>` to keep track
+ :class:`~django.contrib.contenttypes.models.ContentType` to keep track
of which models for which it has created
- :class:`django.contrib.contenttypes.models.ContentType>` instances. You
+ :class:`django.contrib.contenttypes.models.ContentType` instances. You
probably won't ever need to call this method yourself; Django will call
it automatically when it's needed.
43 docs/ref/forms/fields.txt
View
@@ -275,7 +275,7 @@ For each field, we describe the default widget used if you don't specify
* Default widget: ``CheckboxInput``
* Empty value: ``False``
* Normalizes to: A Python ``True`` or ``False`` value.
- * Validates that the check box is checked (i.e. the value is ``True``) if
+ * Validates that the value is ``True`` (e.g. the check box is checked) if
the field has ``required=True``.
* Error message keys: ``required``
@@ -287,9 +287,10 @@ For each field, we describe the default widget used if you don't specify
.. note::
Since all ``Field`` subclasses have ``required=True`` by default, the
- validation condition here is important. If you want to include a checkbox
- in your form that can be either checked or unchecked, you must remember to
- pass in ``required=False`` when creating the ``BooleanField``.
+ validation condition here is important. If you want to include a boolean
+ in your form that can be either ``True`` or ``False`` (e.g. a checked or
+ unchecked checkbox), you must remember to pass in ``required=False`` when
+ creating the ``BooleanField``.
``CharField``
~~~~~~~~~~~~~
@@ -328,7 +329,7 @@ Takes one extra required argument:
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this
field.
-
+
``TypedChoiceField``
~~~~~~~~~~~~~~~~~~~~
@@ -437,7 +438,7 @@ If no ``input_formats`` argument is provided, the default input formats are::
``min_value``, ``max_digits``, ``max_decimal_places``,
``max_whole_digits``
-Takes four optional arguments:
+Takes four optional arguments:
.. attribute:: DecimalField.max_value
.. attribute:: DecimalField.min_value
@@ -449,7 +450,7 @@ Takes four optional arguments:
The maximum number of digits (those before the decimal point plus those
after the decimal point, with leading zeros stripped) permitted in the
value.
-
+
.. attribute:: DecimalField.decimal_places
The maximum number of decimal places permitted.
@@ -522,18 +523,18 @@ extra arguments; only ``path`` is required:
A regular expression pattern; only files with names matching this expression
will be allowed as choices.
-``FloatField``
-~~~~~~~~~~~~~~
-
- * Default widget: ``TextInput``
- * Empty value: ``None``
- * Normalizes to: A Python float.
- * Validates that the given value is an float. Leading and trailing
- whitespace is allowed, as in Python's ``float()`` function.
- * Error message keys: ``required``, ``invalid``, ``max_value``,
- ``min_value``
-
-Takes two optional arguments for validation, ``max_value`` and ``min_value``.
+``FloatField``
+~~~~~~~~~~~~~~
+
+ * Default widget: ``TextInput``
+ * Empty value: ``None``
+ * Normalizes to: A Python float.
+ * Validates that the given value is an float. Leading and trailing
+ whitespace is allowed, as in Python's ``float()`` function.
+ * Error message keys: ``required``, ``invalid``, ``max_value``,
+ ``min_value``
+
+Takes two optional arguments for validation, ``max_value`` and ``min_value``.
These control the range of values permitted in the field.
``ImageField``
@@ -779,10 +780,10 @@ example::
(which is ``"---------"`` by default) with the ``empty_label`` attribute, or
you can disable the empty label entirely by setting ``empty_label`` to
``None``::
-
+
# A custom empty label
field1 = forms.ModelChoiceField(queryset=..., empty_label="(Nothing)")
-
+
# No empty label
field2 = forms.ModelChoiceField(queryset=..., empty_label=None)
2  docs/ref/models/querysets.txt
View
@@ -668,7 +668,7 @@ of the arguments is required, but you should use at least one of them.
The resulting SQL of the above example would be::
- SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id)
+ SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count
FROM blog_blog;
Note that the parenthesis required by most database engines around
49 docs/topics/forms/formsets.txt
View
@@ -86,9 +86,9 @@ displayed.
Formset validation
------------------
-Validation with a formset is about identical to a regular ``Form``. There is
+Validation with a formset is almost identical to a regular ``Form``. There is
an ``is_valid`` method on the formset to provide a convenient way to validate
-each form in the formset::
+all forms in the formset::
>>> ArticleFormSet = formset_factory(ArticleForm)
>>> formset = ArticleFormSet({})
@@ -97,22 +97,25 @@ each form in the formset::
We passed in no data to the formset which is resulting in a valid form. The
formset is smart enough to ignore extra forms that were not changed. If we
-attempt to provide an article, but fail to do so::
+provide an invalid article::
>>> data = {
- ... 'form-TOTAL_FORMS': u'1',
- ... 'form-INITIAL_FORMS': u'1',
+ ... 'form-TOTAL_FORMS': u'2',
+ ... 'form-INITIAL_FORMS': u'0',
... 'form-0-title': u'Test',
- ... 'form-0-pub_date': u'',
+ ... 'form-0-pub_date': u'16 June 1904',
+ ... 'form-1-title': u'Test',
+ ... 'form-1-pub_date': u'', # <-- this date is missing but required
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
- [{'pub_date': [u'This field is required.']}]
+ [{}, {'pub_date': [u'This field is required.']}]
-As we can see the formset properly performed validation and gave us the
-expected errors.
+As we can see, ``formset.errors`` is a list whose entries correspond to the
+forms in the formset. Validation was performed for each of the two forms, and
+the expected error message appears for the second item.
.. _understanding-the-managementform:
@@ -155,20 +158,40 @@ Custom formset validation
~~~~~~~~~~~~~~~~~~~~~~~~~
A formset has a ``clean`` method similar to the one on a ``Form`` class. This
-is where you define your own validation that deals at the formset level::
+is where you define your own validation that works at the formset level::
>>> from django.forms.formsets import BaseFormSet
>>> class BaseArticleFormSet(BaseFormSet):
... def clean(self):
- ... raise forms.ValidationError, u'An error occured.'
+ ... """Checks that no two articles have the same title."""
+ ... if any(self.errors):
+ ... # Don't bother validating the formset unless each form is valid on its own
+ ... return
+ ... titles = []
+ ... for i in range(0, self.total_form_count()):
+ ... form = self.forms[i]
+ ... title = form.cleaned_data['title']
+ ... if title in titles:
+ ... raise forms.ValidationError, "Articles in a set must have distinct titles."
+ ... titles.append(title)
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
- >>> formset = ArticleFormSet({})
+ >>> data = {
+ ... 'form-TOTAL_FORMS': u'2',
+ ... 'form-INITIAL_FORMS': u'0',
+ ... 'form-0-title': u'Test',
+ ... 'form-0-pub_date': u'16 June 1904',
+ ... 'form-1-title': u'Test',
+ ... 'form-1-pub_date': u'23 June 1912',
+ ... }
+ >>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
+ >>> formset.errors
+ [{}, {}]
>>> formset.non_form_errors()
- [u'An error occured.']
+ [u'Articles in a set must have distinct titles.']
The formset ``clean`` method is called after all the ``Form.clean`` methods
have been called. The errors will be found using the ``non_form_errors()``
36 docs/topics/http/urls.txt
View
@@ -40,14 +40,14 @@ algorithm the system follows to determine which Python code to execute:
this is the value of the ``ROOT_URLCONF`` setting, but if the incoming
``HttpRequest`` object has an attribute called ``urlconf``, its value
will be used in place of the ``ROOT_URLCONF`` setting.
-
+
2. Django loads that Python module and looks for the variable
``urlpatterns``. This should be a Python list, in the format returned by
the function ``django.conf.urls.defaults.patterns()``.
-
+
3. Django runs through each URL pattern, in order, and stops at the first
one that matches the requested URL.
-
+
4. Once one of the regexes matches, Django imports and calls the given
view, which is a simple Python function. The view gets passed an
:class:`~django.http.HttpRequest` as its first argument and any values
@@ -263,8 +263,15 @@ value should suffice.
include
-------
-A function that takes a full Python import path to another URLconf that should
-be "included" in this place. See `Including other URLconfs`_ below.
+A function that takes a full Python import path to another URLconf module that
+should be "included" in this place.
+
+.. versionadded:: 1.1
+
+:meth:``include`` also accepts as an argument an iterable that returns URL
+patterns.
+
+See `Including other URLconfs`_ below.
Notes on capturing text in URLs
===============================
@@ -391,6 +398,25 @@ Django encounters ``include()``, it chops off whatever part of the URL matched
up to that point and sends the remaining string to the included URLconf for
further processing.
+.. versionadded:: 1.1
+
+Another posibility is to include additional URL patterns not by specifying the
+URLconf Python module defining them as the `include`_ argument but by using
+directly the pattern list as returned by `patterns`_ instead. For example::
+
+ from django.conf.urls.defaults import *
+
+ extra_patterns = patterns('',
+ url(r'reports/(?P<id>\d+)/$', 'credit.views.report', name='credit-reports'),
+ url(r'charge/$', 'credit.views.charge', name='credit-charge'),
+ )
+
+ urlpatterns = patterns('',
+ url(r'^$', 'apps.main.views.homepage', name='site-homepage'),
+ (r'^help/', include('apps.help.urls')),
+ (r'^credit/', include(extra_patterns)),
+ )
+
.. _`Django Web site`: http://www.djangoproject.com/
Captured parameters
8 docs/topics/i18n.txt
View
@@ -959,11 +959,11 @@ Using the JavaScript translation catalog
To use the catalog, just pull in the dynamically generated script like this::
- <script type="text/javascript" src="/path/to/jsi18n/"></script>
+ <script type="text/javascript" src="{% url django.views.i18n.javascript_catalog %}"></script>
-This is how the admin fetches the translation catalog from the server. When the
-catalog is loaded, your JavaScript code can use the standard ``gettext``
-interface to access it::
+This uses reverse URL lookup to find the URL of the JavaScript catalog view.
+When the catalog is loaded, your JavaScript code can use the standard
+``gettext`` interface to access it::
document.write(gettext('this is to be translated'));
73 tests/regressiontests/admin_views/tests.py
View
@@ -10,6 +10,7 @@
from django.contrib.admin.sites import LOGIN_FORM_KEY
from django.contrib.admin.util import quote
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
+from django.utils.cache import get_max_age
from django.utils.html import escape
# local test models
@@ -1527,3 +1528,75 @@ def test_ordered_inline(self):
self.failUnlessEqual(Category.objects.get(id=2).order, 13)
self.failUnlessEqual(Category.objects.get(id=3).order, 1)
self.failUnlessEqual(Category.objects.get(id=4).order, 0)
+
+
+class NeverCacheTests(TestCase):
+ fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def testAdminIndex(self):
+ "Check the never-cache status of the main index"
+ response = self.client.get('/test_admin/admin/')
+ self.failUnlessEqual(get_max_age(response), 0)
+
+ def testAppIndex(self):
+ "Check the never-cache status of an application index"
+ response = self.client.get('/test_admin/admin/admin_views/')
+ self.failUnlessEqual(get_max_age(response), 0)
+
+ def testModelIndex(self):
+ "Check the never-cache status of a model index"
+ response = self.client.get('/test_admin/admin/admin_views/fabric/')
+ self.failUnlessEqual(get_max_age(response), 0)
+
+ def testModelAdd(self):
+ "Check the never-cache status of a model add page"
+ response = self.client.get('/test_admin/admin/admin_views/fabric/add/')
+ self.failUnlessEqual(get_max_age(response), 0)
+
+ def testModelView(self):
+ "Check the never-cache status of a model edit page"
+ response = self.client.get('/test_admin/admin/admin_views/section/1/')
+ self.failUnlessEqual(get_max_age(response), 0)
+
+ def testModelHistory(self):
+ "Check the never-cache status of a model history page"
+ response = self.client.get('/test_admin/admin/admin_views/section/1/history/')
+ self.failUnlessEqual(get_max_age(response), 0)
+
+ def testModelDelete(self):
+ "Check the never-cache status of a model delete page"
+ response = self.client.get('/test_admin/admin/admin_views/section/1/delete/')
+ self.failUnlessEqual(get_max_age(response), 0)
+
+ def testLogin(self):
+ "Check the never-cache status of login views"
+ self.client.logout()
+ response = self.client.get('/test_admin/admin/')
+ self.failUnlessEqual(get_max_age(response), 0)
+
+ def testLogout(self):
+ "Check the never-cache status of logout view"
+ response = self.client.get('/test_admin/admin/logout/')
+ self.failUnlessEqual(get_max_age(response), 0)
+
+ def testPasswordChange(self):
+ "Check the never-cache status of the password change view"
+ self.client.logout()
+ response = self.client.get('/test_admin/password_change/')
+ self.failUnlessEqual(get_max_age(response), None)
+
+ def testPasswordChangeDone(self):
+ "Check the never-cache status of the password change done view"
+ response = self.client.get('/test_admin/admin/password_change/done/')
+ self.failUnlessEqual(get_max_age(response), None)
+
+ def testJsi18n(self):
+ "Check the never-cache status of the Javascript i18n view"
+ response = self.client.get('/test_admin/jsi18n/')
+ self.failUnlessEqual(get_max_age(response), None)
34 tests/regressiontests/m2m_through_regress/fixtures/m2m_through.json
View
@@ -0,0 +1,34 @@
+[
+ {
+ "pk": "1",
+ "model": "m2m_through_regress.person",
+ "fields": {
+ "name": "Guido"
+ }
+ },
+ {
+ "pk": "1",
+ "model": "auth.user",
+ "fields": {
+ "username": "Guido",
+ "email": "bdfl@python.org",
+ "password": "abcde"
+ }
+ },
+ {
+ "pk": "1",
+ "model": "m2m_through_regress.group",
+ "fields": {
+ "name": "Python Core Group"
+ }
+ },
+ {
+ "pk": "1",
+ "model": "m2m_through_regress.usermembership",
+ "fields": {
+ "user": "1",
+ "group": "1",
+ "price": "100"
+ }
+ }
+]
10 tests/regressiontests/m2m_through_regress/models.py
View
@@ -12,7 +12,9 @@ class Membership(models.Model):
def __unicode__(self):
return "%s is a member of %s" % (self.person.name, self.group.name)
+# using custom id column to test ticket #11107
class UserMembership(models.Model):
+ id = models.AutoField(db_column='usermembership_id', primary_key=True)
user = models.ForeignKey(User)
group = models.ForeignKey('Group')
price = models.IntegerField(default=100)
@@ -196,4 +198,12 @@ class B(models.Model):
# Flush the database, just to make sure we can.
>>> management.call_command('flush', verbosity=0, interactive=False)
+## Regression test for #11107
+Ensure that sequences on m2m_through tables are being created for the through
+model, not for a phantom auto-generated m2m table.
+
+>>> management.call_command('loaddata', 'm2m_through', verbosity=0)
+>>> management.call_command('dumpdata', 'm2m_through_regress', format='json')
+[{"pk": 1, "model": "m2m_through_regress.usermembership", "fields": {"price": 100, "group": 1, "user": 1}}, {"pk": 1, "model": "m2m_through_regress.person", "fields": {"name": "Guido"}}, {"pk": 1, "model": "m2m_through_regress.group", "fields": {"name": "Python Core Group"}}]
+
"""}
Please sign in to comment.
Something went wrong with that request. Please try again.