from __future__ import absolute_import
import logging
from autoslug import AutoSlugField
from django.conf import settings
from django.contrib.auth.models import Group
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from six import python_2_unicode_compatible
from .base import (
form_element_plugin_registry,
form_handler_plugin_registry,
form_wizard_handler_plugin_registry,
get_registered_form_element_plugins,
get_registered_form_handler_plugins,
get_registered_form_wizard_handler_plugins,
)
from .constants import DEFAULT_WIZARD_TYPE, WIZARD_TYPES
__title__ = "fobi.models"
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
__copyright__ = "2014-2019 Artur Barseghyan"
__license__ = "GPL 2.0/LGPL 2.1"
__all__ = (
# Plugins
"AbstractPluginModel",
"FormElement",
"FormHandler",
"FormWizardHandler",
# Entries
"AbstractFormWizardPluginEntry",
"AbstractPluginEntry",
"BaseAbstractPluginEntry",
"FormElementEntry",
"FormEntry",
"FormFieldsetEntry",
"FormHandlerEntry",
"FormWizardEntry",
"FormWizardFormEntry",
"FormWizardHandlerEntry",
)
logger = logging.getLogger(__name__)
# ****************************************************************************
# **************** Safe User import for Django > 1.5, < 1.8 ******************
# ****************************************************************************
AUTH_USER_MODEL = settings.AUTH_USER_MODEL
# ****************************************************************************
# ****************************************************************************
# ****************************************************************************
# ****************************************************************************
# ****************************************************************************
# ******************************* Plugin models ******************************
# ****************************************************************************
# ****************************************************************************
[docs]@python_2_unicode_compatible
class AbstractPluginModel(models.Model):
"""Abstract plugin model.
Used when ``fobi.settings.RESTRICT_PLUGIN_ACCESS`` is set to True.
:Properties:
- `plugin_uid` (str): Plugin UID.
- `users` (django.contrib.auth.models.User): White list of the users
allowed to use the plugin.
- `groups` (django.contrib.auth.models.Group): White list of the
user groups allowed to use the plugin.
"""
# plugin_uid = models.CharField(_("Plugin UID"), max_length=255,
# unique=True, editable=False)
users = models.ManyToManyField(
AUTH_USER_MODEL, verbose_name=_("User"), blank=True
)
groups = models.ManyToManyField(Group, verbose_name=_("Group"), blank=True)
# def __init__(self, *args, **kwargs):
# """
# Add choices.
# """
# super(AbstractPluginModel, self).__init__(*args, **kwargs)
# plugin_uid = self._meta.get_field('plugin_uid')
# plugin_uid._choices = self.get_registered_plugins()
[docs] def get_registered_plugins(self):
"""Get registered plugins."""
raise NotImplementedError(
"You should implement ``get_registered_plugins`` method!"
)
def __str__(self):
return "{0} ({1})".format(
dict(self.get_registered_plugins()).get(self.plugin_uid, ""),
self.plugin_uid,
)
[docs] def plugin_uid_code(self):
"""Plugin uid code.
Mainly used in admin.
"""
return self.plugin_uid
plugin_uid_code.allow_tags = True
plugin_uid_code.short_description = _("UID")
[docs] def plugin_uid_admin(self):
"""Plugin uid admin.
Mainly used in admin.
"""
return self.__str__()
plugin_uid_admin.allow_tags = True
plugin_uid_admin.short_description = _("Plugin")
[docs] def groups_list(self):
"""Groups list.
Flat list (comma separated string) of groups allowed to use the
plugin. Used in Django admin.
:return string:
"""
return ", ".join([g.name for g in self.groups.all()])
groups_list.allow_tags = True
groups_list.short_description = _("Groups")
[docs] def users_list(self):
"""Users list.
Flat list (comma separated string) of users allowed to use the
plugin. Used in Django admin.
:return string:
"""
return ", ".join([u.get_username() for u in self.users.all()])
users_list.allow_tags = True
users_list.short_description = _("Users")
# *****************************************************************************
# *****************************************************************************
# ******************************** Entry models *******************************
# *****************************************************************************
# *****************************************************************************
[docs]@python_2_unicode_compatible
class FormWizardEntry(models.Model):
"""Form wizard entry."""
user = models.ForeignKey(
AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE
)
name = models.CharField(_("Name"), max_length=255)
title = models.CharField(
_("Title"),
max_length=255,
null=True,
blank=True,
help_text=_("Shown in templates if available."),
)
slug = AutoSlugField(
populate_from="name", verbose_name=_("Slug"), unique=True
)
is_public = models.BooleanField(
_("Is public?"),
default=False,
help_text=_("Makes your form wizard visible to the public."),
)
is_cloneable = models.BooleanField(
_("Is cloneable?"),
default=False,
help_text=_("Makes your form wizard cloneable by other users."),
)
success_page_title = models.CharField(
_("Success page title"),
max_length=255,
null=True,
blank=True,
help_text=_(
"Custom message title to display after valid form is " "submitted"
),
)
success_page_message = models.TextField(
_("Success page body"),
null=True,
blank=True,
help_text=_(
"Custom message text to display after valid form is " "submitted"
),
)
show_all_navigation_buttons = models.BooleanField(
_("Show all navigation buttons?"),
default=False,
help_text=_("Show all navigation buttons."),
)
# action = models.CharField(
# _("Action"), max_length=255, null=True, blank=True,
# help_text=_("Custom form action; don't fill this field, unless "
# "really necessary.")
# )
wizard_type = models.CharField(
_("Type"),
max_length=255,
null=False,
blank=False,
choices=WIZARD_TYPES,
default=DEFAULT_WIZARD_TYPE,
help_text=_("Type of the form wizard."),
)
created = models.DateTimeField(
_("Created"), null=True, blank=True, auto_now_add=True
)
updated = models.DateTimeField(
_("Updated"), null=True, blank=True, auto_now=True
)
class Meta(object):
"""Meta class."""
verbose_name = _("Form wizard entry")
verbose_name_plural = _("Form wizard entries")
unique_together = (
("user", "slug"),
("user", "name"),
)
def __str__(self):
return self.name
[docs] def get_absolute_url(self):
"""Get absolute URL.
Absolute URL, which goes to the form-wizard view view.
:return string:
"""
return reverse(
"fobi.view_form_wizard_entry",
kwargs={"form_wizard_entry_slug": self.slug},
)
[docs]@python_2_unicode_compatible
class FormEntry(models.Model):
"""Form entry."""
user = models.ForeignKey(
AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE
)
name = models.CharField(_("Name"), max_length=255)
title = models.CharField(
_("Title"),
max_length=255,
null=True,
blank=True,
help_text=_("Shown in templates if available."),
)
slug = AutoSlugField(
populate_from="name", verbose_name=_("Slug"), unique=True
)
is_public = models.BooleanField(
_("Public?"),
default=False,
help_text=_("Makes your form visible to the public."),
)
active_date_from = models.DateTimeField(
_("Active from"),
null=True,
blank=True,
help_text=_(
"Date and time when the form becomes active "
"in the format: 'YYYY-MM-DD HH:MM'. "
"Leave it blank to activate immediately."
),
)
active_date_to = models.DateTimeField(
_("Active until"),
null=True,
blank=True,
help_text=_(
"Date and time when the form becomes inactive "
"in the format: 'YYYY-MM-DD HH:MM'. "
"Leave it blank to keep active forever."
),
)
inactive_page_title = models.CharField(
_("Inactive form page title"),
max_length=255,
null=True,
blank=True,
help_text=_("Custom message title to display if form is inactive."),
)
inactive_page_message = models.TextField(
_("Inactive form page body"),
null=True,
blank=True,
help_text=_("Custom message text to display if form is inactive."),
)
is_cloneable = models.BooleanField(
_("Cloneable?"),
default=False,
help_text=_("Makes your form cloneable by other users."),
)
# position = models.PositiveIntegerField(
# _("Position"), null=True, blank=True
# )
success_page_title = models.CharField(
_("Success page title"),
max_length=255,
null=True,
blank=True,
help_text=_(
"Custom message title to display after valid form is " "submitted"
),
)
success_page_message = models.TextField(
_("Success page body"),
null=True,
blank=True,
help_text=_(
"Custom message text to display after valid form is " "submitted"
),
)
action = models.CharField(
_("Action"),
max_length=255,
null=True,
blank=True,
help_text=_(
"Custom form action; don't fill this field, unless really "
"necessary."
),
)
created = models.DateTimeField(
_("Created"), null=True, blank=True, auto_now_add=True
)
updated = models.DateTimeField(
_("Updated"), null=True, blank=True, auto_now=True
)
class Meta(object):
"""Meta class."""
verbose_name = _("Form entry")
verbose_name_plural = _("Form entries")
unique_together = (
("user", "slug"),
("user", "name"),
)
def __str__(self):
return self.name
@property
def is_active(self):
active_from_ok = True
active_to_ok = True
now = timezone.now()
if self.active_date_from and now < self.active_date_from:
active_from_ok = False
if self.active_date_to and now > self.active_date_to:
active_to_ok = False
if active_from_ok and active_to_ok:
return True
else:
return False
[docs] def get_absolute_url(self):
"""Get absolute URL.
Absolute URL, which goes to the form-entry view view page.
:return string:
"""
return reverse(
"fobi.view_form_entry", kwargs={"form_entry_slug": self.slug}
)
[docs]@python_2_unicode_compatible
class FormWizardFormEntry(models.Model):
"""Form wizard form entry.
A coupling point between `FormWizardEntry` and `FormEntry`."""
form_wizard_entry = models.ForeignKey(
FormWizardEntry,
verbose_name=_("Form wizard entry"),
null=False,
blank=False,
on_delete=models.CASCADE,
)
form_entry = models.ForeignKey(
FormEntry,
verbose_name=_("Form entry"),
null=False,
blank=False,
on_delete=models.CASCADE,
)
position = models.PositiveIntegerField(_("Position"), null=True, blank=True)
class Meta(object):
"""Meta class."""
abstract = False
verbose_name = _("Form wizard form entry")
verbose_name_plural = _("Form wizard form entries")
ordering = ["position"]
unique_together = (("form_wizard_entry", "form_entry"),)
def __str__(self):
return "{0} - {1}".format(self.form_wizard_entry, self.form_entry)
[docs]@python_2_unicode_compatible
class FormFieldsetEntry(models.Model):
"""Form fieldset entry."""
form_entry = models.ForeignKey(
FormEntry,
verbose_name=_("Form"),
null=True,
blank=True,
on_delete=models.CASCADE,
)
name = models.CharField(_("Name"), max_length=255)
is_repeatable = models.BooleanField(
_("Is repeatable?"),
default=False,
help_text=_("Makes your form fieldset repeatable."),
)
class Meta(object):
"""Meta class."""
verbose_name = _("Form fieldset entry")
verbose_name_plural = _("Form fieldset entries")
unique_together = (("form_entry", "name"),)
def __str__(self):
return self.name
[docs]@python_2_unicode_compatible
class BaseAbstractPluginEntry(models.Model):
"""Base for AbstractPluginEntry.
:Properties:
- `plugin_data` (str): JSON formatted string with plugin data.
"""
plugin_data = models.TextField(
verbose_name=_("Plugin data"), null=True, blank=True
)
[docs] class Meta(object):
"""Meta class."""
abstract = True
def __str__(self):
return "{0} plugin for user {1}".format(
self.plugin_uid, self.entry_user
)
@property
def entry_user(self):
"""Get user from the parent container."""
raise NotImplementedError(
"You should implement ``entry_user`` property!"
)
[docs] def get_registered_plugins(self):
"""Get registered plugins."""
raise NotImplementedError(
"You should implement ``get_registered_plugins`` method!"
)
[docs] def get_registry(self):
"""Get registry."""
raise NotImplementedError(
"You should implement ``get_registry`` method!"
)
[docs] def plugin_uid_code(self):
"""Plugin uid code.
Mainly used in admin.
"""
return self.plugin_uid
plugin_uid_code.allow_tags = True
plugin_uid_code.short_description = _("UID")
[docs] def plugin_name(self):
"""Plugin name."""
return dict(self.get_registered_plugins()).get(self.plugin_uid, "")
[docs] def get_plugin(self, fetch_related_data=False, request=None):
"""Get plugin.
Gets the plugin class (by ``plugin_uid`` property), makes an instance
of it, serves the data stored in ``plugin_data`` field (if available).
Once all is done, plugin is ready to be rendered.
:param bool fetch_related_data: When set to True, plugin is told to
re-fetch all related data (stored in models or other sources).
:return fobi.base.BasePlugin: Subclass of ``fobi.base.BasePlugin``.
"""
# Getting form element plugin from registry.
registry = self.get_registry()
cls = registry.get(self.plugin_uid)
if not cls:
# No need to log here, since already logged in registry.
if registry.fail_on_missing_plugin:
err_msg = registry.plugin_not_found_error_message.format(
self.plugin_uid, registry.__class__
)
raise registry.plugin_not_found_exception_cls(err_msg)
return None
# Creating plugin instance.
plugin = cls(user=self.entry_user)
# So that plugin has the request object
plugin.request = request
return plugin.process(
self.plugin_data, fetch_related_data=fetch_related_data
)
[docs]class AbstractPluginEntry(BaseAbstractPluginEntry):
"""Abstract plugin entry.
:Properties:
- `form_entry` (fobi.models.FormEntry): Form to which the field plugin
belongs to.
- `plugin_uid` (str): Plugin UID.
- `plugin_data` (str): JSON formatted string with plugin data.
"""
form_entry = models.ForeignKey(
FormEntry, verbose_name=_("Form"), on_delete=models.CASCADE
)
[docs] class Meta(object):
"""Meta class."""
abstract = True
# def __init__(self, *args, **kwargs):
# """
# Add choices.
# """
# super(AbstractPluginEntry, self).__init__(*args, **kwargs)
# plugin_uid = self._meta.get_field('plugin_uid')
# plugin_uid._choices = self.get_registered_plugins()
@property
def entry_user(self):
"""Get user."""
return self.form_entry.user
[docs]class FormElementEntry(AbstractPluginEntry):
"""Form field entry.
:Properties:
- `form` (fobi.models.FormEntry): Form to which the field plugin
belongs to.
- `plugin_uid` (str): Plugin UID.
- `plugin_data` (str): JSON formatted string with plugin data.
- `form_fieldset_entry`: Fieldset.
- `position` (int): Entry position.
"""
plugin_uid = models.CharField(
_("Plugin name"),
max_length=255,
# choices=get_registered_form_element_plugins()
)
form_fieldset_entry = models.ForeignKey(
FormFieldsetEntry,
verbose_name=_("Form fieldset"),
null=True,
blank=True,
on_delete=models.CASCADE,
)
position = models.PositiveIntegerField(_("Position"), null=True, blank=True)
class Meta(object):
"""Meta class."""
abstract = False
verbose_name = _("Form element entry")
verbose_name_plural = _("Form element entries")
ordering = ["position"]
[docs] def get_registered_plugins(self):
"""Gets registered plugins."""
return get_registered_form_element_plugins()
[docs] def get_registry(self):
"""Get registry."""
return form_element_plugin_registry
[docs]class FormHandlerEntry(AbstractPluginEntry):
"""Form handler entry.
:Properties:
- `form_entry` (fobi.models.FormEntry): Form to which the handler
plugin belongs to.
- `plugin_uid` (str): Plugin UID.
- `plugin_data` (str): JSON formatted string with plugin data.
"""
plugin_uid = models.CharField(
_("Plugin name"),
max_length=255,
# choices=get_registered_form_handler_plugins()
)
class Meta(object):
"""Meta class."""
abstract = False
verbose_name = _("Form handler entry")
verbose_name_plural = _("Form handler entries")
[docs] def get_registered_plugins(self):
"""Gets registered plugins."""
return get_registered_form_handler_plugins()
[docs] def get_registry(self):
"""Get registry."""
return form_handler_plugin_registry
[docs]class AbstractFormWizardPluginEntry(BaseAbstractPluginEntry):
"""Abstract form wizard plugin entry.
:Properties:
- `form_entry` (fobi.models.FormWizardEntry): FormWizard to which the
plugin belongs to.
- `plugin_uid` (str): Plugin UID.
- `plugin_data` (str): JSON formatted string with plugin data.
"""
form_wizard_entry = models.ForeignKey(
FormWizardEntry, verbose_name=_("Form wizard"), on_delete=models.CASCADE
)
[docs] class Meta(object):
"""Meta class."""
abstract = True
@property
def entry_user(self):
"""Get user."""
return self.form_wizard_entry.user
[docs]class FormWizardHandlerEntry(AbstractFormWizardPluginEntry):
"""Form wizard handler entry.
:Properties:
- `form_wizard_entry` (fobi.models.FormWizardEntry): FormWizard to
which the handler plugin belongs to.
- `plugin_uid` (str): Plugin UID.
- `plugin_data` (str): JSON formatted string with plugin data.
"""
plugin_uid = models.CharField(
_("Plugin name"),
max_length=255,
# choices=get_registered_form_handler_plugins()
)
class Meta(object):
"""Meta class."""
abstract = False
verbose_name = _("Form wizard handler entry")
verbose_name_plural = _("Form wizard handler entries")
[docs] def get_registered_plugins(self):
"""Gets registered plugins."""
return get_registered_form_wizard_handler_plugins()
[docs] def get_registry(self):
"""Get registry."""
return form_wizard_handler_plugin_registry