Code

Changes between Version 184 and Version 185 of BackwardsIncompatibleChanges


Ignore:
Timestamp:
07/18/08 17:11:26 (6 years ago)
Author:
brosner
Comment:

Updated to reflect newforms-admin merge.

Legend:

Unmodified
Added
Removed
Modified
  • BackwardsIncompatibleChanges

    v184 v185  
    6565 * [7949] July 18, 2008 [#MySQL_oldbackendremoved MySQL_old backend removed] 
    6666 * [7952] July 18, 2008 [#Genericviewsusenewforms Create/update generic views use newforms] 
     67 * [7967] July 18, 2008 [#Mergednewforms-adminintotrunk Merged newforms-admin into trunk] 
    6768 
    6869== Database constraint names changed == 
     
    679680 
    680681[7952] makes the create/update generic views use newforms instead of the old deprecated "oldforms" package. You'll probably need to update any templates used by these views. 
     682 
     683== Merged newforms-admin into trunk == 
     684 
     685As of [7967] we have merged newforms-admin into trunk. Here is list of things that have changed: 
     686 
     687{{{ 
     688#!python 
     689 
     690# OLD: 
     691from django.conf.urls.defaults import * 
     692 
     693urlpatterns = patterns('', 
     694    (r'^admin/', include('django.contrib.admin.urls')), 
     695) 
     696 
     697# NEW: 
     698from django.conf.urls.defaults import * 
     699from django.contrib import admin 
     700 
     701admin.autodiscover() 
     702 
     703urlpatterns = patterns('', 
     704    (r'^admin/(.*)', admin.site.root), 
     705) 
     706}}} 
     707 
     708Note that, in this above URLconf example, we're dealing with the object {{{django.contrib.admin.site}}}. This is an instance of {{{django.contrib.admin.AdminSite}}}, which is a class that lets you specify admin-site functionality. The object {{{django.contrib.admin.site}}} is a default {{{AdminSite}}} instance that is created for you automatically, but you can also create other instances as you see fit. 
     709 
     710We use the {{{admin.autodiscover()}}} call above to force the import of the {{{admin.py}}} module of each {{{INSTALLED_APPS}}} entry. This won't be needed if you use your own {{{AdminSite}}} instance since you will likely be importing those modules explicily in a project-level {{{admin.py}}}. This was added in [7872]. 
     711 
     712Previously, there was one "global" version of the admin site, which used all models that contained a {{{class Admin}}}. This new scheme allows for much more fine-grained control over your admin sites, allowing you to have multiple admin sites in the same Django instance. 
     713 
     714In this example, we create two {{{AdminSite}}} instances, registering different models with both. Assume {{{Book}}}, {{{Author}}}, {{{Musician}}} and {{{Instrument}}} are Django model classes (not instances). 
     715 
     716{{{ 
     717#!python 
     718 
     719# project-level admin.py 
     720 
     721from django.contrib import admin 
     722from myproject.myapp.models import Book, Author 
     723from myproject.anotherapp.models import Musician, Instrument 
     724 
     725site1 = admin.AdminSite() 
     726site1.register(Book) 
     727site1.register(Author) 
     728 
     729site2 = admin.AdminSite() 
     730site2.register(Musician) 
     731site2.register(Instrument) 
     732 
     733# URLconf 
     734 
     735from django.conf.urls.defaults import * 
     736from myproject.admin import site1, site2 
     737 
     738urlpatterns = patterns('', 
     739    (r'^book_admin/(.*)', site1.root), 
     740    (r'^music_admin/(.*)', site2.root), 
     741) 
     742}}} 
     743 
     744With this example, if you go to {{{/book_admin/}}}, you'll get a Django admin site for the {{{Book}}} and {{{Author}}} models. If you go to {{{/music_admin/}}}, you'll get a Django admin site for the {{{Musician}}} and {{{Instrument}}} models. 
     745 
     746Admin options -- the inner {{{class Admin}}} -- have changed, too. Models no longer use an inner class to declare their admin site options. In fact, '''all admin functionality has been decoupled from the model syntax'''! How, then, do we declare admin options? Like this: 
     747 
     748{{{ 
     749#!python 
     750# a sample models.py file 
     751from django.db import models 
     752 
     753class Author(models.Model): 
     754    first_name = models.CharField(max_length=30) 
     755    last_name = models.CharField(max_length=30) 
     756 
     757    def __unicode__(self): 
     758        return u'%s %s' % (self.first_name, self.last_name) 
     759 
     760class Book(models.Model): 
     761    title = models.CharField(max_length=100) 
     762    author = models.ForeignKey(Author) 
     763 
     764# a sample admin.py file (in same app) 
     765from django.contrib import admin 
     766from myproject.myapp.models import Author, Book 
     767 
     768class BookAdmin(admin.ModelAdmin): 
     769    list_display = ('title', 'author') 
     770    ordering = ('title',) 
     771 
     772admin.site.register(Author) 
     773admin.site.register(Book, BookAdmin) 
     774}}} 
     775 
     776In this example, we register both {{{Author}}} and {{{Book}}} with the {{{AdminSite}}} instance {{{django.contrib.admin.site}}}. {{{Author}}} doesn't need any custom admin options, so we just call {{{admin.site.register(Author)}}}. {{{Book}}}, on the other hand, has some custom admin options, so we define a {{{BookAdmin}}} class and pass that class as a second argument to {{{admin.site.register()}}}. 
     777 
     778You'll notice the {{{BookAdmin}}} class looks a lot like the old-style {{{class Admin}}}. Almost all of the old {{{class Admin}}} options work exactly the same, with one or two exceptions. (For the options that have changed, we've made them '''much''' more powerful.) In addition to the classic options such as {{{list_display}}} and {{{ordering}}}, the {{{ModelAdmin}}} class introduces a wealth of extra hooks you can use to customize the admin site for that particular model. For example: 
     779 
     780{{{ 
     781#!python 
     782 
     783class BookAdmin(admin.ModelAdmin): 
     784    list_display = ('title', 'author') 
     785    ordering = ('title',) 
     786 
     787    def has_change_permission(self, request, obj=None): 
     788        """ 
     789        John can only edit books by Roald Dahl. 
     790        """ 
     791        if obj and request.user.username == 'john': 
     792            return obj.author.last_name == 'Dahl' 
     793        return super(BookAdmin, self).has_change_permission(request, obj) 
     794}}} 
     795 
     796Look at the class {{{ModelAdmin}}} in the file [source:/django/branches/newforms-admin/django/contrib/admin/options.py django/contrib/admin/options.py] to see all of the methods you can override. This is exciting stuff. 
     797 
     798== To-do list == 
     799 
     800  * See [http://code.djangoproject.com/query?status=new&status=assigned&status=reopened&keywords=%7Enfa-blocker&order=priority list of tickets] blocking the merge to trunk. 
     801  * See [http://code.djangoproject.com/query?status=new&status=assigned&status=reopened&keywords=%7Enfa-someday&order=priority list of tickets] that will be looked at after a merge to trunk. 
     802  * See [http://code.djangoproject.com/query?status=new&status=assigned&status=reopened&status=closed&keywords=%7Enfa-fixed&order=priority list of tickets] that have been fixed. 
     803 
     804== Backwards-incompatible changes == 
     805 
     806This is a (currently incomplete) list of backwards-incompatible changes made in this branch. 
     807 
     808=== Changed Admin.manager option to more flexible hook === 
     809 
     810As of [4342], the {{{manager}}} option to {{{class Admin}}} no longer exists. This option was undocumented, but we're mentioning the change here in case you used it. In favor of this option, {{{class Admin}}} may now define a ```queryset``` method: 
     811 
     812{{{ 
     813#!python 
     814 
     815class BookAdmin(admin.ModelAdmin): 
     816    def queryset(self, request): 
     817        """ 
     818        Filter based on the current user. 
     819        """ 
     820        return self.model._default_manager.filter(user=request.user) 
     821}}} 
     822 
     823=== Changed prepopulate_from to be defined in the Admin class, not database field classes === 
     824 
     825As of [4446], the {{{prepopulate_from}}} option to database fields no longer exists. It's been discontinued in favor of the new {{{prepopulated_fields}}} option on {{{class Admin}}}. The new {{{prepopulated_fields}}} option, if given, should be a dictionary mapping field names to lists/tuples of field names. This change was made in an effort to remove admin-specific options from the model itself. Here's an example comparing old syntax and new syntax: 
     826 
     827{{{ 
     828#!python 
     829 
     830# OLD: 
     831class MyModel(models.Model): 
     832    first_name = models.CharField(max_length=30) 
     833    last_name = models.CharField(max_length=30) 
     834    slug = models.CharField(max_length=60, prepopulate_from=('first_name', 'last_name')) 
     835 
     836    class Admin: 
     837        pass 
     838 
     839# NEW: 
     840class MyModel(models.Model): 
     841    first_name = models.CharField(max_length=30) 
     842    last_name = models.CharField(max_length=30) 
     843    slug = models.CharField(max_length=60) 
     844 
     845from django.contrib import admin 
     846 
     847class MyModelAdmin(admin.ModelAdmin): 
     848    prepopulated_fields = {'slug': ('first_name', 'last_name')} 
     849 
     850admin.site.register(MyModel, MyModelAdmin) 
     851}}} 
     852 
     853=== Moved admin doc views into django.contrib.admindocs === 
     854 
     855As of [4585], the documentation views for the Django admin site were moved into a new package, {{{django.contrib.admindocs}}}. 
     856 
     857The admin docs, which aren't documented very well, were located at {{{docs/}}} in the admin site. They're also linked-to by the "Documentation" link in the upper right of default admin templates. 
     858 
     859Because we've moved the doc views, you now have to activate admin docs explicitly. Do this by adding the following line to your URLconf: 
     860 
     861{{{ 
     862#!python 
     863    (r'^admin/doc/', include('django.contrib.admindocs.urls')), 
     864}}} 
     865 
     866You have to add this line before {{{r'^admin(.*)'}}} otherwise it won't work. 
     867 
     868=== Renamed 'fields' to 'fieldsets', and changed type of 'classes' value === 
     869 
     870'Fields' is used to order and group fields in the change form layout.[[BR]] 
     871It is still available in the new admin, but it accepts only a list of fields. [[BR]] 
     872In case one uses fieldsets to organize the fields, one needs to use 'fieldsets' instead. [[BR]] 
     873Also, if 'classes' is specified in a field specification, then the type of its value needs to be changed from a string to a tuple of strings when migrating to the new 'fieldsets' specification. 
     874 
     875An example: 
     876{{{ 
     877#!python 
     878 
     879# OLD: 
     880class MyModelA(models.Model): 
     881   class Admin: 
     882     fields = ('field1','field2','field3','field4') 
     883 
     884class MyModelB(models.Model): 
     885   class Admin: 
     886     fields = ( 
     887         ('group1', {'fields': ('field1','field2'), 'classes': 'collapse'}), 
     888         ('group2', {'fields': ('field3','field4'), 'classes': 'collapse wide'}), 
     889     ) 
     890 
     891# NEW: 
     892class MyModelAdmin(admin.ModelAdmin): 
     893    fields = ('field1', 'field2', 'field3', 'field4')  # Renaming is optional  
     894 
     895class AnotherModelAdmin(admin.ModelAdmin): 
     896     fieldsets = ( 
     897         ('group1', {'fields': ('field1','field2'), 'classes': ('collapse',)}), 
     898         ('group2', {'fields': ('field3','field4'), 'classes': ('collapse', 'wide')}), 
     899     ) 
     900}}} 
     901 
     902=== Inline editing === 
     903 
     904The syntax is now different and much, much better.  
     905Here is an example: 
     906{{{ 
     907#!python 
     908from django.contrib import admin  
     909 
     910class ChildInline(admin.TabularInline):  
     911    model = Child  
     912    extra = 3  
     913 
     914class ParentAdmin(admin.ModelAdmin): 
     915    model = Parent 
     916    inlines = [ChildInline] 
     917}}} 
     918See [http://code.djangoproject.com/browser/django/branches/newforms-admin/docs/admin.txt#L506 this documentation] for more details on field options for inline classes 
     919 
     920=== Refactored inner Admin ```js``` option to media definitions === 
     921 
     922In [5926] a new method of dealing with media definitions was added. It is now 
     923much more flexible and allows media on more than just a {{{ModelAdmin}}} 
     924classes. 
     925 
     926An example: 
     927{{{ 
     928#!python 
     929 
     930# OLD: 
     931class MyModel(models.Model): 
     932    # not relavent, but here for show 
     933    field1 = models.CharField(max_length=100) 
     934     
     935    class Admin: 
     936        js = ( 
     937            "/static/my_code.js", 
     938        ) 
     939 
     940# NEW: 
     941# in admin.py 
     942 
     943class MyModelAdmin(admin.ModelAdmin): 
     944    class Media: 
     945        js = ( 
     946            "/static/my_code.js", 
     947        ) 
     948}}} 
     949 
     950One very subtle thing to note is previously with trunk the documentation stated: 
     951 
     952    If you use relative URLs — URLs that don’t start with {{{http://}}} or {{{/}}} — then the admin site will automatically prefix these links with {{{settings.ADMIN_MEDIA_PREFIX}}}. 
     953 
     954Which is still partially true with newforms-admin, but now when using relative URLs {{{settings.MEDIA_URL}}} is prepended and '''not''' {{{settings.ADMIN_MEDIA_PREFIX}}}. If you are still looking for the old behavior you can accomplish it by doing the following: 
     955 
     956{{{ 
     957#!python 
     958 
     959from django.conf import settings 
     960 
     961class MyModelAdmin(admin.ModelAdmin): 
     962    class Media: 
     963        js = ( 
     964            settings.ADMIN_MEDIA_PREFIX + "some_file.js", 
     965        ) 
     966}}} 
     967 
     968Make sure the value of {{{settings.ADMIN_MEDIA_PREFIX}}} is a proper absolute URL otherwise it will be treated the same as a relative URL. 
     969 
     970=== Moved raw_id_admin from the model definition === 
     971 
     972The syntax is now separated from the definition of your models.  
     973 
     974An example: 
     975{{{ 
     976#!python 
     977 
     978# OLD: 
     979class MyModel(models.Model):  
     980    field1 = models.ForeignKey(AnotherModel, raw_id_admin=True) 
     981     
     982    class Admin: 
     983        pass 
     984 
     985# NEW: 
     986class MyModelAdmin(admin.ModelAdmin): 
     987    model = MyModel 
     988    raw_id_fields = ('field1',)  
     989}}} 
     990 
     991=== ```django.contrib.auth``` is now using newforms === 
     992 
     993In [7191] ```django.contrib.auth``` has been converted to use newforms as 
     994opposed to using oldforms. If you are relying on the oldforms, you will need 
     995to modify your code/templates to work with newforms. 
     996 
     997=== Moved radio_admin from the model definition === 
     998 
     999An example: 
     1000{{{ 
     1001#!python 
     1002 
     1003# OLD: 
     1004class MyModel(models.Model):  
     1005    field1 = models.ForeignKey(AnotherModel, radio_admin=models.VERTICAL) 
     1006     
     1007    class Admin: 
     1008        pass 
     1009 
     1010# NEW: 
     1011class MyModelAdmin(admin.ModelAdmin): 
     1012    model = MyModel 
     1013    radio_fields = {'field1': admin.VERTICAL} 
     1014}}} 
     1015 
     1016=== Moved filter_interface from the model definition === 
     1017An example: 
     1018{{{ 
     1019#!python 
     1020 
     1021# OLD: 
     1022class MyModel(models.Model): 
     1023    field1 = models.ManyToManyField(AnotherModel, filter_interface=models.VERTICAL) 
     1024    field2 = models.ManyToManyField(YetAnotherModel, filter_interface=models.HORIZONTAL) 
     1025 
     1026# NEW: 
     1027class MyModelAdmin(admin.ModelAdmin): 
     1028    filter_vertical = ('field1',) 
     1029    filter_horizontal = ('field2',) 
     1030}}} 
     1031