= 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. {{{ #!python 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. {{{ #!python 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: Not yet done''' This is easier to remember, clearer and more consistent. Old: {{{ #!python from django.core.db import db cursor = db.cursor() }}} New: {{{ #!python from django.db import connection cursor = connection.cursor() }}} == 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. {{{ #!python 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. {{{ #!python from myproject.people.models import Person p_list = Person.objects.get_list() p = Person.objects.get_object() }}} This doesn't work from an instance. {{{ #!python p = Person.objects.get_object(pk=1) p.objects.get_list() # Raises AttributeError }}} == Override default manager name ("objects") == '''Status: Not yet done''' If a model already has an {{{objects}}} attribute, you'll need to specify an alternate name for the magic {{{objects}}}. {{{ #!python class Person(models.Model): first_name = models.CharField(maxlength=30) last_name = models.CharField(maxlength=30) objects = models.TextField() class META: manager = Manager(name='more_objects') p = Person(first_name='Mary', last_name='Jones', objects='Hello there.') p.save() p.objects == 'Hello there.' Person.more_objects.get_list() }}} == 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: {{{ #!python 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). {{{ #!python 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: Not yet 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. {{{ #!python 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 super(PersonManager, self).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) class META: manager = PersonManager() }}} == Other "module"-level members: Automatic manipulators and !ObjectDoesNotExist exception == '''Status: Not yet done''' {{{ #!python Person.get_add_manipulator() Person.get_change_manipulator() Person.DoesNotExist }}} == Database lookup API changes == See DescriptorFields, rjwittams' proposal on the API changes.