Rattail Extensions ================== Rattail "extensions" exist primarily to allow for custom data to be stored and accessed. An extension may extend the Rattail framework in one or more of the following ways: * adding new tables * adding new enumerations * adding new mapped classes * adding new properties to existing mapped classes * adding new permission definitions Some extensions will add significantly to Rattail (e.g. the ``orders`` extension, which adds enumerations, classes and permissions), while others may add as little as a single property to a core class (e.g. for the sake of object tracking with `ETL `_). Available Extensions -------------------- Here is a list of the extensions which ship with Rattail: .. toctree:: :maxdepth: 1 members orders purchasing timeclock Creating an Extension --------------------- It is very possible that you will need to create an extension of your own. For example, many ETL configurations will need a custom key field (per ETLed class) to track objects between jobs. There are basically two things you need to do in order to *create* your extension: #. Subclass :class:`rattail.FrameworkExtension`. #. Advertise your extension with a ``setuptools`` entry point. See the :class:`rattail.FrameworkExtension` API documentation as well as the rest of the current document for more information regarding the first point. As for the second: Rattail extensions are advertised via the ``setuptools`` entry point group of ``rattail.extensions`` (see `setuptools' documentation `_ for more info). For example, an extension might be advertised with the following entry point definition:: setup( # bunch of stuff... entry_points = """ [rattail.extensions] orders = rattail.ext.orders:OrdersExtension """ ) In addition to *creating* your extension, there must be some application logic somewhere which takes advantage of the functionality it provides. In the typical ETL case for example, there will be at least one script (also yours) which references say, a custom key field provided by your extension. The extension must be activated within your Rattail database before it may be used in an application, however. This can be done with the command:: rattail ext activate Some applications (e.g. `Rattail BONE `_) are able to activate any extensions which they require as part of their own setup process, so it is not always necessary to activate extensions "manually." Adding Tables ------------- An extension may (and usually will) need to track extra data in order to add some additional functionality. Extensions are only allowed to *add new* tables to the schema though; they are not allowed to alter existing tables in any way. This means for instance that even if you just need to add a single attribute to the Product class (e.g. ``Product.smell``), your custom extension still must create a table (e.g. ``product_smells``) to store the values, instead of adding a column to the ``products`` table. An extension is not required to define any new tables, but if it wishes to then it must return a ``sqlalchemy.MetaData`` instance containing the table definitions whenever its :meth:`rattail.FrameworkExtension.get_metadata()` method is called. .. note:: Due to the way Rattail uses this metadata, it must be returned "freshly-created" any time the method is called; i.e. the extension must never return a previously-existing ``MetaData`` instance. When an extension is initially "activated", Rattail will create any of the tables defined by the extension before calling on it to extend the running framework. Similarly, when an extension is "deactivated", Rattail will first call on it to restore the running framework, and then will drop any tables the extension provides. Any extension which provides metadata must also provide a SQLAlchemy-Migrate repository for the sake of schema migrations specific to the extension. The Python module representing this repository must be referenced by the extension's :attr:`rattail.FrameworkExtension.schema` attribute. .. note:: Rattail itself can apply any migrations necessary for the extension; see the :func:`rattail.db.upgrade_schema()` function. Adding Enumerations ------------------- Sometimes class attributes will need to map to enumeration values specific to the domain (and possibly to the organization). This usually (always?) will have to do with workflows; for use in defining things such :attr:`Order.status` and :attr:`OrderEvent.type`. Currently such attributes are represented as ``Integer`` fields in the underlying schema. Each field should only be allowed to contain one of the predefined enumeration values relevant to it. These values are not defined in the database however, and must be defined directly in the Python code. .. .. note:: .. Some similar attributes, however, *are* defined in the database (e.g. .. :class:`rattail.CustomerContactType`). The :mod:`rattail.enum` module is where these enumeration values are held after :func:`rattail.init()` has been called. For an extension to get its definitions to show up there, all it needs to do is provide a :attr:`rattail.FrameworkExtension.enum` attribute which references its own module containing the definitions. Rattail will handle importing each variable found in that module to its own :mod:`rattail.enum` namespace, and after all active extensions have been applied, everything in ``rattail.enum`` will be imported to the :mod:`rattail` root namespace. Adding Classes -------------- An extension may also provide additional (SQLAlchemy-mapped) classes to be imported to the root :mod:`rattail` namespace. Such classes will then be available in the same way as those provided by the core. (See :doc:`rattail.classes` for more info on the core classes.) Classes should be added whenever the extension's :meth:`rattail.FrameworkExtension.extend_classes()` method is called. There is a convenience method (:meth:`rattail.FrameworkExtension.add_class()`) which accepts as its argument a class that you wish to add to Rattail, so a typical example might be:: def extend_classes(self): self.add_class(MyNewClass) .. note:: It is important for such classes to *not yet be mapped* when they're added here; the mappings should happen in :meth:`rattail.FrameworkExtension.extend_mappers()`. Altering Class Mappings ----------------------- Extensions have the option to alter any class mappings whenever their :meth:`rattail.FrameworkExtension.extend_mappers()` method is called. It is here that the extension should apply mappers to any extra classes which it provides during :meth:`rattail.FrameworkExtension.extend_classes()`. However, any of Rattail's core classes may have their mappers extended as well. A common pattern for an extension needing to do this might be: #. Define a table to provide extra fields for a core class. #. Define a class to join the core class to the custom fields. #. Alter the core class mapper to add the custom fields as properties. Adding Permission Definitions ----------------------------- Extensions may also supply additional permission definitions to Rattail. .. todo:: Finish the permission extension docs. Restoring the Framework ----------------------- Please note that while extending the framework is the primary function of extensions, it is just as important that they reverse their own changes when asked. Rattail is able to do most of this on its own, but when the extension's :meth:`rattail.FrameworkExtension.restore_classes()` method is called it must remove any extra classes it previously had added. Again, there is a convenience method available to help out (:meth:`rattail.FrameworkExtension.remove_class()`). A typical example would be:: def restore_classes(self): self.remove_class("MyNewClass") .. note:: While :meth:`rattail.FrameworkExtension.add_class()` expects a real class, :meth:`rattail.FrameworkExtension.remove_class()` expects a class *name*.