Customizing the Admin classes

Release:0.1
Date:June 12, 2010

The Admin classes are the classes that specify how objects should be visualized, they define the look, feel and behaviour of the Application. Most of the behaviour of the Admin classes can be tuned by changing their static attributes. This makes it easy to subclass a default Admin class and tune it to your needs.

Each elixir Entity that is visualized within Camelot has an associated Admin class which specifies how the entity or a list of entities should be visualized.

Those entities are subclasses of the EntityAdmin class in the module camelot/view/elixir_admin.

Usually the Admin class is bound to the Entity class by defining it as an inner class of the Entity class:

class Movie(Entity):
  title = Field(Unicode(60), required=True)

  class Admin(EntityAdmin):
    name = 'Movies'
    list_display = ['title']

Most of the behaviour of the Admin class can be customized by changing the class attributes like name or list_display.

ObjectAdmin

The base type of EntityAdmin, is ObjectAdmin, which specifies most of the class attributes that can be used to customize the interface.

class camelot.admin.object_admin.ObjectAdmin(app_admin, entity)

The ObjectAdmin class describes the interface that will be used to interact with objects of a certain class. The behaviour of this class and the resulting interface can be tuned by specifying specific class attributes:

verbose_name

A human-readable name for the object, singular

verbose_name = ‘movie’

If this isn’t given, the class name will be used

verbose_name_plural

A human-readable name for the object, plural

verbose_name_plural = ‘movies’

If this isn’t given, Camelot will use verbose_name + “s”

list_display

a list with the fields that should be displayed in a table view

form_display

a list with the fields that should be displayed in a form view, defaults to the same fields as those specified in list_display

class Admin(EntityAdmin):
form_display = [‘title’, ‘rating’, ‘cover’]

instead of telling which forms to display. It is also possible to define the form itself

from camelot.view.forms import Form, TabForm, WidgetOnlyForm, HBoxForm

class Admin(EntityAdmin):
form_display = TabForm([
(‘Movie’, Form([
HBoxForm([[‘title’, ‘rating’], WidgetOnlyForm(‘cover’)]), ‘short_description’, ‘releasedate’, ‘director’, ‘script’, ‘genre’, ‘description’, ‘tags’], scrollbars=True)),

(‘Cast’, WidgetOnlyForm(‘cast’))

])

list_filter

A list of fields that should be used to generate filters for in the table view. If the field named is a one2many, many2one or many2many field, the field name should be followed by a field name of the related entity

class Project(Entity):

oranization = OneToMany(‘Organization’) name = Field(Unicode(50))

class Admin(EntityAdmin):
list_display = [‘organization’] list_filter = [‘organization.name’]
../_images/group_box_filter.png

A list of fields that should be searched when the user enters something in the search box in the table view. By default only character fields are searched. For use with one2many, many2one or many2many fields, the same rules as for the list_filter attribute apply

confirm_delete

Indicates if the deletion of an object should be confirmed by the user, defaults to False. Can be set to either True, False, or the message to display when asking confirmation of the deletion.

form_size

a tuple indicating the size of a form view, defaults to (700,500)

form_actions

Actions to be accessible by pushbuttons on the side of a form, a list of tuples (button_label, action_function) where action_function takes as its single argument, a method that returns the the object that was displayed by the form when the button was pressed:

class Admin(EntityAdmin):
form_actions = [(‘Foo’, lamda o_getter:print ‘foo’)]
field_attributes

A dictionary specifying for each field of the model some additional attributes on how they should be displayed. All of these attributes are propagated to the constructor of the delegate of this field:

class Movie(Entity):

title = Field(Unicode(50))

class Admin(EntityAdmin):
list_display = [‘title’] field_attributes = dict(title=dict(editable=False))

Other field attributes process by the admin interface are:

name

The name of the field used, this defaults to the name of the attribute

target

In case of relation fields, specifies the class that is at the other end of the relation. Defaults to the one found by introspection.

admin

In case of relation fields, specifies the admin class that is to be used to visualize the other end of the relation. Defaults to the default admin class of the target class.

model

The QAbstractItemModel class to be used to display collections of this object, defaults to a CollectionProxy

confirm_delete

set to True if the user should get a confirmation dialog before deleting data, defaults to False

TableView

The QWidget class to be used when a table view is needed

Note

While EntityAdmin can only be used for classes that are mapped by Sqlalchemy, ObjectAdmin can be used for plain old python objects as well.

