Code

Changes between Version 58 and Version 59 of RemovingTheMagic


Ignore:
Timestamp:
02/01/06 10:28:54 (8 years ago)
Author:
adrian
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.