Code


Version 30 (modified by ubernostrum, 8 years ago) (diff)

revert some spam

What is the point of the new-admin branch?

UPDATE: As of revision [1434], the new-admin branch has been merged into trunk.

The admin system in trunk utilises a number of workarounds to get around limitations in the Django framework. Anybody wishing to duplicate some part of the admin functionality will also have to duplicate these workarounds. Templates are generated at run time in a way which makes them hard to understand and reuse. The aims of the new-admin branch are

  1. Remove the need for these workarounds, so that writing straight forward code can achieve the results that would be expected from the admin behaviour. This entails a fair number of changes to FormFields, Fields, and the meta system.
  2. Replace the runtime template generation system with a set of templates and template tags that acheive very nearly the same output as the current admin modulo whitespace. During this work, it became tedious to debug certain classes of template errors, and so debugging facilities were added to the template system.

So, although the branch is called new-admin, the benefit to the user comes mainly in the additional functionality available to ordinary views. Customisation of the admin is also easier. Backwards compatibilty should be preserved.

In future, it will also make changes to the admin functionality easier.

Any questions? I am rjwittams on IRC, and you can mail me at robert@…

New-admin changes

The new-admin branch contains a number of changes. All of these changes may be reverted (more likely with some than others) or further modified.

View Functions

For view functions to work properly with all new features, a few changes are in order.

  1. Make sure html2python is called regardless of whether there are errors, but after validation.
  2. Make sure you use flatten_data to get your new_data to fill in your forms, NOT just the __dict__ of the object you are editing, or an empty dict with an AddManipulator. Otherwise, dates, foreign keys, inline editing and a whole host of other things will NOT work properly, just like they do not work on trunk.
  3. Make sure you remove any hacks for modifying the data in your view function. This is now done by the fields themselves in flatten_data and instances where it works unexpectedly should be filed as bugs.

From:

def style_edit(request, object_id):    
    try:
        manipulator = styles.ChangeManipulator(object_id)
    except ObjectDoesNotExist:
        raise Http404

    if request.POST:
        new_data = request.POST.copy()
        
        errors = manipulator.get_validation_errors(new_data)
        
        if not errors:
            manipulator.do_html2python(new_data)   # WRONG!!
            manipulator.save(new_data)
            return HttpResponseRedirect("")
    else:
        # Populate new_data with a "flattened" version of the current data.
        new_data = manipulator.original_object.__dict__ # WRONG!!
        errors = {}
       
        mash_around_with_the_data_dict_to_make_foreign_keys_work(new_data) # WRONG!!   

    # Populate the FormWrapper.
    form = formfields.FormWrapper(manipulator, new_data, errors)
    
    return render_to_response('style_edit', { 'form': form })

To:

def style_edit(request, object_id):    
    try:
        manipulator = styles.ChangeManipulator(object_id)
    except ObjectDoesNotExist:
        raise Http404

    if request.POST:
        new_data = request.POST.copy()
        errors = manipulator.get_validation_errors(new_data)
        
        manipulator.do_html2python(new_data) # CORRECT!!
        if not errors:
            manipulator.save(new_data)
            return HttpResponseRedirect("")
    else:
        # Populate new_data with a "flattened" version of the current data.
        new_data = manipulator.flatten_data() # CORRECT!!
        errors = {}
       

    # Populate the FormWrapper.
    form = formfields.FormWrapper(manipulator, new_data, errors)
    
    return render_to_response('style_edit', { 'form': form })

Without these changes, the new fixes will not work. It should work about as well as the trunk without these changes.

Manipulators and inline editing

Manipulators have a new optional argument to their constructors: follow. The argument is a dictionary indicating which fields and related objects to extract in flatten_data and examine when saving the object.

The default set to follow is:

  • Any fields without editable=False.
  • Any related objects with edit_inline set on the ForeignKey relating to the class the manipulator is concerned with.

This is the set of fields shown by the admin.

Currently, the FormWrapper must be passed edit_inline=True to gain access to these fields. This will probably be removed.

