Context Navigation

Removing the magic

The "magic-removal" branch aims to make several sweeping changes to the Django codebase, removing warts that Django has accumulated over the years. Most changes involve the database API and removing some of its unneeded magic, and other changes involve improving the framework's simplicity and usability.

How to get the branch

Using two versions of Django side-by-side

Here's one way to use Django's trunk and magic-removal branch on the same machine. This assumes Django's trunk (or a release such as 0.90 or 0.91) is installed:

# Get the magic-removal code somewhere on your filesystem. In this example, we use /home/python/django.
$ cd /home/python/django
$ svn co http://code.djangoproject.com/svn/django/branches/magic-removal
# This will have created a "magic-removal" directory.
# Whenever you want to use magic-removal, set the environment variable {{{PYTHONPATH}}} to the directory containing magic-removal.
export PYTHONPATH=/home/python/django/magic-removal

Overview

The biggest changes in magic-removal are:

The magic package django.models no longer exists. To use models, just import the model class from wherever it lives on the Python path. Similarly, the magic modules (such as django.models.polls in the tutorial) no longer exist; now, you interact directly with the model class.

All automatic pluralization is gone.

The database API has changed in several ways.

Various packages, such as the Django template system (previously in django.core.template), have been moved around to make importing less verbose and easier to remember.

Status

Have at it! It's pretty stable, and we just need people to hack at it and make sure all is well.

Database changes you'll need to make

To upgrade from a previous Django installation, you'll need to make some database changes. Obviously, this doesn't apply if you're starting from scratch.

Rename core database tables

We've renamed a bunch of the core Django tables. Due to functionality differences between the various database backends, the SQL to perform the necessary tasks varies slightly between engines.

