Changes between Version 58 and Version 59 of RemovingTheMagic


Ignore:
Timestamp:
Feb 1, 2006, 12:28:54 PM (18 years ago)
Author:
Adrian Holovaty
Comment:

Moved DescriptorFields stuff to this page

Legend:

Unmodified
Added
Removed
Modified
  • RemovingTheMagic

    v58 v59  
    1717 * 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.
    1818 * All automatic pluralization is gone.
    19  * The lookup API is changing so that many methods will become attributes. For example, {{{choice.get_poll()}}} is now {{{choice.poll}}}.
     19 * The database API has changed in several ways.
    2020 * 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.
    2121
     
    164164=== Namespace simplification ===
    165165
    166 See NamespaceSimplification for details.
     166{{{django.utils.httpwrappers}}} has moved to {{{django.http}}}.
     167
     168{{{django.core.exceptions.Http404}}} has moved to {{{django.http.Http404}}}.
     169
     170{{{django.core.template}}} has moved to {{{django.template}}}.
     171
     172{{{django.core.formfields}}} has moved to {{{django.forms}}}.
     173
     174{{{django.core.extensions}}} has moved to {{{django.shortcuts}}}.
     175
     176{{{django.core.extensions.DjangoContext}}} has been renamed to {{{RequestContext}}} and moved to {{{django.template.RequestContext}}}.
     177
     178You'll need to update {{{TEMPLATE_LOADERS}}} in your settings from:
     179{{{
     180#!python
     181TEMPLATE_LOADERS = (
     182    'django.core.template.loaders.filesystem.load_template_source',
     183    'django.core.template.loaders.app_directories.load_template_source',
     184#    'django.core.template.loaders.eggs.load_template_source',
     185)
     186}}}
     187
     188...to:
     189
     190{{{
     191#!python
     192TEMPLATE_LOADERS = (
     193    'django.template.loaders.filesystem.load_template_source',
     194    'django.template.loaders.app_directories.load_template_source',
     195#    'django.template.loaders.eggs.load_template_source',
     196)
     197}}}
     198
     199The "auth" and "core" models have been split and moved to {{{django.contrib}}} as follows:
     200
     201    * {{{django.models.auth}}} has moved to {{{django.contrib.auth.models}}}.
     202    * {{{django.models.core.sites}}} has moved to {{{django.contrib.sites.models}}}.
     203    * {{{django.models.core.contenttypes}}} has moved to {{{django.contrib.contenttypes.models}}}.
     204    * {{{django.models.core.packages}}} has moved to {{{django.contrib.contenttypes.models}}}. (Note that "packages" are going away before magic-removal is done.)
     205
     206Session 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.
     207
     208Also, 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.
    167209
    168210=== Changes to model syntax ===
     
    203245}}}
    204246
     247=== Database connection relocated/renamed ===
     248
     249For any code that uses the raw database connection, use {{{django.db.connection}}} instead of {{{django.core.db.db}}}.
     250
     251Old:
     252{{{
     253#!python
     254from django.core.db import db
     255cursor = db.cursor()
     256}}}
     257
     258New:
     259{{{
     260#!python
     261from django.db import connection
     262cursor = connection.cursor()
     263}}}
     264
     265Backend-specific functions, if you should need them, are available at {{{django.db.backend}}}.
     266
     267Old:
     268{{{
     269#!python
     270from django.core import db
     271db.quote_name('foo')
     272}}}
     273
     274New:
     275{{{
     276#!python
     277from django.db import backend
     278backend.quote_name('foo')
     279}}}
     280
     281Also, 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.
     282
    205283=== Model methods no longer automatically have access to datetime and db modules ===
    206284
     
    230308}}}
    231309
    232 === Access table-level DB API functions via model classes, not with magic modules ===
     310=== Descriptor fields ===
    233311
    234312All "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.
    235313
    236 {{{
    237 #!python
    238 from myproject.people.models import Person
    239 p_list = Person.objects.get_list()
    240 p = Person.objects.get_object()
    241 }}}
    242 
    243 This doesn't work from an instance.
    244 
    245 {{{
    246 #!python
    247 p = Person.objects.get_object(pk=1)
    248 p.objects.get_list() # Raises AttributeError
     314A 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.
     315
     316    * {{{all()}}} -- Returns a {{{QuerySet}}} of all objects in the database. This is like the old {{{get_list()}}}. Takes no arguments.
     317    * {{{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.
     318    * {{{order_by(*fieldnames)}}} -- Returns a {{{QuerySet}}}
     319    * {{{count()}}} -- Returns the count of all objects in the database.
     320    * {{{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')}}}.
     321    * {{{delete()}}} -- Deletes all objects.
     322    * {{{distinct()}}} -- Returns a {{{QuerySet}}} with DISTINCT set.
     323    * {{{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.
     324    * {{{get(**kwargs)}}} -- Like the old {{{get_object()}}}. Returns an object or raises {{{DoesNotExist}}} on error.
     325    * {{{in_bulk(id_list)}}} -- Like the old {{{get_in_bulk()}}}.
     326    * {{{iterator()}}} -- Returns a generator that iterators over results.
     327    * {{{select_related()}}} -- Returns a {{{QuerySet}}} with the "select related" option (which acts the same as before) set.
     328    * {{{values(*fieldnames)}}} -- Like the old {{{get_values()}}}.
     329
     330Each {{{QuerySet}}} has the following methods, which return a clone of the query set with the appropriate changes made:
     331
     332    * {{{filter(**kwargs)}}}
     333    * {{{order_by(*fieldnames)}}}
     334    * {{{iterator()}}}
     335    * {{{count()}}}
     336    * {{{get(**kwargs)}}}
     337    * {{{delete()}}}
     338    * {{{filter(**kwargs)}}}
     339    * {{{select_related()}}}
     340    * {{{order_by(*fieldnames)}}}
     341    * {{{distinct()}}}
     342    * {{{extra(select=None, where=None, params=None, tables=None)}}}
     343
     344Here are some examples, which use the following models:
     345
     346{{{
     347#!python
     348class Reporter(models.Model):
     349    fname = models.CharField(maxlength=30)
     350    lname = models.CharField(maxlength=30)
     351
     352class Site(models.Model):
     353    name = models.CharField(maxlength=20)
     354
     355class Article(models.Model):
     356    headline = models.CharField(maxlength=50)
     357    reporter = models.ForeignKey(Reporter)
     358    pub_date = models.DateField()
     359    sites = models.ManyToManyField(Site)
     360}}}
     361
     362|| '''Old syntax'''                                        || '''New syntax'''                             ||
     363|| {{{reporters.get_list()}}}                             || {{{Reporter.objects.all()}}}                       ||
     364|| {{{reporters.get_list(fname__exact='John')}}}          || {{{Reporter.objects.filter(fname='John')}}}        ||
     365|| {{{reporters.get_list(order_by=('-lname', 'fname'))}}} || {{{Reporter.objects.order_by('-lname', 'fname')}}} ||
     366|| {{{reporters.get_list(fname__exact='John', order_by=('lname',))}}} || {{{Reporter.objects.filter(fname='John').order_by('lname')}}} ||
     367|| {{{reporters.get_object(pk=3)}}}                       || {{{Reporter.objects.get(pk=3)}}}                   ||
     368|| {{{reporters.get_object(fname__contains='John')}}}     || {{{Reporter.objects.get(fname__contains='John')}}} ||
     369|| {{{reporters.get_list(distinct=True)}}}                || {{{Reporter.objects.distinct()}}} ||
     370|| {{{reporters.get_values()}}}                           || {{{Reporter.objects.values()}}} ||
     371|| {{{reporters.get_in_bulk([1, 2])}}}                    || {{{Reporter.objects.in_bulk([1, 2])}}} ||
     372|| {{{reporters.get_in_bulk([1, 2], fname__exact='John')}}} || {{{Reporter.objects.filter(fname='John').in_bulk([1, 2])}}} ||
     373|| '''Date lookup'''                                      ||                                              ||
     374|| {{{articles.get_pub_date_list('year')}}}                     || {{{Article.objects.dates('pub_date', 'year')}}} ||
     375|| '''Many-to-one related lookup'''                        ||                                              ||
     376|| {{{article_obj.reporter_id}}}                                 || {{{article_obj.reporter.id}}} ||
     377|| {{{article_obj.get_reporter()}}}                              || {{{article_obj.reporter}}}    ||
     378|| {{{reporter_obj.get_article_list()}}}                         || {{{reporter_obj.article_set.all()}}} ||
     379|| {{{reporter_obj.get_article_list(headline__exact='Hello')}}}  || {{{reporter_obj.article_set.filter(headline='Hello')}}} ||
     380|| {{{reporter_obj.get_article_count()}}}                        || {{{reporter_obj.article_set.count()}}} ||
     381|| {{{reporter_obj.add_article(headline='Foo')}}}                || {{{reporter_obj.article_set.add(headline='Foo')}}} ||
     382|| (Alternate syntax)                                      || {{{reporter_obj.article_set.add(article_obj)}}} ||
     383|| ("values" lookup, etc., not previously possible)        || {{{reporter_obj.article_set.values()}}} ||
     384|| '''Many-to-many related lookup'''                       ||                         ||
     385|| {{{article_obj.get_site_list()}}}                             || {{{article_obj.sites.all()}}} ||
     386|| {{{article_obj.set_sites([s1.id, s2.id])}}}                   || {{{article_obj.sites.clear(); article_obj.sites.add(s1); article_obj.sites.add(s2)}}} ||
     387|| {{{article_obj.set_sites([s1.id]) # deletion}}}               || {{{article_obj.sites.remove(s2)}}} ||
     388|| {{{site_obj.get_reporter_list()}}}                            || {{{site_obj.reporter_set.all()}}} ||
     389
     390Note 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.
     391
     392Also note that managers can't be accessed from instances:
     393
     394{{{
     395#!python
     396p = Person.objects.get(pk=1)
     397p.objects.all() # Raises AttributeError
    249398}}}
    250399
    251400=== Override default manager name ("objects") ===
    252401
    253 If a model already has an {{{objects}}} attribute, you'll need to specify an alternate name for the magic {{{objects}}}.
     402If a model already has an {{{objects}}} attribute, you'll need to specify an alternate name for the {{{objects}}} manager.
    254403
    255404{{{
     
    264413p.save()
    265414p.objects == 'Hello there.'
    266 Person.people.get_list()
     415Person.people.all()
    267416}}}
    268417
     
    280429    people = models.Manager()
    281430    fun_people = SomeOtherManager()
     431}}}
     432
     433If a manager needs to access its associated model class, it should use {{{self.model}}}. Example:
     434
     435{{{
     436#!python
     437class PersonManager(models.Manager):
     438    def get_fun_person(self):
     439        try:
     440            return self.get(fun=True)
     441        except self.model.DoesNotExist:
     442            print "Doesn't exist."
    282443}}}
    283444
     
    314475}}}
    315476
    316 === Database connection relocated/renamed ===
    317 
    318 For any code that uses the raw database connection, use {{{django.db.connection}}} instead of {{{django.core.db.db}}}.
    319 
    320 Old:
    321 {{{
    322 #!python
    323 from django.core.db import db
    324 cursor = db.cursor()
    325 }}}
    326 
    327 New:
    328 {{{
    329 #!python
    330 from django.db import connection
    331 cursor = connection.cursor()
    332 }}}
    333 
    334 Backend-specific functions, if you should need them, are available at {{{django.db.backend}}}.
    335 
    336 Old:
    337 {{{
    338 #!python
    339 from django.core import db
    340 db.quote_name('foo')
    341 }}}
    342 
    343 New:
    344 {{{
    345 #!python
    346 from django.db import backend
    347 backend.quote_name('foo')
    348 }}}
    349 
    350 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.
    351 
    352477=== Renamed !DoesNotExist exception ===
    353478
     
    369494from path.to.myapp.models import Person
    370495try:
    371     Person.objects.get_object(pk=1)
     496    Person.objects.get(pk=1)
    372497except Person.DoesNotExist:
    373498    print "Not there"
     
    395520}}}
    396521
    397 === Moved "auth" and "core" models to django.contrib ===
    398 
    399 See http://groups.google.com/group/django-developers/browse_thread/thread/276d071a74543448/7d4b1c40c2d53393
    400 
    401  * Old: {{{django.models.auth}}}
    402  * New: {{{django.contrib.auth.models}}}
    403 
    404  * Old: {{{django.models.core.sites}}}
    405  * New: {{{django.contrib.sites.models}}}
    406 
    407  * Old: {{{django.models.core.contenttypes}}}
    408  * New: {{{django.contrib.contenttypes.models}}}
    409 
    410  * Old: {{{django.models.core.packages}}}
    411  * New: {{{django.contrib.contenttypes.models}}} ("Packages" will most likely be removed in the future.)
    412 
    413 === Moved Session model and middleware from core to django.contrib ===
    414 
    415 The location of the session middleware has changed.
    416 
    417  * Old: {{{django.middleware.sessions.SessionMiddleware}}}
    418  * New: {{{django.contrib.sessions.middleware.SessionMiddleware}}}
    419 
    420 Make sure to update your {{{MIDDLEWARE_CLASSES}}} setting, if you're using sessions.
    421 
    422 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.
    423522
    424523=== Changed the parameters you pass to generic views ===
     
    441540#!python
    442541from myproject.blog.models import Entry
    443 info_dict = {
    444     'model': Entry
    445 }
     542info_dict = {'model': Entry}
    446543}}}
    447544
     
    459556=== Moved settings into an instance ===
    460557
    461 To make it easier to switch settings in situations where you would need multiple different settings - for example when trying to use multiple django projects within one server context or when using Django apps within a bigger WSGI scenario - the settings were 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.
    462 
    463 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.
     558Settings 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.
    464559
    465560 * Old: {{{from django.conf.settings import LANGUAGE_CODE}}}
    466561 * New: {{{from django.conf import settings}}}
    467562
     563Wrappers 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.
     564
    468565=== Removed !SilentVariableFailure exception ===
    469566
     
    471568
    472569New behavior: Any exception that has a {{{silent_variable_failure}}} attribute fails silently in the template system. {{{django.core.template.SilentVariableFailure}}} no longer exists.
    473 
    474 
    475 
    476 
    477570
    478571== New functionality you can start using ==
     
    495588}}}
    496589
    497 === You can override table-level functions ===
    498 
    499 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.
    500 
    501 {{{
    502 #!python
    503 from django.db import models
    504 class PersonManager(models.Manager):
    505     def get_list(self, **kwargs):
    506         # Changes get_list() to hard-code a limit=10.
    507         kwargs['limit'] = 10
    508         return models.Manager.get_list(self, **kwargs) # Call the "real" get_list() method.
    509 
    510 class Person(models.Model):
    511     first_name = models.CharField(maxlength=30)
    512     last_name = models.CharField(maxlength=30)
    513     objects = PersonManager()
    514 }}}
    515 
    516 If a manager needs to access its associated class, it should use {{{self.model}}}. Example:
    517 
    518 {{{
    519 #!python
    520 class PersonManager(models.Manager):
    521     def get_fun_person(self):
    522         try:
    523             return self.get_object(fun__exact=True)
    524         except self.model.DoesNotExist:
    525             print "Doesn't exist."
    526 }}}
    527 
    528 
    529 
    530 
    531 
     590=== You can override default QuerySets ===
     591
     592You can specify the default QuerySet (see "Descriptor fields" above) that a manager uses. For example:
     593
     594{{{
     595#!python
     596class PublishedBookManager(models.Manager):
     597    def get_query_set(self):
     598        return super(PublishedBookManager, self).get_query_set().filter(is_published=True)
     599
     600class Book(models.Model):
     601    title = models.CharField(maxlength=50)
     602    author = models.CharField(maxlength=30)
     603    is_published = models.BooleanField()
     604    published_objects = PublishedBookManager()
     605}}}
    532606
    533607== Stuff that still needs to be done ==
    534608
    535 === Automatic manipulators ===
    536 
    537 '''Status: Mostly done, with some quirks left'''
    538 
    539 Old:
    540 {{{
    541 #!python
    542 from django.models.myapp import people
    543 m1 = people.AddManipulator()
    544 m2 = people.ChangeManipulator(3)
    545 }}}
    546 
    547 New:
    548 {{{
    549 #!python
    550 from path.to.myapp.models import Person
    551 m1 = Person.AddManipulator()
    552 m2 = Person.ChangeManipulator(3)
    553 }}}
     609=== Remove automatic manipulators, in favor of validation-aware models ===
     610
     611'''Status: Not done yet'''
    554612
    555613=== Change subclassing syntax ===
     
    558616
    559617See ModelInheritance
    560 
    561 === Database lookup API changes ===
    562 
    563 '''Status: Not done yet'''
    564 
    565 See DescriptorFields, rjwittams' proposal on the API changes.
Back to Top