Development

Symfony12AdminGenerator

You must first sign up to be able to contribute.

Symfony 1.2 Admin Generator Development

This page serves as place where features and modifications of the admin generator in symfony 1.2 can be discussed.

Related Pages

See also: * Admin Generator 1.2 Sprint Notes


Concept by Bernhard Schussek

Please note that this section does not contain official information. The text written in here is solely based on my personal concepts.

I was planning to rewrite parts of the admin generator together with a few members of the symfony usergroup vienna starting in summer '08. I developed a concept of different UI and technical enhancement that are described here. The purpose is to discuss whether some or all of these features can be integrated into the new admin generator. I am willing to spend time and probably money to develop these features, as may be some other people I know personally.

Motivation

The motivation of this rewrite was mainly to increase the usability of the admin generator, so people without much technical knowledge can easily use the interface without lots of configuration/adaption.

In the concept I tried to find generalized data representation schemes which can be used in most use cases, but still are usable enough to be used by inexperienced users.

Notes

  • Most of the ideas presented in here are based on successful applications such as Silverstripe, Jacomo (a very usable and customizable db administration tool developed by my previous employer) and Joyent (a collaboration tool with a very innovative UI).
  • I'm basing my concepts on the current syntax of configuration files. These need to be modified in case the new admin generator makes radical changes here.
  • Many of the examples are incomplete and require further discussion
  • I'm refering to inexperienced, non-technical users when talking about "users"

Besides: I was planning to develop this generator based on Doctrine because of the easier syntax and better support of relations, inheritance and nested sets.

Major Additions

Modification of Contextual Records

Problem

In many applications the business model can be (mostly) separated in two groups:

  1. Informational Entities (valuable information), for instance
    • Article
    • Town
    • Customer
    • Booking
    • Page
  2. Contextual Entities (providing contextual information for the above, mostly "groups" or "categories"), for instance
    • Group
    • Tag
    • Category
    • Type

(of course this separation does not apply to all models, but from my experience to many of them)

The difference between them is that users generally don't want to know about the second.

Example:

For instance, the model may require to define a related "AccommodationType" for "Accommodation" records. Real users will not be interested in the type. They want to create a new accommodation, defining (and eventually creating) a type is only a burden, especially when the type has to be created in a different list view. Users will enter the accommodation creation form, fill half of the fields only to realize that the related type does not exist yet. Advanced users will open the type list in a new tab or window, but inexperienced users will just leave the form and lose all their entered data.

The only real use for the user in such contextual entities is the ability to filter list views (in this example - filter accommodations by type).

So our tasks are:

  1. Make modification of contextual entities a contextual task that doesn't distract the user from doing his actual task
  2. Make filtering by those contextual records easy

Concept

I came up with the following concept when I stumbled upon Joyent Connector while analysing different web applications:

  • Add contextual records through sub-forms while modifying the informational entities (solves 1.)

Example:

Create AccommodationTypes while modifying an accommodation through subforms

Concept Screenshot

  • Display, add and modify contextual entities in a tabbed sidebar in the views of the informational entities they're related to. Don't waste space by creating a dedicated list view which users will never use (solves 1.)

Example:

Display AccommodationTypes in Accommodation list and edit views, create, delete and modify them using AJAX

