Code


Version 15 (modified by adrian, 9 years ago) (diff)

--

Removing the magic

The "magic-removal" branch aims to make several sweeping changes to the Django codebase. Most changes involve the database API and removing some of its unneeded magic, which confuses newbies and is a bit of a wart.

This document explains the changes in the branch.

Models support properties

Status: Done

Unlike before, properties are supported on models.

from django.db import models

class Person(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)

    def _get_full_name(self):
        return "%s %s" % (self.first_name, self.last_name)
    full_name = property(_get_full_name)

Model class and Field classes renamed/relocated

Status: Done

Difference: Import is from django.db.models instead of django.core.meta. This is easier to remember. However, models may not be the best name for it.

from django.db import models

class Person(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)

Database connection relocated/renamed

Status: Done

The connection is now available at django.db.connection. This is easier to remember, clearer and more consistent.

Old:

from django.core.db import db
cursor = db.cursor()

New:

from django.db import connection
cursor = connection.cursor()

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

Old:

from django.core import db
db.quote_name('foo')

New:

from django.db import 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.

Interact directly with model classes, not with magic modules

Status: Done

Import the model class directly from the module in which it was defined. No more django.models.* magic.

from myproject.people.models import Person
p = Person(first_name='John', last_name='Smith')
p.save()

This also removes the need for the module_name parameter.

Access table-level DB API functions via model classes, not with magic modules

Status: Done

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.

from myproject.people.models import Person
p_list = Person.objects.get_list()
p = Person.objects.get_object()

This doesn't work from an instance.

p = Person.objects.get_object(pk=1)
p.objects.get_list() # Raises AttributeError

Override default manager name ("objects")

Status: Done

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

class Person(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)
    objects = models.TextField()
    people = models.Manager()

p = Person(first_name='Mary', last_name='Jones', objects='Hello there.')
p.save()
p.objects == 'Hello there.'
Person.people.get_list()

Multiple managers

Status: Done

You can create as many managers as you want. When necessary (such as on the admin), Django will use the first one defined, in order.

If you define at least one custom manager, it will not get the default "objects" manager.

class Person(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)
    people = models.Manager()
    fun_people = SomeOtherManager()

API usage: Overriding model methods (and pre- and post-save hooks)

Status: Done

Proper subclassing of methods will now work, so you can subclass the automatic save() and delete() methods. This removes the need for the _pre_save() and _post_save() hooks. Example:

class Person(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)

    def save(self):
        self.do_something()
        super(Person, self).save(self) # Call the "real" save() method.
        self.do_something_else()

You can even skip saving (as requested in #1014).

class Person(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)

    def save(self):
        if datetime.date.today() > datetime.date(2005, 1, 1):
            super(Person, self).save(self) # Call the "real" save() method.
        else:
            # Don't save.
            pass

API usage: Overriding table-level functions

Status: Done

You can override any table-level functions, such as get_list() or get_object(). Do this by creating a custom models.Manager subclass and passing it to your model. The term "manager" could be replaced with some other word.

from django.db import models
class PersonManager(models.Manager):
    def get_list(self, **kwargs):
        # Changes get_list() to hard-code a limit=10.
        kwargs['limit'] = 10
        return models.Manager.get_list(self, **kwargs) # Call the "real" get_list() method.

class Person(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)
    objects = PersonManager()

DoesNotExist exception renamed

Status: Done

Instead of people.PersonDoesNotExist, it's not Person.DoesNotExist.

Old:

from django.models.myapp import people
try:
    people.get_object(pk=1)
except people.PersonDoesNotExist:
    print "Not there"

New:

from path.to.myapp import Person
try:
    Person.objects.get_object(pk=1)
except Person.DoesNotExist:
    print "Not there"

Other "module"-level members: Automatic manipulators and ObjectDoesNotExist exception

Status: Not yet done

Person.get_add_manipulator()
Person.get_change_manipulator()
Person.DoesNotExist

Database lookup API changes

See DescriptorFields, rjwittams' proposal on the API changes.