= 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: Done''' The connection is now available at {{{django.db.connection}}}. 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() }}} Backend-specific functions, if you should need them, are available at {{{django.db.backend}}}. Old: {{{ #!python from django.core import db db.quote_name('foo') }}} New: {{{ #!python 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. {{{ #!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: 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() 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. {{{ #!python 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: {{{ #!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: 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 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: {{{ #!python from django.models.myapp import people try: people.get_object(pk=1) except people.PersonDoesNotExist: print "Not there" }}} New: {{{ #!python 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''' {{{ #!python Person.get_add_manipulator() Person.get_change_manipulator() Person.DoesNotExist }}} == Database lookup API changes == See DescriptorFields, rjwittams' proposal on the API changes.