The opensource blog of Justin Quick discussing software/web development, security, and general techie goodness. read more...
Woah OK, it has been waaay too long since my last post. A lot has been going on in my life and I havent had much free time. Never the less, I soldier on and am writing this month to introduce a concept that I have been playing around with recently. The goal of this technique is to add functionality to your django admin interfaces for particularly interesting models on the fly when you might not have control of the application's source code but still want to add your own special sauce. The best example of this use in production is my fork of django-tinymce where I let users define a model and field name to apply the TinyMCE editor to in the admin. In general, here is the list of steps for this technique:
1.Find the models you wish to play with
2.Unregister the existing ModelAdmin (may be in a separate app)
3.Re-register the model with a ModelAdmin you construct on the fly
4.Profit
In order to narrow down the models you wish to apply your hacks to I recommend defining them in a setting. This can be tricky since you cannot (or should not) import any models in settings.py. Instead, define their app label and model name as strings and import them later. This friendly approach ends up with settings that look like:
TINYMCE_ADMIN_FIELDS = { 'stories.story': ('body',), #... }
The above code says take the story model and apply tinymce to the body field. Next we need to unregister the model we want from the admin. This is best done by looking for them in the django.contrib.admin.site._registry dictionary which contains all of the models registered in the default admin site. If you are looking for one particular model then you can probably skip this step, but if you are looking for some found in the above setting for example, looping through this dictionary looking for the models you want is probably the best bet:
for model,modeladmin in admin.site._registry.items(): if model is what im looking for: ...
Unregistering the model is as easy as calling django.contrib.admin.site.unregister(model) so thats all im saying about that. The real fun part is creating a new ModelAdmin on the fly and registering your model with it. There are endless possibilities of what you could change on the fly for any model in the admin. If you prefer having a setting like fieldsets defined a certain way but certain apps whose source code you cannot alter very easily do it wrong, now is your chance to change them. Here is a quick example of changing the change_list_template option on the fly:
admin.site.register(model,type('NewAdmin',(modeladmin.__class__,),{ 'change_list_template': 'my-cool-template.html', }))
This uses the builtin type function to subclass the existing ModelAdmin on the fly, changing only the change_list_template attribute. Type takes three arguments: the name of the new class (not very important here), a list of class inheritance which is just the original ModelAdmin class in this example, and the attributes to set on the new class. This opens up a wide range of attributes and methods in ModelAdmins which you can tinker with without changing the original application's source code.
In order for all of this to work, all the code shown above must be located in someplace that django will recognize. Probably the best place for it is in the admin.py of which ever app you wish to modify. If the app is out of your control, then you can place the snippets into an admin.py of another app. Keep in mind that django's admin goes in the order defined in INSTALLED_APPS so make sure to place your hacking app after the original app in the list of installed apps otherwise it will not recognize the modifications. Here is the full source code of the admin.py for my django-tinymce fork which can also be found on github http://github.com/justquick/django-tinymce/blob/master/tinymce/admin.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | from django.db.models import get_model from django.contrib import admin from django import forms from widgets import TinyMCE import settings # Turn the string repr of a model into the actual model class # 'auth.user' => User FIELDS = settings.ADMIN_FIELDS.copy() for k,v in FIELDS.items(): if isinstance(k, basestring): FIELDS[get_model(*k.split('.'))] = v del FIELDS[k] # A simple model admin addition to add the tinymce widget to fields defined in editor_fields class TinyMCEAdmin(admin.ModelAdmin): editor_fields = () def formfield_for_dbfield(self, db_field, **kwargs): if db_field.name in self.editor_fields: return db_field.formfield(widget=TinyMCE()) return super(TinyMCEAdmin, self).formfield_for_dbfield(db_field, **kwargs) # Comb through the registry and make the swap for model,modeladmin in admin.site._registry.items(): if model in FIELDS: admin.site.unregister(model) admin.site.register(model, type('newadmin', (TinyMCEAdmin, modeladmin.__class__), { 'editor_fields': FIELDS[model], })) |