A piece of refactoring.

This commit is contained in:
Antoine Nguyen
2014-03-26 14:38:07 +01:00
parent 8303a2e4d9
commit 221a433eb0
16 changed files with 391 additions and 120 deletions
+5 -3
View File
@@ -1,7 +1,9 @@
from .domain import DomainFormGeneral, DomainFormOptions, DomainForm
from .domain import (
DomainFormGeneral, DomainFormOptions, DomainForm, DomainWizard
)
from .account import (
AccountFormGeneral, AccountFormMail, AccountPermissionsForm,
AccountForm
AccountForm, AccountWizard
)
from .alias import AliasForm
from .forward import ForwardForm
@@ -13,5 +15,5 @@ __all__ = [
'AccountFormGeneral', 'AccountFormMail', 'AccountPermissionsForm',
'AccountForm', 'AliasForm', 'ImportDataForm', 'ImportIdentitiesForm',
'ExportDataForm', 'ExportDomainsForm', 'ExportIdentitiesForm',
'ForwardForm'
'ForwardForm', 'DomainWizard', 'AccountWizard'
]
+23 -1
View File
@@ -6,7 +6,7 @@ from modoboa.lib.exceptions import PermDeniedException, Conflict, NotFound
from modoboa.lib.permissions import get_account_roles
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.formutils import (
DomainNameField, DynamicForm, TabForms, YesNoField
DomainNameField, DynamicForm, TabForms, WizardForm
)
from modoboa.core.models import User
from modoboa.extensions.admin.models import (
@@ -370,3 +370,25 @@ class AccountForm(TabForms):
return
for f in self.forms[2:]:
f["instance"].save()
class AccountWizard(WizardForm):
"""Account creation wizard.
"""
def __init__(self, request):
super(AccountWizard, self).__init__(request)
self.add_step(
AccountFormGeneral, _("General"), new_args=[request.user]
)
self.add_step(
AccountFormMail, _("Mail"), formtpl="admin/mailform.html"
)
def done(self):
from modoboa.lib.webutils import render_to_json_response
account = self.first_step.form.save()
account.post_create(self.request.user)
mailform = self.steps[1].form
mailform.save(self.request.user, account)
return render_to_json_response(_("Account created"))
+31 -2
View File
@@ -2,10 +2,11 @@ from django import forms
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import Conflict
from modoboa.lib.exceptions import ModoboaException, Conflict
from modoboa.lib.formutils import (
DomainNameField, YesNoField, DynamicForm, TabForms
DomainNameField, YesNoField, WizardForm, DynamicForm, TabForms
)
from modoboa.lib.webutils import render_to_json_response
from modoboa.core.models import User
from modoboa.extensions.admin.models import (
Domain, DomainAlias, Mailbox, Alias, Quota
@@ -269,3 +270,31 @@ class DomainForm(TabForms):
self.forms[0]['instance'].save(user, domalias_post_create=True)
for f in self.forms[1:]:
f["instance"].save(user)
class DomainWizard(WizardForm):
"""Domain creation wizard.
"""
def __init__(self, request):
super(DomainWizard, self).__init__(request)
self.add_step(
DomainFormGeneral, _("General"),
formtpl="admin/domain_general_form.html"
)
self.add_step(
DomainFormOptions, _("Options"),
formtpl="admin/domain_options_form.html",
new_args=[self.request.user]
)
def done(self):
genform = self.first_step.form
domain = genform.save(self.request.user)
domain.post_create(self.request.user)
try:
self.steps[1].form.save(self.request.user, domain)
except ModoboaException:
from django.db import transaction
transaction.rollback()
raise
return render_to_json_response(_("Domain created"))
+8 -40
View File
@@ -12,15 +12,14 @@ from modoboa.lib import parameters, events
from modoboa.lib.webutils import (
_render_to_string, render_to_json_response
)
from modoboa.lib.formutils import CreationWizard
from modoboa.lib.exceptions import (
ModoboaException, PermDeniedException, BadRequest
PermDeniedException
)
from modoboa.lib.templatetags.lib_tags import pagination_bar
from modoboa.extensions.admin.lib import get_sort_order, get_listing_page
from modoboa.extensions.admin.models import Domain, Mailbox
from modoboa.extensions.admin.forms import (
DomainForm, DomainFormGeneral, DomainFormOptions
DomainForm
)
from modoboa.extensions.admin.lib import get_domains
@@ -91,50 +90,19 @@ def domains_list(request):
@transaction.commit_on_success
@reversion.create_revision()
def newdomain(request, tplname="common/wizard_forms.html"):
from modoboa.extensions.admin.forms import DomainWizard
events.raiseEvent("CanCreate", request.user, "domains")
cwizard = CreationWizard()
cwizard.add_step(DomainFormGeneral, _("General"),
[dict(classes="btn-inverse next", label=_("Next"))],
formtpl="admin/domain_general_form.html")
cwizard.add_step(
DomainFormOptions, _("Options"),
[dict(classes="btn-primary submit", label=_("Create")),
dict(classes="btn-inverse prev", label=_("Previous"))],
formtpl="admin/domain_options_form.html",
new_args=[request.user]
)
wizard = DomainWizard(request)
if request.method == "POST":
retcode, data = cwizard.validate_step(request)
if retcode == -1:
raise BadRequest(data)
if retcode == 1:
return render_to_json_response(
{'title': cwizard.get_title(data + 1), 'stepid': data}
)
if retcode == 2:
genform = cwizard.steps[0]["form"]
domain = genform.save(request.user)
domain.post_create(request.user)
try:
cwizard.steps[1]["form"].save(request.user, domain)
except ModoboaException as e:
transaction.rollback()
raise
return render_to_json_response(_("Domain created"))
return render_to_json_response({
'stepid': data, 'form_errors': cwizard.errors
}, status=400)
return wizard.validate_step()
ctx = {"title": _("New domain"),
"action_label": _("Create"),
"action_classes": "submit",
"action": reverse(newdomain),
"formid": "domform"}
cwizard.create_forms()
ctx.update(steps=cwizard.steps)
ctx.update(subtitle="1. %s" % cwizard.steps[0]['title'])
wizard.create_forms()
ctx.update(wizard=wizard)
return render(request, tplname, ctx)
+7 -33
View File
@@ -14,7 +14,7 @@ from modoboa.lib.exceptions import (
from modoboa.lib.webutils import (
_render_to_string, render_to_json_response
)
from modoboa.lib.formutils import CreationWizard
from modoboa.lib.formutils import WizardForm
from modoboa.lib.templatetags.lib_tags import pagination_bar
from modoboa.core.models import User
from modoboa.extensions.admin.models import Mailbox, Domain
@@ -125,43 +125,17 @@ def newaccount(request, tplname='common/wizard_forms.html'):
doesn't work very well with nested functions. Need to wait for
django 1.6 and atomicity.
"""
cwizard = CreationWizard()
cwizard.add_step(AccountFormGeneral, _("General"),
[dict(classes="btn-inverse next", label=_("Next"))],
new_args=[request.user])
cwizard.add_step(AccountFormMail, _("Mail"),
[dict(classes="btn-primary submit", label=_("Create")),
dict(classes="btn-inverse prev", label=_("Previous"))],
formtpl="admin/mailform.html")
from modoboa.extensions.admin.forms import AccountWizard
wizard = AccountWizard(request)
if request.method == "POST":
retcode, data = cwizard.validate_step(request)
if retcode == -1:
raise BadRequest(data)
if retcode == 1:
return render_to_json_response(
{'title': cwizard.get_title(data + 1), 'stepid': data}
)
if retcode == 2:
genform = cwizard.steps[0]["form"]
account = genform.save()
account.post_create(request.user)
mailform = cwizard.steps[1]["form"]
mailform.save(request.user, account)
return render_to_json_response(_("Account created"))
return render_to_json_response({
'stepid': data, 'form_errors': cwizard.errors
}, status=400)
return wizard.validate_step()
ctx = {
'title': _("New account"),
'action': reverse(newaccount),
'formid': 'newaccount_form',
'submit_label': _("Create")
'formid': 'newaccount_form'
}
cwizard.create_forms()
ctx.update(steps=cwizard.steps)
ctx.update(subtitle="1. %s" % cwizard.steps[0]['title'])
wizard.create_forms()
ctx.update(wizard=wizard)
return render(request, tplname, ctx)
+36
View File
@@ -0,0 +1,36 @@
"""
Radicale extension forms.
"""
from django import forms
from django.utils.translation import ugettext as _
from modoboa.lib.webutils import render_to_json_response
from modoboa.lib.formutils import WizardForm
from modoboa.extensions.radicale.models import UserCalendar
class UserCalendarForm(forms.ModelForm):
"""
User calendar form.
"""
class Meta:
model = UserCalendar
fields = ('name', )
class RightsForm(forms.Form):
username = forms.CharField(required=False)
class UserCalendarWizard(WizardForm):
"""
"""
def __init__(self, request):
super(UserCalendarWizard, self).__init__(request)
self.add_step(UserCalendarForm, _("General"))
self.add_step(RightsForm, _("Rights"))
def done(self, request):
calendar = self.first_step.form.save(commit=False)
calendar.mailbox = request.user.mailbox_set.all()[0]
calendar.save()
return render_to_json_response(_("Calendar created"))
+65 -2
View File
@@ -1,3 +1,66 @@
from django.db import models
"""
Radicale extension models.
"""
from __future__ import unicode_literals
# Create your models here.
import abc
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Calendar(models.Model):
"""Abstract calendar definition.
"""
name = models.CharField(max_length=200)
class Meta:
abstract = True
def __str__(self):
return self.name
@property
@abc.abstractmethod
def url(self):
"""Return the calendar URL"""
class UserCalendar(Calendar):
"""User calendar.
We associate the calendar to a mailbox because we need to access
the related domain.
"""
mailbox = models.ForeignKey("admin.Mailbox")
@property
def url(self):
"""Return the calendar URL.
<domain>/user/<localpart>/<name>
"""
return "%s/user/%s/%s" % (
self.mailbox.domain.name, self.mailbox.address, self.name
)
class SharedCalendar(Calendar):
"""Shared calendar.
A shared calendar is associated to a domain and is readable and
writable by all domain members.
"""
domain = models.ForeignKey("admin.Domain")
@property
def url(self):
"""Return the calendar URL.
<domain>/shared/<name>
"""
return "%s/shared/%s" % (
self.mailbox.domain.name, self.name
)
@@ -0,0 +1,17 @@
var Radicale = function() {
};
Radicale.prototype = {
constructor: Radicale,
initialize: function() {
},
add_calendar_cb: function() {
$("#wizard").cwizard({
formid: "newcal_form"
});
}
};
@@ -0,0 +1,20 @@
{% extends "twocols.html" %}{% load i18n radicale_tags %}
{% block html_head %}
<script type="text/javascript" src="{{ STATIC_URL }}js/creation_wizard.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}radicale/js/radicale.js"></script>
<script type="text/javascript">
$(document).ready(function() {
radicale = new Radicale();
});
</script>
{% endblock %}
{% block leftcol %}<div class="well sidebar-nav">
<ul class="nav nav-list">
<li class="nav-header">{% trans "Filter" %}</li>
<li>{% include "admin/searchform.html" %}</li>
<li id="taglist"></li>
<li class="nav-header">{% trans "Actions" %}</li>
{% radicale_left_menu user %}
</ul>
</div>
{% endblock %}
@@ -0,0 +1,4 @@
{% extends "common/generic_modal_form.html" %}{% load crispy_forms_tags %}
{% block formbody %}
{{ form|crispy }}
{% endblock %}
@@ -0,0 +1,31 @@
"""
Custom template tags.
"""
from django import template
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
register = template.Library()
@register.simple_tag
def radicale_left_menu(user):
"""Menu located inside the left column.
:param ``User`` user: current user
:rtype: string
:return: the menu rendered as HTML code
"""
entries = [
{"name": "newcalendar",
"label": _("Add calendar"),
"img": "icon-plus",
"modal": True,
"modalcb": "radicale.add_calendar_cb",
"url": reverse("new_calendar")},
]
return render_to_string('common/menulist.html', {
"entries": entries,
"user": user
})
+3 -1
View File
@@ -1,6 +1,8 @@
from django.conf.urls import patterns
from django.conf.urls import url, patterns
urlpatterns = patterns(
'modoboa.extensions.radicale.views',
(r'^$', 'index'),
(r'^calendars/$', 'calendars'),
url(r'^calendar/$', 'new_calendar', name='new_calendar'),
)
+34 -2
View File
@@ -1,5 +1,37 @@
# Create your views here.
"""
Radicale extension views.
"""
from django.shortcuts import render
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.contrib.auth.decorators import (
login_required
)
from django.utils.translation import ugettext as _
from modoboa.extensions.radicale.forms import UserCalendarWizard
@login_required
def index(request):
pass
return HttpResponseRedirect(reverse(calendars))
@login_required
def calendars(request, tplname="radicale/calendars.html"):
return render(request, tplname, {
"selection": "radicale"
})
@login_required
def new_calendar(request, tplname="common/wizard_forms.html"):
wizard = UserCalendarWizard(request)
if request.method == "POST":
return wizard.validate_step()
wizard.create_forms()
return render(request, tplname, {
"title": _("New calendar"),
"action": reverse(new_calendar),
"formid": "newcal_form",
"wizard": wizard
})
+101 -29
View File
@@ -1,5 +1,6 @@
# coding: utf-8
import re
import abc
from django.utils.translation import ugettext as _, ugettext_lazy
from django.forms import ChoiceField
from django.forms.widgets import RadioSelect, RadioInput
@@ -8,59 +9,130 @@ from django.core.exceptions import ValidationError
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
from django.utils.html import conditional_escape
from modoboa.lib.exceptions import BadRequest
from modoboa.lib.webutils import render_to_json_response
class CreationWizard(object):
def __init__(self, done_cb=None):
class WizardStep(object):
"""A wizard step.
"""
def __init__(self, cls, title, formtpl=None, new_args=None):
"""Constructor.
"""
self._cls = cls
self._title = title
self.formtpl = formtpl
self._new_args = new_args
self._prev = None
self._next = None
self.index = None
self.form = None
@property
def title(self):
if self.index is None:
return self._title
return "%d. %s" % (self.index + 1, self._title)
@property
def prev(self):
return self._prev
@prev.setter
def prev(self, step):
self._prev = step
@property
def next(self):
return self._next
@next.setter
def next(self, step):
self._next = step
def create_form(self, data=None):
args = []
if self._new_args is not None:
args += self._new_args
if data:
args.append(data)
self.form = self._cls(*args)
class WizardForm(object):
"""Custom wizard.
"""
def __init__(self, request, submit_button_label=None):
self.request = request
self.steps = []
self.done_cb = done_cb
self._submit_button_label = submit_button_label
@property
def submit_button_label(self):
if self._submit_button_label is None:
self._submit_button_label = _("Submit")
return self._submit_button_label
@property
def errors(self):
result = {}
for step in self.steps:
for name, value in step['form'].errors.items():
for name, value in step.form.errors.items():
if name == '__all__':
continue
result[name] = value
return result
def add_step(self, cls, title, buttons, formtpl=None, new_args=None):
self.steps += [dict(cls=cls, title=title, buttons=buttons,
formtpl=formtpl, new_args=new_args)]
@property
def first_step(self):
"""Return the first step.
"""
return self.steps[0] if self.steps else None
def add_step(self, cls, title, formtpl=None, new_args=None):
"""Add a new step to the wizard.
"""
step = WizardStep(cls, title, formtpl, new_args)
if self.steps:
step.prev = self.steps[-1]
self.steps[-1].next = step
self.steps += [step]
step.index = len(self.steps) - 1
def create_forms(self, data=None):
for step in self.steps:
args = []
if 'new_args' in step and step["new_args"]:
args += step["new_args"]
if data:
args.append(data)
step["form"] = step["cls"](*args)
step.create_form(data)
def validate_step(self, request):
stepid = request.POST.get("stepid", None)
def validate_step(self):
stepid = self.request.POST.get("stepid", None)
if stepid is None:
return -1, _("Invalid request")
stepid = stepid.replace("step", "")
stepid = int(stepid)
raise BadRequest(_("Invalid request"))
stepid = int(stepid.replace("step", ""))
if stepid < 0 or stepid > len(self.steps):
return -1, _("Invalid request")
self.create_forms(request.POST)
raise BadRequest(_("Invalid request"))
self.create_forms(self.request.POST)
statuses = []
for cpt in xrange(0, stepid):
statuses.append(self.steps[cpt]["form"].is_valid())
statuses.append(self.steps[cpt].form.is_valid())
if False in statuses:
return 0, stepid
return render_to_json_response({
'stepid': stepid, 'form_errors': self.errors
}, status=400)
if stepid == len(self.steps):
if self.done_cb is not None:
self.done_cb(self.steps)
return 2, None
return 1, stepid
return self.done()
return render_to_json_response(
{'title': self.steps[stepid].title, 'stepid': stepid}
)
def get_title(self, stepid):
return "%d. %s" % (stepid, self.steps[stepid - 1]["title"])
@abc.abstractmethod
def done(self):
"""Method to exexute when all steps are validated.
Must be implemented by all sub classes.
:rtype: HttpResponse
"""
class DynamicForm(object):
+6 -7
View File
@@ -1,14 +1,14 @@
{% load i18n lib_tags %}
<div class="modal-header">
<a class="close" data-dismiss="modal">x</a>
<h3>{{ title }} / <small>{{ subtitle }}</small></h3>
<h3>{{ title }} / <small>{{ wizard.first_step.title }}</small></h3>
</div>
<div class="modal-body">
<form class="form-horizontal" action="{{ action }}" enctype="{{ enctype }}"
method="POST" id="{{ formid }}" name="{{ formid }}">{% csrf_token %}
<div id="wizard" class="carousel">
<div class="carousel-inner">{% for step in steps %}
<div id="step{{ forloop.counter }}"
<div class="carousel-inner">{% for step in wizard.steps %}
<div id="step{{ forloop.counter }}"
class="item{% if forloop.first %} active{% endif %}">
{% if step.formtpl %}{% render_form step.form step.formtpl %}{% else %}{% render_form step.form %}{% endif %}
</div>{% endfor %}
@@ -18,12 +18,11 @@
</div>
<div class="modal-footer">
<div class="buttons_list">
{% for step in steps %}
{% for step in wizard.steps %}
<div id="step{{ forloop.counter }}_buttons"
class="bset{% if forloop.first %} active{% endif %}">
{% for button in step.buttons %}
<a href="#" class="btn {{ button.classes }}">{{ button.label }}</a>
{% endfor %}
{% if step.prev %}<a href="#" class="btn btn-inverse prev">{% trans "Previous" %}</a>{% endif %}
{% if step.next %}<a href="#" class="btn btn-inverse next">{% trans "Next" %}</a>{% else %}<a href="#" class="btn btn-primary submit">{{ wizard.submit_button_label }}</a>{% endif %}
</div>
{% endfor %}
<a class="btn" data-dismiss="modal">{% trans "Close" %}</a>