Concept Screenshot (forgot the delete button there, but I think you know what I'm up to)

  • Filter a list of informational entities when clicking on a contextual entity in the tabbed sidebar. Users know this behaviour from browsing f.i. file browsers, music applications etc. (solves 2.)

Example:

When the user clicks on an AccommodationType entry in the sidebar of the Accommodation list view, filter the list by this type.

Concept Screenshot

Realisation

One could realise the sidebar with the following configuration:

generator:
  param:
    model_class: Accommodation
    sidebar:
      # Configuration of the sidebar tabs
      display:
        "Types":         [_types]                                                # custom field that can be overridden by a partial
        "Tags":          [_tags]
      # Configuration of the sidebar widgets
      fields:
        types:           { type: sfSidebarTree, param: relation=Type }           # uses sfSidebarTreeAdminWidget
        tags:            { type: sfSidebarTree, param: relation=Tags add=false }

(Note: this code sample is probably incomplete)

This way, you can create your own widgets for the sidebar and the syntax remains very similar to the configuration of the edit view (see below).

Adding a contextual entity through a subform while editing an informational entity can be handled through a widget (see below).

Widgets

The generator should support widgets, meaning a generalized way of representing and modifying record data. These widgets as I speak of them are different from symfony 1.1 form widgets in that they incorporate view, model (validation) and partially business logic.

Use case 1:

The record class "Accommodation" contains a many-to-one foreign relation "AccommodationType". When editing an accommodation, the user can select its type. Additionally he should be able to create a new type if the desired type does not exist yet. This can be done through a subform for adding the record.

generator:
  param:
    edit:
      AccommodationType: { name: Type, type: SelectRelation, param: add=true } # Uses SelectRelationAdminWidget, the 
                                                                              # original record and the name of the 
                                                                              # field (relation in this case) are 
                                                                              # automatically provided to the widget

Concept Screenshot

The (conceptual) widget incorporates the following logic:

  • display of all available types (model, view)
  • addition of a new type (model, business logic)
  • validation of selected types (model)

Use case 2:

The record class "Accommodation" contains two fields "longitude" and "latitude". They should be modified through a Google Map with a draggable marker. These fields do also appear in other classes, so a reusable widget is needed which can be configured in all classes:

generator:
  param:
    edit:
      _location: { type: GoogleMap, param: longitude=longitude_field latitude=latitude_field } # Uses GoogleMapAdminWidget 
                                                                                               # with the given parameters

Concept Screenshot

The widget incorporates the following logic:

  • display of the map, javascript (view)
  • validation of the coordinates (model)

Simultaneous Modification

This is not as important, but useful anyway.

The user should be able to modify several objects at once by selecting them (see Enhanced List View Actions below) and pressing the button "Edit". When multiple records are modified, the form contains a checkbox "Modify" for each form field which is unchecked by default. Form fields which are the same in all selected records are filled out, the other ones are left empty. If the user changes the content of a form field, checks "Modify" and saves the record, the given property is changed to the new value on all objects.

This feature is very useful for batch editing of all sorts.

Concept Screenshot

Support of Nested Sets

The generator should be able to automatically display nested sets as trees in the list view. Nested sets do always represent hierarchical data and thus can basically always be represented by trees, which are more intuitive to use for people than plain lists.

There shouldn't be any further configuration necessary to enable this feature.

Concept Screenshot

Support of Inheritance

The generator should be able to automatically deal with Doctrine's inheritance schemes. I have too little experience in terms of real use cases to come up with a usable concept here though.

Support of I18N

The generator should be able to deal with translated records. How to do this in detail needs to be discussed.

Minor Additions

The admin generator should feature the generation of a global navigation module linking to the index/list actions of different modules. This could easily be incorporated in a YAML file /apps/myapp/config/generator.yml:

generator:
  param:
    display:
      myModule:  ~                                                        # automatically links as "My module" to myModule/index
      myModule2: { name: "Site", icon: "...png", action: myModule2/edit } # further configuration

Alternatively, this information could be stored directly in the generator.yml (with the disadvantage that custom modules cannot be linked anymore without creating a generator.yml for it)

generator:
  param:
    navigation:    { name: Site, icon: ...png, default_action: edit }

Tabs

It should be possible to group fields into tabs in the edit view. This can be done by reusing the current configuration syntax for fieldsets:

generator:
  param:
    edit:
      display:
        "Details":  [name, email, _location]
        "Rooms:     [...]
        "Prices":   [...]

Concept Screenshot

Enhanced List View Actions

The support of custom actions in the list view should be enhanced. There should be two types of display modes:

  • normal (normal button that does something)
  • collapsible (the visibility hidden partial is toggled when the button is pressed, that could contain filters for the list view etc.

Additionally one should differ between to types of action scope:

  • none (the action does not affect a specific record) - for instance "Create", "Filter"
  • selected (the action affects only selected records) - for instance "Delete", "Edit". One or more records need to be selected using checkboxes in the list view first
generator:
  param:
    list:
      actions:
        _create:  { name: "New..." }                  # Default values: { display: normal, scope: none }
        _edit:    { scope: selected, action: edit }   # The selected record identifiers will be handled to the specified action
        _delete:  { scope: selected, action: delete } 
        _filter:  { display: collapsible }            # The partial _filter.php is toggled open/closed when the button is 
                                                      # pressed and contains a form used to apply specific filters

Concept Screenshot

Francois: "Selected" scope actions already exist in the symfony 1.1 admin generator, but with a different syntax (batch_actions). I'm not sure about the opportunity to break BC here.

Enhanced Support of Relations

The admin generator should provide displaying, sorting and filtering by columns of related records in an intuitive and easy manner:

generator:
  param:
    list:
      display:  [name, AccommodationType.name]         # The field name of the related AccommodationType record is displayed and
                                                       # can be used for sorting
      filters:  [name, AccommodationType.name]         # One can filter by the attribute "name" of the related AccommodationType
      fields:
        AccommodationType.name: { name: Type }         # Configuration of the displayed field name

Leon (LvanderRee?): Hi Bernhard: nice to see you like this idea as well, there is one problem though with your current syntax and that is thay you cannot use the ObjectName to define the related Table, you should use the ForeignKey in order to be able to support multiple links to the same RelatedTable (For example created_by/username, updated_by/username (I use a slash / instead of a dot . since I am used to this, but the dot is fine by me as well.)

Bernhard (bschussek): Hi, thanks for your input! Actually, "AccommodationType" is a Doctrine relation. I just gave it the same name as the class it points to for the sake of clarity. You could replace it with "Type", "Types", "LastCreator", "LastUpdater" or whatever the relation is named. The dot is borrowed from Doctrine's DQL, where exactly the same syntax is used an which I am used to. For propel, the syntax could be different.

Some quick ideas from Matthias Nothhaft (mahono)

  • Please have a look into the store backend of Magento (which may be a generic inspiration as well)
    • I would like to have filters in the head of the list table (each field in its matching column) as implemented in the Magento backend.
  • I would like to have personalized configuration.
    • Each user should be able to enable/disable columns/fields and reorder them as he likes it.
  • Primary key(s) must be editable

Conclusion: Most important is a much more modular system so people can plug in their own alternative implementations of the various parts of the admin magic without having to implement a complete new admin generator just because you want to change a feature. Factories/drivers/Widgets or whatever to provide different implementations of all parts of the admin generator?

Some notes from Tom Boutell at P'unk Ave (boutell)

The ability to manually order items is desirable. But more than that, the ability to create an extension that adds such a capability without bad coding practices is desirable. I'm going to pick on sfPropelManualOrderPlugin here, not to be mean but to illustrate the underlying difficulties its developer faced.

The existing sfPropelManualOrderPlugin looks great and works well... but I never looked at it closely under the hood until today. It turns out that in order to add the sorting feature, sfPropelManualOrderPlugin must contain not only a complete template theme (which is also unfortunate, but is at least apparent on first inspection) but also a complete copy of data/generator/sfPropelAdmin/sortable/template/actions/actions.class.php, the admin class generator itself.

This means that if the admin generator gets security fixes, bug fixes, etc., they are not available to folks relying on sfPropelManualOrderPlugin, who may not even be aware that this is the case. Ouch.

And in any case it's not possible to have two or more such "extensions" to the admin generator in play at the same time.

Why did the author of sfPropelManualOrderPlugin do this way? Because there was no apparent alternative way to deliver the feature.

You can add features to an admin generator class easily on a one-module-at-a-time, application-specific basis, by extending the autogenerated actions class and then overriding single methods like addFiltersCriteria, then returning the result of a call to parent::addFiltersCriteria, etc. But there is currently no good way to share these improvements in a reusable fashion.

Mix-ins can be used to address this problem, but it's quite painful- the application developer hoping to use a plugin that extends the admin generator will need to follow the techniques here:

http://www.symfony-project.org/cookbook/1_0/en/behaviors

Overriding every method that might get further overridden by any of the admin generator extension plugins they hope to use in order to ensure the mixin methods get called.

Even then, the problem of template files (themes) for multiple such extensions simultaneously in use hasn't been resolved. Of course if two plugins alter the same template that's a genuine conflict, but if they alter different templates, there ought to be a simpler means of electing both at once.

Some notes from Gary Feldman (GaryFx?)

I think I got hung up on the term contextual when I first read this, because contexts are generally transient while the things here are clearly persistent. At first I thought the difference was between concrete and abstract (which matches the example but isn't really the idea). The more I think about it, the more I think it's the difference between primary and secondary data. For example, you might be designing a set of rooms, adding furniture to each room. The user has a given context, but the items are all concrete. But in a different context, the same user might be configuring a specific bed, specifying the mattress type, number and type of pillows, linens, etc. So a context represents a particular user task, and each context would have some primary data item and some related, subsidiary (or secondary) data items. Some data tables might always be primary, others might always be secondary, but there could still be some that play different roles at different times. There's nothing in your design that prevents that, but I think the terminology of primary versus secondary (or something similar) captures the idea more clearly, at least for me.

I see a need for fine-grained access control. Using your AccommodationType? widget example, it's possible that many people could be authorized to assign a type to an accommodation, but only some of them might have permission to create new ones. There needs to be a way to express and enforce this.

I also don't see this as being specific admin-related, but then the same is true for the original Admin generator. Really what this is about is using YAML to describe complex form layouts.

Bernhard (bschussek): Thanks for your interesting entry! I didn't think about this subject the way you did, but actually I agree. Contextual entities, as I called them, may not always and in every view be contextual. I don't know whether renaming them to primary and secondary data items solves this issue, as again the "primary" is not explicitely restricted to the current view on the model. Focusing a bit more on the term "view" (as in UML) may be the solution, what do you think?

I agree that it should be possible to have fine-grained control over the permissions of different parts of a widget. As Ivan mentioned, YAML might not be the ideal solution anymore for configuring such a lot of details. Fabien already mentioned in a mail that he wants to move the configuration of the generator from YAML to PHP classes (probably like the new Form framework), so discussion about YAML details might be obsolete anyway.

Some notes from Ivan Zgoniaiko (lking)

Sorry for my bad english (if you found any error, please edit it or delete note if it looks stupid).

First of all thank you for so great concept. Looks like admin generator will be very powerful, like 2 clicks and you have full-featured admin for your module.

Current admin generator works ok when you need to build it for 1 table with few fields. But we have prob with sort and filter by FKs (sorting by FK just don't work and filter by FK is select). Here no any probs with filter with select if you have few rows in it, but here can be a lot of rows. At this way page will not be loaded. Sure we can solve it, we copy/create new template for this filter and make autocoplete for example. But this is good for basic logic. But here can be more complex logic. For example if we have few admins with diff permissions:

1-st admin can see/edit/create everithing and 2-nd admin have access to create/edit only 1 type of objects, and see only 2 types of objects but can't see AccommodationType?. We have admin config for objects with FK, select of FK object is required. 2-nd admin can create/edit this objects, but can't even see FK objects (AccommodationType?). As result 2-nd admin can't save new/edited object because he can't fill required FK.

Additionally here can be different rules for FK objects. Like we want to create new object, but FK required. Here no needed FK object in list. Create it, but this FK object require FK objects of 3-rd and 4-th types. No this objects too, create them. And this chain can be very-very long. As result we'll recive 1 large config for all application in 1 YAML file.

Another prob that here no inheritance of configs for admin generator (at least i didn't found how to do it). Maybe here no sense to use YAML for admin generator at all? I don't want to say that YAML is evil, it is very powerful. But maybe it not so good for admin generator? We used YAML for form validation with symfony 1.0, and with symfony 1.1 we use php. Maybe here is sense to use skeleton for admin like we use skeleton for CRUD?

Some notes from Klemens Ullmann (klemens_u)

I'd like to configure access right per field. But not only on/off as it is now, but read/write access. This would be handy to create "profiles" for different user-groups. Example Users: Admin can edit all fields, Users can just edit some fields like the phone-number. All other fields are not editable, but shown to the user

This implies, that every widget has a "show" and a "edit" method. Per default the "show" method would just print out the value. For foreign keys it would print out the output of the _ _toString() method. Another example: a widget "email". The "edit" method would return a <input type='text /> tag (Using the forms framework's sfWidgetFormInput widget :-). The "show" method would automatically create a clickable link for the email-adress. And the widget's validator would automatically validate if it's a valid email-address.

(my email: klemens[DOT]ullmann[AT]ull[DOT]at)

Some notes from Romain Stévant (Jackovson)

I agree with Gary Feldman (GaryFx?) : I think there is no need of YAML for the admin generator. All tweak possibilities in YAML can be done with PHP and inheritence, and MANY more can be done this way. At my office, we work with a another framework that have a very powerfull admin generator, based on generic PHP files (view, edit, list controllers and templates) that can be inherited to make all tweaks possible : controller side and template side. Some tweaks are juste properties (all YAML could be done like that), others ones are modthed redefinition (mfor ore complex tweaks).

An goold example of this powerfull system : you wan to display comments of an article when showing/editing an article. With our framework, we just have to add one line in the controller (to select related comments for an article), and a line in the template (to display related elements). This way, you can display very easily related data for an element. The display of relateds elements is handled by admin generator for theese elements, so templating tweaks are refactored.

Templates are juste classes (with name convention) with a main method (for example display()) that will display HTML code (not direct included PHP files... I think this should be done into Symfony too, to have a more powerfull and refactored templating system, but thats out of the present topic !). Commonly, a template class will have many methods, private and public, and public ones can be considered as partials. The generic generator have many methods used to display all elements on the page : fields title, fromating fields value (example : display icons for boolean or enum fields) etc. Thats could look like :

 getFieldTitle($fieldName){}//get title for fields
 formatFieldValue($fieldName, $value){}//format a field value
 etc

You just have to know public generic methods to do your tweaking job. (I hope I have been clear... but I am not sure :p)

Antoher idea concern "filters". In our frameworks, I think filters are much more powerfull than in Symfony (1.0/1.1) : you have the possibility to graphically build an SQL query with as many "WHERE" condition you want (WHERE AND AND AND.... no OR for the moment, but it could also be great). So you have a "add a condition" link, that display a dop down list of object proporties (id, name, author, etc). When selecting a property, antoher drop down list appear with all condition type (equal, greater than, containing...), and a text field to add your value (for int or string fields). For "special" fields", the second drop down + text field is replaced by special input : for exemple a yes / no radio button for boolean, unique drop down list with related elements (like categories for a article), etc... This way you have much more powerfull filters.

Another (gadget) feature is the possibilty to choose the displayed rows (in displayble fields) : very usefull for elements that have many fields.

I can post screenshot if I am not clear ;)

(I am sorry for my bad english...)

I hope this will help.

Attachments