This bugtracker is archived (announcement). New tickets are created on github.com. See all framework issues, cms issues, and search the module listings for more specific bugtrackers.
wiki:development/modeladmin
Last modified 6 years ago Last modified on 07/07/08 16:47:27

ModelAdmin? Development Plan

Contact

  • Ingo Schommer (ingo at silverstripe dot com)
  • Sam Minnee (sam at silverstripe dot com)

Status

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',
         );
      }
      
  • 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?

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?

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

Misc