mirror of
https://github.com/django/django.git
synced 2025-07-05 18:29:11 +00:00
[gsoc2009-testing] Merging from trunk, some tests no longer pass
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/test-improvements@11291 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
commit
b162138e4c
1
AUTHORS
1
AUTHORS
@ -131,6 +131,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Andrew Durdin <adurdin@gmail.com>
|
Andrew Durdin <adurdin@gmail.com>
|
||||||
dusk@woofle.net
|
dusk@woofle.net
|
||||||
Andy Dustman <farcepest@gmail.com>
|
Andy Dustman <farcepest@gmail.com>
|
||||||
|
J. Clifford Dyer <jcd@unc.edu>
|
||||||
Clint Ecker
|
Clint Ecker
|
||||||
Nick Efford <nick@efford.org>
|
Nick Efford <nick@efford.org>
|
||||||
eibaan@gmail.com
|
eibaan@gmail.com
|
||||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -62,6 +62,14 @@ class GeoQuerySet(QuerySet):
|
|||||||
"""
|
"""
|
||||||
return self._geom_attribute('centroid', **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):
|
def difference(self, geom, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns the spatial difference of the geographic field in a `difference`
|
Returns the spatial difference of the geographic field in a `difference`
|
||||||
|
@ -8,6 +8,9 @@ from django.contrib.gis.gdal.prototypes.errcheck import \
|
|||||||
check_arg_errcode, check_errcode, check_geom, check_geom_offset, \
|
check_arg_errcode, check_errcode, check_geom, check_geom_offset, \
|
||||||
check_pointer, check_srs, check_str_arg, check_string, check_const_string
|
check_pointer, check_srs, check_str_arg, check_string, check_const_string
|
||||||
|
|
||||||
|
class gdal_char_p(c_char_p):
|
||||||
|
pass
|
||||||
|
|
||||||
def double_output(func, argtypes, errcheck=False, strarg=False):
|
def double_output(func, argtypes, errcheck=False, strarg=False):
|
||||||
"Generates a ctypes function that returns a double value."
|
"Generates a ctypes function that returns a double value."
|
||||||
func.argtypes = argtypes
|
func.argtypes = argtypes
|
||||||
@ -77,9 +80,9 @@ def string_output(func, argtypes, offset=-1, str_result=False):
|
|||||||
"""
|
"""
|
||||||
func.argtypes = argtypes
|
func.argtypes = argtypes
|
||||||
if str_result:
|
if str_result:
|
||||||
# String is the result, don't explicitly define
|
# Use subclass of c_char_p so the error checking routine
|
||||||
# the argument type so we can get the pointer.
|
# can free the memory at the pointer's address.
|
||||||
pass
|
func.restype = gdal_char_p
|
||||||
else:
|
else:
|
||||||
# Error code is returned
|
# Error code is returned
|
||||||
func.restype = c_int
|
func.restype = c_int
|
||||||
|
@ -10,6 +10,7 @@ __all__ = ['geos_boundary', 'geos_buffer', 'geos_centroid', 'geos_convexhull',
|
|||||||
from ctypes import c_char_p, c_double, c_int
|
from ctypes import c_char_p, c_double, c_int
|
||||||
from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, GEOS_PREPARE
|
from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, GEOS_PREPARE
|
||||||
from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string
|
from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string
|
||||||
|
from django.contrib.gis.geos.prototypes.geom import geos_char_p
|
||||||
|
|
||||||
def topology(func, *args):
|
def topology(func, *args):
|
||||||
"For GEOS unary topology functions."
|
"For GEOS unary topology functions."
|
||||||
@ -38,6 +39,7 @@ geos_union = topology(lgeos.GEOSUnion, GEOM_PTR)
|
|||||||
# GEOSRelate returns a string, not a geometry.
|
# GEOSRelate returns a string, not a geometry.
|
||||||
geos_relate = lgeos.GEOSRelate
|
geos_relate = lgeos.GEOSRelate
|
||||||
geos_relate.argtypes = [GEOM_PTR, GEOM_PTR]
|
geos_relate.argtypes = [GEOM_PTR, GEOM_PTR]
|
||||||
|
geos_relate.restype = geos_char_p
|
||||||
geos_relate.errcheck = check_string
|
geos_relate.errcheck = check_string
|
||||||
|
|
||||||
# Routines only in GEOS 3.1+
|
# Routines only in GEOS 3.1+
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import os, unittest
|
import os, unittest
|
||||||
from django.contrib.gis.geos import *
|
from django.contrib.gis.geos import *
|
||||||
from django.contrib.gis.db.backend import SpatialBackend
|
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.contrib.gis.tests.utils import no_mysql, no_oracle, no_spatialite
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from models import City, Location, DirectoryEntry, Parcel, Book, Author
|
from models import City, Location, DirectoryEntry, Parcel, Book, Author
|
||||||
@ -264,6 +264,26 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||||||
# Should be `None`, and not a 'dummy' model.
|
# Should be `None`, and not a 'dummy' model.
|
||||||
self.assertEqual(None, b.author)
|
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.
|
# TODO: Related tests for KML, GML, and distance lookups.
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
|
@ -185,7 +185,11 @@ class RegexURLResolver(object):
|
|||||||
try:
|
try:
|
||||||
sub_match = pattern.resolve(new_path)
|
sub_match = pattern.resolve(new_path)
|
||||||
except Resolver404, e:
|
except Resolver404, e:
|
||||||
tried.extend([(pattern.regex.pattern + ' ' + t) for t in e.args[0]['tried']])
|
sub_tried = e.args[0].get('tried')
|
||||||
|
if sub_tried is not None:
|
||||||
|
tried.extend([(pattern.regex.pattern + ' ' + t) for t in sub_tried])
|
||||||
|
else:
|
||||||
|
tried.append(pattern.regex.pattern)
|
||||||
else:
|
else:
|
||||||
if sub_match:
|
if sub_match:
|
||||||
sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()])
|
sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()])
|
||||||
@ -195,7 +199,7 @@ class RegexURLResolver(object):
|
|||||||
return sub_match[0], sub_match[1], sub_match_dict
|
return sub_match[0], sub_match[1], sub_match_dict
|
||||||
tried.append(pattern.regex.pattern)
|
tried.append(pattern.regex.pattern)
|
||||||
raise Resolver404, {'tried': tried, 'path': new_path}
|
raise Resolver404, {'tried': tried, 'path': new_path}
|
||||||
raise Resolver404, {'tried': [], 'path' : path}
|
raise Resolver404, {'path' : path}
|
||||||
|
|
||||||
def _get_urlconf_module(self):
|
def _get_urlconf_module(self):
|
||||||
try:
|
try:
|
||||||
|
@ -217,6 +217,7 @@ WHEN (new.%(col_name)s IS NULL)
|
|||||||
# continue to loop
|
# continue to loop
|
||||||
break
|
break
|
||||||
for f in model._meta.many_to_many:
|
for f in model._meta.many_to_many:
|
||||||
|
if not f.rel.through:
|
||||||
table_name = self.quote_name(f.m2m_db_table())
|
table_name = self.quote_name(f.m2m_db_table())
|
||||||
sequence_name = get_sequence_name(f.m2m_db_table())
|
sequence_name = get_sequence_name(f.m2m_db_table())
|
||||||
column_name = self.quote_name('id')
|
column_name = self.quote_name('id')
|
||||||
|
@ -121,6 +121,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||||||
style.SQL_TABLE(qn(model._meta.db_table))))
|
style.SQL_TABLE(qn(model._meta.db_table))))
|
||||||
break # Only one AutoField is allowed per model, so don't bother continuing.
|
break # Only one AutoField is allowed per model, so don't bother continuing.
|
||||||
for f in model._meta.many_to_many:
|
for f in model._meta.many_to_many:
|
||||||
|
if not f.rel.through:
|
||||||
output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
|
output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
|
||||||
(style.SQL_KEYWORD('SELECT'),
|
(style.SQL_KEYWORD('SELECT'),
|
||||||
style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())),
|
style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())),
|
||||||
|
@ -464,8 +464,21 @@ class BaseModelFormSet(BaseFormSet):
|
|||||||
return len(self.get_queryset())
|
return len(self.get_queryset())
|
||||||
return super(BaseModelFormSet, self).initial_form_count()
|
return super(BaseModelFormSet, self).initial_form_count()
|
||||||
|
|
||||||
|
def _existing_object(self, pk):
|
||||||
|
if not hasattr(self, '_object_dict'):
|
||||||
|
self._object_dict = dict([(o.pk, o) for o in self.get_queryset()])
|
||||||
|
return self._object_dict.get(pk)
|
||||||
|
|
||||||
def _construct_form(self, i, **kwargs):
|
def _construct_form(self, i, **kwargs):
|
||||||
if i < self.initial_form_count():
|
if self.is_bound and i < self.initial_form_count():
|
||||||
|
pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name)
|
||||||
|
pk = self.data[pk_key]
|
||||||
|
pk_field = self.model._meta.pk
|
||||||
|
pk = pk_field.get_db_prep_lookup('exact', pk)
|
||||||
|
if isinstance(pk, list):
|
||||||
|
pk = pk[0]
|
||||||
|
kwargs['instance'] = self._existing_object(pk)
|
||||||
|
if i < self.initial_form_count() and not kwargs.get('instance'):
|
||||||
kwargs['instance'] = self.get_queryset()[i]
|
kwargs['instance'] = self.get_queryset()[i]
|
||||||
return super(BaseModelFormSet, self)._construct_form(i, **kwargs)
|
return super(BaseModelFormSet, self)._construct_form(i, **kwargs)
|
||||||
|
|
||||||
@ -604,10 +617,6 @@ class BaseModelFormSet(BaseFormSet):
|
|||||||
if not self.get_queryset():
|
if not self.get_queryset():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Put the objects from self.get_queryset into a dict so they are easy to lookup by pk
|
|
||||||
existing_objects = {}
|
|
||||||
for obj in self.get_queryset():
|
|
||||||
existing_objects[obj.pk] = obj
|
|
||||||
saved_instances = []
|
saved_instances = []
|
||||||
for form in self.initial_forms:
|
for form in self.initial_forms:
|
||||||
pk_name = self._pk_field.name
|
pk_name = self._pk_field.name
|
||||||
@ -618,7 +627,7 @@ class BaseModelFormSet(BaseFormSet):
|
|||||||
pk_value = form.fields[pk_name].clean(raw_pk_value)
|
pk_value = form.fields[pk_name].clean(raw_pk_value)
|
||||||
pk_value = getattr(pk_value, 'pk', pk_value)
|
pk_value = getattr(pk_value, 'pk', pk_value)
|
||||||
|
|
||||||
obj = existing_objects[pk_value]
|
obj = self._existing_object(pk_value)
|
||||||
if self.can_delete:
|
if self.can_delete:
|
||||||
raw_delete_value = form._raw_value(DELETION_FIELD_NAME)
|
raw_delete_value = form._raw_value(DELETION_FIELD_NAME)
|
||||||
should_delete = form.fields[DELETION_FIELD_NAME].clean(raw_delete_value)
|
should_delete = form.fields[DELETION_FIELD_NAME].clean(raw_delete_value)
|
||||||
@ -663,6 +672,9 @@ class BaseModelFormSet(BaseFormSet):
|
|||||||
return ((not pk.editable) or (pk.auto_created or isinstance(pk, AutoField))
|
return ((not pk.editable) or (pk.auto_created or isinstance(pk, AutoField))
|
||||||
or (pk.rel and pk.rel.parent_link and pk_is_not_editable(pk.rel.to._meta.pk)))
|
or (pk.rel and pk.rel.parent_link and pk_is_not_editable(pk.rel.to._meta.pk)))
|
||||||
if pk_is_not_editable(pk) or pk.name not in form.fields:
|
if pk_is_not_editable(pk) or pk.name not in form.fields:
|
||||||
|
if form.is_bound:
|
||||||
|
pk_value = form.instance.pk
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
pk_value = self.get_queryset()[index].pk
|
pk_value = self.get_queryset()[index].pk
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
@ -564,7 +564,7 @@ do_filter = register.tag("filter", do_filter)
|
|||||||
#@register.tag
|
#@register.tag
|
||||||
def firstof(parser, token):
|
def firstof(parser, token):
|
||||||
"""
|
"""
|
||||||
Outputs the first variable passed that is not False.
|
Outputs the first variable passed that is not False, without escaping.
|
||||||
|
|
||||||
Outputs nothing if all the passed variables are False.
|
Outputs nothing if all the passed variables are False.
|
||||||
|
|
||||||
@ -575,11 +575,11 @@ def firstof(parser, token):
|
|||||||
This is equivalent to::
|
This is equivalent to::
|
||||||
|
|
||||||
{% if var1 %}
|
{% if var1 %}
|
||||||
{{ var1 }}
|
{{ var1|safe }}
|
||||||
{% else %}{% if var2 %}
|
{% else %}{% if var2 %}
|
||||||
{{ var2 }}
|
{{ var2|safe }}
|
||||||
{% else %}{% if var3 %}
|
{% else %}{% if var3 %}
|
||||||
{{ var3 }}
|
{{ var3|safe }}
|
||||||
{% endif %}{% endif %}{% endif %}
|
{% endif %}{% endif %}{% endif %}
|
||||||
|
|
||||||
but obviously much cleaner!
|
but obviously much cleaner!
|
||||||
@ -589,6 +589,12 @@ def firstof(parser, token):
|
|||||||
|
|
||||||
{% firstof var1 var2 var3 "fallback value" %}
|
{% firstof var1 var2 var3 "fallback value" %}
|
||||||
|
|
||||||
|
If you want to escape the output, use a filter tag::
|
||||||
|
|
||||||
|
{% filter force_escape %}
|
||||||
|
{% firstof var1 var2 var3 "fallback value" %}
|
||||||
|
{% endfilter %}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
bits = token.split_contents()[1:]
|
bits = token.split_contents()[1:]
|
||||||
if len(bits) < 1:
|
if len(bits) < 1:
|
||||||
|
@ -6,10 +6,16 @@ import docutils.nodes
|
|||||||
import docutils.transforms
|
import docutils.transforms
|
||||||
import sphinx
|
import sphinx
|
||||||
import sphinx.addnodes
|
import sphinx.addnodes
|
||||||
import sphinx.builder
|
try:
|
||||||
|
from sphinx import builders
|
||||||
|
except ImportError:
|
||||||
|
import sphinx.builder as builders
|
||||||
import sphinx.directives
|
import sphinx.directives
|
||||||
import sphinx.environment
|
import sphinx.environment
|
||||||
import sphinx.htmlwriter
|
try:
|
||||||
|
import sphinx.writers.html as sphinx_htmlwriter
|
||||||
|
except ImportError:
|
||||||
|
import sphinx.htmlwriter as sphinx_htmlwriter
|
||||||
import sphinx.roles
|
import sphinx.roles
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
|
|
||||||
@ -44,7 +50,7 @@ def setup(app):
|
|||||||
directivename = "django-admin-option",
|
directivename = "django-admin-option",
|
||||||
rolename = "djadminopt",
|
rolename = "djadminopt",
|
||||||
indextemplate = "pair: %s; django-admin command-line option",
|
indextemplate = "pair: %s; django-admin command-line option",
|
||||||
parse_node = lambda env, sig, signode: sphinx.directives.parse_option_desc(signode, sig),
|
parse_node = parse_django_adminopt_node,
|
||||||
)
|
)
|
||||||
app.add_config_value('django_next_version', '0.0', True)
|
app.add_config_value('django_next_version', '0.0', True)
|
||||||
app.add_directive('versionadded', parse_version_directive, 1, (1, 1, 1))
|
app.add_directive('versionadded', parse_version_directive, 1, (1, 1, 1))
|
||||||
@ -102,7 +108,7 @@ class SuppressBlockquotes(docutils.transforms.Transform):
|
|||||||
if len(node.children) == 1 and isinstance(node.children[0], self.suppress_blockquote_child_nodes):
|
if len(node.children) == 1 and isinstance(node.children[0], self.suppress_blockquote_child_nodes):
|
||||||
node.replace_self(node.children[0])
|
node.replace_self(node.children[0])
|
||||||
|
|
||||||
class DjangoHTMLTranslator(sphinx.htmlwriter.SmartyPantsHTMLTranslator):
|
class DjangoHTMLTranslator(sphinx_htmlwriter.SmartyPantsHTMLTranslator):
|
||||||
"""
|
"""
|
||||||
Django-specific reST to HTML tweaks.
|
Django-specific reST to HTML tweaks.
|
||||||
"""
|
"""
|
||||||
@ -125,10 +131,10 @@ class DjangoHTMLTranslator(sphinx.htmlwriter.SmartyPantsHTMLTranslator):
|
|||||||
#
|
#
|
||||||
def visit_literal_block(self, node):
|
def visit_literal_block(self, node):
|
||||||
self.no_smarty += 1
|
self.no_smarty += 1
|
||||||
sphinx.htmlwriter.SmartyPantsHTMLTranslator.visit_literal_block(self, node)
|
sphinx_htmlwriter.SmartyPantsHTMLTranslator.visit_literal_block(self, node)
|
||||||
|
|
||||||
def depart_literal_block(self, node):
|
def depart_literal_block(self, node):
|
||||||
sphinx.htmlwriter.SmartyPantsHTMLTranslator.depart_literal_block(self, node)
|
sphinx_htmlwriter.SmartyPantsHTMLTranslator.depart_literal_block(self, node)
|
||||||
self.no_smarty -= 1
|
self.no_smarty -= 1
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -162,7 +168,7 @@ class DjangoHTMLTranslator(sphinx.htmlwriter.SmartyPantsHTMLTranslator):
|
|||||||
# Give each section a unique ID -- nice for custom CSS hooks
|
# Give each section a unique ID -- nice for custom CSS hooks
|
||||||
# This is different on docutils 0.5 vs. 0.4...
|
# This is different on docutils 0.5 vs. 0.4...
|
||||||
|
|
||||||
if hasattr(sphinx.htmlwriter.SmartyPantsHTMLTranslator, 'start_tag_with_title') and sphinx.__version__ == '0.4.2':
|
if hasattr(sphinx_htmlwriter.SmartyPantsHTMLTranslator, 'start_tag_with_title') and sphinx.__version__ == '0.4.2':
|
||||||
def start_tag_with_title(self, node, tagname, **atts):
|
def start_tag_with_title(self, node, tagname, **atts):
|
||||||
node = {
|
node = {
|
||||||
'classes': node.get('classes', []),
|
'classes': node.get('classes', []),
|
||||||
@ -176,7 +182,7 @@ class DjangoHTMLTranslator(sphinx.htmlwriter.SmartyPantsHTMLTranslator):
|
|||||||
node['ids'] = ['s-' + i for i in old_ids]
|
node['ids'] = ['s-' + i for i in old_ids]
|
||||||
if sphinx.__version__ != '0.4.2':
|
if sphinx.__version__ != '0.4.2':
|
||||||
node['ids'].extend(old_ids)
|
node['ids'].extend(old_ids)
|
||||||
sphinx.htmlwriter.SmartyPantsHTMLTranslator.visit_section(self, node)
|
sphinx_htmlwriter.SmartyPantsHTMLTranslator.visit_section(self, node)
|
||||||
node['ids'] = old_ids
|
node['ids'] = old_ids
|
||||||
|
|
||||||
def parse_django_admin_node(env, sig, signode):
|
def parse_django_admin_node(env, sig, signode):
|
||||||
@ -186,6 +192,25 @@ def parse_django_admin_node(env, sig, signode):
|
|||||||
signode += sphinx.addnodes.desc_name(title, title)
|
signode += sphinx.addnodes.desc_name(title, title)
|
||||||
return sig
|
return sig
|
||||||
|
|
||||||
|
def parse_django_adminopt_node(env, sig, signode):
|
||||||
|
"""A copy of sphinx.directives.CmdoptionDesc.parse_signature()"""
|
||||||
|
from sphinx import addnodes
|
||||||
|
from sphinx.directives.desc import option_desc_re
|
||||||
|
count = 0
|
||||||
|
firstname = ''
|
||||||
|
for m in option_desc_re.finditer(sig):
|
||||||
|
optname, args = m.groups()
|
||||||
|
if count:
|
||||||
|
signode += addnodes.desc_addname(', ', ', ')
|
||||||
|
signode += addnodes.desc_name(optname, optname)
|
||||||
|
signode += addnodes.desc_addname(args, args)
|
||||||
|
if not count:
|
||||||
|
firstname = optname
|
||||||
|
count += 1
|
||||||
|
if not firstname:
|
||||||
|
raise ValueError
|
||||||
|
return firstname
|
||||||
|
|
||||||
def monkeypatch_pickle_builder():
|
def monkeypatch_pickle_builder():
|
||||||
import shutil
|
import shutil
|
||||||
from os import path
|
from os import path
|
||||||
@ -214,12 +239,12 @@ def monkeypatch_pickle_builder():
|
|||||||
|
|
||||||
# copy the environment file from the doctree dir to the output dir
|
# copy the environment file from the doctree dir to the output dir
|
||||||
# as needed by the web app
|
# as needed by the web app
|
||||||
shutil.copyfile(path.join(self.doctreedir, sphinx.builder.ENV_PICKLE_FILENAME),
|
shutil.copyfile(path.join(self.doctreedir, builders.ENV_PICKLE_FILENAME),
|
||||||
path.join(self.outdir, sphinx.builder.ENV_PICKLE_FILENAME))
|
path.join(self.outdir, builders.ENV_PICKLE_FILENAME))
|
||||||
|
|
||||||
# touch 'last build' file, used by the web application to determine
|
# touch 'last build' file, used by the web application to determine
|
||||||
# when to reload its environment and clear the cache
|
# when to reload its environment and clear the cache
|
||||||
open(path.join(self.outdir, sphinx.builder.LAST_BUILD_FILENAME), 'w').close()
|
open(path.join(self.outdir, builders.LAST_BUILD_FILENAME), 'w').close()
|
||||||
|
|
||||||
sphinx.builder.PickleHTMLBuilder.handle_finish = handle_finish
|
builders.PickleHTMLBuilder.handle_finish = handle_finish
|
||||||
|
|
||||||
|
2
docs/_templates/layout.html
vendored
2
docs/_templates/layout.html
vendored
@ -1,6 +1,6 @@
|
|||||||
{% extends "!layout.html" %}
|
{% extends "!layout.html" %}
|
||||||
|
|
||||||
{%- macro secondnav %}
|
{%- macro secondnav() %}
|
||||||
{%- if prev %}
|
{%- if prev %}
|
||||||
« <a href="{{ prev.link|e }}" title="{{ prev.title|e }}">previous</a>
|
« <a href="{{ prev.link|e }}" title="{{ prev.title|e }}">previous</a>
|
||||||
{{ reldelim2 }}
|
{{ reldelim2 }}
|
||||||
|
@ -37,20 +37,19 @@ Set the :setting:`CACHE_MIDDLEWARE_ANONYMOUS_ONLY` setting to ``True``. See the
|
|||||||
How do I automatically set a field's value to the user who last edited the object in the admin?
|
How do I automatically set a field's value to the user who last edited the object in the admin?
|
||||||
-----------------------------------------------------------------------------------------------
|
-----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
At this point, Django doesn't have an official way to do this. But it's an oft-requested
|
The :class:`ModelAdmin` class provides customization hooks that allow you to transform
|
||||||
feature, so we're discussing how it can be implemented. The problem is we don't want to couple
|
an object as it saved, using details from the request. By extracting the current user
|
||||||
the model layer with the admin layer with the request layer (to get the current user). It's a
|
from the request, and customizing the :meth:`ModelAdmin.save_model` hook, you can update
|
||||||
tricky problem.
|
an object to reflect the user that edited it. See :ref:`the documentation on ModelAdmin
|
||||||
|
methods <model-admin-methods>` for an example.
|
||||||
One person hacked up a `solution that doesn't require patching Django`_, but note that it's an
|
|
||||||
unofficial solution, and there's no guarantee it won't break at some point.
|
|
||||||
|
|
||||||
.. _solution that doesn't require patching Django: http://lukeplant.me.uk/blog.php?id=1107301634
|
|
||||||
|
|
||||||
How do I limit admin access so that objects can only be edited by the users who created them?
|
How do I limit admin access so that objects can only be edited by the users who created them?
|
||||||
---------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
See the answer to the previous question.
|
The :class:`ModelAdmin` class also provides customization hooks that allow you to control the
|
||||||
|
visibility and editability of objects in the admin. Using the same trick of extracting the
|
||||||
|
user from the request, the :meth:`ModelAdmin.queryset` and :meth:`ModelAdmin.has_change_permission`
|
||||||
|
can be used to control the visibility and editability of objects in the admin.
|
||||||
|
|
||||||
My admin-site CSS and images showed up fine using the development server, but they're not displaying when using mod_python.
|
My admin-site CSS and images showed up fine using the development server, but they're not displaying when using mod_python.
|
||||||
---------------------------------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -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
|
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
|
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
|
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
|
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
|
implement ``get_db_prep_lookup()``. If you don't, ``get_db_prep_value`` will be
|
||||||
|
@ -378,3 +378,24 @@ as necessary.
|
|||||||
.. _Expat Causing Apache Crash: http://www.dscpl.com.au/articles/modpython-006.html
|
.. _Expat Causing Apache Crash: http://www.dscpl.com.au/articles/modpython-006.html
|
||||||
.. _mod_python FAQ entry: http://modpython.org/FAQ/faqw.py?req=show&file=faq02.013.htp
|
.. _mod_python FAQ entry: http://modpython.org/FAQ/faqw.py?req=show&file=faq02.013.htp
|
||||||
.. _Getting mod_python Working: http://www.dscpl.com.au/articles/modpython-001.html
|
.. _Getting mod_python Working: http://www.dscpl.com.au/articles/modpython-001.html
|
||||||
|
|
||||||
|
If you get a UnicodeEncodeError
|
||||||
|
===============================
|
||||||
|
|
||||||
|
If you're taking advantage of the internationalization features of Django (see
|
||||||
|
:ref:`topics-i18n`) and you intend to allow users to upload files, you must
|
||||||
|
ensure that the environment used to start Apache is configured to accept
|
||||||
|
non-ASCII file names. If your environment is not correctly configured, you
|
||||||
|
will trigger ``UnicodeEncodeError`` exceptions when calling functions like
|
||||||
|
``os.path()`` on filenames that contain non-ASCII characters.
|
||||||
|
|
||||||
|
To avoid these problems, the environment used to start Apache should contain
|
||||||
|
settings analogous to the following::
|
||||||
|
|
||||||
|
export LANG='en_US.UTF-8'
|
||||||
|
export LC_ALL='en_US.UTF-8'
|
||||||
|
|
||||||
|
Consult the documentation for your operating system for the appropriate syntax
|
||||||
|
and location to put these configuration items; ``/etc/apache2/envvars`` is a
|
||||||
|
common location on Unix platforms. Once you have added these statements
|
||||||
|
to your environment, restart Apache.
|
||||||
|
@ -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
|
get a description of the error, a complete Python traceback, and details about
|
||||||
the HTTP request that caused the error.
|
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`
|
To disable this behavior, just remove all entries from the :setting:`ADMINS`
|
||||||
setting.
|
setting.
|
||||||
|
|
||||||
|
@ -704,6 +704,8 @@ objects. Templates can override or extend base admin templates as described in
|
|||||||
If you don't specify this attribute, a default template shipped with Django
|
If you don't specify this attribute, a default template shipped with Django
|
||||||
that provides the standard appearance is used.
|
that provides the standard appearance is used.
|
||||||
|
|
||||||
|
.. _model-admin-methods:
|
||||||
|
|
||||||
``ModelAdmin`` methods
|
``ModelAdmin`` methods
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
@ -792,7 +794,7 @@ return a subset of objects for this foreign key field based on the user::
|
|||||||
class MyModelAdmin(admin.ModelAdmin):
|
class MyModelAdmin(admin.ModelAdmin):
|
||||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||||
if db_field.name == "car":
|
if db_field.name == "car":
|
||||||
kwargs["queryset"] = Car.object.filter(owner=request.user)
|
kwargs["queryset"] = Car.objects.filter(owner=request.user)
|
||||||
return db_field.formfield(**kwargs)
|
return db_field.formfield(**kwargs)
|
||||||
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
|
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
|
||||||
@ -847,7 +849,7 @@ provided some extra mapping data that would not otherwise be available::
|
|||||||
'osm_data': self.get_osm_info(),
|
'osm_data': self.get_osm_info(),
|
||||||
}
|
}
|
||||||
return super(MyModelAdmin, self).change_view(request, object_id,
|
return super(MyModelAdmin, self).change_view(request, object_id,
|
||||||
extra_context=my_context))
|
extra_context=my_context)
|
||||||
|
|
||||||
``ModelAdmin`` media definitions
|
``ModelAdmin`` media definitions
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
@ -177,9 +177,9 @@ The ``ContentTypeManager``
|
|||||||
.. method:: models.ContentTypeManager.clear_cache()
|
.. method:: models.ContentTypeManager.clear_cache()
|
||||||
|
|
||||||
Clears an internal cache used by
|
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
|
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
|
probably won't ever need to call this method yourself; Django will call
|
||||||
it automatically when it's needed.
|
it automatically when it's needed.
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ bytestrings (which shouldn't be too difficult) is the recommended solution.
|
|||||||
Should you decide to use ``utf8_bin`` collation for some of your tables with
|
Should you decide to use ``utf8_bin`` collation for some of your tables with
|
||||||
MySQLdb 1.2.1p2, you should still use ``utf8_collation_ci_swedish`` (the
|
MySQLdb 1.2.1p2, you should still use ``utf8_collation_ci_swedish`` (the
|
||||||
default) collation for the :class:`django.contrib.sessions.models.Session`
|
default) collation for the :class:`django.contrib.sessions.models.Session`
|
||||||
table (usually called ``django_session`` and the table
|
table (usually called ``django_session``) and the
|
||||||
:class:`django.contrib.admin.models.LogEntry` table (usually called
|
:class:`django.contrib.admin.models.LogEntry` table (usually called
|
||||||
``django_admin_log``). Those are the two standard tables that use
|
``django_admin_log``). Those are the two standard tables that use
|
||||||
:class:`~django.db.model.TextField` internally.
|
:class:`~django.db.model.TextField` internally.
|
||||||
|
@ -101,6 +101,14 @@ You can use any number of values in a ``{% cycle %}`` tag, separated by spaces.
|
|||||||
Values enclosed in single (``'``) or double quotes (``"``) are treated as
|
Values enclosed in single (``'``) or double quotes (``"``) are treated as
|
||||||
string literals, while values without quotes are treated as template variables.
|
string literals, while values without quotes are treated as template variables.
|
||||||
|
|
||||||
|
Note that the variables included in the cycle will not be escaped. This is
|
||||||
|
because template tags do not escape their content. If you want to escape the
|
||||||
|
variables in the cycle, you must do so explicitly::
|
||||||
|
|
||||||
|
{% filter force_escape %}
|
||||||
|
{% cycle var1 var2 var3 %}
|
||||||
|
{% endfilter %}
|
||||||
|
|
||||||
For backwards compatibility, the ``{% cycle %}`` tag supports the much inferior
|
For backwards compatibility, the ``{% cycle %}`` tag supports the much inferior
|
||||||
old syntax from previous Django versions. You shouldn't use this in any new
|
old syntax from previous Django versions. You shouldn't use this in any new
|
||||||
projects, but for the sake of the people who are still using it, here's what it
|
projects, but for the sake of the people who are still using it, here's what it
|
||||||
@ -160,8 +168,9 @@ Sample usage::
|
|||||||
firstof
|
firstof
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
Outputs the first variable passed that is not False. Outputs nothing if all the
|
Outputs the first variable passed that is not False, without escaping.
|
||||||
passed variables are False.
|
|
||||||
|
Outputs nothing if all the passed variables are False.
|
||||||
|
|
||||||
Sample usage::
|
Sample usage::
|
||||||
|
|
||||||
@ -170,11 +179,11 @@ Sample usage::
|
|||||||
This is equivalent to::
|
This is equivalent to::
|
||||||
|
|
||||||
{% if var1 %}
|
{% if var1 %}
|
||||||
{{ var1 }}
|
{{ var1|safe }}
|
||||||
{% else %}{% if var2 %}
|
{% else %}{% if var2 %}
|
||||||
{{ var2 }}
|
{{ var2|safe }}
|
||||||
{% else %}{% if var3 %}
|
{% else %}{% if var3 %}
|
||||||
{{ var3 }}
|
{{ var3|safe }}
|
||||||
{% endif %}{% endif %}{% endif %}
|
{% endif %}{% endif %}{% endif %}
|
||||||
|
|
||||||
You can also use a literal string as a fallback value in case all
|
You can also use a literal string as a fallback value in case all
|
||||||
@ -182,6 +191,14 @@ passed variables are False::
|
|||||||
|
|
||||||
{% firstof var1 var2 var3 "fallback value" %}
|
{% firstof var1 var2 var3 "fallback value" %}
|
||||||
|
|
||||||
|
Note that the variables included in the firstof tag will not be escaped. This
|
||||||
|
is because template tags do not escape their content. If you want to escape
|
||||||
|
the variables in the firstof tag, you must do so explicitly::
|
||||||
|
|
||||||
|
{% filter force_escape %}
|
||||||
|
{% firstof var1 var2 var3 "fallback value" %}
|
||||||
|
{% endfilter %}
|
||||||
|
|
||||||
.. templatetag:: for
|
.. templatetag:: for
|
||||||
|
|
||||||
for
|
for
|
||||||
|
@ -263,8 +263,15 @@ value should suffice.
|
|||||||
include
|
include
|
||||||
-------
|
-------
|
||||||
|
|
||||||
A function that takes a full Python import path to another URLconf that should
|
A function that takes a full Python import path to another URLconf module that
|
||||||
be "included" in this place. See `Including other URLconfs`_ below.
|
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
|
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
|
up to that point and sends the remaining string to the included URLconf for
|
||||||
further processing.
|
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/
|
.. _`Django Web site`: http://www.djangoproject.com/
|
||||||
|
|
||||||
Captured parameters
|
Captured parameters
|
||||||
|
@ -223,7 +223,19 @@ Pluralization
|
|||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
Use the function ``django.utils.translation.ungettext()`` to specify pluralized
|
Use the function ``django.utils.translation.ungettext()`` to specify pluralized
|
||||||
messages. Example::
|
messages.
|
||||||
|
|
||||||
|
``ungettext`` takes three arguments: the singular translation string, the plural
|
||||||
|
translation string and the number of objects.
|
||||||
|
|
||||||
|
This function is useful when your need you Django application to be localizable
|
||||||
|
to languages where the number and complexity of `plural forms
|
||||||
|
<http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms>`_ is
|
||||||
|
greater than the two forms used in English ('object' for the singular and
|
||||||
|
'objects' for all the cases where ``count`` is different from zero, irrespective
|
||||||
|
of its value.)
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
from django.utils.translation import ungettext
|
from django.utils.translation import ungettext
|
||||||
def hello_world(request, count):
|
def hello_world(request, count):
|
||||||
@ -232,9 +244,61 @@ messages. Example::
|
|||||||
}
|
}
|
||||||
return HttpResponse(page)
|
return HttpResponse(page)
|
||||||
|
|
||||||
``ungettext`` takes three arguments: the singular translation string, the plural
|
In this example the number of objects is passed to the translation languages as
|
||||||
translation string and the number of objects (which is passed to the
|
the ``count`` variable.
|
||||||
translation languages as the ``count`` variable).
|
|
||||||
|
Lets see a slightly more complex usage example::
|
||||||
|
|
||||||
|
from django.utils.translation import ungettext
|
||||||
|
|
||||||
|
count = Report.objects.count()
|
||||||
|
if count == 1:
|
||||||
|
name = Report._meta.verbose_name
|
||||||
|
else:
|
||||||
|
name = Report._meta.verbose_name_plural
|
||||||
|
|
||||||
|
text = ungettext(
|
||||||
|
'There is %(count)d %(name)s available.',
|
||||||
|
'There are %(count)d %(name)s available.',
|
||||||
|
count
|
||||||
|
) % {
|
||||||
|
'count': count,
|
||||||
|
'name': name
|
||||||
|
}
|
||||||
|
|
||||||
|
Here we reuse localizable, hopefully already translated literals (contained in
|
||||||
|
the ``verbose_name`` and ``verbose_name_plural`` model ``Meta`` options) for
|
||||||
|
other parts of the sentence so all of it is consistently based on the
|
||||||
|
cardinality of the elements at play.
|
||||||
|
|
||||||
|
.. _pluralization-var-notes:
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
When using this technique, make sure you use a single name for every
|
||||||
|
extrapolated variable included in the literal. In the example above note how
|
||||||
|
we used the ``name`` Python variable in both translation strings. This
|
||||||
|
example would fail::
|
||||||
|
|
||||||
|
from django.utils.translation import ungettext
|
||||||
|
from myapp.models import Report
|
||||||
|
|
||||||
|
count = Report.objects.count()
|
||||||
|
d = {
|
||||||
|
'count': count,
|
||||||
|
'name': Report._meta.verbose_name
|
||||||
|
'plural_name': Report._meta.verbose_name_plural
|
||||||
|
}
|
||||||
|
text = ungettext(
|
||||||
|
'There is %(count)d %(name)s available.',
|
||||||
|
'There are %(count)d %(plural_name)s available.',
|
||||||
|
count
|
||||||
|
) % d
|
||||||
|
|
||||||
|
You would get a ``a format specification for argument 'name', as in
|
||||||
|
'msgstr[0]', doesn't exist in 'msgid'`` error when running
|
||||||
|
``django-admin.py compilemessages`` or a ``KeyError`` Python exception at
|
||||||
|
runtime.
|
||||||
|
|
||||||
In template code
|
In template code
|
||||||
----------------
|
----------------
|
||||||
@ -257,6 +321,8 @@ content that will require translation in the future::
|
|||||||
|
|
||||||
<title>{% trans "myvar" noop %}</title>
|
<title>{% trans "myvar" noop %}</title>
|
||||||
|
|
||||||
|
Internally, inline translations use an ``ugettext`` call.
|
||||||
|
|
||||||
It's not possible to mix a template variable inside a string within ``{% trans
|
It's not possible to mix a template variable inside a string within ``{% trans
|
||||||
%}``. If your translations require strings with variables (placeholders), use
|
%}``. If your translations require strings with variables (placeholders), use
|
||||||
``{% blocktrans %}``::
|
``{% blocktrans %}``::
|
||||||
@ -288,8 +354,11 @@ To pluralize, specify both the singular and plural forms with the
|
|||||||
There are {{ counter }} {{ name }} objects.
|
There are {{ counter }} {{ name }} objects.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
|
|
||||||
Internally, all block and inline translations use the appropriate
|
When you use the pluralization feature and bind additional values to local
|
||||||
``ugettext`` / ``ungettext`` call.
|
variables apart from the counter value that selects the translated literal to be
|
||||||
|
used, have in mind that the ``blocktrans`` construct is internally converted
|
||||||
|
to an ``ungettext`` call. This means the same :ref:`notes regarding ungettext
|
||||||
|
variables <pluralization-var-notes>` apply.
|
||||||
|
|
||||||
Each ``RequestContext`` has access to three translation-specific variables:
|
Each ``RequestContext`` has access to three translation-specific variables:
|
||||||
|
|
||||||
|
@ -484,7 +484,7 @@ arguments at time of construction:
|
|||||||
Once you have a ``Client`` instance, you can call any of the following
|
Once you have a ``Client`` instance, you can call any of the following
|
||||||
methods:
|
methods:
|
||||||
|
|
||||||
.. method:: Client.get(path, data={}, follow=False)
|
.. method:: Client.get(path, data={}, follow=False, **extra)
|
||||||
|
|
||||||
|
|
||||||
Makes a GET request on the provided ``path`` and returns a ``Response``
|
Makes a GET request on the provided ``path`` and returns a ``Response``
|
||||||
@ -500,6 +500,17 @@ arguments at time of construction:
|
|||||||
|
|
||||||
/customers/details/?name=fred&age=7
|
/customers/details/?name=fred&age=7
|
||||||
|
|
||||||
|
The ``extra`` keyword arguments parameter can be used to specify
|
||||||
|
headers to be sent in the request. For example::
|
||||||
|
|
||||||
|
>>> c = Client()
|
||||||
|
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7},
|
||||||
|
... HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
|
|
||||||
|
...will send the HTTP header ``HTTP_X_REQUESTED_WITH`` to the
|
||||||
|
details view, which is a good way to test code paths that use the
|
||||||
|
:meth:`django.http.HttpRequest.is_ajax()` method.
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
.. versionadded:: 1.1
|
||||||
|
|
||||||
If you already have the GET arguments in URL-encoded form, you can
|
If you already have the GET arguments in URL-encoded form, you can
|
||||||
@ -523,7 +534,7 @@ arguments at time of construction:
|
|||||||
>>> response.redirect_chain
|
>>> response.redirect_chain
|
||||||
[(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)]
|
[(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)]
|
||||||
|
|
||||||
.. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, follow=False)
|
.. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra)
|
||||||
|
|
||||||
Makes a POST request on the provided ``path`` and returns a
|
Makes a POST request on the provided ``path`` and returns a
|
||||||
``Response`` object, which is documented below.
|
``Response`` object, which is documented below.
|
||||||
@ -574,6 +585,8 @@ arguments at time of construction:
|
|||||||
Note that you should manually close the file after it has been provided
|
Note that you should manually close the file after it has been provided
|
||||||
to ``post()``.
|
to ``post()``.
|
||||||
|
|
||||||
|
The ``extra`` argument acts the same as for :meth:`Client.get`.
|
||||||
|
|
||||||
.. versionchanged:: 1.1
|
.. versionchanged:: 1.1
|
||||||
|
|
||||||
If the URL you request with a POST contains encoded parameters, these
|
If the URL you request with a POST contains encoded parameters, these
|
||||||
@ -590,7 +603,7 @@ arguments at time of construction:
|
|||||||
and a ``redirect_chain`` attribute will be set in the response object
|
and a ``redirect_chain`` attribute will be set in the response object
|
||||||
containing tuples of the intermediate urls and status codes.
|
containing tuples of the intermediate urls and status codes.
|
||||||
|
|
||||||
.. method:: Client.head(path, data={}, follow=False)
|
.. method:: Client.head(path, data={}, follow=False, **extra)
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
.. versionadded:: 1.1
|
||||||
|
|
||||||
@ -602,7 +615,7 @@ arguments at time of construction:
|
|||||||
and a ``redirect_chain`` attribute will be set in the response object
|
and a ``redirect_chain`` attribute will be set in the response object
|
||||||
containing tuples of the intermediate urls and status codes.
|
containing tuples of the intermediate urls and status codes.
|
||||||
|
|
||||||
.. method:: Client.options(path, data={}, follow=False)
|
.. method:: Client.options(path, data={}, follow=False, **extra)
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
.. versionadded:: 1.1
|
||||||
|
|
||||||
@ -613,7 +626,9 @@ arguments at time of construction:
|
|||||||
and a ``redirect_chain`` attribute will be set in the response object
|
and a ``redirect_chain`` attribute will be set in the response object
|
||||||
containing tuples of the intermediate urls and status codes.
|
containing tuples of the intermediate urls and status codes.
|
||||||
|
|
||||||
.. method:: Client.put(path, data={}, content_type=MULTIPART_CONTENT, follow=False)
|
The ``extra`` argument acts the same as for :meth:`Client.get`.
|
||||||
|
|
||||||
|
.. method:: Client.put(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra)
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
.. versionadded:: 1.1
|
||||||
|
|
||||||
@ -625,7 +640,7 @@ arguments at time of construction:
|
|||||||
and a ``redirect_chain`` attribute will be set in the response object
|
and a ``redirect_chain`` attribute will be set in the response object
|
||||||
containing tuples of the intermediate urls and status codes.
|
containing tuples of the intermediate urls and status codes.
|
||||||
|
|
||||||
.. method:: Client.delete(path, follow=False)
|
.. method:: Client.delete(path, follow=False, **extra)
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
.. versionadded:: 1.1
|
||||||
|
|
||||||
@ -636,6 +651,8 @@ arguments at time of construction:
|
|||||||
and a ``redirect_chain`` attribute will be set in the response object
|
and a ``redirect_chain`` attribute will be set in the response object
|
||||||
containing tuples of the intermediate urls and status codes.
|
containing tuples of the intermediate urls and status codes.
|
||||||
|
|
||||||
|
The ``extra`` argument acts the same as for :meth:`Client.get`.
|
||||||
|
|
||||||
.. method:: Client.login(**credentials)
|
.. method:: Client.login(**credentials)
|
||||||
|
|
||||||
.. versionadded:: 1.0
|
.. versionadded:: 1.0
|
||||||
|
@ -326,7 +326,6 @@ class GalleryAdmin(admin.ModelAdmin):
|
|||||||
class PictureAdmin(admin.ModelAdmin):
|
class PictureAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Language(models.Model):
|
class Language(models.Model):
|
||||||
iso = models.CharField(max_length=5, primary_key=True)
|
iso = models.CharField(max_length=5, primary_key=True)
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
@ -401,8 +400,25 @@ class WhatsitInline(admin.StackedInline):
|
|||||||
class FancyDoodadInline(admin.StackedInline):
|
class FancyDoodadInline(admin.StackedInline):
|
||||||
model = FancyDoodad
|
model = FancyDoodad
|
||||||
|
|
||||||
|
class Category(models.Model):
|
||||||
|
collector = models.ForeignKey(Collector)
|
||||||
|
order = models.PositiveIntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('order',)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'%s:o%s' % (self.id, self.order)
|
||||||
|
|
||||||
|
class CategoryAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('id', 'collector', 'order')
|
||||||
|
list_editable = ('order',)
|
||||||
|
|
||||||
|
class CategoryInline(admin.StackedInline):
|
||||||
|
model = Category
|
||||||
|
|
||||||
class CollectorAdmin(admin.ModelAdmin):
|
class CollectorAdmin(admin.ModelAdmin):
|
||||||
inlines = [WidgetInline, DooHickeyInline, GrommetInline, WhatsitInline, FancyDoodadInline]
|
inlines = [WidgetInline, DooHickeyInline, GrommetInline, WhatsitInline, FancyDoodadInline, CategoryInline]
|
||||||
|
|
||||||
admin.site.register(Article, ArticleAdmin)
|
admin.site.register(Article, ArticleAdmin)
|
||||||
admin.site.register(CustomArticle, CustomArticleAdmin)
|
admin.site.register(CustomArticle, CustomArticleAdmin)
|
||||||
@ -426,6 +442,7 @@ admin.site.register(Language, LanguageAdmin)
|
|||||||
admin.site.register(Recommendation, RecommendationAdmin)
|
admin.site.register(Recommendation, RecommendationAdmin)
|
||||||
admin.site.register(Recommender)
|
admin.site.register(Recommender)
|
||||||
admin.site.register(Collector, CollectorAdmin)
|
admin.site.register(Collector, CollectorAdmin)
|
||||||
|
admin.site.register(Category, CategoryAdmin)
|
||||||
|
|
||||||
# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
|
# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
|
||||||
# That way we cover all four cases:
|
# That way we cover all four cases:
|
||||||
|
@ -16,7 +16,8 @@ from django.utils.html import escape
|
|||||||
from models import Article, BarAccount, CustomArticle, EmptyModel, \
|
from models import Article, BarAccount, CustomArticle, EmptyModel, \
|
||||||
ExternalSubscriber, FooAccount, Gallery, ModelWithStringPrimaryKey, \
|
ExternalSubscriber, FooAccount, Gallery, ModelWithStringPrimaryKey, \
|
||||||
Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \
|
Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \
|
||||||
Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit
|
Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \
|
||||||
|
Category
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -964,6 +965,45 @@ class AdminViewListEditable(TestCase):
|
|||||||
|
|
||||||
self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, False)
|
self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, False)
|
||||||
|
|
||||||
|
def test_list_editable_ordering(self):
|
||||||
|
collector = Collector.objects.create(id=1, name="Frederick Clegg")
|
||||||
|
|
||||||
|
Category.objects.create(id=1, order=1, collector=collector)
|
||||||
|
Category.objects.create(id=2, order=2, collector=collector)
|
||||||
|
Category.objects.create(id=3, order=0, collector=collector)
|
||||||
|
Category.objects.create(id=4, order=0, collector=collector)
|
||||||
|
|
||||||
|
# NB: The order values must be changed so that the items are reordered.
|
||||||
|
data = {
|
||||||
|
"form-TOTAL_FORMS": "4",
|
||||||
|
"form-INITIAL_FORMS": "4",
|
||||||
|
|
||||||
|
"form-0-order": "14",
|
||||||
|
"form-0-id": "1",
|
||||||
|
"form-0-collector": "1",
|
||||||
|
|
||||||
|
"form-1-order": "13",
|
||||||
|
"form-1-id": "2",
|
||||||
|
"form-1-collector": "1",
|
||||||
|
|
||||||
|
"form-2-order": "1",
|
||||||
|
"form-2-id": "3",
|
||||||
|
"form-2-collector": "1",
|
||||||
|
|
||||||
|
"form-3-order": "0",
|
||||||
|
"form-3-id": "4",
|
||||||
|
"form-3-collector": "1",
|
||||||
|
}
|
||||||
|
response = self.client.post('/test_admin/admin/admin_views/category/', data)
|
||||||
|
# Successful post will redirect
|
||||||
|
self.failUnlessEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
# Check that the order values have been applied to the right objects
|
||||||
|
self.failUnlessEqual(Category.objects.get(id=1).order, 14)
|
||||||
|
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 AdminSearchTest(TestCase):
|
class AdminSearchTest(TestCase):
|
||||||
fixtures = ['admin-views-users','multiple-child-classes']
|
fixtures = ['admin-views-users','multiple-child-classes']
|
||||||
|
|
||||||
@ -1297,11 +1337,24 @@ class AdminInlineTests(TestCase):
|
|||||||
"fancydoodad_set-2-owner": "1",
|
"fancydoodad_set-2-owner": "1",
|
||||||
"fancydoodad_set-2-name": "",
|
"fancydoodad_set-2-name": "",
|
||||||
"fancydoodad_set-2-expensive": "on",
|
"fancydoodad_set-2-expensive": "on",
|
||||||
|
|
||||||
|
"category_set-TOTAL_FORMS": "3",
|
||||||
|
"category_set-INITIAL_FORMS": "0",
|
||||||
|
"category_set-0-order": "",
|
||||||
|
"category_set-0-id": "",
|
||||||
|
"category_set-0-collector": "1",
|
||||||
|
"category_set-1-order": "",
|
||||||
|
"category_set-1-id": "",
|
||||||
|
"category_set-1-collector": "1",
|
||||||
|
"category_set-2-order": "",
|
||||||
|
"category_set-2-id": "",
|
||||||
|
"category_set-2-collector": "1",
|
||||||
}
|
}
|
||||||
|
|
||||||
result = self.client.login(username='super', password='secret')
|
result = self.client.login(username='super', password='secret')
|
||||||
self.failUnlessEqual(result, True)
|
self.failUnlessEqual(result, True)
|
||||||
Collector(pk=1,name='John Fowles').save()
|
self.collector = Collector(pk=1,name='John Fowles')
|
||||||
|
self.collector.save()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
@ -1483,3 +1536,57 @@ class AdminInlineTests(TestCase):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_ordered_inline(self):
|
||||||
|
"""Check that an inline with an editable ordering fields is
|
||||||
|
updated correctly. Regression for #10922"""
|
||||||
|
# Create some objects with an initial ordering
|
||||||
|
Category.objects.create(id=1, order=1, collector=self.collector)
|
||||||
|
Category.objects.create(id=2, order=2, collector=self.collector)
|
||||||
|
Category.objects.create(id=3, order=0, collector=self.collector)
|
||||||
|
Category.objects.create(id=4, order=0, collector=self.collector)
|
||||||
|
|
||||||
|
# NB: The order values must be changed so that the items are reordered.
|
||||||
|
self.post_data.update({
|
||||||
|
"name": "Frederick Clegg",
|
||||||
|
|
||||||
|
"category_set-TOTAL_FORMS": "7",
|
||||||
|
"category_set-INITIAL_FORMS": "4",
|
||||||
|
|
||||||
|
"category_set-0-order": "14",
|
||||||
|
"category_set-0-id": "1",
|
||||||
|
"category_set-0-collector": "1",
|
||||||
|
|
||||||
|
"category_set-1-order": "13",
|
||||||
|
"category_set-1-id": "2",
|
||||||
|
"category_set-1-collector": "1",
|
||||||
|
|
||||||
|
"category_set-2-order": "1",
|
||||||
|
"category_set-2-id": "3",
|
||||||
|
"category_set-2-collector": "1",
|
||||||
|
|
||||||
|
"category_set-3-order": "0",
|
||||||
|
"category_set-3-id": "4",
|
||||||
|
"category_set-3-collector": "1",
|
||||||
|
|
||||||
|
"category_set-4-order": "",
|
||||||
|
"category_set-4-id": "",
|
||||||
|
"category_set-4-collector": "1",
|
||||||
|
|
||||||
|
"category_set-5-order": "",
|
||||||
|
"category_set-5-id": "",
|
||||||
|
"category_set-5-collector": "1",
|
||||||
|
|
||||||
|
"category_set-6-order": "",
|
||||||
|
"category_set-6-id": "",
|
||||||
|
"category_set-6-collector": "1",
|
||||||
|
})
|
||||||
|
response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
|
||||||
|
# Successful post will redirect
|
||||||
|
self.failUnlessEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
# Check that the order values have been applied to the right objects
|
||||||
|
self.failUnlessEqual(self.collector.category_set.count(), 4)
|
||||||
|
self.failUnlessEqual(Category.objects.get(id=1).order, 14)
|
||||||
|
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)
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
@ -12,7 +12,9 @@ class Membership(models.Model):
|
|||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s is a member of %s" % (self.person.name, self.group.name)
|
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):
|
class UserMembership(models.Model):
|
||||||
|
id = models.AutoField(db_column='usermembership_id', primary_key=True)
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User)
|
||||||
group = models.ForeignKey('Group')
|
group = models.ForeignKey('Group')
|
||||||
price = models.IntegerField(default=100)
|
price = models.IntegerField(default=100)
|
||||||
@ -196,4 +198,12 @@ doing a join.
|
|||||||
# Flush the database, just to make sure we can.
|
# Flush the database, just to make sure we can.
|
||||||
>>> management.call_command('flush', verbosity=0, interactive=False)
|
>>> 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"}}]
|
||||||
|
|
||||||
"""}
|
"""}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user