Configurable customizations¶
At some point you may need to provide a means of configuration for your package, such as api-keys, credentials, solution specific business configuration or run-time configuration for the customer.
Read more about the different types of configuration here
Using Lime Admin and runtime configuration
should be the default options, as this is the most user friendly configuration option
and come with most tools to validate and migrate configuration
Environment¶
You can set your package's default enviromental config in my-package/__init__.py
.
def default_config():
"""Returns the default values for configuration parameters for this package
The resulting configuration will be available under
`lime_config.config['plugins']['{{ cookiecutter.lib_name }}']`
The values here can be overridden in the service's config file.
"""
return {}
The default config will be loaded in case there is no configuration entry in the config.yml, and will also serve as "documentation" for your package's configuration requirements.
Code example:
import lime_config
my_config = lime_config.config['packages']['my-package']
Application Configuration¶
The API used for accessing application configuration lies in the lime_config
module. For usage documentation, see the lime-core documentation
Runtime¶
Configuration specific for your application (database) loaded during
runtime using lime_data
. Typically this configuration should be more
of a "preferences" / non business-critical nature and can be
edited by a user through the web client's Admin Pages. With
lime-project
you can generate the code needed to create a
runtime configuration. Runtime configuration will show up in the
administrator interface in the Web Client.
💡 Ask yourself the question: 'would the customer be OK with losing this data?'. If the answer is No then you should probably move it to the solution configuration instead.
Information
Note that this type of configuration is stored in the database and thus would be restored from a backup. Having for example an URL to an external system might create problems, restoring a staging environment from a production database will make the staging environment connected to the same external system as the production system
Working with lime-data
¶
Code example:
import lime_data
my_prefs = {
'background': 'lime',
'theme': 'mojito'
}
# set data
lime_data.set_data(application, 'prefs', my_prefs, overwrite=True)
# get data
try:
my_prefs = lime_data.get_data(application, 'prefs')
except NotFoundError:
logger.error('No prefs found for application')
Warning
Do not store business-critical or sensitive data such as API keys, urls or credentials here.
Editing configuration in Lime Admin¶
In Lime CRM, configuration is key. It is therefor possible to create a UI to edit your configuration in Lime Admin.
A runtime config can be edited via the Lime Admin page of the web client. To achieve this, you need to create a config module by running the following in the root of your add-on:
$ lime-project generate config-module
This 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
This enables the admin pages to pick up your config and add it to the
Addons Configuration
category. Let's go through the methods that need
some more adaptation.
get_config¶
get_config
will search (via lime_data) for your config with the key
limepkg_cool_package
or limepkg_cool_package.<VERSION>
if version
isn't None
. If the key doesn't exist in the database (because e.g.
this config gets edited for the first time), it raises a
NotFoundError
, so you can provide a default config in the
corresponding except
block.
If get_config
can't find the latest version but an outdated version
instead, it will run all necessary migrations and then return the
migrated version.
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 add-on, 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
.