==================== Index + CRUD Views ==================== A master view can expose any or all of these routes. Each is configured automatically within :meth:`~wuttaweb.views.master.MasterView.defaults()` if the associated class attribute is true: * :meth:`~wuttaweb.views.master.MasterView.index()` - based on :attr:`~wuttaweb.views.master.MasterView.listable` * :meth:`~wuttaweb.views.master.MasterView.create()` - based on :attr:`~wuttaweb.views.master.MasterView.creatable` * :meth:`~wuttaweb.views.master.MasterView.view()` - based on :attr:`~wuttaweb.views.master.MasterView.viewable` * :meth:`~wuttaweb.views.master.MasterView.edit()` - based on :attr:`~wuttaweb.views.master.MasterView.editable` * :meth:`~wuttaweb.views.master.MasterView.delete()` - based on :attr:`~wuttaweb.views.master.MasterView.deletable` The "index" view shows a "grid" by default. Whereas each of the 4 CRUD views will show a "form" (readonly or editable). See also :doc:`/narr/grids/index` and :doc:`/narr/forms/index`. If the view's data model is a typical mapped class, the default CRUD logic may suffice. Otherwise, and sometimes for a mapped class, you may need to provide some of the CRUD logic. Attributes to Override ---------------------- You can set these attributes to control basic CRUD features: **Model** * :attr:`~wuttaweb.views.master.MasterView.model_class` * :attr:`~wuttaweb.views.master.MasterView.model_name` * :attr:`~wuttaweb.views.master.MasterView.model_key` * :attr:`~wuttaweb.views.master.MasterView.model_title` * :attr:`~wuttaweb.views.master.MasterView.model_title_plural` **Config Prefixes** (see also :ref:`config-prefixes`) * :attr:`~wuttaweb.views.master.MasterView.route_prefix` * :attr:`~wuttaweb.views.master.MasterView.permission_prefix` * :attr:`~wuttaweb.views.master.MasterView.url_prefix` * :attr:`~wuttaweb.views.master.MasterView.template_prefix` **Feature Flags** * :attr:`~wuttaweb.views.master.MasterView.listable` * :attr:`~wuttaweb.views.master.MasterView.creatable` * :attr:`~wuttaweb.views.master.MasterView.viewable` * :attr:`~wuttaweb.views.master.MasterView.editable` * :attr:`~wuttaweb.views.master.MasterView.deletable` **Grid Config** * :attr:`~wuttaweb.views.master.MasterView.grid_columns` * :attr:`~wuttaweb.views.master.MasterView.filterable` * :attr:`~wuttaweb.views.master.MasterView.filter_defaults` * :attr:`~wuttaweb.views.master.MasterView.sortable` * :attr:`~wuttaweb.views.master.MasterView.sort_on_backend` * :attr:`~wuttaweb.views.master.MasterView.sort_defaults` * :attr:`~wuttaweb.views.master.MasterView.paginated` * :attr:`~wuttaweb.views.master.MasterView.paginate_on_backend` **Form Config** * :attr:`~wuttaweb.views.master.MasterView.form_fields` Methods to Override ------------------- Here are some methods you may need to override for the CRUD features: **Fetching Data** * :meth:`~wuttaweb.views.master.MasterView.get_grid_data()` * :meth:`~wuttaweb.views.master.MasterView.get_instance()` **Grid Handling** * :meth:`~wuttaweb.views.master.MasterView.make_model_grid()` * :meth:`~wuttaweb.views.master.MasterView.configure_grid()` * :meth:`~wuttaweb.views.master.MasterView.grid_row_class()` **Form Handling** * :meth:`~wuttaweb.views.master.MasterView.make_model_form()` * :meth:`~wuttaweb.views.master.MasterView.configure_form()` * :meth:`~wuttaweb.views.master.MasterView.objectify()` * :meth:`~wuttaweb.views.master.MasterView.persist()` * :meth:`~wuttaweb.views.master.MasterView.delete_instance()` **Creating** * :meth:`~wuttaweb.views.master.MasterView.make_create_form()` * :meth:`~wuttaweb.views.master.MasterView.save_create_form()` * :meth:`~wuttaweb.views.master.MasterView.redirect_after_create()` **Viewing** * :meth:`~wuttaweb.views.master.MasterView.make_view_form()` **Editing** * :meth:`~wuttaweb.views.master.MasterView.make_edit_form()` * :meth:`~wuttaweb.views.master.MasterView.save_edit_form()` * :meth:`~wuttaweb.views.master.MasterView.redirect_after_edit()` **Deleting** * :meth:`~wuttaweb.views.master.MasterView.make_delete_form()` * :meth:`~wuttaweb.views.master.MasterView.save_delete_form()` * :meth:`~wuttaweb.views.master.MasterView.redirect_after_delete()` Related Features ---------------- Beyond the basic CRUD, some master views may elect to use certain related features provided by the base class. As a rule these are opt-in only, not enabled by default. Autocomplete ~~~~~~~~~~~~ A master class can add "autocomplete" support easily enough. For instance if you have a master ``SprocketView`` for sprockets, but somewhere else in the app you want an autocomplete field to select from all sprockets. Subclass (e.g. ``SprocketView``) should set/override the following: * :attr:`~wuttaweb.views.master.MasterView.has_autocomplete` * :meth:`~wuttaweb.views.master.MasterView.autocomplete_data()` * :meth:`~wuttaweb.views.master.MasterView.autocomplete_normalize()` This will expose a route like ``sprockets.autocomplete`` with URL like ``/sprockets/autocomplete``. The URL should be given a ``term`` param via query string: ``/sprockets/autocomplete?term=foo`` Bulk Delete ~~~~~~~~~~~ If :attr:`~wuttaweb.views.master.MasterView.deletable_bulk` is true, then the :meth:`~wuttaweb.views.master.MasterView.delete_bulk()` route is configured. The bulk-delete view is somewhat unique in that it does not immediately perform the bulk delete opertation, before returning the response. Rather, it starts a separate thread for the bulk deletion, and will show a progress bar/page to the user until that completes. Under the hood though, and the reason for this "threading out" approach, the :meth:`~wuttaweb.views.master.MasterView.delete_instance()` method is still called for each record being deleted. This ensures all the "normal" logic happens, as opposed to a literall ``DELETE FROM ...`` SQL statement which while more efficient, may skip the normal logic. (Of course you can override whatever, as needed.) Download ~~~~~~~~ Some model records may have files associated with them, which user can download. Such files are stored on disk, normally within the ``data`` folder under the :term:`app dir`. How those files get to be there, is another story. Here we're just pointing out that once they exist, a master view class has some tools to expose them for download. Subclass should set/override the following: * :attr:`~wuttaweb.views.master.MasterView.downloadable` * :meth:`~wuttaweb.views.master.MasterView.download_path()` This adds e.g. ``sprockets.download`` route with URL like ``/sprockets/{uuid}/download``. The base class provides the actual download logic; subclass just has to provide the file path on disk. This feature assumes a given record may have multiple files associated with it, but that each file will have a unique name. (E.g. if all are stored in the same folder on disk, that will be true.) To download a particulr file then, just pass the ``filaname`` param via query string. If none is provided then it's up to your ``download_path()`` method, what to do. Could return a "default" file path if that makes sense; otherwise ``raise self.notfound()``. Execute ~~~~~~~ Occasionally the concept of "executing" a data model record makes sense to implement. The master view has features around that too. There are 2 main examples of this which come with the framework: batches and upgrades. A batch is essentially a temporary table where certain data is collected, reviewed, and then "applied" to the live system(s). An upgrade record (usually) represents an actual occurrence of the app upgrade procedure. To execute one means to run an upgrade script and capture its output, to be later viewable as part of the record. That should give some idea, probably if you *need* support for the "execute" feature you will know it. But it's somewhat rare. If needed, subclass should set/override the following: * :attr:`~wuttaweb.views.master.MasterView.executable` * :meth:`~wuttaweb.views.master.MasterView.execute_instance()` Merge ~~~~~ A relatively simple "merge 2 records" feature is semi-supported: The base class provides some logic to preview the merge and let user decide which record is kept and which is deleted in the end (if it matters, depending on context). However when it comes to actually performing the data merge, the default logic simply deletes one of the records and does not truly "merge" anything. So the subclass must implement some or all of these, to fully support a merge feature: **Attributes** * :attr:`~wuttaweb.views.master.MasterView.mergeable` * :attr:`~wuttaweb.views.master.MasterView.merge_simple_fields` * :attr:`~wuttaweb.views.master.MasterView.merge_additive_fields` * :attr:`~wuttaweb.views.master.MasterView.merge_coalesce_fields` **Methods** * :meth:`~wuttaweb.views.master.MasterView.merge_get_all_fields()` * :meth:`~wuttaweb.views.master.MasterView.merge_get_data()` * :meth:`~wuttaweb.views.master.MasterView.merge_get_final_data()` * :meth:`~wuttaweb.views.master.MasterView.merge_why_not()` * :meth:`~wuttaweb.views.master.MasterView.merge_execute()` Revision History ~~~~~~~~~~~~~~~~ If the :doc:`Wutta-Continuum ` package is installed and configured, some master views may expose the revision history for each record. This requires a mapped :term:`data model` from the :term:`app database`. In general, this feature is meant to "just work" as long as the app is configured with versioning enabled. Each master view will know from its data model, whether versioning is relevant. If so some extra views will be configured to expose the history. This feature is "pretty nifty" but also has its quirks. It can be difficult to (efficiently) show all change history for a parent record, when one of its child records is changed for instance. If you do wish to include changes for a secondary table, when showing the history for a primary table record, there is one trick available. You can override :meth:`~wuttaweb.views.master.MasterView.get_version_joins()` to make it "join" the secondary table when looking for changes.