To upgrade in SQLite, execute this SQL in your database (some steps are more involved because SQLite has only limited ALTER TABLE functionality. We have to instead create working tables, move the data, and then replace the old tables with the new ones:

Auth_permissions case changes

A change checked into the MR branch on [2745] changed the verbose_names of two modules, Groups and Users to be lowercased instead of uppercased ("users" instead of "Users"). This results in the syncdb function of manage.py to break due to the disparity in existing installations of the Magic Removal branch. In order to fix your auth_permissions table, execute these SQL statements:

Database table-naming scheme has been changed

Database table names formerly were created by joining the app_label and module_name. Example: polls_polls.

Because there's no longer any concept of module_name, database table names are now formed by joining the app_label and model class name (lower case). Example: polls_poll.

As always, this behavior can be overridden on a per-model basis by specifying the db_table attribute in class Meta in your model.

To upgrade, you'll either have to explicitly set db_table in your models or rename your database tables to fit the new naming scheme Django expects. We'd recommend setting db_table, because it's easier.

Code changes you'll need to make

Model class and Field classes renamed/relocated

Change your models to import from django.db.models instead of django.core.meta.

This is changed in templates themselves, too -- notably in the {% extends %} and {% include %} template tags:

Old:

{% extends "base" %}
{% include "some_snippet" %}

New:

{% extends "base.html" %}
{% include "some_snippet.html" %}

As a result of this change, the TEMPLATE_FILE_EXTENSION setting is gone, because there's no need for it.

Clearly, this also means you can start putting whatever extension you want on templates. So if you'd like your templates to have a .txt extension, go for it. If you'd like to keep using .html, that's fine too. If you want to invent your own extension, or not even *use* an extension, be our guest.

All the templates for the django.contrib apps -- most notably, the admin site -- still use .html extensions.

Custom template tags created via inclusion_tag() should note the explicit template name (with the extension). For example, use inclusion_tag('foo/bar.html') instead of inclusion_tag('foo/bar').

If you're using custom template_name arguments to generic views, don't forget to change those calls in your URLconf, too.

Finally, note the syndication framework, which looks for templates with the name of the slug of each feed, now requires an '.html' extension on those templates. For example, if your feed has a slug "hello", the syndication framework will look for the templates feeds/hello_title.html and feeds/hello_description.html. This is backwards-compatible.

Namespace simplification

django.utils.httpwrappers has moved to django.http.

django.core.exceptions.Http404 has moved to django.http.Http404.

django.core.template has moved to django.template.

django.core.formfields has moved to django.forms.

django.core.extensions has moved to django.shortcuts.

django.core.extensions.DjangoContext has been renamed to RequestContext and moved to django.template.RequestContext.

The "auth" and "core" models have been split and moved to django.contrib as follows:

django.models.auth has moved to django.contrib.auth.models.

django.models.core.sites has moved to django.contrib.sites.models.

django.models.core.contenttypes has moved to django.contrib.contenttypes.models.

django.models.core.packages has moved to django.contrib.contenttypes.models. (Note that "packages" are going away before magic-removal is done.)

Session middleware has moved from django.middleware.sessions.SessionMiddleware to django.contrib.sessions.middleware.SessionMiddleware. Make sure to update your MIDDLEWARE_CLASSES setting, if you're using sessions.

Also, the Session model has moved from django/models/core.py to django/contrib/sessions/models.py. If you're accessing the Session model for some reason, note that location change.

Be sure to import your models

Django won't recursively import everything inside a "models" package in your app. In fact, "startapp" doesn't even create "models" as a package anymore! Now Django imports "yourapp.models", and uses whatever classes in there that are subclasses of models.Model. So if you want to keep using a package, you need to go in your ___init__.py and do something like from mymodelmodule import *. -- LaloMartins

Changes to model syntax

class META should now be class Meta. The latter is easier on the eyes.

The following are no longer valid parameters to class Meta and should be removed:

module_name

admin (See "Moved admin options to 'class Admin'" below.)

exceptions (Just put your exceptions in the module that contains the models and access them normally.)

module_constants (Just put your constants in the module that contains the models and access them normally.)

where_constraints (Just use a custom manager. See "Custom managers, and multiple managers" below.)

Moved admin options to 'class Admin'

Instead of admin=meta.Admin in the class META, all admin options are in an inner class Admin.

Database connection relocated/renamed

For any code that uses the raw database connection, use django.db.connection instead of django.core.db.db.

Old:

fromdjango.core.dbimport db
cursor = db.cursor()

New:

fromdjango.dbimport connection
cursor = connection.cursor()

Backend-specific functions, if you should need them, are available at django.db.backend.

Old:

fromdjango.coreimport db
db.quote_name('foo')

New:

fromdjango.dbimport backend
backend.quote_name('foo')

Also, the various backend functionality has been split into three separate modules for each backend -- base.py, creation.py and introspection.py. This is purely for performance and memory savings, so that basic, everyday Django usage doesn't have to load the introspective functionality into memory.

Model methods no longer automatically have access to datetime and db modules

Formerly, each model method magically had access to the datetime module and to the variable db, which represents the current database connection. Now, those have to be imported explicitly.

Descriptor fields

All "table-level" functions -- ways of retrieving records tablewide rather than performing instance-specific tasks -- are now accessed via a model class's objects attribute. They aren't direct methods of a model instance object because we want to keep the "table-wide" and "row-specific" namespaces separate.

A model class's objects attribute is an instance of django.db.models.manager.Manager. A manager has the following methods, all of which return a QuerySet instance.

all() -- Returns a QuerySet of all objects in the database. This is like the old get_list(). Takes no arguments.

filter(**kwargs) -- Returns a QuerySet, filtered by the given keyword arguments. Lookup arguments are in the same style as previously, e.g. pubdate__year=2005, except you can leave off __exact as a convenience. For example, name='John' and name__exact='John' are equivalent. Note that for lookups between applications you can't omit __exact.

exclude(**kwargs) is the same as filter(), but returns objects where the given arguments are not true.

order_by(*fieldnames) -- Returns a QuerySet

count() -- Returns the count of all objects in the database.

dates(field_name, kind) -- Like the old get_FIELD_list() for date fields. For example, old-school get_pubdate_list('year') is now dates('pubdate', 'year').

delete() -- Deletes all objects.

distinct() -- Returns a QuerySet with DISTINCT set.

extra(select=None, where=None, params=None, tables=None) -- Sets the select, where, params and tables arguments, which are in the same format as before.

get(**kwargs) -- Like the old get_object(). Returns an object or raises DoesNotExist on error.

in_bulk(id_list) -- Like the old get_in_bulk().

iterator() -- Returns a generator that iterators over results.

select_related() -- Returns a QuerySet with the "select related" option (which acts the same as before) set.

values(*fieldnames) -- Like the old get_values().

Each QuerySet has the following methods, which return a clone of the query set with the appropriate changes made:

Note that related-object lookup uses the default manager of the related object, which means the API for accessing related objects is completely consistent with the API for accessing objects via a manager.

Also note that managers can't be accessed from instances:

p = Person.objects.get(pk=1)
p.objects.all()# Raises AttributeError

Override default manager name ('objects')

If a model already has an objects attribute, you'll need to specify an alternate name for the objects manager.

Using a custom manager for the admin

Sometimes you'll want to use a different manager for the admin displays (e.g. to display only objects matching some criteria in the admin). You can do this by defining the manager option in your Admin declaration:

Proper subclassing of methods now works, so you can subclass the automatic save() and delete() methods. This removes the need for the _pre_save(), _post_save(), _pre_delete() and _post_delete() hooks -- all of which have been removed. Example:

Moved admin URLconf to shorten its path

get_object_or_404 and get_list_or_404 now take model classes, not modules

Old:

get_object_or_404(polls, pk=1)

New:

get_object_or_404(Poll, pk=1)

Changed the parameters you pass to generic views

Because there's no longer a concept of module_name, the "info_dicts" passed to ​generic views no longer accept "app_label" and "module_name". Instead, pass the parameter "queryset", which should be a QuerySet instance.

Changed template names in generic views

Because there's no longer a concept of module_name, ​generic views no longer create templates based on the module_name. Wherever they used module_name, they now use model_name, a lowercase version of the model name.

Note that app_label remains the same.

These examples assume models live in myproject/blog/models.py.

Old: blog/entries_archive.html

New: blog/entry_archive.html

Moved settings into an instance

Settings have moved out of a dedicated module django.conf.settings into an instance in the django.conf module. So now you need to import the settings object and reference settings as attributes of that instance.

Old: from django.conf.settings import LANGUAGE_CODE

New: from django.conf import settings

Wrappers around the Django machinery can make use of this by exchanging the settings instance with a proxy instance that delegates attribute access to a per-thread or per-location global.

Removed SilentVariableFailure exception

Old behavior: Any exception that subclasses django.core.template.SilentVariableFailure fails silently in the template system.

New behavior: Any exception that has a silent_variable_failure attribute fails silently in the template system. django.core.template.SilentVariableFailure no longer exists.

request.user is now set via middleware

It used to be set in the mod_python and wsgi handlers. You will need to add "django.contrib.auth.middleware.AuthenticationMiddleware" somewhere after"django.contrib.sessions.middleware.SessionMiddleware" in MIDDLEWARE_CLASSES in your settings.py file. Otherwise accessing request.user will raise an AttributeError.

Authentication has been consolidated

Previously, pieces of the authentication system resided in at least 4 different places. Everything has now been consolidated into django.contrib.auth as follows:

django.parts.auth.formfields.AuthenticationForm has moved to django.contrib.auth.forms

django.parts.auth.anonymoususers.AnonymousUser has moved to django.contrib.auth.models

django.views.auth.login.* has moved to django.contrib.auth.views

django.views.decorators.auth.* has moved to django.contrib.auth.decorators

django.views.registration.passwords.PasswordResetForm has moved to django.contrib.auth.forms

django.views.registration.passwords.PasswordChangeForm has moved to django.contrib.auth.forms

django.views.registration.passwords.password_reset has moved to django.contrib.auth.views

django.views.registration.passwords.password_reset_done has moved to django.contrib.auth.views

django.views.registration.passwords.password_change has moved to django.contrib.auth.views

django.views.registration.passwords.password_change_done has moved to django.contrib.auth.views

If you are using any of these classes or functions, you will need to update your code accordingly.