Welcome to Trionyx’s documentation!¶
Introduction¶
Trionyx is a Django web stack/framework for creating business applications. It’s focus is for small company’s that want to use business application instead of Excel and Google doc sheets.
With Trionyx the developer/business can focus on there domain models, business rules and processes and Trionyx will take care of the interface.

Installation¶
To install Trionyx, run:
pip install Trionyx
Create new project¶
For creating a new project, run:
trionyx create_project <project name>
Follow the steps given. And by the end you have a running base project ready to extend.
Getting started¶
Django already gives a solid foundation for building apps. Trionyx add some improvements like auto loading signals/forms/cron’s. It also add new things as default views/api/background tasks (celery).
In this getting started guide we will create a new app, to show you a little of the basics on how Trionyx work and what it can do.
This guide assumes you have followed the installation instructions, and you are now in the root of your new project with the virtual environment active
This guide requires no previous knowledge of Django, but it wont go in depth on how Django works
Your first app¶
First we need to create a new app. Default Trionyx structure all apps go in the apps folder. To create a base app use the following manage command:
./manage.py create_app knowledgebase
In your apps folder should be your new app knowledgebase. For using the app we need to add it the INSTALLED_APPS:
# config/settings/base.py
# ...
INSTALLED_APPS += [
'apps.knowledgebase',
]
# ...
File structure¶
A Trionyx file structure looks as follows. The ones marked as bold, are the ones you typically use in a Trionyx app. The others are used with a Django app and can be used if you need more customization.
- <app name>
- migrations: DB migrations, these are auto generated by Django
- static: folder with your static files (js/css/images/icons)
- templates: HTML template files
- apps.py: Contains the app and model configuration
- cron.py: Cron configuration
- forms.py: Create and register your forms.
- layouts.py: Create your layouts
- models.py: Define your models
- tasks.py: Create your background tasks
- urls.y: Define your custom urls
- views.py: Create your custom views
Create model¶
We need a model to store our articles, this is just a simple Django model. Only difference is that we extend from BaseModel that add some extra Trionyx fields and functions.
# apps/knowledgebase/models.py
from trionyx import models
from django.contrib.contenttypes import fields
class Article(models.BaseModel):
title = models.CharField(max_length=255)
content = models.TextField()
# Generic relation so that different model types can be linked
# More info: https://docs.djangoproject.com/en/2.2/ref/contrib/contenttypes/#generic-relations
linked_object_type = models.ForeignKey(
'contenttypes.ContentType',
models.SET_NULL,
blank=True,
null=True,
)
linked_object_id = models.BigIntegerField(blank=True, null=True)
linked_object = fields.GenericForeignKey('linked_object_type', 'linked_object_id')
After you created the model you need to make a migration (tells the database what to do). And then run the migration to create the database table.
./manage.py makemigrations ./manage.py migrate
If you run your project with make run and you login on it. You will see the menu has a Knowledgebase -> Article entry. You can create/view/edit articles but default list view is only id, and form is not user friendly.
Custom Form¶
Lets update the create and edit form to only show the title and content. And improve the content form field by using a wysiwyg editor.
# apps/knowledgebase/forms.py
from trionyx import forms
from .models import Article
@forms.register(default_create=True, default_edit=True)
class ArticleForm(forms.ModelForm):
content = forms.Wysiwyg()
# We are going to use this later
linked_object_type = forms.ModelChoiceField(ContentType.objects.all(), required=False, widget=forms.HiddenInput())
linked_object_id = forms.IntegerField(required=False, widget=forms.HiddenInput())
class Meta:
model = Article
fields = ['title', 'content']
If you refresh your page you should see an improved create form. When you created an article it is rendered with a simple default layout, we are going to change that later. First do some configuration so that there is a better verbose name, one menu item and a better default list view.
Model configuration¶
You can configure your model in the apps.py, lets change some for Article:
# apps/knowledgebase/apps.py
from trionyx.trionyx.apps import BaseConfig, ModelConfig
class Config(BaseConfig):
"""Knowledgebase configuration"""
name = 'apps.knowledgebase'
verbose_name = 'Knowledgebase'
class Article(ModelConfig):
# Improve default list view for users
list_default_fields = ['created_at', 'created_by', 'title']
# Set a clear verbose name instead of 'Article(1)'
verbose_name = '{title}'
# Move menu item to root and set a nice icon
menu_root = True
menu_icon = 'fa fa-book'
If you take a look now at the list view it looks much more informative. And the extra submenu is also replaced by only one menu item with a nice icon.
Custom Layout¶
Lets update the layout to remove some unnecessary fields and add some new ones.
Layouts are build with components, so you dont need to write HTML.
If you need something custom and dont want to build everything in HTML.
There is the trionyx.layout.HtmlTemplate
component that renders a given django template and context.
# apps/knowledgebase/layouts.py
from trionyx.views import tabs
from trionyx.layout import Column12, Panel, TableDescription
#register a new tab default this will be `general`
@tabs.register('knowledgebase.article')
def article_layout(obj):
return Column12(
Panel(
obj.title, # For panel the first argument is the title,
# all other arguments are components
TableDescription(
'created_by',
'created_at',
'updated_at',
'content',
)
)
)
This looks nice for your model, lets use that generic field that we created on the model and form. As you can see with the tab register you can create a tab for every model you want. We are going to at a knowledgebase tab to the admin -> users:
# apps/knowledgebase/layouts.py
from trionyx.views import tabs
from trionyx.layout import Column12, Panel, TableDescription, Button, Component, Html
from django.contrib.contenttypes.models import ContentType
from trionyx.urls import model_url
from .models import Article
# ...
@tabs.register('trionyx.user', code='knowledgebase')
def user_layout(obj):
content_type = ContentType.objects.get_for_model(obj)
return Column12(
# Render a create button
Button(
'create article',
url=model_url(Article, 'dialog-create', params={
'linked_object_type': content_type.id,
'linked_object_id': obj.id,
}),
dialog=True,
dialog_reload_tab='knowledgebase',
css_class='btn btn-flat bg-theme btn-block'
),
# Render every article in a new Panel
*[
Panel(
art.title,
TableDescription(
'created_by',
'created_at',
'updated_at',
# Components that accept fields can do so in different formats
# Default is string of field name and it will get the label and value
{
'label': 'Content',
'value': Component(
Html(art.content),
Button(
'Edit',
url=model_url(art, 'dialog-edit'),
dialog=True,
dialog_reload_tab='knowledgebase',
css_class='btn btn-flat bg-theme btn-block'
),
)
},
object=art,
)
) for art in Article.objects.filter(
linked_object_type=content_type,
linked_object_id=obj.id,
)
]
)
If you reload the page on a user you will see a new tab knowledgebase. Articles that you create here are shown in the tab.
Signals¶
Django uses signals to allow you to get notifications on certain events from other apps. In this example we are going to use signals on our own Model to send an email to all users when a new article is created.
# apps/knowledgebase/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from trionyx.trionyx.models import User
from .models import Article
@receiver(post_save, sender=Article)
def notify_users(sender, instance, created=False, **kwargs):
if created:
for user in User.objects.all():
user.send_email(
subject=f"New Article: {instance.title}",
body=instance.content,
)
You can find more information about signals here, Only thing that Trionyx does is auto import signals.py from all apps.
Background Task¶
Trionyx uses celery for background tasks, it comes preconfigured with 3 queue’s. For more information go here. For our app we are going to use a schedulad background task to send a summary every week.
# apps/knowledgebase/tasks.py
from trionyx.tasks import shared_task
from django.utils import timezone
from trionyx.trionyx.models import User
from .models import Article
@shared_task()
def email_summary():
count = Article.objects.filter(created_at__gt=timezone.now() - timezone.timedelta(days=7)).count()
for user in User.objects.all():
user.send_email(
subject=f"There are {count} new articles",
body=f"There are {count} new articles",
)
To make this task run every week we need to add it to the cron. You can do this from inside your app by creating a cron.py.
# apps/knowledgebase/cron.py
from celery.schedules import crontab
schedule = {
'article-summary-every-sunday': {
'task': 'apps.knowledgebase.tasks.email_summary',
'schedule': crontab(minute=0, hour=0, day_of_week=0)
},
}
Now email_summary will be run every sunday. For more information on scheduling go here
API¶
If you go to http://localhost:8000/api/ you can see that Trionyx automatically created an API entry point. Trionyx make use of the Django REST framework you can easily create your own endpoint or change the serializer user by the generated end point.
# apps/knowledgebase/serializers.py or apps/knowledgebase/api/serializers.py
from trionyx.api import serializers
from trionyx.trionyx.models import User
from .models import Article
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'email', 'first_name', 'last_name']
@serializers.register
class UserSerializer(serializers.ModelSerializer):
created_by = UserSerializer()
class Meta:
model = Article
fields = ['created_by', 'title', 'content']
I hope you have a better understanding on how to use Trionyx. And that it can help you build you business application with the focus on your data and processes.
Settings¶
All Trionyx base settings
-
trionyx.settings.
gettext_noop
(s)[source]¶ Return same string, Dummy function to find translatable strings with makemessages
-
trionyx.settings.
get_env_var
(setting: str, default: Optional[Any] = None, configs: Dict[str, Any] = {'DEBUG': True, 'SECRET_KEY': 'Not a secret'}) → Any[source]¶ Get environment variable from the environment json file
Default environment file is environment.json in the root of project, Other file path can be set with the TRIONYX_CONFIG environment variable
-
trionyx.settings.
get_watson_search_config
(language)[source]¶ Get watson language config, default to pg_catalog.english for not supported language
List of supported languages can be found on https://github.com/etianen/django-watson/wiki/Language-support#language-support
-
trionyx.settings.
LOGIN_EXEMPT_URLS
= ['^static', '^(api|openapi)', '^basic-auth']¶ A list of urls that dont require a login
-
trionyx.settings.
TX_APP_NAME
= 'Trionyx'¶ Full application name
-
trionyx.settings.
TX_LOGO_NAME_START
= 'Tri'¶ The first characters of the name that are bold
-
trionyx.settings.
TX_LOGO_NAME_END
= 'onyx'¶ The rest of the characters
-
trionyx.settings.
TX_LOGO_NAME_SMALL_START
= 'T'¶ The first character or characters of the small logo that is bold
-
trionyx.settings.
TX_LOGO_NAME_SMALL_END
= 'X'¶ The last character or characters of the small logo that is normal
-
trionyx.settings.
TX_THEME_COLOR
= 'purple'¶ The theme skin color (header). Aviable colors: blue, yellow, green, purple, red, black. All colors have a light version blue-light
-
trionyx.settings.
TX_COMPANY_NAME
= 'Trionyx'¶ Company name
-
trionyx.settings.
TX_COMPANY_ADDRESS_LINES
= []¶ Company address lines
-
trionyx.settings.
TX_COMPANY_TELEPHONE
= ''¶ Company telephone number
-
trionyx.settings.
TX_COMPANY_WEBSITE
= ''¶ Company website address
-
trionyx.settings.
TX_COMPANY_EMAIL
= ''¶ Company email address
-
trionyx.settings.
TX_DISABLE_AUDITLOG
= False¶ Disable auditlog
-
trionyx.settings.
TX_DISABLE_API
= False¶ Diable API
-
trionyx.settings.
TX_MODEL_OVERWRITES
= {}¶ Config to overwrite models, its a dict where the key is the original app_label.model_name and value is the new one.
TX_MODEL_OVERWRITES = { 'trionyx.User': 'local.User', }
-
trionyx.settings.
TX_MODEL_CONFIGS
= {}¶ Dict with configs for non Trionyx model, example:
TX_MODEL_CONFIGS = { 'auth.group': { 'list_default_fields': ['name'], 'disable_search_index': False, } }
-
trionyx.settings.
TX_DB_LOG_LEVEL
= 30¶ The DB log level for logging
-
trionyx.settings.
TX_CHANGELOG_HASHTAG_URL
= None¶ Url to convert all hastags to example: https://github.com/krukas/Trionyx/issues/{tag}
-
trionyx.settings.
TX_SHOW_CHANGELOG_NEW_VERSION
= True¶ Show changelog dialog with new version
-
trionyx.settings.
TX_SHOW_FOOTER
= True¶ Show footer
Config¶
Model configuration¶
-
class
trionyx.config.
ModelConfig
(model: Type[django.db.models.base.Model], MetaConfig=None)[source]¶ ModelConfig holds all config related to a model that is used for trionyx functionality.
Model configs are auto loaded from the apps config file. In the apps config class create a class with same name as model and set appropriate config as class attribute.
# apps.blog.apps.py class BlogConfig(BaseConfig): ... # Example config for Category model class Category(ModelConfig): verbose_name = '{name}' list_default_fields = ['id', 'created_at', 'name'] list_search_fields = ['name', 'description']
Menu name, default is model verbose_name_plural
Menu order
Exclude model from menu
Add menu item to root instead of under the app menu
Menu css icon, is ony used when root menu item
-
global_search
= True¶ Enable global search for model
-
disable_search_index
= False¶ Disable search index, use full for model with no list view but with allot of records
-
search_fields
= []¶ Fields to use for searching, default is all CharField and TextField
-
search_exclude_fields
= []¶ Fields you don’t want to use for search
-
search_title
= None¶ Search title of model works the same as verbose_name, defaults to __str__. Is given high priority in search and is used in global search
-
search_description
= None¶ Search description of model works the same as verbose_name, default is empty, Is given medium priority and is used in global search page
-
list_fields
= None¶ Customise the available fields for model list view, default all model fields are available.
list_fields is an array of dict with the field description, the following options are available:
- field: Model field name (is used for sort and getting value if no renderer is supplied)
- label: Column name in list view, if not set verbose_name of model field is used
- renderer: function(model, field) that returns a JSON serializable date, when not set model field is used.
list_fields = [ { 'field': 'first_name', 'label': 'Real first name', 'renderer': lambda model field: model.first_name.upper() } ]
-
list_default_fields
= None¶ Array of fields that default is used in form list
Array of fields to prefetch for query, use this for relations that are used in search or renderer
-
list_default_sort
= '-pk'¶ Default sort field for list view
-
list_update_queryset
= None¶ Function to update queryset
-
api_fields
= None¶ Fields used in API POST/PUT/PATCH methods, fallback on fields used in create and edit forms
-
api_description
= None¶ Description text that is shown in the API documentation
-
api_disable
= False¶ Disable API for model
-
verbose_name
= '{model_name}({id})'¶ Verbose name used for displaying model, default value is “{model_name}({id})”
- format can be used to get model attributes value, there are two extra values supplied:
- app_label: App name
- model_name: Class name of model
List with button configurations to be displayed in view header bar
view_header_buttons = [ { 'label': 'Send email', # string or function 'url': lambda obj : reverse('blog.post', kwargs={'pk': obj.id}), # string or function 'type': 'default', 'show': lambda obj, context: context.get('page') == 'view', # Function that gives True or False if button must be displayed 'dialog': True, 'dialog_options': """function(data, dialog){ // Example that will close dialog on success if (data.success) { dialog.close(); } }""" } ]
Display add button for this model
Display change button for this model
Display delete button for this model
-
disable_view
= False¶ Disable view for this model
-
disable_add
= False¶ Disable add for this model
-
disable_change
= False¶ Disable change for this model
-
disable_delete
= False¶ Disable delete for this model
-
admin_view_only
= False¶ Only admins can view this model
-
admin_add_only
= False¶ Only admins can add this model
-
admin_change_only
= False¶ Only admins can change this model
-
admin_delete_only
= False¶ Only admins can delete this model
-
auditlog_disable
= False¶ Disable auditlog for this model
-
auditlog_ignore_fields
= None¶ Auditlog fields to be ignored
-
hide_permissions
= False¶ Dont show model in permissions tree, prevent clutter from internal models
-
is_trionyx_model
¶ Check if config is for Trionyx model
-
get_url
(view_name: str, model: django.db.models.base.Model = None, code: str = None) → str[source]¶ Get url for model
-
get_field_type
(field: django.db.models.fields.Field) → str[source]¶ Get field type base on model field class
Get header buttons for given page and object
Layout and Components¶
Layouts are used to render a view for an object. Layouts are defined and registered in layouts.py in an app.
Example of a tab layout for the user profile:
@tabs.register('trionyx.profile')
def account_overview(obj):
return Container(
Row(
Column2(
Panel(
'Avatar',
Img(src="{}{}".format(settings.MEDIA_URL, obj.avatar)),
collapse=True,
),
),
Column10(
Panel(
'Account information',
DescriptionList(
'email',
'first_name',
'last_name',
),
)
),
)
)
-
class
trionyx.layout.
Layout
(*components, **options)[source]¶ Layout object that holds components
-
find_component_by_id
(id=None, current_comp=None)[source]¶ Find component by id, gives back component and parent
-
set_object
(object)[source]¶ Set object for rendering layout and set object to all components
Parameters: object – Returns:
-
add_component
(component, id=None, path=None, before=False, append=False)[source]¶ Add component to existing layout can insert component before or after component
Parameters: - component –
- id – component id
- path – component path, example: container.row.column6[1].panel
- append – append component to selected component from id or path
Returns:
-
-
class
trionyx.layout.
Component
(*components, **options)[source]¶ Base component can be use as an holder for other components
-
template_name
= ''¶ Component template to be rendered, default template only renders child components
-
js_files
= []¶ List of required javascript files
-
css_files
= []¶ List of required css files
-
set_object
(object, force=False, layout_id=None)[source]¶ Set object for rendering component and set object to all components
when object is set the layout should be complete with all components. So we also use it to set the layout_id so it’s available in the updated method and also prevent whole other lookup of all components.
Parameters: object – Returns:
-
-
class
trionyx.layout.
ComponentFieldsMixin
[source]¶ Mixin for adding fields support and rendering of object(s) with fields.
-
fields
= []¶ List of fields to be rendered. Item can be a string or dict, default options:
- field: Name of object attribute or dict key to be rendered
- label: Label of field
- value: Value to be rendered (Can also be a component)
- format: String format for rendering field, default is ‘{0}’
- renderer: Render function for rendering value, result will be given to format. (lambda value, **options: value)
Based on the order the fields are in the list a __index__ is set with the list index, this is used for rendering a object that is a list.
fields = [ 'first_name', 'last_name' ] fields = [ 'first_name', { 'label': 'Real last name', 'value': object.last_name } ]
-
fields_options
= {}¶ Options available for the field, this is not required to set options on field.
- default: Default option value when not set.
fields_options = { 'width': { 'default': '150px', } }
-
objects
= []¶ List of object to be rendered, this can be a QuerySet, list or string. When its a string it will get the attribute of the object.
The items in the objects list can be a mix of Models, dicts or lists.
-
parse_string_field
(field_data)[source]¶ Parse a string field to dict with options
String value is used as field name. Options can be given after = symbol. Where key value is separated by : and different options by ;, when no : is used then the value becomes True.
Example 1: field_name
# Output { 'field': 'field_name' }
Example 3 field_name=option1:some value;option2: other value
# Output { 'field': 'field_name', 'option1': 'some value', 'option2': 'other value', }
Example 3 field_name=option1;option2: other value
# Output { 'field': 'field_name', 'option1': True, 'option2': 'other value', }
Parameters: field_data (str) – Return dict:
-
-
class
trionyx.layout.
HtmlTemplate
(template_name, context=None, css_files=None, js_files=None, **options)[source]¶ HtmlTemplate render django html template
-
class
trionyx.layout.
HtmlTagWrapper
(*args, **kwargs)[source]¶ HtmlTagWrapper wraps given component in given html tag
-
tag
= 'div'¶ Html tag nam
-
valid_attr
= []¶ Valid attributes that can be used
-
color_class
= ''¶ When color is set the will be used as class example: btn btn-{color}
-
attr
= {}¶ Dict with html attributes
-
-
class
trionyx.layout.
OnclickTag
(*components, url=None, model_url=None, model_params=None, model_code=None, sidebar=False, dialog=False, dialog_options=None, dialog_reload_tab=None, dialog_reload_sidebar=False, dialog_reload_layout=False, **options)[source]¶ HTML tag with onlick for url or dialog
-
class
trionyx.layout.
Field
(field, renderer=None, format=None, **options)[source]¶ Render single field from object
-
class
trionyx.layout.
Alert
(html, alert='success', no_margin=False, **options)[source]¶ Bootstrap alert
-
class
trionyx.layout.
Panel
(title, *components, **options)[source]¶ Bootstrap panel available options
- title
- footer_components
- collapse
- contextual: primary, success, info, warning, danger
-
class
trionyx.layout.
DescriptionList
(*fields, **options)[source]¶ Bootstrap description, fields are the params. available options
- horizontal
-
class
trionyx.layout.
ProgressBar
(field='', value=0, max_value=100, size='md', striped=False, active=False, **options)[source]¶ Bootstrap progressbar, fields are the params
-
class
trionyx.layout.
TableDescription
(*fields, **options)[source]¶ Bootstrap table description, fields are the params
-
class
trionyx.layout.
Table
(objects, *fields, css_class='table', header=True, condensed=True, hover=False, striped=False, bordered=False, **options)[source]¶ Bootstrap table
- footer: array with first items array/queryset and other items are the fields,
- Same way how the constructor works
Can be string with field name relation, Queryset or list
Get all footer fields
Render footer object
Render footer objects
Forms¶
Default Trionyx will generate a form for all fields without any layout. Forms can be created and registered in the forms.py.
-
trionyx.forms.
register
(code: Optional[str] = None, model_alias: Optional[str] = None, default_create: Optional[bool] = False, default_edit: Optional[bool] = False, minimal: Optional[bool] = False, create_permission: Optional[str] = None, edit_permission: Optional[str] = None)¶ Register form for given model_alias, if no model_alias is given the Meta.model is used to generate the model alias.
Parameters: - code (str) – Code to identify form
- model_alias (str) – Alias for a model (if not provided the Meta.model is used)
- default_create (bool) – Use this form for create
- default_edit (bool) – Use this form for editing
- minimal (bool) – Use this form for minimal create
# <app>/forms.py from trionyx import forms @forms.register(default_create=True, default_edit=True) class UserForm(forms.ModelForm): class Meta: model = User
Layout¶
Forms are rendered in Trionyx with crispy forms using the bootstrap3 template.
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset, Div
class UserUpdateForm(forms.ModelForm):
# your form fields
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
'email',
Div(
Fieldset(
'Personal info',
'first_name',
'last_name',
css_class="col-md-6",
),
Div(
'avatar',
css_class="col-md-6",
),
css_class="row"
),
Fieldset(
'Change password',
'new_password1',
'new_password2',
),
)
Crispy Forms¶
Standard¶
-
class
crispy_forms.layout.
Layout
(*fields)[source]¶ Form Layout. It is conformed by Layout objects: Fieldset, Row, Column, MultiField, HTML, ButtonHolder, Button, Hidden, Reset, Submit and fields. Form fields have to be strings. Layout objects Fieldset, Row, Column, MultiField and ButtonHolder can hold other Layout objects within. Though ButtonHolder should only hold HTML and BaseInput inherited classes: Button, Hidden, Reset and Submit.
Example:
helper.layout = Layout( Fieldset('Company data', 'is_company' ), Fieldset(_('Contact details'), 'email', Row('password1', 'password2'), 'first_name', 'last_name', HTML('<img src="/media/somepicture.jpg"/>'), 'company' ), ButtonHolder( Submit('Save', 'Save', css_class='button white'), ), )
-
class
crispy_forms.layout.
ButtonHolder
(*fields, **kwargs)[source]¶ Layout object. It wraps fields in a <div class=”buttonHolder”>
This is where you should put Layout objects that render to form buttons like Submit. It should only hold HTML and BaseInput inherited objects.
Example:
ButtonHolder( HTML(<span style="display: hidden;">Information Saved</span>), Submit('Save', 'Save') )
-
class
crispy_forms.layout.
BaseInput
(name, value, **kwargs)[source]¶ A base class to reduce the amount of code in the Input classes.
-
class
crispy_forms.layout.
Submit
(*args, **kwargs)[source]¶ Used to create a Submit button descriptor for the {% crispy %} template tag:
submit = Submit('Search the Site', 'search this site')
Note
The first argument is also slugified and turned into the id for the submit button.
-
class
crispy_forms.layout.
Button
(*args, **kwargs)[source]¶ Used to create a Submit input descriptor for the {% crispy %} template tag:
button = Button('Button 1', 'Press Me!')
Note
The first argument is also slugified and turned into the id for the button.
-
class
crispy_forms.layout.
Hidden
(name, value, **kwargs)[source]¶ Used to create a Hidden input descriptor for the {% crispy %} template tag.
-
class
crispy_forms.layout.
Reset
(*args, **kwargs)[source]¶ Used to create a Reset button input descriptor for the {% crispy %} template tag:
reset = Reset('Reset This Form', 'Revert Me!')
Note
The first argument is also slugified and turned into the id for the reset.
-
class
crispy_forms.layout.
Fieldset
(legend, *fields, **kwargs)[source]¶ Layout object. It wraps fields in a <fieldset>
Example:
Fieldset("Text for the legend", 'form_field_1', 'form_field_2' )
The first parameter is the text for the fieldset legend. This text is context aware, so you can do things like:
Fieldset("Data for {{ user.username }}", 'form_field_1', 'form_field_2' )
-
class
crispy_forms.layout.
MultiField
(label, *fields, **kwargs)[source]¶ MultiField container. Renders to a MultiField <div>
-
class
crispy_forms.layout.
Div
(*fields, **kwargs)[source]¶ Layout object. It wraps fields in a <div>
You can set css_id for a DOM id and css_class for a DOM class. Example:
Div('form_field_1', 'form_field_2', css_id='div-example', css_class='divs')
-
class
crispy_forms.layout.
Row
(*fields, **kwargs)[source]¶ Layout object. It wraps fields in a div whose default class is “formRow”. Example:
Row('form_field_1', 'form_field_2', 'form_field_3')
-
class
crispy_forms.layout.
Column
(*fields, **kwargs)[source]¶ Layout object. It wraps fields in a div so the wrapper can be used as a column. Example:
Column('form_field_1', 'form_field_2')
- Depending on the template, css class associated to the div is formColumn, row, or nothing. For this last case, you
must provide css classes. Example:
Column('form_field_1', 'form_field_2', css_class='col-xs-6',)
-
class
crispy_forms.layout.
HTML
(html)[source]¶ Layout object. It can contain pure HTML and it has access to the whole context of the page where the form is being rendered.
Examples:
HTML("{% if saved %}Data saved{% endif %}") HTML('<input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />')
-
class
crispy_forms.layout.
Field
(*args, **kwargs)[source]¶ Layout object, It contains one field name, and you can add attributes to it easily. For setting class attributes, you need to use css_class, as class is a Python keyword.
Example:
Field('field_name', style="color: #333;", css_class="whatever", id="field_name")
-
class
crispy_forms.layout.
MultiWidgetField
(*args, **kwargs)[source]¶ Layout object. For fields with
MultiWidget
as widget, you can pass additional attributes to each widget.Example:
MultiWidgetField( 'multiwidget_field_name', attrs=( {'style': 'width: 30px;'}, {'class': 'second_widget_class'} ), )
Note
To override widget’s css class use
class
notcss_class
.
Bootstrap¶
-
class
crispy_forms.bootstrap.
PrependedAppendedText
(field, prepended_text=None, appended_text=None, *args, **kwargs)[source]¶
-
class
crispy_forms.bootstrap.
FormActions
(*fields, **kwargs)[source]¶ Bootstrap layout object. It wraps fields in a <div class=”form-actions”>
Example:
FormActions( HTML(<span style="display: hidden;">Information Saved</span>), Submit('Save', 'Save', css_class='btn-primary') )
-
class
crispy_forms.bootstrap.
InlineCheckboxes
(*args, **kwargs)[source]¶ Layout object for rendering checkboxes inline:
InlineCheckboxes('field_name')
-
class
crispy_forms.bootstrap.
InlineRadios
(*args, **kwargs)[source]¶ Layout object for rendering radiobuttons inline:
InlineRadios('field_name')
-
class
crispy_forms.bootstrap.
StrictButton
(content, **kwargs)[source]¶ Layout object for rendering an HTML button:
Button("button content", css_class="extra")
-
class
crispy_forms.bootstrap.
Container
(name, *fields, **kwargs)[source]¶ Base class used for Tab and AccordionGroup, represents a basic container concept
-
class
crispy_forms.bootstrap.
ContainerHolder
(*fields, **kwargs)[source]¶ Base class used for TabHolder and Accordion, groups containers
-
class
crispy_forms.bootstrap.
Tab
(name, *fields, **kwargs)[source]¶ Tab object. It wraps fields in a div whose default class is “tab-pane” and takes a name as first argument. Example:
Tab('tab_name', 'form_field_1', 'form_field_2', 'form_field_3')
-
class
crispy_forms.bootstrap.
TabHolder
(*fields, **kwargs)[source]¶ TabHolder object. It wraps Tab objects in a container. Requires bootstrap-tab.js:
TabHolder( Tab('form_field_1', 'form_field_2'), Tab('form_field_3') )
-
class
crispy_forms.bootstrap.
AccordionGroup
(name, *fields, **kwargs)[source]¶ Accordion Group (pane) object. It wraps given fields inside an accordion tab. It takes accordion tab name as first argument:
AccordionGroup("group name", "form_field_1", "form_field_2")
-
class
crispy_forms.bootstrap.
Accordion
(*args, **kwargs)[source]¶ Accordion menu object. It wraps AccordionGroup objects in a container:
Accordion( AccordionGroup("group name", "form_field_1", "form_field_2"), AccordionGroup("another group name", "form_field") )
-
class
crispy_forms.bootstrap.
Alert
(content, dismiss=True, block=False, **kwargs)[source]¶ Alert generates markup in the form of an alert dialog
Alert(content=’<strong>Warning!</strong> Best check yo self, you’re not looking too good.’)
Celery background tasks¶
Trionyx uses Celery for background tasks, for full documentation go to Celery 4.1 documentation.
Configuration¶
Default there is no configuration required if standard RabbitMQ server is installed on same server. Default broker url is: amqp://guest:guest@localhost:5672//
Queue’s¶
Default Trionyx configuration has three queue’s:
- cron: Every tasks started by Celery beat is default put in the cron queue.
- low_prio: Is the default Queue every other tasks started by other processes are put in this queue.
- high_prio: Queue can be used for putting high priority tasks, default no tasks are put in high_prio queue.
Time limit¶
Default configuration sets the soft time limit of tasks to 1 hour and hard time limit to 1 hour and 5 minutes. You can catch a soft time limit with the SoftTimeLimitExceeded, and with the default configuration you have 5 minutes to clean up a task.
You can change the time limit with the settings CELERY_TASK_SOFT_TIME_LIMIT and CELERY_TASK_TIME_LIMIT
Creating background task¶
Tasks mused by defined in the file tasks.py in your Django app. Tasks in the tasks.py will by auto detected by Celery.
Example of a task with arguments:
from celery import shared_task
@shared_task
def send_email(email):
# Send email
# You can call this task normally by:
send_email('test@example.com')
# Or you can run this task in the background by:
send_email.delay('test@example.com')
Note
If you are using Django signals like post_save to start tasks, make sure you use transaction.on_commit
Running task periodically (cron)¶
You can run a task periodically by defining a schedule in the cron.py in you Django app.
from celery.schedules import crontab
schedule = {
'spammer': {
'task': 'app.test.tasks.send_email',
'schedule': crontab(minute='*'),
}
}
Running celery (development)¶
If you have a working broker installed and configured you can run celery with:
celery worker -A celery_app -B -l info
Live setup (systemd)¶
For live deployment you want to run celery as a daemon, more info in the Celery documentation
celery.service¶
/etc/systemd/system/celery.service
[Unit]
Description=Celery Service
After=network.target
[Service]
Type=forking
# Change this to Username and group that Trionyx project is running on.
User=celery
Group=celery
EnvironmentFile=-/etc/conf.d/celery
# Change this to root of your Trionyx project
WorkingDirectory=/root/of/trionyx/projext
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} \
-A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS}'
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} \
--pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} \
-A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS}'
[Install]
WantedBy=multi-user.target
Configuration file¶
/etc/conf.d/celery
CELERYD_NODES="cron_worker low_prio_worker high_prio_worker"
# Absolute or relative path to the 'celery' command:
CELERY_BIN="/usr/local/bin/celery"
CELERY_APP="celery_app"
# Extra command-line arguments to the worker
CELERYD_OPTS="-Ofair \
-Q:cron_worker cron -c:cron_worker 4 \
-Q:low_prio_worker low_prio -c:low_prio_worker 8 \
-Q:high_prio_worker high_prio -c:high_prio_worker 4"
# - %n will be replaced with the first part of the nodename.
# - %I will be replaced with the current child process index
# and is important when using the prefork pool to avoid race conditions.
CELERYD_PID_FILE="/var/run/celery/%n.pid"
CELERYD_LOG_FILE="/var/log/celery/%n%I.log"
CELERYD_LOG_LEVEL="INFO"
Note
Make sure that the PID and LOG file directory is writable for the user that is running Celery.
Widgets¶
Widgets are used on the dashboard and are rendered with Vue.js component.
-
class
trionyx.widgets.
BaseWidget
[source]¶ Base widget to extend for creating custom widgets. Custom widgets are created in widgets.py in root of app folder.
Example of random widget:
# <app dir>/widgets.py RandomWidget(BaseWidget): code = 'random' name = 'Random widget' description = 'Shows random string' def get_data(self, request, config): return utils.random_string(16)
<!-- template path: widgets/random.html --> <script type="text/x-template" id="widget-random-template"> <div :class="widgetClass"> <div class="box-header with-border"> <!-- Get title from config, your form fields are also available in the config --> <h3 class="box-title">[[widget.config.title]]</h3> </div> <!-- /.box-header --> <div class="box-body"> <!-- vue data property will be filled with the get_data results method ---> [[data]] </div> </div> </script> <script> <!-- The component must be called `widget-<code>` --> Vue.component('widget-random', { mixins: [TxWidgetMixin], template: '#widget-random-template', }); </script>
-
code
= None¶ Code for widget
-
permission
= None¶ Permission to use this widget
-
name
= ''¶ Name for widget is also used as default title
-
description
= ''¶ Short description on what the widget does
-
config_form_class
= None¶ Form class used to change the widget. The form cleaned_data is used as the config
-
default_width
= 4¶ Default width of widget, is based on grid system with max 12 columns
-
default_height
= 20¶ Default height of widget, each step is 10px
-
fixed_width
= None¶ Set a fixed width for widget
-
fixed_height
= None¶ Set a fixed height for widget
-
is_resizable
= None¶ Is widget resizable
-
template
¶ Template path widgets/{code}.html overwrite to set custom path
-
image
¶ Image path img/widgets/{code}.jpg overwrite to set custom path
-
get_data
(request: django.http.request.HttpRequest, config: dict)[source]¶ Get data for widget, function needs te be overwritten on widget implementation
-
config_fields
¶ Get the config field names
-
Deploying¶
You can deploy your Trionyx project any way that works for your environment. If you are just looking for a simple deployment to dedicated server (or VPS). Trionyx provides a complete Ansible role for setting up and deploying your project to a clean Ubuntu server.
Server setup created by Ansible:
- Nginx (https with Letsencrypt)
- Gunicorn (gevent)
- PostgreSQL with pgbouncer
- RabbitMQ
- Firewall (ufw, fail2ban)
- Auto update with unattended-upgrades
Creating Ansible playbook¶
Prerequisites:
- Newly installed Ubuntu 18.04 server
- Domain name that points to that server
- Git repository of your project
Before you begin you need to install Ansible:
pip install --user ansible
After you have installed Ansible you can create a Trionyx playbook by running:
trionyx create_ansible <domain> <repo>
Follow the instructions and the end you will have an Ubuntu server running with your Project.
Server maintenance¶
Security updates are automatically installed with unattended-upgrades. For the normal system update there is an upgrade.yml playbook.
Warning
The upgrade.yml playbook will restart the server if an updated package required a system reboot.
You can run the system upgrade playbook with following command:
ansible-playbook upgrade.yml -i production
How to write reusable apps¶
Trionyx support auto registering off reusable apps with the setup entry_points. This means you only have to pip install <reusable app> in your Trionyx project and it will be auto loaded into project, no further configuration needed.
Create reusable app¶
To create a complete working base structure for your reusable app run:
trionyx create_reusable_app <name>
You now can create your Trionyx app how you do normally. And when you are ready with your first version you can upload it to PyPi.
Changelog¶
[3.0.0] - 08-05-2021¶
Compatibility breaking changes: Upgrade to Django 3.2 and reset DB migrations for JSONField
Added¶
- Add option to make model view/add/change/delete admin only
- Make CELERY_BROKER_URL set by env
- Add vuex.js
- Add option to hide footer
- Add settings to always filter list queryset
Changed¶
- Panel component title attribute now accepts component
- Add helper functions to get/set local data
- Add helper function get_current_user and make get_current_user available in tasks if a user started the task
- Celery hooks are auto loaded from settings
- Remove /media/ from xsendfile
- Search by field label name
- Inline show non field errors
- Allow model_url to have an empty viewname
Fixed¶
- AppSettings crashes on migrate because variable table does not yet exists
- Fix ProgrammingError: relation “django_cache” does not exist
- Fix verbose name not saved
- Graph widget only first 30 records instead of lasts 30 records
- Button dialog custom url error
[2.2.0] - 03-09-2020¶
Compatibility breaking changes: Remove -custom for code url path
Added¶
- Allow apps to add global css/js files
- Add Field component
- Add option to hide table header
- Add options to disable auditlog
- App settings can be overridden with system variables
- Add config to disable viewing of model
- Add option should_render to components
- New projects will print emails to console in development
- Add options to set custom create/edit permission on form
- Add permission to dashboard widgets and widget data
- Add celery command for development with auto reload on file change
- Add ImageField renderer
- Add foreign field renderer that renders object with a tag
- Add Json field renderer
- Add action column to list view with actions view,edit and delete (remove row click)
- Add graph dashboard widget + improve widget options
Changed¶
- Make raised Exceptions more explicit
- Update models to use settings.AUTH_USER_MODEL and in code to get_user_model()
- Remove -custom for code url path
- Allow for muliple dialog reload options
- Change select_related to prefetch_related to prevent join errors on not null fields
- Do model clean on objects for MassUpdate
- Several small Improvement
- Add no_link options to renderers with an a tag, for list view its won’t render value in a tag
- Close list fields popover on outside click
- Change many to many field renderer to use a tags
Fixed¶
- Form Datetimepicker format is not set in __init__
- Summernote popovers remain on page if dialog was closed
[2.1.3] - 08-04-2020¶
Added¶
- Add support for __format__ for LazyFieldRenderer (used by model verbose_name)
- Add support for CTRL+Click and scroll wheel click on list view item to open new tab
Changed¶
- Remove Google fonts
Fixed¶
- #51 Filters datepicker won’t work if previous selected field was a select
- Mass update crashes on collecting fields from forms when custom __init__ is used
[2.1.1] - 02-04-2020¶
Added¶
- Add login redirect to previous visited page
Fixed¶
- Fix multiple enumerations are added to list view on slow load
- Fix drag column order on list view out order after drag event
- get_current_request not working in streaming response
[2.1.0] - 12-02-2020¶
Added¶
- Add create_reusable_app command
- Add ProgressBar component
- Add Unordered and Ordered list components
- Add LineChart, BarChart, PieChart and DoughnutChart component
- Add options to register data function for a widget
- Add support for file upload in dialog
- Add full/extra-large size options to dialog
- Add link target option to header buttons
- Add Date value renderer
- Add current url to dialog object
- Add Link component
- Add component option to lock object
- Add footer with Trionyx and app version
- Add changelog dialog with auto show on version change
- Add command to generate favicon
- Add Ansible upgrade playbook for quickstart
- Add user API token reset link
- Add JS helper runOnReady function
- Add basic-auth authentication view
- Add ajax form choices and multiple choices field
Changed¶
- Update translations
- Add traceback stack to DB logs with no Exception
- Set max_page of 1000 for API and default page size to 25
- Moved depend JS to static files
- Change logging to file rotation for quickstart project
- Improve Table component styling options
Fixed¶
- Widget config popup is blank
- Fix form layout Depend not working on create/update view
- Fix widget config_form_class is not set
- Fix list_value_renderer crashes on non string list items
- Fix list load loop on fast reloads (eq spam next button)
- Fix Makefile translate commands
- Fix CreateDialog permission check wasn’t working
- Fix model alias tabs not working
- Fix Quickstart reusable app
- Fix log messages is not formatted in db logger
- Fix BaseTask can be executed to fast
- Fix prevent large header titles pushing buttons and content away
[2.0.1] - 19-12-2019¶
Added¶
- Add helper function for setting the Watson search language
Changed¶
- Small improvements to prevent double SQL calls
- #39 Make python version configurable for Makefile
Fixed¶
- Ansible role name is not found
- JsonField does not work in combination with jsonfield module
[2.0.0] - 11-12-2019¶
Compatibility breaking changes: drop support for python 3.5
Added¶
- Add generic model sidebar
- Add Summernote wysiwyg editor
- Add more tests and MyPy
- Add getting started guide to docs and improve README
- Add more bootstrap components
- Add frontend layout update function
- Add system variables
- Add helper class for app settings
- Add support for inline forms queryset
- Add company information to settings
- Add price template filter
- Add ability for forms to set page title and submit label
- Add options to display create/change/delete buttons
- Add signals for permissions
Changed¶
- Drop support for python 3.5
- Improve api serializer registration
- Improve list view column sizes
- Move from vitualenv to venv
- Make inline formset dynamic
- Make delete button available on edit page
- Make header buttons generic and show them on list and edit page
- Header buttons can be shown based on tab view
Fixed¶
- Cant go to tab if code is same as code in jstree
- Several small fixes and changes
[1.0.4] - 31-10-2019¶
Changed¶
- Improved new project creation
Fixed¶
- Filter related choices are not shown
[1.0.3] - 30-10-2019¶
Fixed¶
- Fixed to early reverse lookup
- Fixed not all quickstart files where included
[1.0.2] - 30-10-2019¶
Changed¶
- Dialog form initial also uses GET params
- model_url accept GET params as dict
- Improve Button component
- ComponentFieldsMixin fields can now render a Component
- Add option to Component to force update object
- Base Component can be used as an holder for Components to be rendered
- Add debug comments to Component output
Fixed¶
- Delete dialog does not return success boolean
- Fixed html component not rendering html and tag not closed
[1.0.0] - 29-10-2019¶
Compatibility breaking changes: Migrations are cleared
Added¶
- Add get_current_request to utils
- Add DB logger
- Add options to disable create/update/delete for model
- Add debug logging for form errors
- Add audit log for models
- Add user last_online field
- Add support for inline formsets
- Add rest API support
- Add option to add extra buttons to header
- Add search to list fields select popover
- Add Dashboard
- Add Audtilog dashboard widget
- Add model field summary widget
- Add auto import Trionyx apps with pip entries
- Add data choices lists for countries/currencies/timezones
- Add language support + add Dutch translations
- Add user timezone support
- Add CacheLock contectmanager
- Add locale_overide and send_email to user
- Add mass select selector to list view
- Add mass delete action
- Add Load js/css from forms and components
- Add view and edit permissions with jstree
- Add mass update action
- Add BaseTask for tracking background task progress
- Add support for related fields in list and auto add related to queryset
- Add layout component find/add/delete
- Add model overwrites support that are set with settings
- Add renderers for email/url/bool/list
Changed¶
- Set fallback for user profile name and avatar
- Improve header visibility
- Make filters separate vuejs component + function to filter queryset
- Improve theme colors and make theme square
- Update AdminLTE+plugins and Vue.js and in DEBUG use development vuejs
- Refactor inline forms + support single inline form
- Auditlog values are rendered with renderer
- Changed pagination UX
- Show filter label instead of field name
Fixed¶
- Project create settings BASE_DIR was incorrect
- Menu item with empty filtered childs is shown
- Make verbose_name field not required
- Global search is activated on CTRL commands
- Auditlog delete record has no name
- Created by was not set
- Auditlog gives false positives for Decimal fields
- Render date: localtime() cannot be applied to a naive datetime
- Fix model list dragging + fix drag and sort align
- Fixed None value is rendered as the string None
[0.2.0] - 04-06-2019¶
Compatibility breaking changes
Added¶
- Form register and refactor default forms to use this
- Add custom form urls + shortcut model_url function
- Add layout register + layout views
- Add model verbose_name field + change choices to use verbose_name query
- Add permission checks and hide menu/buttons with no permission
Changed¶
- Render fields for verbose_name and search title/description
- Move all dependencies handling to setup.py
- Upgrade to Django 2.2 and update other dependencies
- refactor views/core from Django app to Trionyx package
- Rename navigation to menu
- Move navigtaion.tabs to views.tabs
- Quickstart project settings layout + add environment.json
Fixed¶
- Cant search in fitler select field
- Datetimepicker not working for time
- Travis build error
- Button component