caluma-interval
Caluma companion app for periodic usage of forms.
Periodic usage of forms
There are forms you might want to use periodically. This project adds support for this
by utilizing the meta
-field of forms.
Example use case
Your flux capacitor needs new plutonium once every two weeks. So you have a form
refill-plutonium-in-flux-capacitor
you want to use every two weeks.
This can be achieved with following interval inside the meta field of this form:
{
"interval": {
"interval": "P2W",
"workflow_slug": "delorean-workflow"
}
}
This will make sure, that exactly two weeks after the last refill, a new case will be opened for this form.
Now let's say, it's important to you, that this case is always opened on a monday (shortening the interval if needed). Further you want to set a start date, to make sure no case will be opened before that.
This can be achieved with following interval:
{
"interval": {
"interval": "2019-03-18/P2W",
"weekday": 0,
"workflow_slug": "delorean-workflow"
}
}
Features
- Handle periodic usage of forms
- Optionally set a start date
- Optionally force a specific weekday
- Will never start multiple cases for the same form
Meta field
The meta
-field is a JSONField and can be found on a variety of Caluma-objects. We're
only interested in forms though.
Interval definition
caluma-interval checks the meta
-field of forms for an interval
-key.
Example:
{
"interval": {
"interval": "2018-03-01/P1Y2M10D",
"weekday": 1,
"workflow_slug": "my-test-workflow"
}
}
Fields
interval
ISO8601 time interval notation.
We use {{startdate}}/{{duration}}
, where startdate is optional. If omitted, the
case will be opened immediately.
For duration we use a subset of ISO8601 duration without time.
You can find the regex we use below in ValidationClass.
weekday
An optional weekday (zero-indexed integer). This makes sure, that only on this weekday a case for this form will be started.
This will never exceed the configured interval.
workflow_slug
The slug for the corresponding workflow.
Configuration
You can configure caluma_interval with environment variables or CLI arguments, whereas CLI arguments take precedence.
usage: __main__.py [-h] [-c STRING] [-i STRING] [-s STRING] [-u STRING] [-d]
[-v]
Caluma companion app for periodic usage of forms
optional arguments:
-h, --help show this help message and exit
-c STRING, --caluma-endpoint STRING
defaults to "http://caluma:8000/graphql"
-i STRING, --oidc-client-id STRING
-s STRING, --oidc-client-secret STRING
-u STRING, --oidc-token-uri STRING
-d, --debug print debug messages
-v, --version show program's version number and exit
The corresponding environment variables are:
- CALUMA_ENDPOINT - defaults to
http://caluma:8000/graphql
- OIDC_CLIENT_ID
- OIDC_CLIENT_SECRET
- OIDC_TOKEN_URI
Additional environment variables for when using the docker image:
- CALUMA_HOST - defaults to
caluma
- CALUMA_PORT - defaults to
8000
To enable oidc authentication over plain http (only for testing!), set
OAUTHLIB_INSECURE_TRANSPORT
to 1
.
Authentication
As Caluma uses OpenID Connect (OIDC), we need to fetch a token from the OIDC provider.
For this you have to provide a client id, client secret and a token uri.
If none of them are provided, the client will make unauthenticated requests, which (hopefully) will fail in your prod setup.
ValidationClass
You may want to add a custom ValidationClass to Caluma in order to validate the content
of meta['interval']
:
import re
from datetime import datetime
from caluma.core.validations import BaseValidation, validation_for
from caluma.form.schema import SaveForm
from caluma.workflow.models import Workflow
from rest_framework import exceptions
# Regex to parse ISO8601 periods WITHOUT time information
ISO8601_PERIOD_DATE_REGEX = re.compile(
r"^P(?!$)"
r"(\d+(?:[,\.]\d+)?Y)?"
r"(\d+(?:[,\.]\d+)?M)?"
r"(\d+(?:[,\.]\d+)?W)?"
r"(\d+(?:[,\.]\d+)?D)?$"
)
def validate_interval(interval):
interval_list = interval["interval"].split("/")
if len(interval_list) == 2:
try:
datetime.strptime(interval_list[0], "%Y-%m-%d")
except ValueError:
raise exceptions.ValidationException("Failed to parse startdate!")
elif len(interval_list) > 2:
raise exceptions.ValidationException("Failed to parse interval!")
if not ISO8601_PERIOD_DATE_REGEX.match(interval_list[-1]):
raise exceptions.ValidationException("Failed to parse period!")
class FormIntervalValidation(BaseValidation):
@validation_for(SaveForm)
def validate_save_form(self, mutation, data, info):
if "meta" not in data and "interval" not in data["meta"]:
return data
interval = data["meta"]["interval"]
if "interval" not in interval:
raise exceptions.ValidationException("Interval must be set!")
if "workflow_slug" not in interval:
raise exceptions.ValidationException("workflow_slug must be set!")
try:
Workflow.obj.get(slug=interval["workflow_slug"])
except Workflow.DoesNotExist:
raise exceptions.ValidationException(
"Failed to get workflow with provided workflow_slug!"
)
if "weekday" in interval:
if not interval["weekday"] >= 0 <= 6:
raise exceptions.ValidationException(
"Weekday must be an integer from 0 to 6!"
)
validate_interval(interval['interval'])
docker-compose integration
interval:
image: ghcr.io/projectcaluma/caluma-interval:latest
build:
context: .
depends_on:
- caluma
Contribution
We use following tools in order to standardize development and releases:
- flake8
- black
- isort
- commitlint
pre-commit hooks
Pre commit hooks is an additional option instead of executing checks in your editor of choice.
First create a virtualenv with the tool of your choice before running below commands:
pip install pre-commit
pip install -r requirements-dev.txt -U
pre-commit install
pre-commit install --hook=pre-commit
pre-commit install --hook=commit-msg