Skip to content

Runtime Configuration

A runtime configuration can be edited via the Lime Admin page of the web client. Runtime configuration is described in greater detail here. Creating a runtime configuration is as easy as generating a config-module in your project. Use lime-project to scaffold it:

$ lime-project generate config-module

This command generates code in your project that makes it possible for lime-crm to automatically register and render your configuration module in the web client's Administrators Page. Browse to "Settings" in Lime Admin to see what it looks like!

The command yields the following code in limepkg-cool-package/limepkg_cool_package/config/__init__.py:

import lime_admin.plugins
from .schema import create_schema


class RuntimeConfig(lime_admin.plugins.AdminPlugin):
   """A class to represent the plugin in Lime Administration.

   An instance has access to a `lime_application.application.Application`
   object via `self.application`.

   """

   @property
   def name(self):
       """The name of the plugin

       Note:
           The name is used as the key when persisting the config and
           also as the key for the plugin itself.
       """
       return 'limepkg_cool_package'

   @property
   def title(self):
       """The title of the plugin
       """
       return 'Cool Package'

   @property
   def version(self):
       """The version of the config

       Note:
           Should be incremented if
           the config format has changed. The version (if not None)
           is appended to the name when persisting the config.
       """
       return None

   def get_config(self):
       """Function to retrieve a persisted config

       Note:
           If a config doesn't exist, then a default config should be
           persisted and then returned.

           If version has changed, then any existing config should be
           upgraded, persisted and then returned.

           There needs to be a really, really good reason for not returning
           a config at all.

       Returns:
           `dict`

       Raises:
           `lime_admin.plugins.NotFoundError` if the config doesn't exist.
       """
       try:
           return super().get_config()
       except lime_admin.plugins.NotFoundError:
           return {}

   def get_schema(self):
       """Function to retrieve a schema for the config.

       Returns:
           :class:`marshmallow.Schema`
       """
       return create_schema(self.application)

   def set_config(self, config):
       """Function to persist a config.

       Note:
           The config should be validated before it's persisted.

       Args:
           config (dict): The config to be persisted.

       Raises:
           `lime_admin.plugins.ValueError` if the config is invalid.
       """
       super().set_config(config=config)


def register_config():
"""Function that is called by host when it's registering plugins.

Returns:
    :class:`Plugin`
"""
return RuntimeConfig

Okay, that was a lot of code, but most of it is actually self explanatory. However, we will describe get_config and get_schema in separate sections below.

get_config

get_config will use lime_data to load config stored under the key limepkg_cool_package or limepkg_cool_package.<VERSION>. If no config exists, a NotFoundError is raised. You can provide a default configuration in the corresponding except block.

get_schema

In order to get a UI for your config, you need to provide a schema. You can add the logic for that in the generated schema.py file. We use the marshmallow framework, so please check their documentation for how to declare your own schema.

In addition to the generic marshmallow fields you can also use Lime specific fields:

  • lime-core:lime_type.fields.LimeTypeField
  • lime-core:lime_type.fields.LimePropertyField
  • lime-core:lime_filter.fields.StoredFilterField
  • lime-core:lime_authentication.fields.GroupField
  • lime-core:lime_authentication.fields.UserField

Here's an example for how to use those fields. You can go to <DOMAIN>/<APP-NAME>/webadmin/#/showcase-config to see the resulting form.

from lime_type.fields import LimeTypeField, LimePropertyField
from lime_filter.fields import StoredFilterField
from lime_authentication.fields import GroupField, UserField
from marshmallow import Schema, fields

class LimetypeFieldsSchema(Schema):
    class Meta:
        ordered = True

    limetype = LimeTypeField(application=self.application,
                             title='LimeTypeField',
                             description='Choose a limetype based '
                                         'on your application')
    lime_property = LimePropertyField(
        application=self.application,
        limetype_field='limetype',
        title='LimePropertyField',
        description='Choose properties from the limetype you '
                    'previously selected')

class LimeAuthenticationSchema(Schema):
    class Meta:
        ordered = True

    groups = GroupField(application=self.application,
                        title='GroupField',
                        description='Choose from all groups of your '
                                    'application')
    users = UserField(application=self.application,
                      title='UserField',
                      description='Choose from all users of your '
                                  'application')

class LimeFilterSchema(Schema):
    filter = StoredFilterField(
        application=self.application,
        title='StoredFilterField',
        description='Choose from all stored filters. You could also '
                    'depend the selection on a previously selected '
                    'limetype')

class ShowcaseSchema(Schema):
    class Meta:
        ordered = True

    lime_type = fields.Nested(title='Limetype fields',
                              nested=LimetypeFieldsSchema)
    lime_filter = fields.Nested(title='Filter fields',
                                nested=LimeFilterSchema)
    lime_authentication = fields.Nested(
        title='Authentication fields',
        nested=LimeAuthenticationSchema)

Migrations

If you want to change the schema of an already released solution or package, you will have to provide a migration, so that any saved and outdated config can be adapted to the new structure.

Let's assume your schema has been released like this:

from marshmallow import Schema, fields
class CoolPackageSchema(Schema):
   some_field = fields.Str(title='Cool field')

For some reason you want to update it to this:

from marshmallow import Schema, fields
class CoolPackageSubSchema(Schema):
   some_field = fields.Str(title='Cool field')

class CoolPackageSchema(Schema):
   group_field = fields.Nested(title='Cool category',
   nested=CoolPackageSubSchema)

That means for any saved config you want to migrate {'cool_field': 'some value'} to {'group_field': {'cool_field': 'some value'}}. Therefore, add a module called migrations.py to your config package:

import lime_admin.plugins


class Migration1(lime_admin.plugins.ConfigMigration):
    """ describe the schema changes
    """

    @property
    def version(self):
        return 1  # the new version, after running the upgrade

    def upgrade(self, config):
        return {'group_field': config}

    def downgrade(self, config):
        return config.get('group_field', {})

Afterwards, add the following method to your RuntimeConfig class and change the version from None to 1:

@property
def version(self):
    return 1
def get_migrations(self):
    """Get a list of migrations for the config.
      Returns: :class:`list`(:class:`lime_admin.plugins.Migration`)
    """
   return [Migration1()]

Now whenever get_config will find an older version than 1, it will run the migration and return the upgraded structure.

The next time you need a migration, you should write another implementation of lime_admin.plugins.ConfigMigration, add it to the array of migrations and increase the version to 2.