General Resources
Via the projectroles app, SODAR Core provides optional features, APIs and templates for common functionality and layout regardless of the app type. These resources are described in this document.
Icons
To use icons in your apps, use the iconify
class along with the collection
and icon name into the data-icon
attribute. See
Iconify and
django-iconify
documentation for further information.
Example:
<i class="iconify" data-icon="mdi:home"></i>
Also make sure to modify the icon
attribute of your app plugins to include
the full collection:name
syntax for Iconify icons.
In certain client side Javascript implementations in which icons are loaded or
replaced dynamically, you may have to refer to these URLs as a direct img
element:
<img src="/icons/mdi/home.svg" />
For modifiers such as color and size when using img
tags,
see here.
SODAR Core uses the Material Design Icons
collection for its own apps. You can also use additional collections supported
by Iconify on your site by retrieving them with the geticons
management
command. Multiple collections can be downloaded with a single command, as seen
in the example below. Make sure to run collectstatic
after this command.
$ ./manage.py geticons -c collection1 collection2
$ ./manage.py collectstatic
Forms
This section contains guidelines for implementing forms.
Form Base Classes
Although not required, it is recommended to use common SODAR Core base classes
with built-in helpers for your Django forms. SODARForm
and
SODARModelForm
extend Django’s Form
and ModelForm
respectively.
These base classes can be imported from projectroles.forms
. Currently they
add logging to add_error()
calls, which helps administrators track form
issues encountered by users. Further improvements are to be added in the future.
SODAR User Selection Field
Projectroles offers a custom field, widget and accompanying Ajax API views for autocomplete-enabled selection of SODAR users in Django forms. The field will handle providing appropriate choices according to the view context and user permissions, also allowing for customization.
The recommended way to use the built-in user form field is by using the
SODARUserChoiceField
class found in projectroles.forms
. The field
extends Django’s ModelChoiceField
and takes most of the same keyword
arguments in its init function, with the exception of queryset
,
to_field_name
, limit_choices_to
and widget
which will be overridden.
The init function also takes new arguments which are specified below:
scope
Scope of users to include (string). Options:
all
: All users on the siteproject
: Limit search to users in given projectproject_exclude
Exclude existing users of given project
project
Project object or project UUID string (optional)
exclude
List of User objects or User UUIDs to exclude (optional)
forward
Parameters to forward to autocomplete view (optional)
url
Autocomplete ajax class override (optional)
widget_class
Widget class override (optional)
Below is an example of the classes usage. Note that you can also define the
field as a form class member, but the project
or exclude
values are
not definable at that point. The following example assumes you are setting up
your project app form with an extra project
argument.
from projectroles.forms import SODARUserChoiceField
class YourForm(forms.ModelForm):
class Meta:
# ...
def __init__(self, project, *args, **kwargs):
# ...
self.fields['user'] = SODARUserChoiceField(
label='User',
help_text='Select user for your thing here',
required=True,
scope='project',
project=project,
exclude=[unwanted_user]
)
For more examples of usage of this field and its widget, see
projectroles.forms
. If the field class does not suit your needs, you can also
retrieve the related widget to your own field with
projectroles.forms.get_user_widget()
.
To provide required Javascript and CSS includes for DAL in your form, make sure
to include form.media
in your template. Example:
<div class="container-fluid sodar-page-container">
<form method="post">
{{ form.media }}
{{ form | crispy }}
{% ... %}
</form>
</div>
If using customized Javascript for your widget, the corresponding JS file can be
provided in the javascript
block. See the django-autocomplete-light
documentation for more information on how to customize your widget.
Markdown
For fields supporting markdown, it is recommended to use the
SODARPagedownWidget
found in projectroles.models
.
Submit Multi-Click Protection
To avoid unwanted effects from a user clicking the submit button on a form
multiple times, it is recommended to use the sodar-btn-submit-once
class on
the submit button in your server-side form templates. Introducing this class
will disable the button after the initial click while the form is submitted.
This is especially recommended for forms responsible for creating objects.
Invalid Form View Mixin
Adding projectroles.views.InvalidFormMixin
to your create or update view
displays a standardized Django message notifying the user of form submission
failure. This is recommended to be used especially for long scrolling forms,
where errors pinned to specific fields may be initially invisible to the user.
Template Includes and Helpers
This section details general template includes and helpers provided by SODAR Core. Unless otherwise mentioned, these can be imported from the projectroles app.
For common template tags, see Common Template Tags.
Pagination Template
A common template for adding navigation for list pagination can be found in
projectroles/_pagination.html
. This can be included to any Django
ListView
template which provides the paginate_by
definition, enabling
pagination. If a smaller layout is desired, the pg_small
argument can be
used. An example can be seen below:
{% include 'projectroles/_pagination.html' with pg_small=True %}
Project Badge
Projectroles provides a project badge which can be used to display a fixed-size link to a category or a project among text. It can be included in your template as follows:
{% include 'projectroles/_project_badge.html' with project=project color='info' can_view=True %}
The following arguments are expected:
project
Project object for the related project or category.
color
String for the badge color (must correspond to bootstrap classes, e.g. “info” or “success”).
can_view
Boolean for whether the current user should have access to view the project.
Tour Help
SODAR Core uses Shepherd
to present an optional interactive tour for a rendered page. To enable the tour
in your template, set it up inside the javascript
template block. Within an
inline javascript structure, set the tourEnabled
variable to true
and
add steps according to the
Shepherd documentation.
Example:
{% block javascript %}
{{ block.super }}
{# Tour content #}
<script type="text/javascript">
tourEnabled = true;
/* Normal step */
tour.addStep('id_of_step', {
title: 'Step Title',
text: 'Description of the step',
attachTo: '#some-element top',
advanceOn: '.docs-link click',
showCancelLink: true
});
/* Conditional step */
if ($('.potentially-existing-element').length) {
tour.addStep('id_of_another_step', {
title: 'Another Title',
text: 'Another description here',
attachTo: '.potentially-existing-element right',
advanceOn: '.docs-link click',
showCancelLink: true
});
}
</script>
{% endblock javascript %}
Warning
Make sure you call {{ block.super }}
at the start of the declared
javascript
block or you will overwrite the site’s default Javascript
setup!
App Settings
SODAR Core offers a common framework and API for defining, setting and accessing modifiable settings from your apps. This makes it possible to introduce variables changeable in runtime for different purposes and scopes without the need to manage additional Django models in your apps. App settings are supported for plugins in project and site apps.
The settings are defined as Python dictionaries in your project or site app’s plugin. An example of a definition can be seen below:
class ProjectAppPlugin(ProjectAppPluginPoint):
# ...
app_settings = {
'allow_public_links': {
'scope': APP_SETTING_SCOPE_PROJECT,
'type': 'BOOLEAN',
'default': False,
'label': 'Allow public links',
'description': 'Allow generation of public links for files',
'user_modifiable': True,
}
}
Each setting must define a scope
. The options for this are as follows, as
defined in SODAR_CONSTANTS
:
APP_SETTING_SCOPE_PROJECT
Setting related to a project and displayed in the project create/update form.
APP_SETTING_SCOPE_USER
Site-wide setting related to a user and displayed in the user profile form.
APP_SETTING_SCOPE_PROJECT_USER
User setting related to a project, managed by your project app.
APP_SETTING_SCOPE_SITE
Site-wide setting similar to Django settings but modifiable in runtime.
The rest of the attributes are listed below:
type
Value type for the settings. Available options are
BOOLEAN
,INTEGER
,STRING
andJSON
.default
Default value for the setting. This is returned if no value has been set. Can alternatively be a callable with the signature
callable(project=None, user=None)
.global
Boolean for allowing/disallowing editing in target sites for remote projects. Relevant to
SOURCE
sites. If setTrue
, the value can not be edited on target sites, the default value beingFalse
(optional).label
Label string to be displayed in forms for
PROJECT
andUSER
scope settings (optional).placeholder
Placeholder string to be displayed in forms for
PROJECT
andUSER
scope settings (optional).description
Description string shown in forms for
PROJECT
andUSER
scope settings (optional).options
List of selectable options for
INTEGER
andSTRING
type settings. Can alternatively be a callable with the signaturecallable(project=None, user=None)
returning a list of strings or key/label tuples (optional).user_modifiable
Boolean value for whether this setting should be displayed in project or user forms. If false, will be displayed only to superusers. Set to true if your app is expected to manage this setting. Applicable for
PROJECT
andUSER
scope settings (optional).project_types
List of project types this setting is allowed to be set for. Defaults to
[PROJECT_TYPE_PROJECT]
(optional).
The settings are retrieved using AppSettingAPI
provided by the
projectroles app. Below is an example of invoking the API. For the full API
docs, see
app settings API documentation.
from projectroles.app_settings import AppSettingAPI
app_settings = AppSettingAPI()
app_settings.get('plugin_name', 'setting_name', project_object) # Etc..
There is no need to separately generate settings for projects or users. If the
setting object does not exist in the Django database when
AppSettingAPI.get()
is first called on the setting and argument combination,
it is created based on the default value and the default value is returned.
If you modify definitions during development or retire a setting, run the
cleanappsettings
management command to delete unneeded app settings from
the Django database:
$ ./manage.py cleanappsettings
Project Modifying API
If your site needs to perform specific actions when projects are created or modified, or when project membership is altered, you can implement the project modifying API in your app plugin. This can be useful if your site e.g. maintains project data and access in other external databases or needs to set up some specific data on project changes.
Note
This API is intended for special cases. If you’re unsure why you wouldn’t need it on your site, it is possible you don’t. Using it unnecessarily might complicate your site implementation.
This API works for project apps and
backend apps. To use it, it is recommend to include the
ProjectModifyPluginMixin
in your plugin class and implement the methods
relevant to your site’s needs. An example of this can be seen below.
from projectroles.plugins import ProjectModifyPluginMixin
class ProjectAppPlugin(ProjectModifyPluginMixin, ProjectAppPluginPoint):
# ...
def perform_project_modify(
self,
project,
action,
project_settings,
old_data=None,
old_settings=None,
request=None,
):
pass # Your implementation goes here
You will also need to set PROJECTROLES_ENABLE_MODIFY_API=True
in your site’s
Django settings to enable calling this API.
Project modification operations will be cancelled and reverted if errors are encountered at any point in the project modify API calls. If your site has multiple apps implementing this API, you should also implement reversion methods for each operations to assert a clean rollback. These methods are also included in the class.
You can control the order of the apps in which this API is called by listing
your plugins in the PROJECTROLES_MODIFY_API_APPS
Django setting. This will
also affect the order of reversing.
To synchronize data for existing projects in development, you can implement the
perform_project_sync()
method.
Multi-Plugin Apps
In many cases, you may want to declare multiple app plugins within a single SODAR Core app. For example, you may want your app to have both project specific and site specific views and maybe also a backend API to be used by other apps.
For an example of a multi-plugin app in SODAR Core itself, see the Timeline App.
There is no limit on how many plugins you can define for a SODAR Core app and they may be of different types. However, certain conditions should be followed when creating multi-plugin apps:
Plugin names are expected to be unique. Not adhering to this may cause unexpected side-effects.
For plugins with related UI views, it is strongly recommended to name all your plugins starting with the app name. For example, if your project app plugin is named
yourapp
, it is recommended to name the secondary site app plugin e.g.yourapp_site
. This ensures the SODAR Core UI can detect your app and higlight active apps correctly.If your app includes multiple plugins with UI views, it is recommended to provide only the UI views relevant to that plugin in the
urls
attribute. This, again, ensures apps are correctly detected and highlighted in the UI.
Management Command Logger
When developing management commands for your apps, you may want to log certain
events while also ensuring relevant output is provided to the administrator
issuing a command. For this SODAR Core provides the
ManagementCommandLogger
class. It can be called like the standard Python
logger with shortcut commands such as info()
, debug()
etc. If you need
to access the actual Python logger being used, you can access it via
ManagementCommandLogger.logger
. Example of logger usage can be seen below.
from projectroles.management.logging import ManagementCommandLogger
logger = ManagementCommandLogger(__name__)
logger.info('Testing')
Note
The use of this logger class assumes your site sets up logging similarly to
the example site and the SODAR Django Site template, including the use of a
LOGGING_LEVEL
Django settings variable.
Hint
To disable redundant console output from commands using this logger in e.g.
your site’s test configuration, you can set the
LOGGING_DISABLE_CMD_OUTPUT
Django setting to True
.
Testing
SODAR Core provides a range of ready made testing classes and mixins for
different aspects of SODAR app testing, from user permissions to UI testing.
See projectroles.tests
for different base classes.
Test Settings
SODAR Core provides settings for configuring your UI tests, if using the base
UI test classes found in projectroles.tests.test_ui
. Default values for
these settings can be found in config/settings/test.py
. The settings are as
follows:
PROJECTROLES_TEST_UI_CHROME_OPTIONS
Options for Chrome through Selenium. Can be used to e.g. enable/disable headless testing mode.
PROJECTROLES_TEST_UI_WINDOW_SIZE
Custom browser window size.
PROJECTROLES_TEST_UI_WAIT_TIME
Maximum wait time for UI test operations
PROJECTROLES_TEST_UI_LEGACY_LOGIN
If set
True
, use the legacy UI login and redirect function for testing with different users. This can be used if e.g. issues with cookie-based logins are encountered.
Base Test Classes and Helpers
For base classes and mixins with useful helpers, see the projectroles.tests
modules. The test cases also provide useful examples on how to set up your own
tests.
Note
For REST API testing, SODAR Core uses separate base test classes for the
internal SODAR Core API, and the API views implemented in the actual site
built on SODAR Core. For the API views in your site, make sure to test them
using e.g. TestAPIViewsBase
and not TestCoreAPIViewsBase
.
Debugging
Debugging helpers and tips are detailed in this section.
Profiling Middleware
SODAR Core provides a cProfile using profiler for tracing back sources of page
loading slowdowns. To enable the profiler middleware, include
projectroles.middleware.ProfilerMiddleware
in MIDDLEWARE
under your site
configuration. It is recommended to use a settings variable for this similar to
the example site configuration, where PROJECTROLES_ENABLE_PROFILING
controls
this.
Once enabled, adding the ?prof
query string attribute to and URL displays
the profiling information.