django-orchestra-test/orchestra/contrib/saas
Santiago L 48ef1f21e3 Navigate through FK field to related model
Fix regression introduced by 7d975637d5
when there is a misunderstanding while replacing deprecated rel.to
2021-05-12 14:38:17 +02:00
..
backends Added nextcloud support 2017-06-09 08:28:44 +00:00
migrations Update migrations to include mandatory on_delete 2021-04-22 14:18:01 +02:00
services Navigate through FK field to related model 2021-05-12 14:38:17 +02:00
README.md Random fixes 2016-06-17 10:00:04 +00:00
__init__.py Added mailer 2015-05-04 21:52:53 +02:00
admin.py Dont delete lists when deleting domains 2016-04-15 09:56:10 +00:00
api.py Removed unused imports and patterns 2015-05-19 13:27:04 +00:00
apps.py Refactored dashboard icons and menu registration 2015-05-07 14:09:37 +00:00
fields.py Replace field.rel.to with field.remote_field.model 2021-04-22 14:44:47 +02:00
filters.py PEP8 compliance 2015-10-05 13:31:08 +00:00
forms.py DJ1.9 compat field.related 2016-04-30 15:10:39 +00:00
models.py Define on_delete argument for ForeignKey and OneToOneField 2021-04-22 10:28:00 +02:00
serializers.py Removed unused imports and patterns 2015-05-19 13:27:04 +00:00
settings.py Added nextcloud support 2017-06-09 08:28:44 +00:00
signals.py Added bounces mailbox to phplist SaaS service 2015-09-22 10:24:04 +00:00
validators.py Fixes on deployment 2015-10-03 19:50:24 +00:00

README.md

SaaS - Software as a Service

This app provides support for services that follow the SaaS model. Traditionally known as multi-site or multi-tenant web applications where a single installation of a CMS provides accounts for multiple isolated tenants.

Service declaration

Each service is defined by a SoftwareService subclass, you can find examples on the services module.

The minimal service declaration will be:

class DrupalService(SoftwareService):
    name = 'drupal'
    verbose_name = "Drupal"
    icon = 'orchestra/icons/apps/Drupal.png'
    site_domain = settings.SAAS_MOODLE_DOMAIN

Additional attributes can be used to further customize the service class to your needs.

Custom forms

If a service needs to keep track of additional information (other than a user/site name, is_active, custom_url, or database) an extra form and serializer should be provided. For example, WordPress requires to provide an email address for account creation, and the assigned blog ID is required for effectively identify the account for update and delete operations. In this case we provide two forms, one for account creation and another for change:

class WordPressForm(SaaSBaseForm):
    email = forms.EmailField(label=_("Email"),
        help_text=_("A new user will be created if the above email address is not in the database.<br>"
                    "The username and password will be mailed to this email address."))

class WordPressChangeForm(WordPressForm):
    blog_id = forms.IntegerField(label=("Blog ID"), widget=widgets.SpanWidget, required=False,
        help_text=_("ID of this blog used by WordPress, the only attribute that doesn't change."))

WordPressForm provides the email field, and WordPressChangeForm adds the blog_id on top of it. blog_id will be represented as a readonly field on the form (widget=widgets.SpanWidget), so no modification will be allowed.

Additionally, SaaSPasswordForm provides a password field for the common case when a password needs to be provided in order to create a new account. You can subclass SaaSPasswordForm or use it directly on the Service.form field.

Serializer for extra data

In case we need to save extra information of the service (email and blog_id in our current example) we should provide a serializer that serializes this bits of information into JSON format so they can be saved and retrieved from the database data field.

class WordPressDataSerializer(serializers.Serializer):
    email = serializers.EmailField(label=_("Email"))
    blog_id = serializers.IntegerField(label=_("Blog ID"), allow_null=True, required=False)

Now we have everything needed for declaring the WordPress service.

class WordPressService(SoftwareService):
    name = 'wordpress'
    verbose_name = "WordPress"
    form = WordPressForm
    change_form = WordPressChangeForm
    serializer = WordPressDataSerializer
    icon = 'orchestra/icons/apps/WordPress.png'
    change_readonly_fields = ('email', 'blog_id')
    site_domain = settings.SAAS_WORDPRESS_DOMAIN
    allow_custom_url = settings.SAAS_WORDPRESS_ALLOW_CUSTOM_URL

Notice that two optional forms can be provided form and change_form. When non of them is provided, SaaS will provide a default one for you. When only form is provided, it will be used for both, add view and change view. If both are provided, form will be used for the add view and change_form for the change view. This last option allows us to display the blog_id back to the user, only when we know its value (after creation).

change_readonly_fields is a tuple with the name of the fields that can not be edited once the service has been created.

allow_custom_url is a boolean flag that defines whether this service is allowed to have custom URL's (URL of any form) or not. In case it does, additional steps are required for interfacing with orchestra.contrib.websites, such as having an enabled website directive (WEBSITES_ENABLED_DIRECTIVES) that knows where the SaaS webapp is running, such as 'orchestra.contrib.websites.directives.WordPressSaaS'.

Backend

A backend class is required to interface with the web application and perform save() and delete() operations on it.

  • The more reliable way of interfacing with the application is by means of a CLI (e.g. Moodle), but not all CMS come with this tool.
  • The second preferable way is using some sort of networked API, possibly HTTP-based (e.g. gitLab). This is less reliable because additional moving parts are used underneath the interface; a busy web server can timeout our requests.
  • The least preferred way is interfacing with an HTTP-HTML interface designed for human consumption, really painful to implement but sometimes is the only way (e.g. WordPress).

Some applications do not support multi-tenancy by default, but we can hack the configuration file of such apps and generate table prefix or database name based on some property of the URL. Example of this services are moodle and phplist respectively.

Settings

Enabled services should be added into the SAAS_ENABLED_SERVICES settings tuple, providing its full module path, e.g. 'orchestra.contrib.saas.services.moodle.MoodleService'.

Parameters that should allow easy configuration on each deployment should be defined as settings. e.g. SAAS_WORDPRESS_DOMAIN. Take a look at the settings module.