[1.9.x] Fixed #25968 -- Changed project/app templates to use a "py-tpl" suffix.

Debian packages unconditionally byte-compile .py files on installation and
do not silence errors by design. Therefore, we need a way of shipping these
invalid .py files without a .py extension but ensuring that when we
template them, they end up as .py.

We don't special-case .py files so that the all the TemplateCommand
command-line options (eg. extra_files and extensions) still work entirely
as expected and it may even be useful for other formats too.

Backport of abc0777b63 from master
This commit is contained in:
Chris Lamb 2016-01-24 10:06:01 +01:00 committed by Tim Graham
parent 20b217b0b0
commit 3306106fb1
20 changed files with 58 additions and 7 deletions

View File

@ -42,6 +42,11 @@ class TemplateCommand(BaseCommand):
# Can't perform any active locale changes during this command, because
# setting might not be available at all.
leave_locale_alone = True
# Rewrite the following suffixes when determining the target filename.
rewrite_template_suffixes = (
# Allow shipping invalid .py files without byte-compilation.
('.py-tpl', '.py'),
)
def add_arguments(self, parser):
parser.add_argument('name', help='Name of the application or project.')
@ -139,6 +144,11 @@ class TemplateCommand(BaseCommand):
old_path = path.join(root, filename)
new_path = path.join(top_dir, relative_dir,
filename.replace(base_name, name))
for old_suffix, new_suffix in self.rewrite_template_suffixes:
if new_path.endswith(old_suffix):
new_path = new_path[:-len(old_suffix)] + new_suffix
break # Only rewrite once
if path.exists(new_path):
raise CommandError("%s already exists, overlaying a "
"project or app into an existing "
@ -149,7 +159,7 @@ class TemplateCommand(BaseCommand):
# accidentally render Django templates files
with open(old_path, 'rb') as template_file:
content = template_file.read()
if filename.endswith(extensions) or filename in extra_files:
if new_path.endswith(extensions) or filename in extra_files:
content = content.decode('utf-8')
template = Engine().from_string(content)
content = template.render(context)

View File

@ -1147,6 +1147,15 @@ with the ``--name`` option. The :class:`template context
To work around this problem, you can use the :ttag:`templatetag`
templatetag to "escape" the various parts of the template syntax.
In addition, to allow Python template files that contain Django template
language syntax while also preventing packaging systems from trying to
byte-compile invalid ``*.py`` files, template files ending with ``.py-tpl``
will be renamed to ``.py``.
.. versionchanged:: 1.9.2
Renaming of ``.py-tpl`` to ``.py`` was added.
.. _source: https://github.com/django/django/tree/master/django/conf/app_template/
startproject <projectname> [destination]

View File

@ -4,7 +4,24 @@ Django 1.9.2 release notes
*Under development*
Django 1.9.2 fixes several bugs in 1.9.1.
Django 1.9.2 fixes several bugs in 1.9.1 and makes a small backwards
incompatible change that hopefully doesn't affect any users.
Backwards incompatible change: ``.py-tpl`` files rewritten in project/app templates
===================================================================================
The addition of some Django template language syntax to the default app
template in Django 1.9 means those files now have some invalid Python syntax.
This causes difficulties for packaging systems that unconditionally
byte-compile ``*.py`` files.
To remedy this, a ``.py-tpl`` suffix is now used for the project and app
template files included in Django. The ``.py-tpl`` suffix is replaced with
``.py`` by the ``startproject`` and ``startapp`` commands. For example, a
template with the filename ``manage.py-tpl`` will be created as ``manage.py``.
Please file a ticket if you have a custom project template containing
``.py-tpl`` files and find this behavior problematic.
Bugfixes
========

View File

@ -1016,7 +1016,7 @@ a Django application with this structure::
``SyntaxError`` when installing Django setuptools 5.5.x
-------------------------------------------------------
When installing Django 1.9+ with setuptools 5.5.x, you'll see::
When installing Django 1.9 or 1.9.1 with setuptools 5.5.x, you'll see::
Compiling django/conf/app_template/apps.py ...
File "django/conf/app_template/apps.py", line 4
@ -1033,7 +1033,8 @@ When installing Django 1.9+ with setuptools 5.5.x, you'll see::
It's safe to ignore these errors (Django will still install just fine), but you
can avoid them by upgrading setuptools to a more recent version. If you're
using pip, you can upgrade pip using ``pip install -U pip`` which will also
upgrade setuptools.
upgrade setuptools. This is resolved in later versions of Django as described
in the :doc:`/releases/1.9.2`.
Miscellaneous
-------------

View File

@ -165,8 +165,7 @@ This is the recommended way to install Django.
1. Install pip_. The easiest is to use the `standalone pip installer`_. If your
distribution already has ``pip`` installed, you might need to update it if
it's outdated. If it's outdated, you'll know because installation won't
work. If you're using an old version of setuptools, you might see some
:ref:`harmless SyntaxErrors <syntax-error-old-setuptools-django-19>` also.
work.
2. Take a look at virtualenv_ and virtualenvwrapper_. These tools provide
isolated Python environments, which are more practical than installing

View File

@ -181,7 +181,7 @@ class AdminScriptTestCase(unittest.TestCase):
pass
conf_dir = os.path.dirname(upath(conf.__file__))
template_manage_py = os.path.join(conf_dir, 'project_template', 'manage.py')
template_manage_py = os.path.join(conf_dir, 'project_template', 'manage.py-tpl')
test_manage_py = os.path.join(self.test_dir, 'manage.py')
shutil.copyfile(template_manage_py, test_manage_py)

View File

@ -1,13 +1,28 @@
import os
import shutil
import unittest
from django import conf
from django.test import TestCase
from django.utils import six
from django.utils._os import upath
@unittest.skipIf(six.PY2,
'Python 2 cannot import the project template because '
'django/conf/project_template doesn\'t have an __init__.py file.')
class TestStartProjectSettings(TestCase):
def setUp(self):
# Ensure settings.py exists
project_dir = os.path.join(
os.path.dirname(upath(conf.__file__)),
'project_template',
'project_name',
)
template_settings_py = os.path.join(project_dir, 'settings.py-tpl')
test_settings_py = os.path.join(project_dir, 'settings.py')
shutil.copyfile(template_settings_py, test_settings_py)
self.addCleanup(os.remove, test_settings_py)
def test_middleware_classes_headers(self):
"""