============================ Customize the Master Views ============================ This quickly becomes a sprawling topic; there are a myriad of ways one might customize a master view class. Unfortunately there is no "wizardry" to guide you through any of this, you are armed only with the documentation and source code. For this tutorial we will limit to just a few relatively simple changes. It's a simple app after all, not much else needs to be spiced up if you're just trying to capture/track some data. Adding hyperlinks for grid column cells --------------------------------------- When you browse the ``/vehicles/`` URL (and assuming you already created at least one vehicle), you will notice the Action hyperlinks on the right, but all of the data columns are just plain text. The grid class considers the View action URL to be "canonical" for a row, and it's often helpful to make one or more of the data columns render with a hyperlink using the View URL. This is straightforward using :meth:`~wuttaweb:wuttaweb.grids.base.Grid.set_link()` - for instance we'll do that for the ``VehicleView`` like so:: from wuttaweb.views import MasterView from poser.db.model import Vehicle class VehicleView(MasterView): model_class = Vehicle # .... def configure_grid(self, grid): super().configure_grid(grid) # wrap these column values with view hyperlinks grid.set_link("name") #grid.set_link("make") #grid.set_link("model") And similarly we'll go ahead and do this for the ``MileageEntryView``:: from wuttaweb.views import MasterView from poser.db.model import MileageEntry class MileageEntryView(MasterView): model_class = MileageEntry # .... def configure_grid(self, grid): super().configure_grid(grid) # wrap these column values with view hyperlinks grid.set_link("date") grid.set_link("vehicle") Note that we added the hyperlink logic for the ``vehicle`` column. This may be confusing at first, since when you're looking at a vehicle name in the grid, and it's a hyperlink, you might expect that to go to the vehicle's page. But no, these hyperlinks always/only use the View action URL, whatever that happens to be for each row (mileage entry in this case). It's of course possible to make a cell render with hyperlink which points somewhere else, but that requires more/different custom logic. Fixing a foreign key reference form field ----------------------------------------- Not sure what else to call this "customization" but currently it is part of the game. In our example it's the Vehicle reference field when we need to create or edit a Mileage Entry record. The default form logic is not yet smart enough to handle such fields well. Indeed the behavior is effectively broken; hence we must "fix" it. And really the fix here gives us a way to introduce more customization points, so there's some silver lining. The first thing we must do is create a new "schema node type" class representing the "object reference" in question. In our case we need to reference a vehicle, so our node type will be based around that. Create/edit the module at ``poser/web/forms/schema.py`` to define the type class (you may need to make the ``forms`` dir and create empty ``__init__.py`` for it as well):: from wuttaweb.forms.schema import ObjectRef from poser.db.model import Vehicle class VehicleRef(ObjectRef): model_class = Vehicle def sort_query(self, query): # nb. could also do: query.order_by(Vehicle.make, Vehicle.model) return query.order_by(Vehicle.name) def get_object_url(self, vehicle): return self.request.route_url("vehicles.view", uuid=vehicle.uuid) As you can see this will control the ordering of vehicle records, e.g. for use in a dropdown, and also specifies how to generate a URL to view a given vehicle. See :class:`~wuttaweb:wuttaweb.forms.schema.ObjectRef` for more about the base schema type. With that in place we can go back to edit the ``MileageEntryView`` class, and tell it to use the custom type via :meth:`~wuttaweb:wuttaweb.forms.base.Form.set_node()`. This time we must configure the *form* instead of the grid:: from poser.web.forms.schema import VehicleRef class MileageEntryView(MasterView): # .... def configure_form(self, form): super().configure_form(form) # use custom node type for vehicle reference form.set_node("vehicle", VehicleRef(self.request)) Now when you create or edit a mileage entry, things should look much better! As a sanity check you can try to submit an *empty* form and it should not actually create the mileage entry, but give you some warnings instead. You should also be able to create a new record okay as long as you give it the required values. Setting default column sort for a grid -------------------------------------- Now if you add very many mileage entry records you will at some point notice that the grid is sortable, and then you may notice that it is *not* sorted by default. (For instance, after you first login to the app.) That's an easy fix via :attr:`~wuttaweb:wuttaweb.views.master.MasterView.sort_defaults`, e.g. like this:: class MileageEntryView(MasterView): sort_defaults = ("date", "desc") It's generally a good idea to set default sort for all master views, so here's the other one:: class VehicleView(MasterView): sort_defaults = "name" Note that you only have to specify column name unless you want a descending sort, as in the first example. Also note that while in fact the default template theme supports *multi-column sorting* there is one caveat, you can't specify a multi-column sort as the default. Single column only for the default sort. Setting custom grid cell rendering ---------------------------------- Quite often you may need to tweak how grid cell values are rendered. This is handled on a per-column basis of course. Here we'll do a couple of pretty basic things for the mileage entry grid, declaring one column should be rendered as "currency" (dollars) and another as a "quantity" (which adds thousand-separator commas):: class MileageEntryView(MasterView): def configure_grid(self, grid): super().configure_grid(grid) # .... # show payment with dollar sign and 2 decimals grid.set_renderer("fillup_payment", "currency") # add thousands separator for odometer grid.set_renderer("odometer", "quantity") The :meth:`~wuttaweb:wuttaweb.grids.base.Grid.set_renderer()` method has a few built-in renderers such as those shown here, but you can also give it a callable which lets you render any way you like. Note that how a grid "renders" a column's values does not change their *sorting* behavior, since that is handled on the server side within the SQL database. (At least for typical scenarios.) Unfortunately the grid rendering logic is *only* used by the grid, and will not affect the mileage entry *form* at all. So we make similar changes to it next. Setting custom form field types ------------------------------- While we can just give the grid a "renderer callable" and thereby make column values appear however we like, it's not that simple for form fields. Each field is represented by multiple objects under the hood, which handle the data validation but also the readonly display. The latter can be customized in more than one way, but ultimately the field "widget" will do the rendering. (See also :doc:`wuttaweb:narr/forms/index`.) So one option is to set the widget explicitly, but you can also set the "type" for the field, which in turn will usually determine the widget. We actually showed an example of this earlier, for the Vendor reference field using a custom node schema type. This time around we'll use :meth:`~wuttaweb:wuttaweb.forms.base.Form.set_node()` for the payment and odometer fields, for an effect similar to what we did for the grid above:: from wuttaweb.forms.schema import WuttaMoney, WuttaQuantity class MileageEntryView(MasterView): def configure_form(self, form): super().configure_form(form) # .... # use "money" type for better rendering form.set_node("fillup_payment", WuttaMoney(self.request)) # use "quantity" type for better rendering form.set_node("odometer", WuttaQuantity(self.request)) But while we're here there's another related tweak we should make. There is a special ``notes`` widget we can use for such fields. This will use a ``