examples of dictionarys to be used as follow arguments.

Suppress the field 'name'.

follow = {
  'name': False,
}

Show the editable=False field 'job'

 follow = { 'job' : True, }

Show the set of related objects in the module 'choices'

follow = {
   'choices': True,
}

Show the set of related objects in the module 'choices', but suppress the 'poll' field of each choice object:

  follow = {
     'choices': { 
        'poll': False
     }
  }

Put it all together:

  follow = {
    'name' : False,
    'job' : True,
    'choices' : {
       'poll' : False
    }
  }

An example of using this in a view function:

def style_edit_inline(request, object_id):    
    try:
        follow = {
           'choices' : True,
        }
        manipulator = styles.ChangeManipulator(object_id, follow)# <------ EXTRA ARGUMENT
    except ObjectDoesNotExist:
        raise Http404

    if request.POST:
        new_data = request.POST.copy()
        
        errors = manipulator.get_validation_errors(new_data)
        
        manipulator.do_html2python(new_data)
        if not errors:
            manipulator.save(new_data)
            return HttpResponseRedirect("")
    else:
        # Populate new_data with a "flattened" version of the current data.
        new_data = manipulator.flatten_data()
        errors = {}
       

    # Populate the FormWrapper.
    form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True) # <----- EXTRA ARGUMENT
    
  
    return render_to_response('style_edit_inline', { 'form': form })

how to use this on your page

you would need to add the following snippet to your template to display the editable choices in the poll form.

{% for choice in form.choices %}
 {{ choice.id }}
 {{ choice.name }}
 {{ choice.votes }}
{% endfor %}

Templates

A new tag, called include, which is an improvement on ssi parsed. See #598.

Improved error reporting when debug is on. See #603. Also note that loaders must return a tuple of (source, filename) rather than just the source of a loaded template. To make this work, add TEMPLATE_DEBUG = True to your settings file, and also make sure DEBUG=True. Eg

DEBUG = True 
TEMPLATE_DEBUG = DEBUG

Template Tag decorators

defining tags in the 'standard' branch requires you to parse the string the user enters, and can be quite daunting for a newbie.

In the new-admin branch you can now create a tag for use in your templates by simply doing the following:

from django.core.template.decorators import simple_tag
...
...
def monkey_tag(verb):
    return "I %s no evil" % verb
monkey_tag = simple_tag(monkey_tag)

you would then just load the tag library in your page like

{% load monkey %}
{% monkey_tag "hear" %}

Template tag decorators see #625 goes into more detail.

Admin converted to separate templates

The admin is now rendered using separate templates. These can be overloaded, meaning the admin is a bit more easily customizable.

An example is change forms - these are the forms used to modify and add objects in the admin. The templates are selected in the following order :

  • admin/<app_label>/<object_name>/change_form
  • admin/<app_label>/change_form
  • admin/change_form

So it is possible to change the form just for one kind of object, or for one app. This makes it possible to add in extra information or change existing blocks eg branding.

So if we take the polls app as an example, we may want to customise the admin page just for the polls object.

We create a file admin/polls/polls/change_form.html in our apps template directory, with the contents:

{%extends "admin/change_form" %}

{% block branding %} Customised title {% endblock %}
{% block after_field_sets %} Some interesting information after the field sets  {% endblock %}

This works using normal template inheritance, so make sure you inherit from the right thing ( usually "admin/change_form", but for an object in an app that has its own change form, it may be "admin/<app_name>/change_form" ).

In the change forms, the following blocks are available for custom overriding:

  • branding - included from "admin/base"
  • form_top - at the top of the form after the title.
  • after_field_sets - after standard field sets, before the related objects
  • after_related_objects - after edit_inline objects

Inline editing can be customised. rather than using edit_inline=meta.TABULAR or meta.SOURCE, you can define a custom inline editing mode. This is done by subclassing BoundRelatedObject, and using that class. eg edit_inline=HorizontalScroller.