Ajax inlines

AjaxInline

../_images/ajax_inlines.png

This is a very powerfull way to display children rows of a parent. It’s use can be alternative to that of TabularInline but differs in many details:

  1. Rows can be added only when the parent has already been saved. If you want to abort the creation of the father if the children are not created, this is not the correct tool.

  2. Add/Change/Delete is done via iframe/ajax, that make it fast and user-friendly. On the other hand Tabular/StakedInline use a single huge form.

  3. Any row is displayed using official django result_list templatetag, the same used in changelist, the ChangeList object used has a modified ChangeList.get_query_set() to filter children of a parent. You can customize it or simply customize AjaxInline.get_queryset() method.

    All fields defined in AjaxInline’s list_display attribute will be treated in the standard way for list_display on ModelForm i.e.: you can define functions and the like.

  1. Layout can only be controlled via tabs

  2. You need to register a ModelAdmin separately for the Child Model you want to edit See below for Row customization.

  3. DataTable jQuery plugin, take care of presenting data in an effective way, sorting is done locally, searching is done by default on any field.

  4. Only one ajax_inline can be registered for each model, that means that you cannot use 2 different ajax_inlines to edit/delete records

To add AjaxInline to your ModelAdmin, resulting in something similar to what is shown in the figure can be accomplished as follows:

from django.contrib import admin

from jmb.jadmin import AjaxInline, ConstrainedModelForm, register_inline
from jmb.jadmin.options import JumboModelAdmin

class ContactAjaxInline(AjaxInline):
    model = Contact
    fk_name = 'company'
    list_display = ('title', 'first_name', 'role','user', )

class CompanyModelAdmin(JumboModelAdmin):
    ...
    tabs = (
         ('main', {}),
         ('contacts', {'items' : [ContactAjaxInline]}),
         ...
    )

 class ContactForm(ConstrainedModelForm):
    class Meta:
        model = Contact
    hidden_fields = ('organization',) # ALTERNATIVELY you can use get_form below

 class ContactModelAdmin(JumboModelAdmin):
    model = Contact
    # form = ContactForm
    def get_form(self, request, obj=None, **kwargs):
        '''
        Return a form that forces all fields in the GET as not changeable
        '''
        # Alternative to declaring hidden_fields in the ConstraintForm
        hidden_fields = [key for key in request.GET.keys() if not key.startswith('_')]
        name = "%sForm" % self.model.__name__

        return type(name, (ContactForm,), {
            'hidden_fields' : hidden_fields,
        })



 register_inline(ContactAjaxInline)
 admin.site.register(Contact, ContactModelAdmin)
 admin.site.register(Company, CompanyModelAdmin)

You need to use ConstrainedModelForm to make sure foreign keys to the parent are not writeble

Row customization

ajax_inline.html uses Django’s result_list templatetag to render the single object, so any standard way to customize the changelist can be used: field_names in list_display can be functions defined on the ModelAdmin or on the model as described in django documentation.

When you add/modify a record you use the standard ModleAdmin called in an iframe. After saving the obj the object itself is rendered in the same way it would be in the chagelist, that implies you need to register which AjaxInlines will be used so that redsponse_add/change can use it. This is solved by registering the inlines via register_inline()

ConstraintForm

When editing an inline you want to hide the foreign_key field: it simply isn’t usefull and you want to prevent the used to change it. But it must be present in the form as HiddenField as it’s needed when saving the record.

You can use set the widget to Hidden using a CostrainedForm and declaring it as hidden in the class. If you plan to use the same form both as AjaxInlien and in a standard change_form, you can set hidden fields dinamically as shown in the example above

API

class jmb.jadmin.ajax_inlines.AjaxParentModelAdmin(model, admin_site)[sorgente]

A ModelAdmin that provides handling for _hjson parameter that returns the object formatted according to a registered AjaxInline

delete_view(request, object_id, extra_context=None)[sorgente]

The “delete” admin view for this model.

get_urls()[sorgente]

Add a view named <app_label>_<model_name>_json that returns an array as the following:

data_json = [{
       "pk": 9134,
       "model": "Ticket",
       "fields": '<tr class="row1">....</tr>'} ]
data_json[0].method = ""
action = ''
message = ''

message will be the pending messages for the user

json_view(request, obj=None, pk=None)[sorgente]

Return a json view of the a single object. Used to refresh the changelist.

Parametri
  • request – the request

  • obj – the object to be returned

response_add(request, obj, post_url_continue=None)[sorgente]

Return a response. Handles _hjson in GET

response_change(request, obj)[sorgente]

Return a response. Handles _hjson in GET

class jmb.jadmin.ajax_inlines.ConstrainedModelForm(*args, **kwargs)[sorgente]

A ModelForm that forces HiddenInput on fields declared in hidden_fields

It’s main goal is to be used when a child is edited and the parent must be forced without giving the user the opportunity to alter it.

It’s __init__() calls hide_field() for any field declared in hidden_fields. If you need to implement more logic in your __init__ you can simply call hide_field() by youself.

We use it along with a javascript code (jmb.hide_input()) that hide the label of the relative fields called from within change_form.html

class jmb.jadmin.ajax_inlines.ChildrenChangeList(request, parent=None, fk_name=None, model=None, list_display=(), list_display_links=(), list_filter=None, date_hierarchy=None, search_fields=(), list_select_related=True, list_per_page=200, list_max_show_all=200, single_child=None, list_editable=None, model_admin=None, ajax_inline=None)[sorgente]

A ChangeList that has as queryset the children of a parent

This is used to represent all children of a parent using the same features used in a normal changelist but when using AjaxInline

It’s also used from within json_response feeding a standard ModelAdmin as ajax__inline parameter

add_edit_delete_icons()[sorgente]

Add icons to edit and delete

Disabled since it should be done by_hand Creates problems when this ChangeList is used from changelist

class jmb.jadmin.ajax_inlines.AjaxInline(parent_model=None, admin_site=None, parent=None)[sorgente]

Options for inline editing of model instances.

This is an inline whose editing happens via separate iframes but that are displayed along with normal ones.

Parametri
  • parent_model – needed to get the foreign key

  • admin_site – the admin_site, needed to get the ModelAdmin (?)

  • parent – needed to get all children when rendering the changelist

clean_table_headers(html)[sorgente]

Return a table with js stripped from headers :arg html: the output of th template

get_queryset(request)[sorgente]

Return a queryset of all the objects to represent in this run

Parametri

request – HttpRequest

get_rows(obj)[sorgente]

Return all the rows that should be displayed by this AjaxInLine

Parametri

obj – the object whose row we want to fetch

render()[sorgente]

Render the whole section using self.template (default: ajax_inline.html) The search path follows the same rules as change_form.html & similar.

ajax_inline.html uses standard result_list used in change_list.html

If the parent is missing (not yet saved) and no single_child is defined it returns rendered_presaved() (that defults to empty string)

render_presaved()[sorgente]

Return html code to be used when parent object is not saved yet

render_row()[sorgente]

Return the single row inside a return_list output We know there is just one tr in this tbody

set_changelist(request, obj=None)[sorgente]

Add a changelist to the inline so that rendering can occurre

Parametri
  • request – An HttpRequest

  • obj – the only object that should be returned by this changelist