From 5875c4e01c16540547cfa7bb5c4b379f5476d990 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Wed, 4 Aug 2010 18:47:42 +0000 Subject: [PATCH] [soc2010/test-refactor] Merged up to trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2010/test-refactor@13468 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../contrib/gis/db/backends/mysql/creation.py | 2 +- django/contrib/gis/db/models/sql/compiler.py | 4 +- django/contrib/gis/tests/relatedapp/models.py | 5 + django/contrib/gis/tests/relatedapp/tests.py | 10 +- django/core/management/commands/flush.py | 10 +- django/db/backends/postgresql/creation.py | 2 +- django/db/backends/postgresql/operations.py | 9 +- django/db/models/fields/related.py | 5 +- django/db/models/sql/query.py | 5 +- django/template/defaultfilters.py | 6 +- docs/Makefile | 67 +++++-- docs/_ext/djangodocs.py | 171 ++++++----------- .../djangodocs}/genindex.html | 2 +- .../djangodocs}/layout.html | 4 +- .../djangodocs}/modindex.html | 2 +- .../djangodocs}/search.html | 2 +- .../djangodocs/static}/default.css | 0 .../djangodocs/static}/djangodocs.css | 0 .../static}/docicons-behindscenes.png | Bin .../djangodocs/static}/docicons-note.png | Bin .../static}/docicons-philosophy.png | Bin .../djangodocs/static}/homepage.css | 0 .../djangodocs/static}/reset-fonts-grids.css | 0 docs/_theme/djangodocs/theme.conf | 4 + docs/conf.py | 174 +++++++++++++++--- docs/internals/documentation.txt | 5 + docs/ref/contrib/admin/index.txt | 110 ++++++----- docs/ref/models/instances.txt | 2 +- tests/regressiontests/backends/models.py | 17 ++ tests/regressiontests/backends/tests.py | 19 +- tests/regressiontests/templates/filters.py | 7 +- tests/regressiontests/templates/tests.py | 33 +++- 32 files changed, 437 insertions(+), 240 deletions(-) rename docs/{_templates => _theme/djangodocs}/genindex.html (68%) rename docs/{_templates => _theme/djangodocs}/layout.html (97%) rename docs/{_templates => _theme/djangodocs}/modindex.html (67%) rename docs/{_templates => _theme/djangodocs}/search.html (69%) rename docs/{_static => _theme/djangodocs/static}/default.css (100%) rename docs/{_static => _theme/djangodocs/static}/djangodocs.css (100%) rename docs/{_static => _theme/djangodocs/static}/docicons-behindscenes.png (100%) rename docs/{_static => _theme/djangodocs/static}/docicons-note.png (100%) rename docs/{_static => _theme/djangodocs/static}/docicons-philosophy.png (100%) rename docs/{_static => _theme/djangodocs/static}/homepage.css (100%) rename docs/{_static => _theme/djangodocs/static}/reset-fonts-grids.css (100%) create mode 100644 docs/_theme/djangodocs/theme.conf diff --git a/django/contrib/gis/db/backends/mysql/creation.py b/django/contrib/gis/db/backends/mysql/creation.py index 93fd2e6166..dda77ea6ab 100644 --- a/django/contrib/gis/db/backends/mysql/creation.py +++ b/django/contrib/gis/db/backends/mysql/creation.py @@ -6,7 +6,7 @@ class MySQLCreation(DatabaseCreation): from django.contrib.gis.db.models.fields import GeometryField output = super(MySQLCreation, self).sql_indexes_for_field(model, f, style) - if isinstance(f, GeometryField): + if isinstance(f, GeometryField) and f.spatial_index: qn = self.connection.ops.quote_name db_table = model._meta.db_table idx_name = '%s_%s_id' % (db_table, f.column) diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index 78eeeafe19..55dc4a66ec 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -95,7 +95,7 @@ class GeoSQLCompiler(compiler.SQLCompiler): return result def get_default_columns(self, with_aliases=False, col_aliases=None, - start_alias=None, opts=None, as_pairs=False): + start_alias=None, opts=None, as_pairs=False, local_only=False): """ Computes the default columns for selecting every field in the base model. Will sometimes be called to pull in related models (e.g. via @@ -121,6 +121,8 @@ class GeoSQLCompiler(compiler.SQLCompiler): if start_alias: seen = {None: start_alias} for field, model in opts.get_fields_with_model(): + if local_only and model is not None: + continue if start_alias: try: alias = seen[model] diff --git a/django/contrib/gis/tests/relatedapp/models.py b/django/contrib/gis/tests/relatedapp/models.py index 726f9826c0..2e9a62b61f 100644 --- a/django/contrib/gis/tests/relatedapp/models.py +++ b/django/contrib/gis/tests/relatedapp/models.py @@ -38,6 +38,11 @@ class Author(models.Model): name = models.CharField(max_length=100) objects = models.GeoManager() +class Article(models.Model): + title = models.CharField(max_length=100) + author = models.ForeignKey(Author, unique=True) + objects = models.GeoManager() + class Book(models.Model): title = models.CharField(max_length=100) author = models.ForeignKey(Author, related_name='books', null=True) diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py index 184b65b9c7..5d3d00f08d 100644 --- a/django/contrib/gis/tests/relatedapp/tests.py +++ b/django/contrib/gis/tests/relatedapp/tests.py @@ -4,7 +4,7 @@ from django.contrib.gis.db.models import Collect, Count, Extent, F, Union from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.tests.utils import mysql, oracle, postgis, spatialite, no_mysql, no_oracle, no_spatialite from django.conf import settings -from models import City, Location, DirectoryEntry, Parcel, Book, Author +from models import City, Location, DirectoryEntry, Parcel, Book, Author, Article cities = (('Aurora', 'TX', -97.516111, 33.058333), ('Roswell', 'NM', -104.528056, 33.387222), @@ -291,6 +291,14 @@ class RelatedGeoModelTest(unittest.TestCase): self.assertEqual(4, len(coll)) self.assertEqual(ref_geom, coll) + def test15_invalid_select_related(self): + "Testing doing select_related on the related name manager of a unique FK. See #13934." + qs = Article.objects.select_related('author__article') + # This triggers TypeError when `get_default_columns` has no `local_only` + # keyword. The TypeError is swallowed if QuerySet is actually + # evaluated as list generation swallows TypeError in CPython. + sql = str(qs.query) + # TODO: Related tests for KML, GML, and distance lookups. def suite(): diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 6836fe35ca..98e3f3151b 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -1,7 +1,7 @@ from optparse import make_option from django.conf import settings -from django.db import connections, transaction, models, DEFAULT_DB_ALIAS +from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS from django.core.management import call_command from django.core.management.base import NoArgsCommand, CommandError from django.core.management.color import no_style @@ -66,7 +66,13 @@ The full error: %s""" % (connection.settings_dict['NAME'], e)) # Emit the post sync signal. This allows individual # applications to respond as if the database had been # sync'd from scratch. - emit_post_sync_signal(models.get_models(), verbosity, interactive, db) + all_models = [ + (app.__name__.split('.')[-2], + [m for m in models.get_models(app, include_auto_created=True) + if router.allow_syncdb(db, m)]) + for app in models.get_apps() + ] + emit_post_sync_signal(all_models, verbosity, interactive, db) # Reinstall the initial_data fixture. kwargs = options.copy() diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py index e3587f0e37..9984716389 100644 --- a/django/db/backends/postgresql/creation.py +++ b/django/db/backends/postgresql/creation.py @@ -64,7 +64,7 @@ class DatabaseCreation(BaseDatabaseCreation): # a second index that specifies their operator class, which is # needed when performing correct LIKE queries outside the # C locale. See #12234. - db_type = f.db_type() + db_type = f.db_type(connection=self.connection) if db_type.startswith('varchar'): output.append(get_index_sql('%s_%s_like' % (db_table, f.column), ' varchar_pattern_ops')) diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index 76f25410fb..e8ce3f242b 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -56,7 +56,8 @@ class DatabaseOperations(BaseDatabaseOperations): def last_insert_id(self, cursor, table_name, pk_name): # Use pg_get_serial_sequence to get the underlying sequence name # from the table name and column name (available since PostgreSQL 8) - cursor.execute("SELECT CURRVAL(pg_get_serial_sequence('%s','%s'))" % (table_name, pk_name)) + cursor.execute("SELECT CURRVAL(pg_get_serial_sequence('%s','%s'))" % ( + self.quote_name(table_name), pk_name)) return cursor.fetchone()[0] def no_limit_value(self): @@ -98,7 +99,7 @@ class DatabaseOperations(BaseDatabaseOperations): column_name = 'id' sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \ (style.SQL_KEYWORD('SELECT'), - style.SQL_TABLE(table_name), + style.SQL_TABLE(self.quote_name(table_name)), style.SQL_FIELD(column_name)) ) return sql @@ -120,7 +121,7 @@ class DatabaseOperations(BaseDatabaseOperations): if isinstance(f, models.AutoField): output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ (style.SQL_KEYWORD('SELECT'), - style.SQL_TABLE(model._meta.db_table), + style.SQL_TABLE(qn(model._meta.db_table)), style.SQL_FIELD(f.column), style.SQL_FIELD(qn(f.column)), style.SQL_FIELD(qn(f.column)), @@ -132,7 +133,7 @@ class DatabaseOperations(BaseDatabaseOperations): if not f.rel.through: output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ (style.SQL_KEYWORD('SELECT'), - style.SQL_TABLE(model._meta.db_table), + style.SQL_TABLE(qn(f.m2m_db_table())), style.SQL_FIELD('id'), style.SQL_FIELD(qn('id')), style.SQL_FIELD(qn('id')), diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 5830a794df..232d5d5e74 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -812,6 +812,9 @@ class ForeignKey(RelatedField, Field): to_field = to_field or (to._meta.pk and to._meta.pk.name) kwargs['verbose_name'] = kwargs.get('verbose_name', None) + if 'db_index' not in kwargs: + kwargs['db_index'] = True + kwargs['rel'] = rel_class(to, to_field, related_name=kwargs.pop('related_name', None), limit_choices_to=kwargs.pop('limit_choices_to', None), @@ -819,8 +822,6 @@ class ForeignKey(RelatedField, Field): parent_link=kwargs.pop('parent_link', False)) Field.__init__(self, **kwargs) - self.db_index = True - def validate(self, value, model_instance): if self.rel.parent_link: return diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 0913399e2a..ec477447f3 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1090,10 +1090,7 @@ class Query(object): # exclude the "foo__in=[]" case from this handling, because # it's short-circuited in the Where class. # We also need to handle the case where a subquery is provided - entry = self.where_class() - entry.add((Constraint(alias, col, None), 'isnull', True), AND) - entry.negate() - self.where.add(entry, AND) + self.where.add((Constraint(alias, col, None), 'isnull', False), AND) if can_reuse is not None: can_reuse.update(join_list) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 4b720093b3..d8e7e91efe 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -11,9 +11,10 @@ except ImportError: from django.template import Variable, Library from django.conf import settings from django.utils import formats -from django.utils.translation import ugettext, ungettext from django.utils.encoding import force_unicode, iri_to_uri +from django.utils.html import conditional_escape from django.utils.safestring import mark_safe, SafeData +from django.utils.translation import ugettext, ungettext register = Library() @@ -496,10 +497,9 @@ def join(value, arg, autoescape=None): """ value = map(force_unicode, value) if autoescape: - from django.utils.html import conditional_escape value = [conditional_escape(v) for v in value] try: - data = arg.join(value) + data = conditional_escape(arg).join(value) except AttributeError: # fail silently but nicely return value return mark_safe(data) diff --git a/docs/Makefile b/docs/Makefile index f6a92b6835..9301315040 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -12,20 +12,26 @@ PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* @@ -40,6 +46,11 @@ dirhtml: @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @@ -65,12 +76,42 @@ qthelp: @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django.qhc" +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/django" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index efcd94d4bc..cee14ba6f1 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -1,9 +1,9 @@ """ Sphinx plugins for Django documentation. """ +import os -import docutils.nodes -import docutils.transforms +from docutils import nodes, transforms try: import json except ImportError: @@ -14,26 +14,12 @@ except ImportError: from django.utils import simplejson as json except ImportError: json = None -import os -import sphinx -import sphinx.addnodes -try: - from sphinx import builders -except ImportError: - import sphinx.builder as builders -try: - import sphinx.builders.html as builders_html -except ImportError: - builders_html = builders + +from sphinx import addnodes, roles +from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.writers.html import SmartyPantsHTMLTranslator from sphinx.util.console import bold -import sphinx.directives -import sphinx.environment -try: - import sphinx.writers.html as sphinx_htmlwriter -except ImportError: - import sphinx.htmlwriter as sphinx_htmlwriter -import sphinx.roles -from docutils import nodes + def setup(app): app.add_crossref_type( @@ -74,21 +60,20 @@ def setup(app): app.add_transform(SuppressBlockquotes) app.add_builder(DjangoStandaloneHTMLBuilder) - # Monkeypatch PickleHTMLBuilder so that it doesn't die in Sphinx 0.4.2 - if sphinx.__version__ == '0.4.2': - monkeypatch_pickle_builder() - def parse_version_directive(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): env = state.document.settings.env is_nextversion = env.config.django_next_version == arguments[0] ret = [] - node = sphinx.addnodes.versionmodified() + node = addnodes.versionmodified() ret.append(node) if not is_nextversion: if len(arguments) == 1: linktext = 'Please, see the release notes ' % (arguments[0]) - xrefs = sphinx.roles.xfileref_role('ref', linktext, linktext, lineno, state) + try: + xrefs = roles.XRefRole()('ref', linktext, linktext, lineno, state) # Sphinx >= 1.0 + except: + xrefs = roles.xfileref_role('ref', linktext, linktext, lineno, state) # Sphinx < 1.0 node.extend(xrefs[0]) node['version'] = arguments[0] else: @@ -103,29 +88,29 @@ def parse_version_directive(name, arguments, options, content, lineno, env.note_versionchange(node['type'], node['version'], node, lineno) return ret - -class SuppressBlockquotes(docutils.transforms.Transform): + +class SuppressBlockquotes(transforms.Transform): """ Remove the default blockquotes that encase indented list, tables, etc. """ default_priority = 300 - + suppress_blockquote_child_nodes = ( - docutils.nodes.bullet_list, - docutils.nodes.enumerated_list, - docutils.nodes.definition_list, - docutils.nodes.literal_block, - docutils.nodes.doctest_block, - docutils.nodes.line_block, - docutils.nodes.table + nodes.bullet_list, + nodes.enumerated_list, + nodes.definition_list, + nodes.literal_block, + nodes.doctest_block, + nodes.line_block, + nodes.table ) - + def apply(self): - for node in self.document.traverse(docutils.nodes.block_quote): + for node in self.document.traverse(nodes.block_quote): if len(node.children) == 1 and isinstance(node.children[0], self.suppress_blockquote_child_nodes): node.replace_self(node.children[0]) -class DjangoHTMLTranslator(sphinx_htmlwriter.SmartyPantsHTMLTranslator): +class DjangoHTMLTranslator(SmartyPantsHTMLTranslator): """ Django-specific reST to HTML tweaks. """ @@ -133,42 +118,41 @@ class DjangoHTMLTranslator(sphinx_htmlwriter.SmartyPantsHTMLTranslator): # Don't use border=1, which docutils does by default. def visit_table(self, node): self.body.append(self.starttag(node, 'table', CLASS='docutils')) - + # ? Really? def visit_desc_parameterlist(self, node): self.body.append('(') self.first_param = 1 - + def depart_desc_parameterlist(self, node): self.body.append(')') - pass - + # # Don't apply smartypants to literal blocks # def visit_literal_block(self, node): self.no_smarty += 1 - sphinx_htmlwriter.SmartyPantsHTMLTranslator.visit_literal_block(self, node) + SmartyPantsHTMLTranslator.visit_literal_block(self, node) def depart_literal_block(self, node): - sphinx_htmlwriter.SmartyPantsHTMLTranslator.depart_literal_block(self, node) + SmartyPantsHTMLTranslator.depart_literal_block(self, node) self.no_smarty -= 1 - + # - # Turn the "new in version" stuff (versoinadded/versionchanged) into a + # Turn the "new in version" stuff (versionadded/versionchanged) into a # better callout -- the Sphinx default is just a little span, # which is a bit less obvious that I'd like. # - # FIXME: these messages are all hardcoded in English. We need to chanage + # FIXME: these messages are all hardcoded in English. We need to change # that to accomodate other language docs, but I can't work out how to make - # that work and I think it'll require Sphinx 0.5 anyway. + # that work. # version_text = { 'deprecated': 'Deprecated in Django %s', 'versionchanged': 'Changed in Django %s', 'versionadded': 'New in Django %s', } - + def visit_versionmodified(self, node): self.body.append( self.starttag(node, 'div', CLASS=node['type']) @@ -178,41 +162,31 @@ class DjangoHTMLTranslator(sphinx_htmlwriter.SmartyPantsHTMLTranslator): len(node) and ":" or "." ) self.body.append('%s ' % title) - + def depart_versionmodified(self, node): self.body.append("\n") - + # Give each section a unique ID -- nice for custom CSS hooks - # 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': - def start_tag_with_title(self, node, tagname, **atts): - node = { - 'classes': node.get('classes', []), - 'ids': ['s-%s' % i for i in node.get('ids', [])] - } - return self.starttag(node, tagname, **atts) - - else: - def visit_section(self, node): - old_ids = node.get('ids', []) - node['ids'] = ['s-' + i for i in old_ids] - if sphinx.__version__ != '0.4.2': - node['ids'].extend(old_ids) - sphinx_htmlwriter.SmartyPantsHTMLTranslator.visit_section(self, node) - node['ids'] = old_ids + def visit_section(self, node): + old_ids = node.get('ids', []) + node['ids'] = ['s-' + i for i in old_ids] + node['ids'].extend(old_ids) + SmartyPantsHTMLTranslator.visit_section(self, node) + node['ids'] = old_ids def parse_django_admin_node(env, sig, signode): command = sig.split(' ')[0] env._django_curr_admin_command = command title = "django-admin.py %s" % sig - signode += sphinx.addnodes.desc_name(title, title) + signode += addnodes.desc_name(title, title) 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 + try: + from sphinx.domains.std import option_desc_re # Sphinx >= 1.0 + except: + from sphinx.directives.desc import option_desc_re # Sphinx < 1.0 count = 0 firstname = '' for m in option_desc_re.finditer(sig): @@ -228,44 +202,8 @@ def parse_django_adminopt_node(env, sig, signode): raise ValueError return firstname -def monkeypatch_pickle_builder(): - import shutil - from os import path - try: - import cPickle as pickle - except ImportError: - import pickle - - def handle_finish(self): - # dump the global context - outfilename = path.join(self.outdir, 'globalcontext.pickle') - f = open(outfilename, 'wb') - try: - pickle.dump(self.globalcontext, f, 2) - finally: - f.close() - self.info(bold('dumping search index...')) - self.indexer.prune(self.env.all_docs) - f = open(path.join(self.outdir, 'searchindex.pickle'), 'wb') - try: - self.indexer.dump(f, 'pickle') - finally: - f.close() - - # copy the environment file from the doctree dir to the output dir - # as needed by the web app - shutil.copyfile(path.join(self.doctreedir, builders.ENV_PICKLE_FILENAME), - path.join(self.outdir, builders.ENV_PICKLE_FILENAME)) - - # touch 'last build' file, used by the web application to determine - # when to reload its environment and clear the cache - open(path.join(self.outdir, builders.LAST_BUILD_FILENAME), 'w').close() - - builders.PickleHTMLBuilder.handle_finish = handle_finish - - -class DjangoStandaloneHTMLBuilder(builders_html.StandaloneHTMLBuilder): +class DjangoStandaloneHTMLBuilder(StandaloneHTMLBuilder): """ Subclass to add some extra things we need. """ @@ -278,9 +216,14 @@ class DjangoStandaloneHTMLBuilder(builders_html.StandaloneHTMLBuilder): self.warn("cannot create templatebuiltins.js due to missing simplejson dependency") return self.info(bold("writing templatebuiltins.js...")) - xrefs = self.env.reftargets.keys() - templatebuiltins = dict([('ttags', [n for (t,n) in xrefs if t == 'ttag']), - ('tfilters', [n for (t,n) in xrefs if t == 'tfilter'])]) + try: + xrefs = self.env.reftargets.keys() + templatebuiltins = dict([('ttags', [n for (t,n) in xrefs if t == 'ttag']), + ('tfilters', [n for (t,n) in xrefs if t == 'tfilter'])]) + except AttributeError: + xrefs = self.env.domaindata["std"]["objects"] + templatebuiltins = dict([('ttags', [n for (t,n) in xrefs if t == 'templatetag']), + ('tfilters', [n for (t,n) in xrefs if t == 'templatefilter'])]) outfilename = os.path.join(self.outdir, "templatebuiltins.js") f = open(outfilename, 'wb') f.write('var django_template_builtins = ') diff --git a/docs/_templates/genindex.html b/docs/_theme/djangodocs/genindex.html similarity index 68% rename from docs/_templates/genindex.html rename to docs/_theme/djangodocs/genindex.html index 60c19efd45..486994ae91 100644 --- a/docs/_templates/genindex.html +++ b/docs/_theme/djangodocs/genindex.html @@ -1,4 +1,4 @@ -{% extends "!genindex.html" %} +{% extends "basic/genindex.html" %} {% block bodyclass %}{% endblock %} {% block sidebarwrapper %}{% endblock %} \ No newline at end of file diff --git a/docs/_templates/layout.html b/docs/_theme/djangodocs/layout.html similarity index 97% rename from docs/_templates/layout.html rename to docs/_theme/djangodocs/layout.html index 70e029c3ac..ef91dd77a9 100644 --- a/docs/_templates/layout.html +++ b/docs/_theme/djangodocs/layout.html @@ -1,4 +1,4 @@ -{% extends "!layout.html" %} +{% extends "basic/layout.html" %} {%- macro secondnav() %} {%- if prev %} @@ -61,7 +61,7 @@ Home {{ reldelim2 }} Table of contents {{ reldelim2 }} Index {{ reldelim2 }} - Modules + Modules diff --git a/docs/_templates/modindex.html b/docs/_theme/djangodocs/modindex.html similarity index 67% rename from docs/_templates/modindex.html rename to docs/_theme/djangodocs/modindex.html index 96a1d2080a..59a5cb31bd 100644 --- a/docs/_templates/modindex.html +++ b/docs/_theme/djangodocs/modindex.html @@ -1,3 +1,3 @@ -{% extends "!modindex.html" %} +{% extends "basic/modindex.html" %} {% block bodyclass %}{% endblock %} {% block sidebarwrapper %}{% endblock %} \ No newline at end of file diff --git a/docs/_templates/search.html b/docs/_theme/djangodocs/search.html similarity index 69% rename from docs/_templates/search.html rename to docs/_theme/djangodocs/search.html index 8bd6dbd332..943478ce75 100644 --- a/docs/_templates/search.html +++ b/docs/_theme/djangodocs/search.html @@ -1,3 +1,3 @@ -{% extends "!search.html" %} +{% extends "basic/search.html" %} {% block bodyclass %}{% endblock %} {% block sidebarwrapper %}{% endblock %} \ No newline at end of file diff --git a/docs/_static/default.css b/docs/_theme/djangodocs/static/default.css similarity index 100% rename from docs/_static/default.css rename to docs/_theme/djangodocs/static/default.css diff --git a/docs/_static/djangodocs.css b/docs/_theme/djangodocs/static/djangodocs.css similarity index 100% rename from docs/_static/djangodocs.css rename to docs/_theme/djangodocs/static/djangodocs.css diff --git a/docs/_static/docicons-behindscenes.png b/docs/_theme/djangodocs/static/docicons-behindscenes.png similarity index 100% rename from docs/_static/docicons-behindscenes.png rename to docs/_theme/djangodocs/static/docicons-behindscenes.png diff --git a/docs/_static/docicons-note.png b/docs/_theme/djangodocs/static/docicons-note.png similarity index 100% rename from docs/_static/docicons-note.png rename to docs/_theme/djangodocs/static/docicons-note.png diff --git a/docs/_static/docicons-philosophy.png b/docs/_theme/djangodocs/static/docicons-philosophy.png similarity index 100% rename from docs/_static/docicons-philosophy.png rename to docs/_theme/djangodocs/static/docicons-philosophy.png diff --git a/docs/_static/homepage.css b/docs/_theme/djangodocs/static/homepage.css similarity index 100% rename from docs/_static/homepage.css rename to docs/_theme/djangodocs/static/homepage.css diff --git a/docs/_static/reset-fonts-grids.css b/docs/_theme/djangodocs/static/reset-fonts-grids.css similarity index 100% rename from docs/_static/reset-fonts-grids.css rename to docs/_theme/djangodocs/static/reset-fonts-grids.css diff --git a/docs/_theme/djangodocs/theme.conf b/docs/_theme/djangodocs/theme.conf new file mode 100644 index 0000000000..be43c723ae --- /dev/null +++ b/docs/_theme/djangodocs/theme.conf @@ -0,0 +1,4 @@ +[theme] +inherit = basic +stylesheet = default.css +pygments_style = trac diff --git a/docs/conf.py b/docs/conf.py index 90e0a6bcb5..606ee6b5ad 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,28 +8,35 @@ # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). # -# All configuration values have a default value; values that are commented out -# serve to show the default value. +# All configuration values have a default; values that are commented out +# serve to show the default. import sys import os -# If your extensions are in another directory, add it here. -sys.path.append(os.path.join(os.path.dirname(__file__), "_ext")) +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext"))) -# General configuration -# --------------------- +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ["djangodocs"] # Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] +# templates_path = [] # The suffix of source filenames. source_suffix = '.txt' +# The encoding of source files. +#source_encoding = 'utf-8-sig' + # The master toctree document. master_doc = 'contents' @@ -37,8 +44,10 @@ master_doc = 'contents' project = 'Django' copyright = 'Django Software Foundation and contributors' -# The default replacements for |version| and |release|, also used in various -# other places throughout the built documents. + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. # # The short X.Y version. version = '1.2' @@ -47,14 +56,22 @@ release = version # The next version to be released django_next_version = '1.3' +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = '%B %d, %Y' -# List of documents that shouldn't be included in the build. -#unused_docs = [] +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True @@ -75,13 +92,35 @@ pygments_style = 'trac' # Note: exclude_dirnames is new in Sphinx 0.5 exclude_dirnames = ['.svn'] -# Options for HTML output -# ----------------------- +# -- Options for HTML output --------------------------------------------------- -# The style sheet to use for HTML and HTML Help pages. A file of that name -# must exist either in Sphinx' static/ path, or in one of the custom paths -# given in html_static_path. -html_style = 'default.css' +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "djangodocs" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ["_theme"] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -110,17 +149,38 @@ html_translator_class = "djangodocs.DjangoHTMLTranslator" html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +#html_domain_indices = True -# If true, the reST sources are included in the HTML build as _sources/. -html_copy_source = True +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Djangodoc' +modindex_common_prefix = ["django."] -# Options for LaTeX output -# ------------------------ + +# -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' @@ -132,9 +192,24 @@ htmlhelp_basename = 'Djangodoc' # (source start file, target name, title, author, document class [howto/manual]). #latex_documents = [] latex_documents = [ - ('contents', 'django.tex', 'Django Documentation', 'Django Software Foundation', 'manual'), + ('contents', 'django.tex', u'Django Documentation', + u'Django Software Foundation', 'manual'), ] +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + # Additional stuff for the LaTeX preamble. #latex_preamble = '' @@ -142,10 +217,53 @@ latex_documents = [ #latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +#latex_domain_indices = True -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# If this isn't set to True, the LaTex writer can only handle six levels of headers. -latex_use_parts = True +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('contents', 'django', 'Django Documentation', ['Django Software Foundation'], 1) +] + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'Django' +epub_author = u'Django Software Foundation' +epub_publisher = u'Django Software Foundation' +epub_copyright = u'2010, Django Software Foundation' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True diff --git a/docs/internals/documentation.txt b/docs/internals/documentation.txt index 81480abf9a..9aa8551266 100644 --- a/docs/internals/documentation.txt +++ b/docs/internals/documentation.txt @@ -15,6 +15,11 @@ __ http://docutils.sourceforge.net/ To actually build the documentation locally, you'll currently need to install Sphinx -- ``easy_install Sphinx`` should do the trick. +.. note:: + + Generation of the Django documentation will work with Sphinx version 0.6 + or newer, but we recommend going straigh to Sphinx 1.0 or newer. + Then, building the html is easy; just ``make html`` from the ``docs`` directory. To get started contributing, you'll want to read the `ReStructuredText diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index f7aefa457d..4d84dfafbc 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1027,90 +1027,88 @@ The difference between these two is merely the template used to render them. The ``InlineModelAdmin`` class is a subclass of ``ModelAdmin`` so it inherits all the same functionality as well as some of its own: -``model`` -~~~~~~~~~ +.. attribute:: InlineModelAdmin.model -The model in which the inline is using. This is required. + The model in which the inline is using. This is required. -``fk_name`` -~~~~~~~~~~~ +.. attribute:: InlineModelAdmin.fk_name -The name of the foreign key on the model. In most cases this will be dealt -with automatically, but ``fk_name`` must be specified explicitly if there are -more than one foreign key to the same parent model. + The name of the foreign key on the model. In most cases this will be dealt + with automatically, but ``fk_name`` must be specified explicitly if there + are more than one foreign key to the same parent model. -``formset`` -~~~~~~~~~~~ +.. attribute:: InlineModelAdmin.formset -This defaults to ``BaseInlineFormSet``. Using your own formset can give you -many possibilities of customization. Inlines are built around -:ref:`model formsets `. + This defaults to ``BaseInlineFormSet``. Using your own formset can give you + many possibilities of customization. Inlines are built around + :ref:`model formsets `. -``form`` -~~~~~~~~ +.. attribute:: InlineModelAdmin.form -The value for ``form`` defaults to ``ModelForm``. This is what is -passed through to ``inlineformset_factory`` when creating the formset for this -inline. + The value for ``form`` defaults to ``ModelForm``. This is what is passed + through to ``inlineformset_factory`` when creating the formset for this + inline. .. _ref-contrib-admin-inline-extra: -``extra`` -~~~~~~~~~ +.. attribute:: InlineModelAdmin.extra -This controls the number of extra forms the formset will display in addition -to the initial forms. See the -:ref:`formsets documentation ` for more information. -.. versionadded:: 1.2 + This controls the number of extra forms the formset will display in addition + to the initial forms. See the + :ref:`formsets documentation ` for more information. -For users with JavaScript-enabled browsers, an "Add another" link is -provided to enable any number of additional inlines to be added in -addition to those provided as a result of the ``extra`` argument. + .. versionadded:: 1.2 -The dynamic link will not appear if the number of currently displayed -forms exceeds ``max_num``, or if the user does not have JavaScript -enabled. + For users with JavaScript-enabled browsers, an "Add another" link is + provided to enable any number of additional inlines to be added in addition + to those provided as a result of the ``extra`` argument. + + The dynamic link will not appear if the number of currently displayed forms + exceeds ``max_num``, or if the user does not have JavaScript enabled. .. _ref-contrib-admin-inline-max-num: -``max_num`` -~~~~~~~~~~~ +.. attribute:: InlineModelAdmin.max_num -This controls the maximum number of forms to show in the inline. This doesn't -directly correlate to the number of objects, but can if the value is small -enough. See :ref:`model-formsets-max-num` for more information. + This controls the maximum number of forms to show in the inline. This + doesn't directly correlate to the number of objects, but can if the value + is small enough. See :ref:`model-formsets-max-num` for more information. -``raw_id_fields`` -~~~~~~~~~~~~~~~~~ +.. attribute:: InlineModelAdmin.raw_id_fields -By default, Django's admin uses a select-box interface () for + fields that are ``ForeignKey``. Sometimes you don't want to incur the + overhead of having to select all the related instances to display in the + drop-down. -``raw_id_fields`` is a list of fields you would like to change -into a ``Input`` widget for either a ``ForeignKey`` or ``ManyToManyField``:: + ``raw_id_fields`` is a list of fields you would like to change into a + ``Input`` widget for either a ``ForeignKey`` or ``ManyToManyField``:: - class BookInline(admin.TabularInline): - model = Book - raw_id_fields = ("pages",) + class BookInline(admin.TabularInline): + model = Book + raw_id_fields = ("pages",) -``template`` -~~~~~~~~~~~~ -The template used to render the inline on the page. +.. attribute:: InlineModelAdmin.template -``verbose_name`` -~~~~~~~~~~~~~~~~ + The template used to render the inline on the page. -An override to the ``verbose_name`` found in the model's inner ``Meta`` class. +.. attribute:: InlineModelAdmin.verbose_name -``verbose_name_plural`` -~~~~~~~~~~~~~~~~~~~~~~~ + An override to the ``verbose_name`` found in the model's inner ``Meta`` + class. + +.. attribute:: InlineModelAdmin.verbose_name_plural + + An override to the ``verbose_name_plural`` found in the model's inner + ``Meta`` class. + +.. attribute:: InlineModelAdmin.can_delete + + Specifies whether or not inline objects can be deleted in the inline. + Defaults to ``True``. -An override to the ``verbose_name_plural`` found in the model's inner ``Meta`` -class. Working with a model with two or more foreign keys to the same parent model --------------------------------------------------------------------------- diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index dd14dd1ce7..1e72e0c662 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -107,7 +107,7 @@ special key that is used for errors that are tied to the entire model instead of to a specific field. You can access these errors with ``NON_FIELD_ERRORS``:: - from django.core.validators import ValidationError, NON_FIELD_ERRORS + from django.core.exceptions import ValidationError, NON_FIELD_ERRORS try: article.full_clean() except ValidationError, e: diff --git a/tests/regressiontests/backends/models.py b/tests/regressiontests/backends/models.py index e3137f2710..7315408d49 100644 --- a/tests/regressiontests/backends/models.py +++ b/tests/regressiontests/backends/models.py @@ -1,3 +1,5 @@ +from django.contrib.contenttypes import generic +from django.contrib.contenttypes.models import ContentType from django.conf import settings from django.db import models from django.db import connection, DEFAULT_DB_ALIAS @@ -37,6 +39,21 @@ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql': m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.ManyToManyField(Person,blank=True) +class Tag(models.Model): + name = models.CharField(max_length=30) + content_type = models.ForeignKey(ContentType, related_name='backend_tags') + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + + +class Post(models.Model): + name = models.CharField(max_length=30) + text = models.TextField() + tags = generic.GenericRelation('Tag') + + class Meta: + db_table = 'CaseSensitive_Post' + qn = connection.ops.quote_name __test__ = {'API_TESTS': """ diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index ee3ccdc72f..19d7fc5f6c 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -6,7 +6,7 @@ import unittest from django.conf import settings from django.core import management from django.core.management.color import no_style -from django.db import backend, connection, DEFAULT_DB_ALIAS +from django.db import backend, connection, connections, DEFAULT_DB_ALIAS from django.db.backends.signals import connection_created from django.test import TestCase @@ -137,6 +137,23 @@ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql': for statement in connection.ops.sql_flush(no_style(), tables, sequences): cursor.execute(statement) +class SequenceResetTest(TestCase): + def test_generic_relation(self): + "Sequence names are correct when resetting generic relations (Ref #13941)" + # Create an object with a manually specified PK + models.Post.objects.create(id=10, name='1st post', text='hello world') + + # Reset the sequences for the database + cursor = connection.cursor() + commands = connections[DEFAULT_DB_ALIAS].ops.sequence_reset_sql(no_style(), [models.Post]) + for sql in commands: + cursor.execute(sql) + + # If we create a new object now, it should have a PK greater + # than the PK we specified manually. + obj = models.Post.objects.create(name='New post', text='goodbye world') + self.assertTrue(obj.pk > 10) + def connection_created_test(sender, **kwargs): print 'connection_created signal' diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py index 3d6284e881..d351c550b7 100644 --- a/tests/regressiontests/templates/filters.py +++ b/tests/regressiontests/templates/filters.py @@ -328,7 +328,12 @@ def get_filter_tests(): 'join03': (r'{{ a|join:" & " }}', {'a': ['alpha', 'beta & me']}, 'alpha & beta & me'), 'join04': (r'{% autoescape off %}{{ a|join:" & " }}{% endautoescape %}', {'a': ['alpha', 'beta & me']}, 'alpha & beta & me'), - + # Test that joining with unsafe joiners don't result in unsafe strings (#11377) + 'join05': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': ' & '}, 'alpha & beta & me'), + 'join06': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta & me'), + 'join07': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': ' & ' }, 'alpha & beta & me'), + 'join08': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta & me'), + 'date01': (r'{{ d|date:"m" }}', {'d': datetime(2008, 1, 1)}, '01'), 'date02': (r'{{ d|date }}', {'d': datetime(2008, 1, 1)}, 'Jan. 1, 2008'), #Ticket 9520: Make sure |date doesn't blow up on non-dates diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 5902e8d5e7..2f2df65e96 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -506,6 +506,17 @@ class Templates(unittest.TestCase): 'basic-syntax28': ("{{ a.b }}", {'a': SilentGetItemClass()}, ('', 'INVALID')), 'basic-syntax29': ("{{ a.b }}", {'a': SilentAttrClass()}, ('', 'INVALID')), + # Something that starts like a number but has an extra lookup works as a lookup. + 'basic-syntax30': ("{{ 1.2.3 }}", {"1": {"2": {"3": "d"}}}, "d"), + 'basic-syntax31': ("{{ 1.2.3 }}", {"1": {"2": ("a", "b", "c", "d")}}, "d"), + 'basic-syntax32': ("{{ 1.2.3 }}", {"1": (("x", "x", "x", "x"), ("y", "y", "y", "y"), ("a", "b", "c", "d"))}, "d"), + 'basic-syntax33': ("{{ 1.2.3 }}", {"1": ("xxxx", "yyyy", "abcd")}, "d"), + 'basic-syntax34': ("{{ 1.2.3 }}", {"1": ({"x": "x"}, {"y": "y"}, {"z": "z", "3": "d"})}, "d"), + + # Numbers are numbers even if their digits are in the context. + 'basic-syntax35': ("{{ 1 }}", {"1": "abc"}, "1"), + 'basic-syntax36': ("{{ 1.2 }}", {"1": "abc"}, "1.2"), + # List-index syntax allows a template to access a certain item of a subscriptable object. 'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"), @@ -592,7 +603,7 @@ class Templates(unittest.TestCase): #filters should accept empty string constants 'filter-syntax20': ('{{ ""|default_if_none:"was none" }}', {}, ""), - + ### COMMENT SYNTAX ######################################################## 'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"), 'comment-syntax02': ("{# this is hidden #}hello{# foo #}", {}, "hello"), @@ -1285,7 +1296,8 @@ class Templates(unittest.TestCase): # Regression test for #11270. 'cache17': ('{% load cache %}{% cache 10 long_cache_key poem %}Some Content{% endcache %}', {'poem': 'Oh freddled gruntbuggly/Thy micturations are to me/As plurdled gabbleblotchits/On a lurgid bee/That mordiously hath bitled out/Its earted jurtles/Into a rancid festering/Or else I shall rend thee in the gobberwarts with my blurglecruncheon/See if I dont.'}, 'Some Content'), - + + ### AUTOESCAPE TAG ############################################## 'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"), 'autoescape-tag02': ("{% autoescape off %}{{ first }}{% endautoescape %}", {"first": "hello"}, "hello"), @@ -1314,6 +1326,23 @@ class Templates(unittest.TestCase): # implementation details (fortunately, the (no)autoescape block # tags can be used in those cases) 'autoescape-filtertag01': ("{{ first }}{% filter safe %}{{ first }} x"}, template.TemplateSyntaxError), + + # ifqeual compares unescaped vales. + 'autoescape-ifequal01': ('{% ifequal var "this & that" %}yes{% endifequal %}', { "var": "this & that" }, "yes" ), + + # Arguments to filters are 'safe' and manipulate their input unescaped. + 'autoescape-filters01': ('{{ var|cut:"&" }}', { "var": "this & that" }, "this that" ), + 'autoescape-filters02': ('{{ var|join:" & \" }}', { "var": ("Tom", "Dick", "Harry") }, "Tom & Dick & Harry" ), + + # Literal strings are safe. + 'autoescape-literals01': ('{{ "this & that" }}',{}, "this & that" ), + + # Iterating over strings outputs safe characters. + 'autoescape-stringiterations01': ('{% for l in var %}{{ l }},{% endfor %}', {'var': 'K&R'}, "K,&,R," ), + + # Escape requirement survives lookup. + 'autoescape-lookup01': ('{{ var.key }}', { "var": {"key": "this & that" }}, "this & that" ), + }