Better tabforms.

This commit is contained in:
Antoine Nguyen
2014-03-28 08:36:50 +01:00
parent 221a433eb0
commit a2aeabcf62
17 changed files with 525 additions and 162 deletions
+38 -7
View File
@@ -1,6 +1,7 @@
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from django.http import QueryDict
from django.core.urlresolvers import reverse
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import PermDeniedException, Conflict, NotFound
from modoboa.lib.permissions import get_account_roles
@@ -8,6 +9,7 @@ from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.formutils import (
DomainNameField, DynamicForm, TabForms, WizardForm
)
from modoboa.lib.webutils import render_to_json_response
from modoboa.core.models import User
from modoboa.extensions.admin.models import (
Domain, Mailbox, Alias
@@ -319,23 +321,33 @@ class AccountPermissionsForm(forms.Form, DynamicForm):
class AccountForm(TabForms):
"""Account edition form.
def __init__(self, user, *args, **kwargs):
self.user = user
"""
def __init__(self, request, *args, **kwargs):
self.user = request.user
self.forms = [
dict(id="general", title=_("General"), cls=AccountFormGeneral,
new_args=[user], mandatory=True),
new_args=[self.user], mandatory=True),
dict(id="mail", title=_("Mail"), formtpl="admin/mailform.html",
cls=AccountFormMail),
dict(id="perms", title=_("Permissions"), formtpl="admin/permsform.html",
cls=AccountPermissionsForm)
]
cbargs = [user]
cbargs = [self.user]
if "instances" in kwargs:
cbargs += [kwargs["instances"]["general"]]
self.forms += events.raiseQueryEvent("ExtraAccountForm", *cbargs)
super(AccountForm, self).__init__(*args, **kwargs)
super(AccountForm, self).__init__(request, *args, **kwargs)
def extra_context(self, context):
account = self.instances["general"]
context.update({
'title': account.username,
'formid': 'accountform',
'action': reverse("edit_account", args=[account.id]),
})
def check_perms(self, account):
if account.is_superuser:
@@ -356,8 +368,14 @@ class AccountForm(TabForms):
return False
return True
def save_general_form(self):
self.account = self.forms[0]["instance"].save()
def is_valid(self):
"""Two steps validation.
"""
self.instances["general"].oldgroup = self.instances["general"].group
if super(AccountForm, self).is_valid(mandatory_only=True):
self.account = self.forms[0]["instance"].save()
return super(AccountForm, self).is_valid(optional_only=True)
return False
def save(self):
"""Custom save method
@@ -365,12 +383,18 @@ class AccountForm(TabForms):
As forms interact with each other, it is simpler to make
custom code to save them.
"""
events.raiseEvent(
"AccountModified", self.instances["general"], self.account
)
self.forms[1]["instance"].save(self.user, self.account)
if len(self.forms) <= 2:
return
for f in self.forms[2:]:
f["instance"].save()
def done(self):
return render_to_json_response(_("Account updated"))
class AccountWizard(WizardForm):
"""Account creation wizard.
@@ -384,6 +408,13 @@ class AccountWizard(WizardForm):
AccountFormMail, _("Mail"), formtpl="admin/mailform.html"
)
def extra_context(self, context):
context.update({
'title': _("New account"),
'action': reverse("new_account"),
'formid': 'newaccount_form'
})
def done(self):
from modoboa.lib.webutils import render_to_json_response
+59 -14
View File
@@ -1,6 +1,8 @@
from django import forms
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from django.core.urlresolvers import reverse
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import ModoboaException, Conflict
from modoboa.lib.formutils import (
@@ -244,32 +246,68 @@ class DomainFormOptions(forms.Form):
class DomainForm(TabForms):
def __init__(self, user, *args, **kwargs):
self.user = user
self.forms = []
if user.has_perm("admin.change_domain"):
self.forms.append(dict(
id="general", title=_("General"), formtpl="admin/domain_general_form.html",
cls=DomainFormGeneral, mandatory=True
))
"""Domain edition form.
cbargs = [user]
"""
def __init__(self, request, *args, **kwargs):
self.user = request.user
self.forms = []
if self.user.has_perm("admin.change_domain"):
self.forms.append({
"id": "general",
"title": _("General"),
"formtpl": "admin/domain_general_form.html",
"cls": DomainFormGeneral,
"mandatory": True
})
cbargs = [self.user]
if "instances" in kwargs:
cbargs += [kwargs["instances"]["general"]]
self.forms += events.raiseQueryEvent("ExtraDomainForm", *cbargs)
if not self.forms:
self.active_id = "admins"
super(DomainForm, self).__init__(*args, **kwargs)
super(DomainForm, self).__init__(request, *args, **kwargs)
def save(self, user):
"""Custom save method
def extra_context(self, context):
domain = self.instances["general"]
domadmins = [u for u in domain.admins
if self.request.user.can_access(u) and not u.is_superuser]
if not self.request.user.is_superuser:
domadmins = [u for u in domadmins if u.group == "DomainAdmins"]
context.update({
"title": domain.name,
"action": reverse("edit_domain", args=[domain.pk]),
"formid": "domform",
"domain": domain,
"domadmins": domadmins
})
def is_valid(self):
"""Custom validation.
We just save the current name before it is potentially
modified.
"""
self.instances["general"].oldname = self.instances["general"].name
return super(DomainForm, self).is_valid()
def save(self):
"""Custom save method.
As forms interact with each other, it is easier to make custom
code to save them.
"""
self.forms[0]['instance'].save(user, domalias_post_create=True)
self.forms[0]['instance'].save(
self.request.user, domalias_post_create=True
)
for f in self.forms[1:]:
f["instance"].save(user)
f["instance"].save(self.request.user)
def done(self):
events.raiseEvent("DomainModified", self.instances["general"])
return render_to_json_response(_("Domain modified"))
class DomainWizard(WizardForm):
@@ -287,6 +325,13 @@ class DomainWizard(WizardForm):
new_args=[self.request.user]
)
def extra_context(self, context):
context.update({
"title": _("New domain"),
"action": reverse("new_domain"),
"formid": "domform"
})
def done(self):
genform = self.first_step.form
domain = genform.save(self.request.user)
+5 -4
View File
@@ -7,8 +7,8 @@ urlpatterns = patterns(
url(r'^domains/$', 'domains', name="domains"),
(r'^domains/list/$', '_domains'),
(r'^domains/flatlist/$', 'domains_list'),
(r'^domains/new/', 'newdomain'),
(r'^domains/(?P<dom_id>\d+)/edit/$', 'editdomain'),
url(r'^domains/new/', 'newdomain', name='new_domain'),
url(r'^domains/(?P<dom_id>\d+)/edit/$', 'editdomain', name="edit_domain"),
(r'^domains/(?P<dom_id>\d+)/delete/$', 'deldomain'),
)
@@ -21,8 +21,9 @@ urlpatterns += patterns(
(r'^identities/quotas/$', 'list_quotas'),
(r'^accounts/list/$', 'accounts_list'),
(r'^accounts/new/$', 'newaccount'),
(r'^accounts/edit/(?P<accountid>\d+)/$', 'editaccount'),
url(r'^accounts/new/$', 'newaccount', name="new_account"),
url(r'^accounts/edit/(?P<accountid>\d+)/$', 'editaccount',
name="edit_account"),
(r'^accounts/delete/(?P<accountid>\d+)/$', 'delaccount'),
)
+3 -36
View File
@@ -89,21 +89,11 @@ def domains_list(request):
@permission_required("admin.add_domain")
@transaction.commit_on_success
@reversion.create_revision()
def newdomain(request, tplname="common/wizard_forms.html"):
def newdomain(request):
from modoboa.extensions.admin.forms import DomainWizard
events.raiseEvent("CanCreate", request.user, "domains")
wizard = DomainWizard(request)
if request.method == "POST":
return wizard.validate_step()
ctx = {"title": _("New domain"),
"action_label": _("Create"),
"action_classes": "submit",
"action": reverse(newdomain),
"formid": "domform"}
wizard.create_forms()
ctx.update(wizard=wizard)
return render(request, tplname, ctx)
return DomainWizard(request).process()
@login_required
@@ -117,30 +107,7 @@ def editdomain(request, dom_id, tplname="admin/editdomainform.html"):
instances = dict(general=domain)
events.raiseEvent("FillDomainInstances", request.user, domain, instances)
if request.method == "POST":
domain.oldname = domain.name
form = DomainForm(request.user, request.POST, instances=instances)
if form.is_valid():
form.save(request.user)
events.raiseEvent("DomainModified", domain)
return render_to_json_response(_("Domain modified"))
return render_to_json_response({
'form_errors': form.errors
}, status=400)
domadmins = [u for u in domain.admins
if request.user.can_access(u) and not u.is_superuser]
if not request.user.is_superuser:
domadmins = [u for u in domadmins if u.group == "DomainAdmins"]
ctx = {"title": domain.name,
"action_label": _("Update"),
"action_classes": "submit",
"action": reverse(editdomain, args=[dom_id]),
"formid": "domform",
"domain": domain,
"tabs": DomainForm(request.user, instances=instances),
"domadmins": domadmins}
return render(request, tplname, ctx)
return DomainForm(request, instances=instances).process()
@login_required
+28 -39
View File
@@ -2,7 +2,6 @@ import reversion
from django.shortcuts import render
from django.db import transaction
from django.utils.translation import ugettext as _, ungettext
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
@@ -14,7 +13,6 @@ from modoboa.lib.exceptions import (
from modoboa.lib.webutils import (
_render_to_string, render_to_json_response
)
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
@@ -22,7 +20,7 @@ from modoboa.extensions.admin.lib import (
get_sort_order, get_listing_page, get_identities
)
from modoboa.extensions.admin.forms import (
AccountForm, AccountFormGeneral, AccountFormMail
AccountForm
)
@@ -116,7 +114,7 @@ def list_quotas(request, tplname="admin/quotas.html"):
@permission_required("core.add_user")
@transaction.commit_on_success
@reversion.create_revision()
def newaccount(request, tplname='common/wizard_forms.html'):
def newaccount(request):
"""Create a new account.
.. note:: An issue still remains int this code: if all validation
@@ -126,17 +124,7 @@ def newaccount(request, tplname='common/wizard_forms.html'):
django 1.6 and atomicity.
"""
from modoboa.extensions.admin.forms import AccountWizard
wizard = AccountWizard(request)
if request.method == "POST":
return wizard.validate_step()
ctx = {
'title': _("New account"),
'action': reverse(newaccount),
'formid': 'newaccount_form'
}
wizard.create_forms()
ctx.update(wizard=wizard)
return render(request, tplname, ctx)
return AccountWizard(request).process()
@login_required
@@ -153,32 +141,33 @@ def editaccount(request, accountid, tplname="common/tabforms.html"):
instances = dict(general=account, mail=mb, perms=account)
events.raiseEvent("FillAccountInstances", request.user, account, instances)
return AccountForm(request, instances=instances).process()
if request.method == "POST":
classes = {}
form = AccountForm(request.user, request.POST,
instances=instances, classes=classes)
account.oldgroup = account.group
if form.is_valid(mandatory_only=True):
form.save_general_form()
if form.is_valid(optional_only=True):
events.raiseEvent("AccountModified", account, form.account)
form.save()
return render_to_json_response(_("Account updated"))
return render_to_json_response({'form_errors': form.errors}, status=400)
# if request.method == "POST":
# classes = {}
# form = AccountForm(request.user, request.POST,
# instances=instances, classes=classes)
# account.oldgroup = account.group
# if form.is_valid(mandatory_only=True):
# form.save_general_form()
# if form.is_valid(optional_only=True):
# events.raiseEvent("AccountModified", account, form.account)
# form.save()
# return render_to_json_response(_("Account updated"))
# return render_to_json_response({'form_errors': form.errors}, status=400)
ctx = {
'title': account.username,
'formid': 'accountform',
'action': reverse(editaccount, args=[accountid]),
'action_label': _('Update'),
'action_classes': 'submit',
'tabs': AccountForm(request.user, instances=instances)
}
active_tab_id = request.GET.get("active_tab", "default")
if active_tab_id != "default":
ctx["tabs"].active_id = active_tab_id
return render(request, tplname, ctx)
# ctx = {
# 'title': account.username,
# 'formid': 'accountform',
# 'action': reverse(editaccount, args=[accountid]),
# 'action_label': _('Update'),
# 'action_classes': 'submit',
# 'tabs': AccountForm(request.user, instances=instances)
# }
# active_tab_id = request.GET.get("active_tab", "default")
# if active_tab_id != "default":
# ctx["tabs"].active_id = active_tab_id
# return render(request, tplname, ctx)
@login_required
@@ -1,8 +1,13 @@
"""
Postfix relay domains extension forms.
"""
from django import forms
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from django.core.urlresolvers import reverse
from modoboa.lib import events
from modoboa.lib.formutils import DynamicForm, DomainNameField, TabForms
from modoboa.lib.webutils import render_to_json_response
from modoboa.extensions.admin.models import Domain, DomainAlias
from .models import RelayDomain, RelayDomainAlias
@@ -130,28 +135,44 @@ class RelayDomainForm(TabForms):
We use a *tabs* compatible form because extensions can add their
own tab. (ex: amavis)
"""
def __init__(self, user, *args, **kwargs):
self.user = user
def __init__(self, request, *args, **kwargs):
self.user = request.user
self.forms = []
if user.has_perm("postfix_relay_domains.change_relaydomain"):
if self.user.has_perm("postfix_relay_domains.change_relaydomain"):
self.forms.append({
'id': 'general', 'title': _("General"),
'id': 'general', 'title': _("General"),
'formtpl': 'postfix_relay_domains/relaydomain_form.html',
'cls': RelayDomainFormGeneral, 'mandatory': True
})
cbargs = [user]
cbargs = [self.user]
if "instances" in kwargs:
cbargs += [kwargs["instances"]["general"]]
self.forms += events.raiseQueryEvent("ExtraRelayDomainForm", *cbargs)
super(RelayDomainForm, self).__init__(*args, **kwargs)
super(RelayDomainForm, self).__init__(request, *args, **kwargs)
def save(self, user):
def extra_context(self, context):
"""Additional content.
"""
rdom = self.instances["general"]
context.update({
'action': reverse("edit_relaydomain", args=[rdom.id]),
'formid': 'rdomform',
'title': rdom.name
})
def save(self):
"""Custom save method
As forms interact with each other, it is easier to make custom
code to save them.
"""
self.forms[0]['instance'].save(user, rdomalias_post_create=True)
self.forms[0]['instance'].save(
self.request.user, rdomalias_post_create=True
)
for f in self.forms[1:]:
f["instance"].save(user)
f["instance"].save(self.request.user)
def done(self):
events.raiseEvent('RelayDomainModified', self.instances["general"])
return render_to_json_response(_('Relay domain modified'))
@@ -1,9 +1,10 @@
from django.conf.urls import patterns
from django.conf.urls import patterns, url
urlpatterns = patterns(
'modoboa.extensions.postfix_relay_domains.views',
(r'^relaydomains/new/', 'create'),
(r'^relaydomains/(?P<rdom_id>\d+)/edit/$', 'edit'),
url(r'^relaydomains/(?P<rdom_id>\d+)/edit/$', 'edit',
name='edit_relaydomain'),
(r'^relaydomains/(?P<rdom_id>\d+)/delete/$', 'delete'),
(r'^relaydomains/scan_services/$', 'scan_for_services'),
)
@@ -48,26 +48,7 @@ def edit(request, rdom_id, tplname='common/tabforms.html'):
raise PermDeniedException
instances = {'general': rdom}
events.raiseEvent("FillRelayDomainInstances", request.user, rdom, instances)
if request.method == 'POST':
rdom.oldname = rdom.name
form = RelayDomainForm(request.user, request.POST, instances=instances)
if form.is_valid():
form.save(request.user)
events.raiseEvent('RelayDomainModified', rdom)
return render_to_json_response(_('Relay domain modified'))
return render_to_json_response(
{'form_errors': form.errors}, status=400
)
ctx = {
'action': reverse(edit, args=[rdom.id]),
'formid': 'rdomform',
'title': rdom.name,
'action_label': _("Update"),
'action_classes': "submit",
'tabs': RelayDomainForm(request.user, instances=instances)
}
return render(request, tplname, ctx)
return RelayDomainForm(request, instances=instances).process()
@login_required
+43 -5
View File
@@ -2,10 +2,11 @@
Radicale extension forms.
"""
from django import forms
from django.core.urlresolvers import reverse
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
from modoboa.lib.formutils import WizardForm, TabForms
from modoboa.extensions.radicale.models import UserCalendar, SharedCalendar
class UserCalendarForm(forms.ModelForm):
@@ -17,20 +18,57 @@ class UserCalendarForm(forms.ModelForm):
fields = ('name', )
class SharedCalendarForm(forms.ModelForm):
"""
Shared calendar form.
"""
class Meta:
model = SharedCalendar
class RightsForm(forms.Form):
username = forms.CharField(required=False)
class UserCalendarWizard(WizardForm):
"""
"""Custom wizard to create a new calendar.
"""
def __init__(self, request):
super(UserCalendarWizard, self).__init__(request)
self.add_step(UserCalendarForm, _("General"))
self.add_step(RightsForm, _("Rights"))
def done(self, request):
def extra_context(self, context):
context.update({
"title": _("New calendar"),
"action": reverse("new_user_calendar"),
"formid": "newcal_form"
})
def done(self):
calendar = self.first_step.form.save(commit=False)
calendar.mailbox = request.user.mailbox_set.all()[0]
calendar.mailbox = self.request.user.mailbox_set.all()[0]
calendar.save()
return render_to_json_response(_("Calendar created"))
class UserCalendarEditionForm(TabForms):
"""User calendar edition form.
"""
def __init__(self, *args, **kwargs):
self.forms = []
self.forms.append({
"id": "general",
"title": _("General"),
"formtpl": "",
"cls": UserCalendarForm,
"mandatory": True
})
super(UserCalendarEditionForm, self).__init__(*args, **kwargs)
def extra_context(self, context):
context.update({
"title": self.instances["general"].name,
"formid": "ucal_form"
})
+47 -3
View File
@@ -3,9 +3,10 @@ Radicale extension models.
"""
from __future__ import unicode_literals
import abc
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _
from modoboa.extensions.admin.models import Domain
@python_2_unicode_compatible
@@ -21,9 +22,27 @@ class Calendar(models.Model):
return self.name
@property
@abc.abstractmethod
def url(self):
"""Return the calendar URL"""
raise NotImplementedError
@property
def type(self):
"""Return calendar type"""
raise NotImplementedError
class UserCalendarManager(models.Manager):
"""Custom UserCalendar manager.
"""
def get_for_admin(self, admin):
"""Return the list of calendars this admin can access.
:param ``core.User`` admin: administrator
"""
domains = Domain.objects.get_for_admin(admin)
return self.get_query_set().filter(mailbox__domain__in=domains)
class UserCalendar(Calendar):
@@ -35,6 +54,8 @@ class UserCalendar(Calendar):
"""
mailbox = models.ForeignKey("admin.Mailbox")
objects = UserCalendarManager()
@property
def url(self):
"""Return the calendar URL.
@@ -45,6 +66,23 @@ class UserCalendar(Calendar):
self.mailbox.domain.name, self.mailbox.address, self.name
)
@property
def type(self):
return _("user")
class SharedCalendarManager(models.Manager):
"""Custom SharedCalendar manager.
"""
def get_for_admin(self, admin):
"""Return the list of calendars this admin can access.
:param ``core.User`` admin: administrator
"""
domains = Domain.objects.get_for_admin(admin)
return self.get_query_set().filter(domain__in=domains)
class SharedCalendar(Calendar):
"""Shared calendar.
@@ -55,6 +93,8 @@ class SharedCalendar(Calendar):
"""
domain = models.ForeignKey("admin.Domain")
objects = SharedCalendarManager()
@property
def url(self):
"""Return the calendar URL.
@@ -62,5 +102,9 @@ class SharedCalendar(Calendar):
<domain>/shared/<name>
"""
return "%s/shared/%s" % (
self.mailbox.domain.name, self.name
self.domain.name, self.name
)
@property
def type(self):
return _("shared")
@@ -1,17 +1,47 @@
var Radicale = function() {
this.initialize();
};
Radicale.prototype = {
constructor: Radicale,
initialize: function() {
$(document).on(
"click", "a[name=delcalendar]", this.del_calendar
);
},
add_calendar_cb: function() {
$("#wizard").cwizard({
formid: "newcal_form"
});
},
add_shared_calendar_cb: function() {
$('.submit').on('click', $.proxy(function(e) {
simple_ajax_form_post(e, {
formid: "newsharedcal_form",
reload_on_success: false
/*success_cb: $.proxy(this.reload_listing, this)*/
});
}, this));
},
/**
* Delete a user calendar.
*/
del_calendar: function(event) {
event.preventDefault();
var $link = $(this);
if (!confirm($link.attr("title"))) {
return;
}
$.ajax({
url: $link.attr("href"),
type: "DELETE"
}).done(function(data) {
$("body").notify("success", data, 2000);
});
}
};
@@ -18,3 +18,27 @@ $(document).ready(function() {
</ul>
</div>
{% endblock %}
{% block apparea %}{% load lib_tags %}
<table id="{{ tableid }}" class="table {{ styles }}">
<thead>
<tr>
<th id="name" class="sortable" data-sort_order="identity" width="">{% trans "Name" %}</th>
<th id="type">{% trans "Type" %}</th>
<th id="url">{% trans "Address" %}</th>
<th id="actions" width="">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for cal in calendars %}
<tr id="{{ cal.id }}" class="" name="{{ forloop.counter }}">
<td name="name">{% calendar_modify_link cal %}</td>
<td name="type">
<span class="label label-info"><a href="#" class="filter">{{ cal.type }}</a></span>
</td>
<td name="url">{{ cal.url }}</td>
<td name="actions">{% calendar_actions cal %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
@@ -5,6 +5,8 @@ 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 _
from modoboa.lib.webutils import render_actions
from modoboa.lib.templatetags.lib_tags import render_link
register = template.Library()
@@ -18,14 +20,56 @@ def radicale_left_menu(user):
:return: the menu rendered as HTML code
"""
entries = [
{"name": "newcalendar",
{"name": "newusercalendar",
"label": _("Add calendar"),
"img": "icon-plus",
"modal": True,
"modalcb": "radicale.add_calendar_cb",
"url": reverse("new_calendar")},
"url": reverse("new_user_calendar")},
]
if user.group != "SimpleUsers":
entries += [
{"name": "newsharedcalendar",
"label": _("Add shared calendar"),
"img": "icon-plus",
"modal": True,
"modalcb": "radicale.add_shared_calendar_cb",
"url": reverse("new_shared_calendar")},
]
return render_to_string('common/menulist.html', {
"entries": entries,
"user": user
})
@register.simple_tag
def calendar_modify_link(calendar):
"""
"""
linkdef = {"label": calendar.name, "modal": True}
if calendar.__class__.__name__ == "UserCalendar":
linkdef["url"] = reverse(
"user_calendar", args=[calendar.pk]
)
#linkdef["modalcb"] = "admin.domainform_cb"
else:
linkdef["url"] = reverse(
"shared_calendar", args=[calendar.pk]
)
return render_link(linkdef)
@register.simple_tag
def calendar_actions(calendar):
"""
"""
actions = [{
"name": "delcalendar",
"title": _("Delete %s?" % calendar),
"img": "icon-trash"
}]
if calendar.__class__.__name__ == 'UserCalendar':
actions[0]["url"] = reverse("user_calendar", args=[calendar.id])
else:
actions[0]["url"] = reverse("shared_calendar", args=[calendar.id])
return render_actions(actions)
+6 -1
View File
@@ -4,5 +4,10 @@ urlpatterns = patterns(
'modoboa.extensions.radicale.views',
(r'^$', 'index'),
(r'^calendars/$', 'calendars'),
url(r'^calendar/$', 'new_calendar', name='new_calendar'),
url(r'^usercalendar/$', 'new_user_calendar', name='new_user_calendar'),
url(r'^usercalendar/(?P<pk>\d+)/$', 'user_calendar', name='user_calendar'),
url(r'^sharedcalendar/$', 'new_shared_calendar',
name='new_shared_calendar'),
url(r'^sharedcalendar/(?P<pk>\d+)/$', 'shared_calendar',
name='shared_calendar'),
)
+79 -11
View File
@@ -1,6 +1,8 @@
"""
Radicale extension views.
"""
from itertools import chain
from django.shortcuts import render
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
@@ -8,7 +10,13 @@ from django.contrib.auth.decorators import (
login_required
)
from django.utils.translation import ugettext as _
from modoboa.extensions.radicale.forms import UserCalendarWizard
from modoboa.lib.webutils import render_to_json_response
from modoboa.lib.exceptions import NotFound, PermDeniedException
from modoboa.extensions.radicale.forms import (
UserCalendarWizard, SharedCalendarForm, UserCalendarEditionForm
)
from modoboa.extensions.radicale.models import UserCalendar, SharedCalendar
@login_required
@@ -18,20 +26,80 @@ def index(request):
@login_required
def calendars(request, tplname="radicale/calendars.html"):
if request.user.group == "SimpleUsers":
cals = UserCalendar.objects.filter(
mailbox=request.user.mailbox_set.all()[0]
)
else:
cals = chain(
UserCalendar.objects.get_for_admin(request.user),
SharedCalendar.objects.get_for_admin(request.user)
)
return render(request, tplname, {
"selection": "radicale"
"selection": "radicale",
"calendars": cals
})
@login_required
def new_calendar(request, tplname="common/wizard_forms.html"):
wizard = UserCalendarWizard(request)
def new_user_calendar(request):
return UserCalendarWizard(request).process()
@login_required
def user_calendar(request, pk):
"""
"""
try:
ucal = UserCalendar.objects.get(pk=pk)
except UserCalendar.DoesNotExist:
raise NotFound
instances = {"general": ucal}
if request.method == "DELETE":
# Check ownership
ucal.delete()
return render_to_json_response(_("Calendar removed"))
return UserCalendarEditionForm(request, instances=instances).process()
# if request.method == "POST":
# pass
# form = UserCalendarEditionForm(instances=instances)
# return render(request, "common/tabforms.html", {
# "title": ucal.name, "tabs": form
# })
@login_required
def new_shared_calendar(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
form = SharedCalendarForm(request.POST)
if form.is_valid():
form.save()
return render_to_json_response(_("Calendar created"))
return render_to_json_response(
{"form_errors": form.errors}, status=400
)
form = SharedCalendarForm()
return render(request, "common/generic_modal_form.html", {
"form": form,
"formid": "newsharedcal_form",
"title": _("New shared calendar"),
"action": reverse("new_shared_calendar"),
"action_classes": "submit",
"action_label": _("Submit")
})
@login_required
def shared_calendar(request, pk):
"""
"""
try:
scal = SharedCalendar.objects.get(pk=pk)
except SharedCalendar.DoesNotExist:
raise NotFound
if not request.user.can_access(scal.domain):
raise PermDeniedException
if request.method == "DELETE":
scal.delete()
return render_to_json_response(_("Calendar removed"))
+2 -1
View File
@@ -28,7 +28,8 @@ class SieveClient(object):
self.msc = Client(parameters.get_admin("SERVER"),
int(parameters.get_admin("PORT")),
debug=False)
use_starttls = True if parameters.get_admin("STARTTLS") == "yes" else False
use_starttls = True if parameters.get_admin("STARTTLS") == "yes" \
else False
authmech = parameters.get_admin("AUTHENTICATION_MECH")
if authmech == "AUTO":
authmech = None
+79 -6
View File
@@ -1,14 +1,18 @@
# coding: utf-8
import re
import abc
from django.shortcuts import render
from django.utils.translation import ugettext as _, ugettext_lazy
from django.forms import ChoiceField
from django.forms.widgets import RadioSelect, RadioInput
from django.forms.fields import CharField, Field
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
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
@@ -63,6 +67,10 @@ class WizardStep(object):
class WizardForm(object):
"""Custom wizard.
"""
__metaclass__ = abc.ABCMeta
template_name = "common/wizard_forms.html"
def __init__(self, request, submit_button_label=None):
self.request = request
self.steps = []
@@ -125,6 +133,21 @@ class WizardForm(object):
{'title': self.steps[stepid].title, 'stepid': stepid}
)
def extra_context(self, context):
"""Provide additional information to template's context.
"""
pass
def process(self):
"""Process the request.
"""
if self.request.method == "POST":
return self.validate_step()
self.create_forms()
context = {"wizard": self}
self.extra_context(context)
return render(self.request, self.template_name, context)
@abc.abstractmethod
def done(self):
"""Method to exexute when all steps are validated.
@@ -170,16 +193,20 @@ class TabForms(object):
displayed using tabs. It is different from a classical formset
because it can contain different forms.
"""
def __init__(self, data=None, instances=None, classes=None):
template_name = "common/tabforms.html"
def __init__(self, request, instances=None, classes=None):
self.request = request
to_remove = []
for fd in self.forms:
args = []
kwargs = {}
if "new_args" in fd:
args += fd["new_args"]
if data is not None:
args.append(data)
if request.method == "POST":
args.append(request.POST)
if instances is not None:
self.instances = instances
if hasattr(self, "check_%s" % fd["id"]):
if not getattr(self, "check_%s" % fd["id"])(instances[fd["id"]]):
to_remove += [fd]
@@ -198,6 +225,12 @@ class TabForms(object):
@property
def errors(self):
"""Return validation errors.
We aggregate all form errors into one dictionary.
:rtype: dict
"""
result = {}
for f in self.forms:
for name, value in f['instance'].errors.items():
@@ -205,8 +238,13 @@ class TabForms(object):
continue
result[name] = value
return result
def is_valid(self, mandatory_only=False, optional_only=False):
"""Check if the form is valid.
:param boolean mandatory_only:
:param boolean optional_only:
"""
to_remove = []
for f in self.forms:
if mandatory_only and \
@@ -224,8 +262,10 @@ class TabForms(object):
self.forms = [f for f in self.forms if not f in to_remove]
return True
def save(self, *args, **kwargs):
raise RuntimeError
@abc.abstractmethod
def save(self):
"""Save objects here.
"""
def remove_tab(self, tabid):
for f in self.forms:
@@ -240,6 +280,39 @@ class TabForms(object):
for form in self.forms:
yield form
def extra_context(self, context):
""""Provide additional information to template's context.
"""
pass
@abc.abstractmethod
def done(self):
"""Actions to execute after the form has been validated and saved.
:rtype: HttpResponse instance
"""
def process(self):
"""Process the received request.
"""
if self.request.method == "POST":
if self.is_valid():
self.save()
return self.done()
return render_to_json_response(
{'form_errors': self.errors}, status=400
)
context = {
"tabs": self,
"action_label": _("Update"),
"action_classes": "submit",
}
self.extra_context(context)
active_tab_id = self.request.GET.get("active_tab", "default")
if active_tab_id != "default":
context["tabs"].active_id = active_tab_id
return render(self.request, self.template_name, context)
#
# Custom fields from here
#