Context Navigation

Lllama's handy how-do-I guide to newforms admin.

I've been using the newforms admin for a while now. I switched to the branch as I was hacking up a data entry app which a modified admin would have been perfect for. Since I envisioned having to create some new forms I thought I'd try and use newforms in the admin. Newforms-admin seemed like the logical choice. As it turns out I didn't really need to create my own forms, and this is really because of the new outstanding powers of the branch. (I'm now of the opinion that the admin is so powerful it's effectively its own framework. Not sure what that means but that's the feeling I get).

What follows is a series of FAQ/Howto style questions that I've reverse engineered out of what I've come up with. I hope these will be helpful to someone (the first few are pretty much identical to the existing docs, so skip down a bit if you're using the branch already).

Q: How do I change the attributes for a widget on a single field in my model.

A: Again, override the formfield_for_dbfield method:

defformfield_for_dbfield(self, db_field,**kwargs):
field =super(MyModelAdmin,self).formfield_for_dbfield(db_field,**kwargs)# Get the default fieldif db_field.name =="the_field_I_want_to_change":# Check if it's the one you want
field.widget.attrs['class']="my_new_class"# Poke in the new return field

The above will enforce the permission on both GET and POST requests. Put in an if request.method =="POST": or similar if you want to distinguish between the two.

Q: I've tweaked something which generated a message for the user but I don't want them to see it. How can I get rid of it?

A: In the overridden view function call the following:

request.user.get_and_delete_messages()

Q: How do I add an extra column to the change list view?

A: I've got a report model and I've added a link on the change view to allow users to download the report as a Word file. To add your own link you'll need to do two things. First create the following template tags (in project/app/templatetags/change_list_extras.py):

fromdjango.contrib.admin.templatetags.admin_listimport items_for_result, result_headers
fromdjango.templateimport Library
fromdjango.utils.safestringimport mark_safe
register = Library()defresults(cl, additional_links):"""
Rewrite of original function to add additional columns after each result
in the change list.
"""for res in cl.result_list:
rl =list(items_for_result(cl,res))for link in additional_links:
rl.append(mark_safe(link['url_template']%(VAR,)))# Make sure you have enough VARs for any %s's in your extra content. Note mark_safeyield rl
defextended_result_list(cl, additional_links):"""
Rewrite of original function to add an additional columns after each result
in the change list.
"""
headers =list(result_headers(cl))for header in additional_links:
headers.append(header)return{'cl': cl,'result_headers': headers,'results':list(results(cl, additional_links))}# This function is an example template tag for use in an overridden change_list.html template.defmy_model_result_list(cl):
additional_links =({'text':'Actions','sortable':False,'url_template':'<td>YOUR ADDITIONAL CONTENT HERE</td>'},)return extended_result_list(cl, additional_links)
my_model_result_list = register.inclusion_tag("admin/change_list_results.html")(my_model_result_list)

I wanted to add columns to my model but I didn't like the above method very much, mostly because I would have to
state variables in the results function, which I wanted to generalize away together with extended_result_list.

Instead of using Python string format (i.e. the (VAR,) above), I take advantage of Django's template system.

First, move results and extended_result_list away. I created a separate module called admin_extras and put them (rewritten)
in __init__.py:

These functions can now be hooked up to any model using a custom tag for it. I happen to
have an Article-model and want to display an extra column with publishing options.

I wouldn't really recommend the name change_list_extras, since you probably want one of these
for every model (to keep them decoupled). <model_name>_change_list_extras or <model_name>_admin_extras
are probably better candidates.

Note that I've changed 'additional_links' to 'additional_cols' and 'url_template' to more generic 'template'. Inside
'template', I can use the context variable 'obj' to refer to the current row. If you should be so inclined, you could of course
modify article_result_list to send a dictionary of its own to merge with the one in results.

I haven't tested using {% url %} myself, but I see no reason why it wouldn't work!

Note that by default the content returned from my_column will be escaped. To avoid this, allow_tags must
be set to true and the string should be marked as safe. Here is two other options that might be handy:

# Change header titleof the column:
my_column.short_description ='My nice column'# Make column show the icon for true/false
my_column.boolean =True# Make column sortable. Note it is ONLY possible to sort by an already existing model field# and not on the actually values returned by the my_column method.
my_column.admin_order_field ='some_field'

Q: I want to add some field specific template content in my change form. How, how?

A: Copy the change_form.html file from the admin app. The fields are looped over in the following bit of code:

N.B. You'll have to learn how to edit ODT and DOCX xml by yourself. Good luck with that.

Q: Where did prepopulate_from go? My SlugField don't work!

A: There's no longer a prepopulate_from option on database Field classes. Instead, there's a new prepopulated_fields option for your admin class. This should be a dictionary mapping field names (as strings) to lists/tuples of field names that prepopulate them

Q: What happened to filter_interface?

A: Use filter_vertical and filter_horizontal on the ModelAdmin class like so:

If you want custom validation for a field in the admin and newforms ​ModelForm instances you can subclass a field in your model and override formfield(). You can then return the form field you like or a complete ​custom form field.

And you want to use created and updated in your list view, but not in the change and add views.

classArticleOptions(admin.ModelAdmin):
save_on_top =True
list_display =('headline','updated','created')
hidden_fields =("created","updated")# Define which fields you'd like to be hidden here.defget_fieldsets(self, request, obj=None):
superclass =super(ArticleOptions,self)
fieldsets = superclass.get_fieldsets(request, obj)# Here we cycle through the fieldsets and remove the created and updated# fields by name. Factoring this out left as an exercise for the reader.for fs in fieldsets:
fs[1]['fields']=[f for f in fs[1]['fields']if f notinself.hidden_fields]return fieldsets
defadd_view(self, request):if request.method =="POST":# Change the created field to be a datetime object.
request.POST['created']= datetime.datetime.today()# The admin will be expecting date and time as separate fields# as the default widget is rendered as two text fields.# You can get away with a date object for the date
request.POST['updated_0']= datetime.date.today()# But it's easier to poke in a text string for the time (less typing).
request.POST['updated_1']= datetime.datetime.now().strftime("%H:%M:%S")returnsuper(ArticleOptions,self).add_view(request)defchange_view(self, request, obj_id):if request.method =="POST":
old_issue = Article.objects.get(id=obj_id)# Here we use the old created date as we're changing, not adding.
request.POST['created']= old_issue.created
# Same as above for the updated time
request.POST['updated_0']= datetime.date.today()
request.POST['updated_1']= datetime.datetime.now().strftime("%H:%M:%S")returnsuper(ArticleOptions,self).change_view(request, obj_id)

Q: How can I pass extra context variables into my add and change views?

Q: How can I pass extra context variables into my index page?

You can't. At least not in the same way as above.

There are two ways you can do this at present, the messy way or the 'proper' way.

The messy way is as follows.
Create a file called extras.py in your project's root directory (where urls.py lives).
In it create a copy of the index method from django.contrib.admin.sites (from the class AdminSite).
Modify the method to add in your extra context variables.

Now you'll need to replace the existing index method with your new one

admin.site.index = new.instancemethod(new_index_method)

this isn't that pretty but it works.

The 'proper' way will be to create your own AdminSite instance and register your models to it. The problem with this method is that you will also need to register all of the models from the auth app if you want to manage your users.

Q: How can I change where I get sent after saving a new entry to the database?

A: You'll need to override the save_add method in your ModelAdmin. In the example below I've got a link on one
model's change form which points to the add view of another and passes along the GET variable "rep_id".
In the overriden method I check for this and tweak the location field of the HTTPResponseRedirect. Overriding
save_add gives us the added advantage of having all validation etc performed beforehand for us.

Q: How do I filter the ChoiceField based upon attributes of the current ModelAdmin instance?

A: This is currently worked on in ticket #3987. However, this patch is not likely to be implemented, as it is not
backward compatible.

There is another way that works with 1.0 just fine. Override the _ _ call _ _ method to add the
request object to the current ModelAdmin instance. At that point, you can reference the request object inside formfield_for_dbfield,
and retrieve the current Model instance, which you may then use to filter a ChoiceField based upon the current values
in the Model. The following example filters a ChoiceField based upon attributes of the the current Model instance. The same thing may
be done within an InlineAdmin, retrieving the parent object instance, and using it to filter the ChoiceField on the InlineAdmin instances:

classSiteAdmin(ModelAdmin):def__call__(self, request, url):#Add in the request object, so that it may be referenced#later in the formfield_for_dbfield function.self.request = request
returnsuper(SiteAdmin,self).__call__(request, url)defformfield_for_dbfield(self, db_field,**kwargs):
field =super(SiteAdmin,self).formfield_for_dbfield(db_field,**kwargs)# Get the default fieldif db_field.name =='home_page':#Add the null object
my_choices =[('','---------')]#Grab the current site id from the URL.
my_choices.extend(Page.objects.filter(site=self.request.META['PATH_INFO'].split('/')[-2]).values_list('id','name'))print my_choices
field.choices = my_choices
return field