-
Notifications
You must be signed in to change notification settings - Fork 507
Developing your own plugin
The architecture of FIR is very modular and can be extended using plugins.
A plugin is a set of files in a folder, which are defined like this:
fir
├── fir_<yourplugin> # name of your plugin
│ ├── urls.py # URLs of your plugins. If defined, this file MUST contain two variables, "urlpatterns" and "app_name"
│ ├── hooks.py # Hooks file. Can contain code to enrich existing functionalities of FIR (eg, add a keyword that that be used in the search bar, etc...).
│ ├── views.py # The controller (and not the view, as its name would imply). Will handle response to HTTP requests
│ ├── models.py # Should contain "objects" of your plugin, the ones that will be stored in DB. Examples of "object" are todos, incidents, comments, etc...
│ ├── migrations
│ │ └── 0001.py # Django migrations related to changes in models.py. Can be generated using 'manage.py makemigrations'
│ ├── templates
│ │ └── fir_<yourplugin>
│ │ └── sometemplate.html # May contain some Jinja2 templates
│ ├── templatetags
│ │ └── <exemple-of-tag>.py # If you are using Jinja2 tags in your templates, the code of those tags should be placed here
│ ├── admin.py # Can contain code to customize Django Admin panel
│ ├── forms.py # Can contain code relative to forms.
│ ├── fixtures
│ │ └── 001-<yourplugin>.json # May contain initial data relative to your plugin. Can be generated using 'manage.py dumpdata'
│ ├── locale
│ │ └── <country-code>
│ │ └── LC_MESSAGES
│ │ └── django.po # Localization: language file of your plugin. Can be generated using 'django-admin makemessages -l <country-code>'
│ ├── static
│ │ └── fir_<yourplugin>
│ │ ├── vendor
│ │ │ └── <public library folder> # Location of library code: bootstrap, javascript libraries, etc...
│ │ └── <yourplugin>.js # Location of custom javascript, font or CSS files
│ ├── README.md # Readme describing what your plugin does and how to install it
│ └── requirements.txt # Python requirements of your library, if any.
In order to create your own plugin, you just need to create a folder named fir_<yourplugin>
in the root directory of FIR.
All files in a plugin are optional, you are free to create them or not depending on your plugin's purpose
One very important file in a plugin is urls.py
. This file MUST contain two variables:
-
app_name
, which should contain the name of your plugin:app_name = "fir_myplugin"
-
urlpatterns
, which should be a list of URLs that can be called. If your plugin doesn't have any HTTP endpoint, you can set this variable to an empty list (urlpatterns = []
)
I can also contain an optional third variable, api_urls = []
, which should be a list of your plugin API routes.
This file is optional, but quite important : if it is missing, then your plugin won't have any URL/API route.
During development of your plugin, you may want to add additional content into an existing HTML page of FIR.
This is exactly what plugin_points
are designed for!
Plugin points are a way for your plugin to embed additional content into an existing Jinja2 template of FIR.
For instance, if a plugin point named dashboard_tab
has been defined somewhere in any FIR template, then you can create a file at fir_<yourplugin>/template/fir_<yourplugin>/plugins/dashboard_tab.html
. This file will be included automatically at the location of the plugin point.
Here is how Plugin points are declared in FIR templates : {% plugin_point 'dashboard_tab' %}
. Plugin points have been defined in most existing HTML files of FIR, feel free to use them!
You may also want to enrich existing functionnalities of FIR when developping your plugin. For instance, you may want to:
- Add new fields to an incident object
- enrich default fields searched when an user type text in the search bar
- enrich the search bar with new keywords (
myfield:somevalue
) - provide new API endpoints
if you want to implement such things, you can create a file named hooks.py
in your plugin folder. This file must contain a variable named hooks
which should contains all hooks.
hooks = {
"incident_fields": [], # Can contain custom extra fields of an incident
"keyword_filter": {}, # Can contain keywords that can be used in the search bar ("field:value")
"search_filter": [], # Can contain default fields searched when an user type text in the search bar
}
Let's detail each hook
This hook can contain extra fields which are added to an incident. It can contain the following data:
hooks = {
"incident_fields": [
(
"name", # name of the new fields group (a group is attached to a django model, and can contain multiple fields)
djangoform, # Django ModelForm. Need to be defined if you want the field to available in templates (an therefore editable by the user in GUI).
apiserializer, # API ModelSerializer. Need to be defined if you want the field to be displayed/available in API
{"field1": filterField1, "field2": filterField2}, # list of API filters. Need to be defined if you want to select specific incidents in API via GET parameters
),
(
"name2", # name of another fields group
None, # If you don't need the field to be available in templates or in API, you can enter None instead of defining an unused ModelForm/ModelSerializer/Filter
apiserializer2,
{"field3": filterField1, "field4": filterField4},
),
....
],
}
You can use fir_nuggets to see an example.
You can use fir_nuggets to see an example.
Let's say we want to add a new field to incidents: a checkbox "Was reviewed by audit team".
We can create a custom, private plugin for this purpose
First, let's create our plugin
(in fir root directory)
mkdir fir_customfields
cd fir_customfields
Then, let's define our model: Create a file models.py
with the following content:
from django.db import models
from incidents.models import Incident
class CustomFields(models.Model):
incident = models.OneToOneField(Incident, on_delete=models.CASCADE, null=True)
was_reviewed_by_audit_team = models.BooleanField(default=False)
We now need to create:
- Django forms to be able to use this field in the GUI
- API Serializer & filters to be able to view the fields in the API
First, let's focus on the form. Create a file forms.py
with the following content:
from django.forms import ModelForm
from django import forms
from fir_customfields.models import CustomFields
class CustomFieldsForm(ModelForm):
"""
Django form corresponding to the new fields
A django form is required if you want the user to be able to set this field
when creating/editing an indicent
"""
was_reviewed_by_audit_team = forms.BooleanField(required=False)
class Meta:
model = CustomFields
fields = [
"was_reviewed_by_audit_team",
]
Since we are creating a form, let's also create the jinja2 templates which will use it
mkdir -p templates/fir_customfields/plugins/
We will use the plugin_points details_properties
and new_event_additional_fields_row1
in this example.
Therefore, create templates/fir_customfields/plugins/new_event_additional_fields_row1.html
with the following content
<div class="col-sm-2 checkbox aligned-with-stack{% if form.customfields.was_reviewed_by_audit_team.errors %} has-error{%endif%}">
<label>{{ form.customfields.was_reviewed_by_audit_team }} Was reviewed by the Audit Team</label>
{% include 'events/_form_errors.html' with errors=form.customfields.was_reviewed_by_audit_team.errors %}
</div>
and create templates/fir_customfields/plugins/details_properties.html
with the following content:
<td class="ps-3 pe-3 delim border-start border-secondary-subtle bg-secondary-subtle border-bottom-0">Reviewed By Audit Team</td><td class="ps-3 pe-3 border-secondary-subtle bg-secondary-subtle border-bottom-0">{{event.customfields.was_reviewed_by_audit_team }}</td>
Then, let's focus on the API. Create a file api.py
with the following content:
from rest_framework import serializers
import django_filters.rest_framework as filters
from fir_customfields.models import CustomFields
class CustomFieldsSerializer(serializers.ModelSerializer):
"""
API Serializer corresponding to the new fields
A serializer is in charge of converting JSON data
from API HTTP requests/responses to Django objects, and vice versa
"""
was_reviewed_by_audit_team = serializers.BooleanField(
default=False, read_only=False
)
class Meta:
model = CustomFields
fields = [
"was_reviewed_by_audit_team",
]
Finally, we need to create hooks.py
with the following content:
import django_filters.rest_framework as filters
from fir_customfields.models import CustomFields
from fir_customfields.api import CustomFieldsSerializer
from fir_customfields.forms import CustomFieldsForm
hooks = {
"incident_fields": [
(
"customfields", # name of the fields group in "incident" object
# if one incident can have multiple instance of your object (OneToMany/ManyToMany
# relationship), this name need to end with '_set'
CustomFieldsForm, # Django Form
CustomFieldsSerializer(
many=False, read_only=False, required=False # API Serializer
),
{ # API filters corresponding to the new fields
# Filters are GET parameters used to select specific incidents
# eg: '/api/incidents?was_reviewed_by_audit_team=True'
"was_reviewed_by_audit_team": filters.BooleanFilter(
field_name="customfields__was_reviewed_by_audit_team"
),
},
),
]
}
We now have the base for our module! The last thing we need is to
Our module is now ready. We now need to enable the module, and create the module tables in database (django migrations) :
(in fir root directory)
echo "fir_customfields" >> fir/config/installed_apps.txt
python manage.py makemigrations fir_customfields
python manage.py migrate fir_customfields
That's it! You now have have created a new field