Plugins logic inot a plugins app

This commit is contained in:
Marc Aymerich 2014-11-12 16:33:40 +00:00
parent 49b7be33e3
commit 971b1b6874
26 changed files with 212 additions and 148 deletions

23
TODO.md
View File

@ -169,17 +169,17 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* validate address.forward: if mailbox in account.mailboxes then: _("Please use mailboxes field") or consider removing mailbox support on forward (user@pangea.org instead) * validate address.forward: if mailbox in account.mailboxes then: _("Please use mailboxes field") or consider removing mailbox support on forward (user@pangea.org instead)
* remove order in account admin and others admininlines * remove ordering in account admin and others admininlines
* Databases.User add reverse M2M databases widget (like mailbox.addresses) * Databases.User add reverse M2M databases widget (like mailbox.addresses)
* Change permissions periodically on the web server, to ensure security * Change (correct) permissions periodically on the web server, to ensure security ?
* Root owned logs on user's home ? * Root owned logs on user's home ? yes
* reconsider binding webapps to systemusers (pangea multiple users wordpress-ftp, moodle-pangea, etc) * reconsider binding webapps to systemusers (pangea multiple users wordpress-ftp, moodle-pangea, etc)
* Secondary user home in /home/secondaryuser and simlink to /home/main/webapps/app so it can have private storage? * Secondary user home in /home/secondaryuser and simlink to /home/main/webapps/app so it can have private storage?
* Grant permissions like in webfaction * Grant permissions to systemusers, the problem of creating a related permission model is out of sync with the server-side. evaluate tradeoff
* Secondaryusers home should be under mainuser home. i.e. /home/mainuser/webapps/seconduser_webapp/ * Secondaryusers home should be under mainuser home. i.e. /home/mainuser/webapps/seconduser_webapp/
* Make one dedicated CGI user for each account only for CGI execution (fpm/fcgid). Different from the files owner, and without W permissions, so attackers can not inject backdors and malware. * Make one dedicated CGI user for each account only for CGI execution (fpm/fcgid). Different from the files owner, and without W permissions, so attackers can not inject backdors and malware.
@ -195,24 +195,13 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* domain validation parse named-checzone output to assign errors to fields * domain validation parse named-checzone output to assign errors to fields
* Directory Protection on webapp and use webapp path as base path (validate) * Directory Protection on webapp and use webapp path as base path (validate)
* User [Group] webapp/website option (validation) which overrides default mainsystemuser * User [Group] webapp/website option (validation) which overrides default mainsystemuser
* validate systemuser.home * validate systemuser.home
* Create plugin app
* Create options widget
* generic options fpm/fcgid/uwsgi webapps (num procs, idle io timeout)
* webapp backend option compatibility check? * webapp backend option compatibility check?
* Route help text with model name when selecting backend
* Service instance name when selecting content_type
* Address.forward mailbbox validate not available on mailboxes
* Miscellaneous service construct form for specific data, fields, validation, uniquenes.. etc (domain usecase) * Miscellaneous service construct form for specific data, fields, validation, uniquenes.. etc (domain usecase)
* miscellaneous.indentifier.endswith(('.org', '.es', '.cat'))

View File

