mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
Better tabforms.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user