From 7a9df7de990ea3ce50b08415de5568dde6b017a3 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Fri, 2 May 2014 16:01:18 +0200 Subject: [PATCH] wip: Radicale extension Access rules definition works! --- modoboa/extensions/admin/forms/account.py | 7 +- modoboa/extensions/admin/views/identity.py | 26 ------ modoboa/extensions/radicale/forms.py | 87 +++++++++++++++++-- modoboa/extensions/radicale/models.py | 6 +- .../radicale/static/radicale/css/radicale.css | 12 +++ .../static/radicale/js/jquery.dynamicrule.js | 11 ++- .../radicale/static/radicale/js/radicale.js | 7 ++ .../templates/radicale/accessrule.html | 7 ++ .../templates/radicale/calendars.html | 1 + .../templates/radicale/rightsform.html | 27 ++---- .../radicale/templatetags/radicale_tags.py | 25 ++++++ modoboa/extensions/radicale/views.py | 2 +- modoboa/lib/formutils.py | 3 +- 13 files changed, 154 insertions(+), 67 deletions(-) create mode 100644 modoboa/extensions/radicale/static/radicale/css/radicale.css create mode 100644 modoboa/extensions/radicale/templates/radicale/accessrule.html diff --git a/modoboa/extensions/admin/forms/account.py b/modoboa/extensions/admin/forms/account.py index a9072f7a5..75a256d44 100644 --- a/modoboa/extensions/admin/forms/account.py +++ b/modoboa/extensions/admin/forms/account.py @@ -344,15 +344,10 @@ class AccountForm(TabForms): def __init__(self, request, *args, **kwargs): self.user = request.user self.forms = [ -<<<<<<< HEAD - dict(id="general", title=_("General"), cls=AccountFormGeneral, - new_args=[self.user], mandatory=True), -======= dict(id="general", title=_("General"), formtpl="admin/account_general_form.html", cls=AccountFormGeneral, - new_args=[user], mandatory=True), ->>>>>>> master + 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", diff --git a/modoboa/extensions/admin/views/identity.py b/modoboa/extensions/admin/views/identity.py index 7b3872967..b97cf7e37 100644 --- a/modoboa/extensions/admin/views/identity.py +++ b/modoboa/extensions/admin/views/identity.py @@ -143,32 +143,6 @@ def editaccount(request, accountid, tplname="common/tabforms.html"): 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) - - # 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 @permission_required("core.delete_user") diff --git a/modoboa/extensions/radicale/forms.py b/modoboa/extensions/radicale/forms.py index 1e87883a0..27a30aea4 100644 --- a/modoboa/extensions/radicale/forms.py +++ b/modoboa/extensions/radicale/forms.py @@ -3,7 +3,7 @@ Radicale extension forms. """ from django import forms from django.core.urlresolvers import reverse -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext as _, ugettext_lazy from modoboa.lib.webutils import render_to_json_response from modoboa.lib.formutils import WizardForm, TabForms, DynamicForm from modoboa.extensions.radicale.models import UserCalendar, SharedCalendar @@ -29,9 +29,17 @@ class SharedCalendarForm(forms.ModelForm): class RightsForm(forms.Form, DynamicForm): """ """ - username = forms.CharField(required=False) - read_access = forms.BooleanField(initial=False, label=_("Read")) - write_access = forms.BooleanField(initial=False, label=_("Write")) + username = forms.CharField( + required=False, widget=forms.widgets.TextInput( + attrs={"placeholder": ugettext_lazy("Username")} + ) + ) + read_access = forms.BooleanField( + initial=False, label=_("Read"), required=False + ) + write_access = forms.BooleanField( + initial=False, label=_("Write"), required=False + ) def __init__(self, *args, **kwargs): from django.http import QueryDict @@ -43,13 +51,65 @@ class RightsForm(forms.Form, DynamicForm): self.calendar = None super(RightsForm, self).__init__(*args, **kwargs) + if self.calendar: + cpt = 1 + for rule in self.calendar.rules.all(): + self._create_field( + forms.EmailField, "username_%d" % cpt, + rule.mailbox.full_address + ) + self._create_field( + forms.BooleanField, "read_access_%d" % cpt, rule.read + ) + self._create_field( + forms.BooleanField, "write_access_%d" % cpt, rule.write + ) + cpt += 1 + if args and isinstance(args[0], QueryDict): self._load_from_qdict(args[0], "username", forms.EmailField) + self._load_from_qdict(args[0], "read_access", forms.BooleanField) + self._load_from_qdict(args[0], "write_access", forms.BooleanField) - def _create_field(self, typ, name, value=None, pos=None): + def save(self): + """Custom save method. """ - """ - super(RightsForm, self)._create_field(typ, name, value, pos) + import re + from modoboa.lib.emailutils import split_mailbox + from modoboa.lib.exceptions import BadRequest + from modoboa.extensions.admin.models import Mailbox + from .models import AccessRule + + usernames = {} + for name, value in self.cleaned_data.iteritems(): + if not name.startswith("username") or not value: + continue + res = re.match(r"[^_]+_(\d+)$", name) + pos = int(res.group(1)) if res else None + usernames[value] = pos + for rule in self.calendar.rules.select_related().all(): + if not rule.mailbox.full_address in usernames: + rule.delete() + for username, pos in usernames.iteritems(): + local_part, domname = split_mailbox(username) + try: + mbox = Mailbox.objects.get( + address=local_part, domain__name=domname + ) + except Mailbox.DoesNotExist: + raise BadRequest(_("Mailbox %s does not exist")) + if pos: + raccess = self.cleaned_data.get("read_access_%d" % pos, False) + waccess = self.cleaned_data.get("write_access_%d" % pos, False) + else: + raccess = self.cleaned_data.get("read_access", False) + waccess = self.cleaned_data.get("write_access", False) + acr, created = AccessRule.objects.get_or_create( + mailbox=mbox, calendar=self.calendar + ) + acr.read = raccess + acr.write = waccess + acr.save() class UserCalendarWizard(WizardForm): @@ -96,7 +156,18 @@ class UserCalendarEditionForm(TabForms): super(UserCalendarEditionForm, self).__init__(*args, **kwargs) def extra_context(self, context): + calendar = self.instances["general"] context.update({ "title": self.instances["general"].name, - "formid": "ucal_form" + "formid": "ucal_form", + "action": reverse("user_calendar", args=[calendar.id]) }) + + def save(self): + """Custom save method. + """ + calendar = self.forms[0]["instance"].save() + self.forms[1]["instance"].save() + + def done(self): + return render_to_json_response(_("Calendar updated")) diff --git a/modoboa/extensions/radicale/models.py b/modoboa/extensions/radicale/models.py index d42705877..389d59a48 100644 --- a/modoboa/extensions/radicale/models.py +++ b/modoboa/extensions/radicale/models.py @@ -115,7 +115,11 @@ class AccessRule(models.Model): mailbox = models.ForeignKey("admin.Mailbox") read = models.BooleanField(default=False) write = models.BooleanField(default=False) - calendar = models.ForeignKey(UserCalendar) + calendar = models.ForeignKey(UserCalendar, related_name="rules") + last_update = models.DateTimeField(auto_now=True) + + class Meta: + unique_together = ('mailbox', 'calendar') def __str__(self): access = "r" if self.read else "" diff --git a/modoboa/extensions/radicale/static/radicale/css/radicale.css b/modoboa/extensions/radicale/static/radicale/css/radicale.css new file mode 100644 index 000000000..000967846 --- /dev/null +++ b/modoboa/extensions/radicale/static/radicale/css/radicale.css @@ -0,0 +1,12 @@ +#original_row { + margin-left: 100px; +} + +.access-rule { + margin-top: 5px; + margin-left: 100px; +} + +.addrule, .delrule { + margin-left: 10px; +} \ No newline at end of file diff --git a/modoboa/extensions/radicale/static/radicale/js/jquery.dynamicrule.js b/modoboa/extensions/radicale/static/radicale/js/jquery.dynamicrule.js index 09e2c42bf..af7eb82df 100644 --- a/modoboa/extensions/radicale/static/radicale/js/jquery.dynamicrule.js +++ b/modoboa/extensions/radicale/static/radicale/js/jquery.dynamicrule.js @@ -52,11 +52,14 @@ $btn.find("i").removeClass("icon-plus").addClass("icon-trash"); $btn.removeClass("addrule").addClass("delrule"); $clone.find("#id_username") - .attr("id", "id_username_" + this.rules_counter); + .attr("id", "id_username_" + this.rules_counter) + .attr("name", "username_" + this.rules_counter); $clone.find("#id_read_access") - .attr("id", "id_read_access_" + this.rules_counter); + .attr("id", "id_read_access_" + this.rules_counter) + .attr("name", "read_access_" + this.rules_counter); $clone.find("#id_write_access") - .attr("id", "id_write_access_" + this.rules_counter); + .attr("id", "id_write_access_" + this.rules_counter) + .attr("name", "write_access_" + this.rules_counter); $clone.insertAfter(this.$element); this.rules_counter++; this.reset_original_rule(); @@ -69,7 +72,7 @@ event.preventDefault(); var $link = get_target(event, "a"); - $link.parents("tr").remove(); + $link.parent().remove(); } }; diff --git a/modoboa/extensions/radicale/static/radicale/js/radicale.js b/modoboa/extensions/radicale/static/radicale/js/radicale.js index 4abcc6490..17a17dea8 100644 --- a/modoboa/extensions/radicale/static/radicale/js/radicale.js +++ b/modoboa/extensions/radicale/static/radicale/js/radicale.js @@ -49,6 +49,13 @@ Radicale.prototype = { edit_calendar_cb: function() { $("#original_row").dynamicrule(); + $('.submit').on('click', $.proxy(function(e) { + simple_ajax_form_post(e, { + formid: "ucal_form", + reload_on_success: false, + success_cb: $.proxy(this.reload_listing, this) + }); + }, this)); }, add_shared_calendar_cb: function() { diff --git a/modoboa/extensions/radicale/templates/radicale/accessrule.html b/modoboa/extensions/radicale/templates/radicale/accessrule.html new file mode 100644 index 000000000..ef4aa4fc0 --- /dev/null +++ b/modoboa/extensions/radicale/templates/radicale/accessrule.html @@ -0,0 +1,7 @@ +{% load i18n %} +
+ {{ username }} + + + +
diff --git a/modoboa/extensions/radicale/templates/radicale/calendars.html b/modoboa/extensions/radicale/templates/radicale/calendars.html index bc96c752d..bbdb06597 100644 --- a/modoboa/extensions/radicale/templates/radicale/calendars.html +++ b/modoboa/extensions/radicale/templates/radicale/calendars.html @@ -1,5 +1,6 @@ {% extends "twocols.html" %}{% load i18n radicale_tags %} {% block html_head %} + diff --git a/modoboa/extensions/radicale/templates/radicale/rightsform.html b/modoboa/extensions/radicale/templates/radicale/rightsform.html index f4a93d5be..3cbd20874 100644 --- a/modoboa/extensions/radicale/templates/radicale/rightsform.html +++ b/modoboa/extensions/radicale/templates/radicale/rightsform.html @@ -1,23 +1,12 @@ -{% load lib_tags %} +{% load lib_tags radicale_tags %} {% if error %}
{{ error }}
{% endif %}
{% csrf_token %} - - - - - - - - - - - - - - - - - -
{{ form.username.label }}{{ form.read_access.label }}{{ form.write_access.label }}
{{ form.username }}{{ form.read_access }}{{ form.write_access }}
+
+ {{ form.username }} + + + +
+ {% render_rule_fields form %}
diff --git a/modoboa/extensions/radicale/templatetags/radicale_tags.py b/modoboa/extensions/radicale/templatetags/radicale_tags.py index b6307204a..4bd5810c5 100644 --- a/modoboa/extensions/radicale/templatetags/radicale_tags.py +++ b/modoboa/extensions/radicale/templatetags/radicale_tags.py @@ -73,3 +73,28 @@ def calendar_actions(calendar): else: actions[0]["url"] = reverse("shared_calendar", args=[calendar.id]) return render_actions(actions) + + +@register.simple_tag +def render_rule_fields(form): + """Render access rules for a given calendar. + """ + from django.forms import forms + + cpt = 1 + result = "" + while True: + fname = "username_%d" % cpt + if not fname in form.fields: + break + rfieldname = "read_access_%d" % cpt + wfieldname = "write_access_%d" % cpt + result += render_to_string('radicale/accessrule.html', { + "username": forms.BoundField(form, form.fields[fname], fname), + "read_access": forms.BoundField( + form, form.fields[rfieldname], rfieldname), + "write_access": forms.BoundField( + form, form.fields[wfieldname], wfieldname) + }) + cpt += 1 + return result diff --git a/modoboa/extensions/radicale/views.py b/modoboa/extensions/radicale/views.py index 0ddb984e9..de32cd586 100644 --- a/modoboa/extensions/radicale/views.py +++ b/modoboa/extensions/radicale/views.py @@ -58,7 +58,7 @@ def user_calendar(request, pk): ucal = UserCalendar.objects.get(pk=pk) except UserCalendar.DoesNotExist: raise NotFound - instances = {"general": ucal, "rights": None} + instances = {"general": ucal, "rights": ucal} if request.method == "DELETE": # Check ownership ucal.delete() diff --git a/modoboa/lib/formutils.py b/modoboa/lib/formutils.py index aff2409ba..153021bdd 100644 --- a/modoboa/lib/formutils.py +++ b/modoboa/lib/formutils.py @@ -8,7 +8,6 @@ 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 @@ -67,7 +66,7 @@ class WizardStep(object): class WizardForm(object): """Custom wizard. """ - __metaclass__ = abc.ABCMeta + __metaclass__ = abc.ABCMeta template_name = "common/wizard_forms.html"