Changes between Version 184 and Version 185 of BackwardsIncompatibleChanges


Ignore:
Timestamp:
Jul 18, 2008, 7:11:26 PM (16 years ago)
Author:
Brian Rosner
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
Back to Top