Last modified 5 years ago
ModelAdmin? Development Plan
Contact
- Ingo Schommer (ingo at silverstripe dot com)
- Sam Minnee (sam at silverstripe dot com)
Status
- Alpha, development branch (see http://open.silverstripe.com/browser/modules/sapphire/branches/roa)
Overview
In summary, ModelAdmin is an improved version of GenericDataAdmin. It is a complete rewrite that isn't backward-compatible, however the existing GenericDataAdmin? will still be bundled with SilverStripe? so that existing applications still work. ModelAdmin? itself is fairly simple, but relies on a number of new technologies:
- Field scaffolding: Each database field and relation has
- SearchContext?: Search forms, the queries for thier their results, can be
For usage documentation, see http://doc.silverstripe.com/doku.php?id=modeladmin.
Rationale for Model Admin
This is some of the thinking that led to the decision to build ModelAdmin?:
Current Shortcomings of GenericDataAdmin?
- No tight connection from management interface to datamodel, mostly custom controller logic
- Heavy customization in search logic
- Manual form building (e.g. instanciation of complextablefields for relation editing)
- Base layer not customizeable enough, mostly function overrides without re-usage
- No support for management of multiple datatypes and hierarchies
Core Assumptions
- Datamodels are too custom to have a common base, so we don't provide one. The module will provide best practices and copypaste examples.
- Rich data layer that has enough information about itself and object relations to produce a useable interface "out of the box"
- GenericDataAdmin? provides one interface for multiple DataObject? types, which should be related to each other via has_one/has_many/many_many. This has several implications on the UI:
- Multiple "create <dataobject>" buttons at the top (or dropdown selection)
- Verbose URLs (admin/generic/show/Member/1 instead of admin/mycustompath/show/1)
- One search interface for each DataObject? type, and no "out-of-the-box" searching across different types (e.g. you either search for a Contact's Name or an Organisation's ZIP, but can't search for "Show me all contacts named XYZ in ZIP area 99999")
- SearchContexts? are customizeable to allow for querying across different types
- Its impossible to retain backwards compatibility to the current GDA implementation
- Q: How extensible do we want to make the search->result "widget"? It would be useful for TableListField? searching, but might turn out to be too complicated to be useful
Features: Datamodel
Searchable Fields
- Implement DataObject::$searchable_fields and DataObject::getCMSSearchFields()
- (Sam) Instead of getCMSSearchFields(), we have defaultSearchContext(), that returns a SearchContext? object - a combination of a set of search fields an instructions on how to use them. This being the case, we may as well drop DataObject::$searchable_fields altogether, and define our search fields simply by implementing defaultSearchContext(). Because it's a PHP function, it's much more flexible for defining search information. Here's a simple example, but it could be much more complex.
function defaultSearchContext() { return new SearchContext( 'Field1', 'Field2', 'Field3', ); }
- (Sam) Instead of getCMSSearchFields(), we have defaultSearchContext(), that returns a SearchContext? object - a combination of a set of search fields an instructions on how to use them. This being the case, we may as well drop DataObject::$searchable_fields altogether, and define our search fields simply by implementing defaultSearchContext(). Because it's a PHP function, it's much more flexible for defining search information. Here's a simple example, but it could be much more complex.
- Scaffold searchform based on these fields
- Optional: Default mapping between DBField types and SearchFilter?
- Optional: also evaluated in our fulltext site search
- Optional: allow for "dot notation" in searchable fields
SearchContext?
- Main goal: Abstract search<->result behaviour away from the view, should be pluggable to TableListField?, Report Views and GDA results
- SearchContext? acts on one or more DataObject? types and auto-generates a SQLQuery with the correct joins and fields
- Query should be customizeable either by adding custom search-filters, or modifying the query-object before getting results
- $searchable_fields have a default mapping to a SearchFilter? based on their DBField type (e.g. "Date" class would map to a "DateSearchFilter?" and render with the "DateField?" FormField? subclass)
- Responsible for processing client input through $_REQUEST (or api call?)
- (Sam) Don't access $_REQUEST directly; $searchParams should be an argument to getResultsQuery().
- Responsible for paging, as SearchContext? handles the query object
- Have most of the sql coming from the SQLQuery object, which is the starting point for any DB abstraction layer
- We don't allow for multiple "views" of searchable fields by default, e.g. an extended searchform for GDA, and a shortened form for tablelistfield - but we'll make it easy to customize the default output.
- Q: Does SearchContext? return a query object or a set of data to the view?
- (Sam) Both: let's have two access methods - getResultsQuery() AND getResults(). The latter would obviously call the former. The key to flexibility is being able to access things in multiple ways. :-)
SearchFilter?
- SearchFilter? should use the DBField->dataValue() method to convert/reformat any input values
- Text related search filters should be able to trigger case sensitivity
- Create DataObject::$db_labelsAllow to overrid
- Possible SearchFilter? classes:
- Varchar,HTMLVarchar: TextSearchFilter? (rendered with TextField?)
- Text,HTMLText: FulltextSearchFilter?, PartialMatchSearchFilter?, ExactMatchSearchFilter? (rendered with TextField?)
- Date: DateSearchFilter? (rendered with DateField?)
- Time: TimeSearchFilter? (rendered with DateField?)
- SSDateTime: DateTimeSearchFilter? (rendered with PopupDateTimeField?)
- Boolean: BooleanSearchFilter? (rendered with CheckboxField?)
- Enum: TextSearchFilter? (rendered with DropdownField?), MultiselectFilter? (rendered with CheckboxSetField?)
- Int: ?
- Percentage: ?
- Currency: ?
- Q: How do we handle localization?
- (Sam) Do you mean text label localization? I don't see how this is a big deal - we do it the same way we've localised everything else?
- (Ingo) I mean localizing dates/currencies etc. - should FormField? or DBField be responsible for filtering this data?
Formfield Scaffolding
- Default implementation of DataObject::getCMSFields()
- Separate Tab for each has_many/many_many object relation, containing a ComplexTableField? subclass
- Optional: Hook a refactored RequiredFields? validator to a model knowing its own $required_fields
- Q: Do we explicitly support validation? some validation routines are flaky when used in the backend
- (Sam) Validation in the admin should work. We may need to wait until after the JS upgrade to do this.
- Q: Conceptually it would be nice to have relations represented as an object as well (see Django), so we can treat all "editable" properties of an object equally
Features: GenericDataAdmin?
- Port useful features down from CRMAdmin and deprecate it
- Leave access control for search/display/saving for later
- Use TableListField? as result display (versatile formatting options, integrated CSV export)
- GDA needs to be able to construct a default edit link for all objects it manages (currently its a single GenericDataAdmin?->getLink() method)
- Q: We manage more than one type on GDA, should result display be customizeable on DataObject? rather than GDA implementation?
- Q: Should actions for a form like "save", "delete", "publish" be handled by DataObject? or GDA?
Features: UI
- Editor for many_many extrafields (build on Romains work?)
- Better editor for has_many/many_many relations (autocomplete instead of big list with checkboxes) - possibly as a CTF module
CRM Development Plan
Features: CommunicationForm?
- Better error handling and user errors
- Batch sending of emails (see NewsletterAdmin? and BatchProcess?.php)
Code Mockups
DataObject? $searchable_fields
class DataObject extends ViewableData {
function getCMSSearchFields() {
$fields = new FieldSet();
foreach(self::$searchable_fields as $fieldName) {
// would generate automatically for Organisation subclass:
//new DateRangeSearchFilter('Created'),
//new TextSearchFilter('Title'),
//new MultiselectSearchField('Type'),
}
return $fields;
}
}
class Organisation extends DataObject {
static $db = array(
'Title' => 'Varchar',
'Street' => 'Text',
'Type' => 'Enum("a,b,c")',
'HasAffiliates' => 'Boolean',
);
static $searchable_fields = array(
'Created',
'Title',
'Type'
);
function getCMSSearchFields() {
$fields = parent::getCMSSearchFields();
$fields->replaceByName('Type', new TextSearchFilter('Type'));
return $fields;
}
function getCMSSearchFilters() {
}
}
DBField extensions
class DBField extends Object {
static $default_scaffold_formfield = "TextField";
static $default_search_formfield = "TextField";
static $default_search_filter = "TextSearchFilter";
}
SearchFilter?
class SearchFilter extends Object {
function __construct($name) {
$this->name = $name;
}
function updateQuery($resultSet, $tableName, SQLQuery &$query) {
$SQL_val = Convert::raw2sql($resultSet[$this->name]);
$query->addFilter("`$tableName`.`$this->name` = '$SQL_val'");
}
}
SearchContext?
// TODO is this really a controller?
class SearchContext extends Controller {
function __construct($controller, $name, $fields, $filters) {
}
function getQuery($searchFilters) {
$q = new SQLQuery("*","Member");
$this->processFilters($q);
return $q;
}
function getResults($searchFilters, $start, $limit) {
$q = $this->getQuery($searchFilters);
$q->limit = $start ? "$start, $limit" : $limit;
$output = new DataObjectSet();
foreach($q->execute() as $row) {
$className = $row['RecordClassName'];
$output->push(new $className($row));
}
// do the setting of start/limit on the dataobjectset
return $output;
}
function processFilters($searchFilters, SQLQuery &$query) {
foreach($this->filters as $filter) {
$filter->updateQuery($searchFilters, $tableName, $query);
}
}
}
Links
- Django ModelForms Tutorial
- Django ModelForms Documentation
- Django Form Models sourcecode
- Ruby on Rails ActiveResource Presentation
- http://www.therailsway.com/2007/9/3/using-activeresource-to-consume-web-services
- http://microformats.org/wiki/rest/rails
- http://weblog.techno-weenie.net/2006/12/12/taking-ares-out-for-a-test-drive
Misc
- Q: Do we want to implement http://www.opensearch.org?
- (Sam) Not at this stage; could be a good idea for the future though.