EntityAdmin

EntityAdmin is a specialization of ObjectAdmin, to be used for classes that are mapped by Sqlalchemy. EntityAdmin will use introspection to determine field types and assign according delegates and editors.

Form View Actions

Form actions are objects that can be put in the form_actions list of the object admin interfaces. Those actions then appear on the form and can be executed by the end users. The most convenient method to create custom actions is to subclass FormAction and implement a custom run method

class MyAction(FormAction):

  def run(self, entity_getter):
    print 'Hello World'

class Movie(Entity):
  title = Field(Unicode(60), required=True)

  Admin(EntityAdmin):
    list_display = ['title']
    form_actions = [MyAction('Hello')]

Several subclasses of FormAction exist to provide common use cases such as executing a function in the model thread or printing a report.

To customize the look of the action button on the form, the render method should be overwritten.

Printing reports in the form view

class camelot.admin.form_action.PrintHtmlFormAction(name, icon=Icon('tango/16x16/actions/document-print.png'))

Create an action for a form that pops up a print preview for generated html. Overwrite the html function to customize the html that should be shown:

class PrintMovieAction(PrintHtmlFormAction):

  def html(self, movie):
    html = '<h1>' + movie.title + '</h1>'
    html += movie.description
  return html

class Movie(Entity):
  title = Field(Unicode(60), required=True)
  description = Field(camelot.types.RichText)

  class Admin(EntityAdmin):
    list_display = ['title', 'description']
    form_actions = [PrintMovieAction('summary')]

will put a print button on the form :

../_images/print_html_form_action.png
HtmlDocument the class used to render the html, by default this is

a QTextDocument, but a QtWebKit.QWebView can be used as well.

Validators

Before an object is written to the database it needs to be validated, and the user needs to be informed in case the object is not valid.

By default Camelot does some introspection on the model to check the validity of an object, to make sure it will be able to write the object to the database.

But this might not be enough. If more validation is needed, a custom Validator class can be defined. The default EntityValidator class is located in camelot/admin/validator/entity_validator. This class can be subclassed to create a custom validator. The new class should then be bound to the Admin class :

from camelot.admin.validator.entity_validator import EntityValidator
from camelot.admin.entity_admin import EntityAdmin

class PersonValidator(EntityValidator):

    def objectValidity(self, entity_instance):
        messages = super(PersonValidator,self).objectValidity(entity_instance)
        if (not entity_instance.first_name) or (len(entity_instance.first_name) < 3):
            messages.append("A person's first name should be at least 2 characters long")
        return messages
    
class Admin(EntityAdmin):
    verbose_name = 'Person'
    list_display = ['first_name', 'last_name']
    validator = PersonValidator    

Its most important method is objectValidity, which takes an object as argument and should return a list of strings explaining why the object is invalid. These strings will then be presented to the user.

Notice that this method will always get called outside of the GUI thread, so the call will never block the GUI.

When the user tries to leave a form in an invalid state, a platform dependent dialog box will appear.

../_images/entity_validator.png

ApplicationAdmin

class camelot.admin.application_admin.ApplicationAdmin

The Application Admin class defines how the application should look like, it also ties python classes to their associated admin classes. It’s behaviour can be steered by overwriting its static attributes or it’s methods :

name

The name of the application, as it will appear in the title of the main window.

sections

A list containing the various sections that should appear in the left panel of the mainwindow.

../_images/picture2.png
backup_mechanism

A subclass of camelot.core.backup.BackupMechanism that enables the application to perform backups an restores.

from camelot.view.art import Icon
from camelot.admin.application_admin import ApplicationAdmin
from camelot.admin.section import Section

class MyApplicationAdmin(ApplicationAdmin):
  
  name = 'Camelot Video Store'
    
  def get_sections(self):
    from camelot.model.memento import Memento
    from camelot.model.authentication import Person, Organization
    from camelot.model.i18n import Translation    
    from example.model import Movie, Tag
    return [Section('movies', 
                    Icon('tango/22x22/mimetypes/x-office-presentation.png'),
                    items = [Movie, Tag]),
            Section('relation',
                    Icon('tango/22x22/apps/system-users.png'),
                    items = [Person, Organization]),
            Section('configuration',
                    Icon('tango/22x22/categories/preferences-system.png'),
                    items = [Memento, Translation])
            ]