Caluma companion app for 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.
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"
}
}
- Handle periodic usage of forms
- Optionally set a start date
- Optionally force a specific weekday
- Will never start multiple cases for the same form
The meta
-field is a JSONField and can be found on a variety of Caluma-objects. We're
only interested in forms though.
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"
}
}
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.
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.
The slug for the corresponding workflow.
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
.
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.
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'])
interval:
image: ghcr.io/projectcaluma/caluma-interval:latest
build:
context: .
depends_on:
- caluma
We use following tools in order to standardize development and releases:
- flake8
- black
- isort
- commitlint
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