@ -163,75 +163,6 @@ class ExtendedModelAdmin(ChangeViewActionsMixin, ChangeAddFieldsMixin, admin.Mod
return qs return qs
class SelectPluginAdminMixin(object):
plugin = None
plugin_field = None
def get_form(self, request, obj=None, **kwargs):
if obj:
self.form = getattr(obj, '%s_class' % self.plugin_field)().get_form()
else:
self.form = self.plugin.get_plugin(self.plugin_value)().get_form()
return super(SelectPluginAdminMixin, self).get_form(request, obj=obj, **kwargs)
def get_urls(self):
""" Hooks select account url """
urls = super(SelectPluginAdminMixin, self).get_urls()
opts = self.model._meta
info = opts.app_label, opts.model_name
select_urls = patterns("",
url("/select-plugin/$",
wrap_admin_view(self, self.select_plugin_view),
name='%s_%s_select_plugin' % info),
)
return select_urls + urls
def select_plugin_view(self, request):
opts = self.model._meta
context = {
'opts': opts,
'app_label': opts.app_label,
'field': self.plugin_field,
'field_name': opts.get_field_by_name(self.plugin_field)[0].verbose_name,
'plugin': self.plugin,
'plugins': self.plugin.get_plugins(),
}
template = 'admin/orchestra/select_plugin.html'
return render(request, template, context)
def add_view(self, request, form_url='', extra_context=None):
""" Redirects to select account view if required """
if request.user.is_superuser:
plugin_value = request.GET.get(self.plugin_field) or request.POST.get(self.plugin_field)
if plugin_value or len(self.plugin.get_plugins()) == 1:
self.plugin_value = plugin_value
if not plugin_value:
self.plugin_value = self.plugin.get_plugins()[0].get_plugin_name()
b = self.plugin_value
context = {
'title': _("Add new %s") % camel_case_to_spaces(self.plugin_value),
}
context.update(extra_context or {})
return super(SelectPluginAdminMixin, self).add_view(request, form_url=form_url,
extra_context=context)
return redirect('./select-plugin/?%s' % request.META['QUERY_STRING'])
def change_view(self, request, object_id, form_url='', extra_context=None):
obj = self.get_object(request, unquote(object_id))
plugin_value = getattr(obj, self.plugin_field)
context = {
'title': _("Change %s") % camel_case_to_spaces(plugin_value),
}
context.update(extra_context or {})
return super(SelectPluginAdminMixin, self).change_view(request, object_id,
form_url=form_url, extra_context=context)
def save_model(self, request, obj, form, change):
if not change:
setattr(obj, self.plugin_field, self.plugin_value)
obj.save()
class ChangePasswordAdminMixin(object): class ChangePasswordAdminMixin(object):
change_password_form = AdminPasswordChangeForm change_password_form = AdminPasswordChangeForm
change_user_password_template = 'admin/orchestra/change_password.html' change_user_password_template = 'admin/orchestra/change_password.html'

View File

@ -10,6 +10,14 @@ from orchestra.apps.accounts.admin import AccountAdminMixin
from .models import MiscService, Miscellaneous from .models import MiscService, Miscellaneous
from orchestra.apps.plugins.admin import SelectPluginAdminMixin, PluginAdapter
class MiscServicePlugin(PluginAdapter):
model = MiscService
name_field = 'name'
class MiscServiceAdmin(ExtendedModelAdmin): class MiscServiceAdmin(ExtendedModelAdmin):
list_display = ('name', 'verbose_name', 'num_instances', 'has_amount', 'is_active') list_display = ('name', 'verbose_name', 'num_instances', 'has_amount', 'is_active')
list_editable = ('has_amount', 'is_active') list_editable = ('has_amount', 'is_active')
@ -32,15 +40,38 @@ class MiscServiceAdmin(ExtendedModelAdmin):
return qs.annotate(models.Count('instances', distinct=True)) return qs.annotate(models.Count('instances', distinct=True))
class MiscellaneousAdmin(AccountAdminMixin, admin.ModelAdmin): class MiscellaneousAdmin(AccountAdminMixin, SelectPluginAdminMixin, admin.ModelAdmin):
list_display = ('service', 'amount', 'active', 'account_link') list_display = ('service', 'amount', 'active', 'account_link')
plugin_field = 'service'
plugin = MiscServicePlugin
def get_service(self, obj):
if obj is None:
return self.plugin.get_plugin(self.plugin_value)().instance
else:
return obj.service
def get_fields(self, request, obj=None): def get_fields(self, request, obj=None):
if obj is None: fields = ['account', 'description', 'is_active']
return ('service', 'account', 'description', 'amount', 'is_active') if obj is not None:
elif not obj.service.has_amount: fields = ['account_link', 'description', 'is_active']
return ('service', 'account_link', 'description', 'is_active') service = self.get_service(obj)
return ('service', 'account_link', 'description', 'amount', 'is_active') if service.has_amount:
fields.insert(-1, 'amount')
# if service.has_identifier:
# fields.insert(1, 'identifier')
return fields
def get_form(self, request, obj=None, **kwargs):
form = super(SelectPluginAdminMixin, self).get_form(request, obj=obj, **kwargs)
service = self.get_service(obj)
def clean_identifier(self, service=service):
validator = settings.MISCELLANEOUS_IDENTIFIER_VALIDATORS.get(service.name, None)
if validator:
validator(self.cleaned_data['identifier'])
form.clean_identifier = clean_identifier
return form
admin.site.register(MiscService, MiscServiceAdmin) admin.site.register(MiscService, MiscServiceAdmin)

View File

@ -14,9 +14,9 @@ class MiscService(models.Model):
help_text=_("Human readable name")) help_text=_("Human readable name"))
description = models.TextField(_("description"), blank=True, description = models.TextField(_("description"), blank=True,
help_text=_("Optional description")) help_text=_("Optional description"))
has_identifier = models.BooleanField(_("has identifier"), default=True, # has_identifier = models.BooleanField(_("has identifier"), default=True,
help_text=_("Designates if this service has a <b>unique text</b> field that " # help_text=_("Designates if this service has a <b>unique text</b> field that "
"identifies it or not.")) # "identifies it or not."))
has_amount = models.BooleanField(_("has amount"), default=False, has_amount = models.BooleanField(_("has amount"), default=False,
help_text=_("Designates whether this service has <tt>amount</tt> " help_text=_("Designates whether this service has <tt>amount</tt> "
"property or not.")) "property or not."))
@ -39,8 +39,8 @@ class Miscellaneous(models.Model):
related_name='instances') related_name='instances')
account = models.ForeignKey('accounts.Account', verbose_name=_("account"), account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
related_name='miscellaneous') related_name='miscellaneous')
identifier = NullableCharField(_("identifier"), max_length=256, null=True, unique=True, # identifier = NullableCharField(_("identifier"), max_length=256, null=True, unique=True,
blank=True, help_text=_("A unique identifier for this service.")) # help_text=_("A unique identifier for this service."))
description = models.TextField(_("description"), blank=True) description = models.TextField(_("description"), blank=True)
amount = models.PositiveIntegerField(_("amount"), default=1) amount = models.PositiveIntegerField(_("amount"), default=1)
is_active = models.BooleanField(_("active"), default=True, is_active = models.BooleanField(_("active"), default=True,
@ -51,6 +51,7 @@ class Miscellaneous(models.Model):
verbose_name_plural = _("miscellaneous") verbose_name_plural = _("miscellaneous")
def __unicode__(self): def __unicode__(self):
# return self.identifier or str(self.service)
return "{0}-{1}".format(str(self.service), str(self.account)) return "{0}-{1}".format(str(self.service), str(self.account))
@cached_property @cached_property
@ -61,8 +62,8 @@ class Miscellaneous(models.Model):
return self.is_active return self.is_active
def clean(self): def clean(self):
if self.identifier: # if self.identifier:
self.identifier = self.identifier.strip() # self.identifier = self.identifier.strip()
self.description = self.description.strip() self.description = self.description.strip()

View File

@ -3,7 +3,7 @@ from functools import partial
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.utils import plugins from orchestra.apps import plugins
from . import methods from . import methods

View File

@ -2,9 +2,10 @@ from django.contrib import admin
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ChangeViewActionsMixin, SelectPluginAdminMixin, ExtendedModelAdmin from orchestra.admin import ChangeViewActionsMixin, ExtendedModelAdmin
from orchestra.admin.utils import admin_colored, admin_link from orchestra.admin.utils import admin_colored, admin_link
from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin
from orchestra.apps.plugins.admin import SelectPluginAdminMixin
from . import actions from . import actions
from .methods import PaymentMethod from .methods import PaymentMethod

View File

@ -2,7 +2,7 @@ from dateutil import relativedelta
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from orchestra.utils import plugins from orchestra.apps import plugins
from orchestra.utils.functional import cached from orchestra.utils.functional import cached
from orchestra.utils.python import import_class from orchestra.utils.python import import_class

View File

@ -12,7 +12,7 @@ from django_iban.forms import IBANFormField
from django_iban.validators import IBANValidator, IBAN_COUNTRY_CODE_LENGTH from django_iban.validators import IBANValidator, IBAN_COUNTRY_CODE_LENGTH
from rest_framework import serializers from rest_framework import serializers
from orchestra.forms import PluginDataForm from orchestra.apps.plugins.forms import PluginDataForm
from .. import settings from .. import settings
from .options import PaymentMethod from .options import PaymentMethod

View File

@ -31,7 +31,7 @@ class PaymentSourceSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
def metadata(self): def metadata(self):
meta = super(PaymentSourceSerializer, self).metadata() meta = super(PaymentSourceSerializer, self).metadata()
meta['data'] = { meta['data'] = {
method.get_plugin_name(): method().get_serializer()().metadata() method.get_name(): method().get_serializer()().metadata()
for method in PaymentMethod.get_plugins() for method in PaymentMethod.get_plugins()
} }
return meta return meta

View File

@ -0,0 +1 @@
from .options import *

View File

@ -0,0 +1,108 @@
from django.conf.urls import patterns, url
from django.contrib.admin.utils import unquote
from django.shortcuts import render, redirect
from django.utils.text import camel_case_to_spaces
from django.utils.translation import ugettext_lazy as _
from orchestra.admin.utils import wrap_admin_view
from orchestra.utils.functional import cached
class SelectPluginAdminMixin(object):
plugin = None
plugin_field = None
def get_form(self, request, obj=None, **kwargs):
if obj:
self.form = getattr(obj, '%s_class' % self.plugin_field)().get_form()
else:
self.form = self.plugin.get_plugin(self.plugin_value)().get_form()
return super(SelectPluginAdminMixin, self).get_form(request, obj=obj, **kwargs)
def get_urls(self):
""" Hooks select account url """
urls = super(SelectPluginAdminMixin, self).get_urls()
opts = self.model._meta
info = opts.app_label, opts.model_name
select_urls = patterns("",
url("/select-plugin/$",
wrap_admin_view(self, self.select_plugin_view),
name='%s_%s_select_plugin' % info),
)
return select_urls + urls
def select_plugin_view(self, request):
opts = self.model._meta
context = {
'opts': opts,
'app_label': opts.app_label,
'field': self.plugin_field,
'field_name': opts.get_field_by_name(self.plugin_field)[0].verbose_name,
'plugin': self.plugin,
'plugins': self.plugin.get_plugins(),
}
template = 'admin/plugins/select_plugin.html'
return render(request, template, context)
def add_view(self, request, form_url='', extra_context=None):
""" Redirects to select account view if required """
if request.user.is_superuser:
plugin_value = request.GET.get(self.plugin_field) or request.POST.get(self.plugin_field)
if plugin_value or len(self.plugin.get_plugins()) == 1:
self.plugin_value = plugin_value
if not plugin_value:
self.plugin_value = self.plugin.get_plugins()[0].get_name()
context = {
'title': _("Add new %s") % camel_case_to_spaces(self.plugin_value),
}
context.update(extra_context or {})
return super(SelectPluginAdminMixin, self).add_view(request, form_url=form_url,
extra_context=context)
return redirect('./select-plugin/?%s' % request.META['QUERY_STRING'])
def change_view(self, request, object_id, form_url='', extra_context=None):
obj = self.get_object(request, unquote(object_id))
plugin_value = getattr(obj, self.plugin_field)
context = {
'title': _("Change %s") % camel_case_to_spaces(str(plugin_value)),
}
context.update(extra_context or {})
return super(SelectPluginAdminMixin, self).change_view(request, object_id,
form_url=form_url, extra_context=context)
def save_model(self, request, obj, form, change):
if not change:
setattr(obj, self.plugin_field, self.plugin_value)
obj.save()
class PluginAdapter(object):
""" Adapter class for using model classes as plugins """
model = None
name_field = None
def __init__(self, instance):
self.instance = instance
@classmethod
@cached
def get_plugins(cls):
plugins = []
for instance in cls.model.objects.filter(is_active=True):
plugins.append(cls(instance))
return plugins
@classmethod
def get_plugin(cls, name):
return cls(cls.model.objects.get(**{cls.name_field:name}))
@property
def verbose_name(self):
return self.instance.verbose_name or str(getattr(self.instance, self.name_field))
def get_name(self):
return getattr(self.instance, self.name_field)
def __call__(self):
return self

View File

@ -0,0 +1,27 @@
from django import forms
class PluginDataForm(forms.ModelForm):
data = forms.CharField(widget=forms.HiddenInput, required=False)
def __init__(self, *args, **kwargs):
super(PluginDataForm, self).__init__(*args, **kwargs)
# TODO remove it well
try:
self.fields[self.plugin_field].widget = forms.HiddenInput()
except KeyError:
pass
instance = kwargs.get('instance')
if instance:
for field in self.declared_fields:
initial = self.fields[field].initial
self.fields[field].initial = instance.data.get(field, initial)
def clean(self):
data = {}
for field in self.declared_fields:
try:
data[field] = self.cleaned_data[field]
except KeyError:
data[field] = self.data[field]
self.cleaned_data['data'] = data

View File

@ -1,4 +1,4 @@
from .functional import cached from orchestra.utils.functional import cached
class Plugin(object): class Plugin(object):
@ -8,7 +8,7 @@ class Plugin(object):
icon = None icon = None
@classmethod @classmethod
def get_plugin_name(cls): def get_name(cls):
return cls.__name__ return cls.__name__
@classmethod @classmethod
@ -19,7 +19,7 @@ class Plugin(object):
@cached @cached
def get_plugin(cls, name): def get_plugin(cls, name):
for plugin in cls.get_plugins(): for plugin in cls.get_plugins():
if plugin.get_plugin_name() == name: if plugin.get_name() == name:
return plugin return plugin
raise KeyError('This plugin is not registered') raise KeyError('This plugin is not registered')
@ -30,14 +30,14 @@ class Plugin(object):
if verbose[0]: if verbose[0]:
return cls.verbose_name return cls.verbose_name
else: else:
return cls.get_plugin_name() return cls.get_name()
@classmethod @classmethod
def get_plugin_choices(cls): def get_plugin_choices(cls):
choices = [] choices = []
for plugin in cls.get_plugins(): for plugin in cls.get_plugins():
verbose = plugin.get_verbose_name() verbose = plugin.get_verbose_name()
choices.append((plugin.get_plugin_name(), verbose)) choices.append((plugin.get_name(), verbose))
return sorted(choices, key=lambda e: e[1]) return sorted(choices, key=lambda e: e[1])

View File

@ -18,9 +18,9 @@
<div class="dashboard-module-content"> <div class="dashboard-module-content">
<ul class="fluent-dashboard-appiconlist clearfix" style="padding: 0"> <ul class="fluent-dashboard-appiconlist clearfix" style="padding: 0">
{% for plugin in plugins %} {% for plugin in plugins %}
<li><a class="fluent-dashboard-icon" href="../?{{ field }}={{ plugin.get_plugin_name }}&{{ request.META.QUERY_STRING }}"> <li><a class="fluent-dashboard-icon" href="../?{{ field }}={{ plugin.get_name }}&{{ request.META.QUERY_STRING }}">
<img src="{% static plugin.icon %}" width="48" height="48" alt="{{ plugin.get_name }}"></a> <img src="{% static plugin.icon %}" width="48" height="48" alt="{{ plugin.get_name }}"></a>
<a class="fluent-dashboard-icon-caption" href="../?{{ field }}={{ plugin.get_plugin_name }}&{{ request.META.QUERY_STRING }}">{{ plugin.verbose_name }}</a></li> <a class="fluent-dashboard-icon-caption" href="../?{{ field }}={{ plugin.get_name }}&{{ request.META.QUERY_STRING }}">{{ plugin.verbose_name }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -28,7 +28,7 @@
{% else %} {% else %}
<ul> <ul>
{% for plugin in plugins %} {% for plugin in plugins %}
<li><a style="font-size:small;" href="../?{{ field }}={{ plugin.get_plugin_name }}&{{ request.META.QUERY_STRING }}">{{ plugin.verbose_name }}</<a></li> <li><a style="font-size:small;" href="../?{{ field }}={{ plugin.get_name }}&{{ request.META.QUERY_STRING }}">{{ plugin.verbose_name }}</<a></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}

View File

@ -1,7 +1,7 @@
from django.contrib import admin from django.contrib import admin
from orchestra.admin import SelectPluginAdminMixin
from orchestra.apps.accounts.admin import AccountAdminMixin from orchestra.apps.accounts.admin import AccountAdminMixin
from orchestra.apps.plugins.admin import SelectPluginAdminMixin
from .models import SaaS from .models import SaaS
from .services import SoftwareService from .services import SoftwareService

View File

@ -3,8 +3,8 @@ from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from orchestra.apps.plugins.forms import PluginDataForm
from orchestra.core import validators from orchestra.core import validators
from orchestra.forms import PluginDataForm
from .options import SoftwareService from .options import SoftwareService

View File

@ -1,7 +1,7 @@
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.forms import PluginDataForm from orchestra.apps.plugins.forms import PluginDataForm
from .options import SoftwareService from .options import SoftwareService

View File

@ -1,7 +1,7 @@
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.forms import PluginDataForm from orchestra.apps.plugins.forms import PluginDataForm
from .options import SoftwareService from .options import SoftwareService

View File

@ -1,7 +1,7 @@
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.forms import PluginDataForm from orchestra.apps.plugins.forms import PluginDataForm
from .options import SoftwareService from .options import SoftwareService

View File

@ -1,7 +1,7 @@
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.forms import PluginDataForm from orchestra.apps.plugins.forms import PluginDataForm
from .options import SoftwareService from .options import SoftwareService

View File

@ -2,7 +2,7 @@ from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.utils import plugins from orchestra.apps import plugins
from orchestra.utils.functional import cached from orchestra.utils.functional import cached
from orchestra.utils.python import import_class from orchestra.utils.python import import_class

View File

@ -1,7 +1,7 @@
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.forms import PluginDataForm from orchestra.apps.plugins.forms import PluginDataForm
from .options import SoftwareService from .options import SoftwareService

View File

@ -2,7 +2,7 @@ from django import forms
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.forms import PluginDataForm from orchestra.apps.plugins.forms import PluginDataForm
from .options import SoftwareService from .options import SoftwareService

View File

@ -7,7 +7,7 @@ from django.contrib.contenttypes.models import ContentType
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.utils import plugins from orchestra.apps import plugins
from orchestra.utils.humanize import text2int from orchestra.utils.humanize import text2int
from orchestra.utils.python import AttrDict from orchestra.utils.python import AttrDict

View File

@ -89,6 +89,7 @@ INSTALLED_APPS = (
'orchestra.apps.miscellaneous', 'orchestra.apps.miscellaneous',
'orchestra.apps.bills', 'orchestra.apps.bills',
'orchestra.apps.payments', 'orchestra.apps.payments',
'orchestra.apps.plugins',
# Third-party apps # Third-party apps
'django_extensions', 'django_extensions',

View File

@ -6,32 +6,6 @@ from .. import settings
from ..core.validators import validate_password from ..core.validators import validate_password
class PluginDataForm(forms.ModelForm):
data = forms.CharField(widget=forms.HiddenInput, required=False)
def __init__(self, *args, **kwargs):
super(PluginDataForm, self).__init__(*args, **kwargs)
# TODO remove it well
try:
self.fields[self.plugin_field].widget = forms.HiddenInput()
except KeyError:
pass
instance = kwargs.get('instance')
if instance:
for field in self.declared_fields:
initial = self.fields[field].initial
self.fields[field].initial = instance.data.get(field, initial)
def clean(self):
data = {}
for field in self.declared_fields:
try:
data[field] = self.cleaned_data[field]
except KeyError:
data[field] = self.data[field]
self.cleaned_data['data'] = data
class UserCreationForm(forms.ModelForm): class UserCreationForm(forms.ModelForm):
""" """
A form that creates a user, with no privileges, from the given username and A form that creates a user, with no privileges, from the given username and