diff --git a/modoboa/extensions/admin/forms/account.py b/modoboa/extensions/admin/forms/account.py
index 98b173940..afaaabe58 100644
--- a/modoboa/extensions/admin/forms/account.py
+++ b/modoboa/extensions/admin/forms/account.py
@@ -151,7 +151,7 @@ class AccountFormMail(forms.Form, DynamicForm):
name = "aliases_%d" % cpt
self._create_field(forms.EmailField, name, alias.full_address)
cpt += 1
- self.fields["email"].initial = self.mb.full_address
+ self.fields["email"].initial = self.mb.full_address
self.fields["quota_act"].initial = self.mb.use_domain_quota
if not self.mb.use_domain_quota and self.mb.quota:
self.fields["quota"].initial = self.mb.quota
diff --git a/modoboa/extensions/radicale/forms.py b/modoboa/extensions/radicale/forms.py
index 89d56d3e4..1e87883a0 100644
--- a/modoboa/extensions/radicale/forms.py
+++ b/modoboa/extensions/radicale/forms.py
@@ -5,7 +5,7 @@ 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, TabForms
+from modoboa.lib.formutils import WizardForm, TabForms, DynamicForm
from modoboa.extensions.radicale.models import UserCalendar, SharedCalendar
@@ -26,8 +26,30 @@ class SharedCalendarForm(forms.ModelForm):
model = SharedCalendar
-class RightsForm(forms.Form):
+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"))
+
+ def __init__(self, *args, **kwargs):
+ from django.http import QueryDict
+
+ if "instance" in kwargs:
+ self.calendar = kwargs["instance"]
+ del kwargs["instance"]
+ else:
+ self.calendar = None
+ super(RightsForm, self).__init__(*args, **kwargs)
+
+ if args and isinstance(args[0], QueryDict):
+ self._load_from_qdict(args[0], "username", forms.EmailField)
+
+ def _create_field(self, typ, name, value=None, pos=None):
+ """
+ """
+ super(RightsForm, self)._create_field(typ, name, value, pos)
class UserCalendarWizard(WizardForm):
@@ -61,10 +83,16 @@ class UserCalendarEditionForm(TabForms):
self.forms.append({
"id": "general",
"title": _("General"),
- "formtpl": "",
"cls": UserCalendarForm,
"mandatory": True
})
+ self.forms.append({
+ "id": "rights",
+ "title": _("Rights"),
+ "cls": RightsForm,
+ "formtpl": "radicale/rightsform.html",
+ "mandatory": True
+ })
super(UserCalendarEditionForm, self).__init__(*args, **kwargs)
def extra_context(self, context):
diff --git a/modoboa/extensions/radicale/models.py b/modoboa/extensions/radicale/models.py
index 4521c3538..d42705877 100644
--- a/modoboa/extensions/radicale/models.py
+++ b/modoboa/extensions/radicale/models.py
@@ -108,3 +108,17 @@ class SharedCalendar(Calendar):
@property
def type(self):
return _("shared")
+
+
+@python_2_unicode_compatible
+class AccessRule(models.Model):
+ mailbox = models.ForeignKey("admin.Mailbox")
+ read = models.BooleanField(default=False)
+ write = models.BooleanField(default=False)
+ calendar = models.ForeignKey(UserCalendar)
+
+ def __str__(self):
+ access = "r" if self.read else ""
+ access += "w" if self.write else ""
+ return "%s access rule to %s -> " \
+ % (self.mailbox, self.calendar, access if access else "no access")
diff --git a/modoboa/extensions/radicale/static/radicale/js/jquery.dynamicrule.js b/modoboa/extensions/radicale/static/radicale/js/jquery.dynamicrule.js
new file mode 100644
index 000000000..09e2c42bf
--- /dev/null
+++ b/modoboa/extensions/radicale/static/radicale/js/jquery.dynamicrule.js
@@ -0,0 +1,94 @@
+/**
+ * @module
+ */
+
+(function($) {
+ var DynamicRule = function(element, options) {
+ this.initialize(element, options);
+ };
+
+ DynamicRule.prototype = {
+ constructor: DynamicRule,
+
+ /**
+ * Constructor.
+ */
+ initialize: function(element, options) {
+ this.$element = $(element);
+ this.options = $.extend({}, $.fn.dynamicrule.defaults, options);
+ this.rules_counter = 1;
+
+ $(document).on(
+ "click", "a[class=addrule]", $.proxy(this.add_rule, this)
+ );
+ $(document).on(
+ "click", "a[class=delrule]", $.proxy(this.delete_rule, this)
+ );
+ },
+
+ /**
+ * Reset the current rule to default values.
+ */
+ reset_original_rule: function() {
+ this.$element.find("#id_username").prop("value", "");
+ this.$element.find("#id_read_access").prop("checked", "");
+ this.$element.find("#id_write_access").prop("checked", "");
+ },
+
+ /**
+ * Save the current rule to a new set of elements and reset
+ * the original ones.
+ */
+ add_rule: function(event) {
+ var rulename = $("#id_username").prop("value");
+
+ event.preventDefault();
+ if (rulename === "") {
+ return;
+ }
+ var $clone = this.$element.clone();
+ var $btn = $clone.find("a");
+
+ $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);
+ $clone.find("#id_read_access")
+ .attr("id", "id_read_access_" + this.rules_counter);
+ $clone.find("#id_write_access")
+ .attr("id", "id_write_access_" + this.rules_counter);
+ $clone.insertAfter(this.$element);
+ this.rules_counter++;
+ this.reset_original_rule();
+ },
+
+ /**
+ * Delete a given rule.
+ */
+ delete_rule: function(event) {
+ event.preventDefault();
+ var $link = get_target(event, "a");
+
+ $link.parents("tr").remove();
+ }
+ };
+
+ $.fn.dynamicrule = function(method) {
+ return this.each(function() {
+ var $this = $(this),
+ data = $this.data('dynamicrule'),
+ options = typeof method === "object" && method;
+
+ if (!data) {
+ $this.data('dynamicrule', new DynamicRule(this, options));
+ }
+ if (typeof method === "string") {
+ data[method]();
+ }
+ });
+ };
+
+ $.fn.dynamicrule.defaults = {
+ };
+
+})(jQuery);
diff --git a/modoboa/extensions/radicale/static/radicale/js/radicale.js b/modoboa/extensions/radicale/static/radicale/js/radicale.js
index 367f54f9f..4abcc6490 100644
--- a/modoboa/extensions/radicale/static/radicale/js/radicale.js
+++ b/modoboa/extensions/radicale/static/radicale/js/radicale.js
@@ -1,28 +1,62 @@
-var Radicale = function() {
- this.initialize();
+/**
+ * @module radicale
+ * @desc Utility class for the radicale extension
+ */
+var Radicale = function(options) {
+ Listing.call(this, options);
};
Radicale.prototype = {
constructor: Radicale,
- initialize: function() {
+ defaults: {
+ deflocation: "calendars/"
+ },
+
+ initialize: function(options) {
+ Listing.prototype.initialize.call(this, options);
+ this.options = $.extend({}, this.defaults, this.options);
+ this.options.defcallback = $.proxy(this.list_cb, this);
+ this.navobj = new History(this.options);
$(document).on(
"click", "a[name=delcalendar]", this.del_calendar
);
},
+ list_cb: function(data) {
+ $("#listing").html(data.table);
+ this.update_listing(data);
+ $("a.filter").click($.proxy(this.filter_by_tag, this));
+ },
+
+ /**
+ * Refresh the listing.
+ *
+ */
+ reload_listing: function(data) {
+ this.navobj.update(true);
+ if (data) {
+ $("body").notify("success", data, 2000);
+ }
+ },
+
add_calendar_cb: function() {
$("#wizard").cwizard({
- formid: "newcal_form"
+ formid: "newcal_form",
+ success_callback: $.proxy(this.reload_listing, this)
});
},
+ edit_calendar_cb: function() {
+ $("#original_row").dynamicrule();
+ },
+
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)*/
+ reload_on_success: false,
+ success_cb: $.proxy(this.reload_listing, this)
});
}, this));
},
@@ -40,8 +74,8 @@ Radicale.prototype = {
$.ajax({
url: $link.attr("href"),
type: "DELETE"
- }).done(function(data) {
- $("body").notify("success", data, 2000);
- });
+ }).done($.proxy(this.reload_listing, this));
}
};
+
+Radicale.prototype = $.extend({}, Listing.prototype, Radicale.prototype);
diff --git a/modoboa/extensions/radicale/templates/radicale/calendar_list.html b/modoboa/extensions/radicale/templates/radicale/calendar_list.html
new file mode 100644
index 000000000..294ae5ac2
--- /dev/null
+++ b/modoboa/extensions/radicale/templates/radicale/calendar_list.html
@@ -0,0 +1,23 @@
+{% load i18n lib_tags radicale_tags %}
+
+
+
+ | {% trans "Name" %} |
+ {% trans "Type" %} |
+ {% trans "Address" %} |
+ {% trans "Actions" %} |
+
+
+
+ {% for cal in calendars %}
+
+ | {% calendar_modify_link cal %} |
+
+ {{ cal.type }}
+ |
+ {{ cal.url }} |
+ {% calendar_actions cal %} |
+
+ {% endfor %}
+
+
diff --git a/modoboa/extensions/radicale/templates/radicale/calendars.html b/modoboa/extensions/radicale/templates/radicale/calendars.html
index b30b6faad..bc96c752d 100644
--- a/modoboa/extensions/radicale/templates/radicale/calendars.html
+++ b/modoboa/extensions/radicale/templates/radicale/calendars.html
@@ -1,10 +1,14 @@
{% extends "twocols.html" %}{% load i18n radicale_tags %}
{% block html_head %}
+
+
+
+
{% endblock %}
@@ -18,27 +22,4 @@ $(document).ready(function() {
{% endblock %}
-{% block apparea %}{% load lib_tags %}
-
-
-
- | {% trans "Name" %} |
- {% trans "Type" %} |
- {% trans "Address" %} |
- {% trans "Actions" %} |
-
-
-
- {% for cal in calendars %}
-
- | {% calendar_modify_link cal %} |
-
- {{ cal.type }}
- |
- {{ cal.url }} |
- {% calendar_actions cal %} |
-
- {% endfor %}
-
-
-{% endblock %}
+{% block apparea %}{% endblock %}
diff --git a/modoboa/extensions/radicale/templates/radicale/rightsform.html b/modoboa/extensions/radicale/templates/radicale/rightsform.html
new file mode 100644
index 000000000..f4a93d5be
--- /dev/null
+++ b/modoboa/extensions/radicale/templates/radicale/rightsform.html
@@ -0,0 +1,23 @@
+{% load lib_tags %}
+{% if error %}{{ error }}
{% endif %}
+
diff --git a/modoboa/extensions/radicale/templatetags/radicale_tags.py b/modoboa/extensions/radicale/templatetags/radicale_tags.py
index ffae882b4..b6307204a 100644
--- a/modoboa/extensions/radicale/templatetags/radicale_tags.py
+++ b/modoboa/extensions/radicale/templatetags/radicale_tags.py
@@ -51,7 +51,7 @@ def calendar_modify_link(calendar):
linkdef["url"] = reverse(
"user_calendar", args=[calendar.pk]
)
- #linkdef["modalcb"] = "admin.domainform_cb"
+ linkdef["modalcb"] = "radicale.edit_calendar_cb"
else:
linkdef["url"] = reverse(
"shared_calendar", args=[calendar.pk]
diff --git a/modoboa/extensions/radicale/views.py b/modoboa/extensions/radicale/views.py
index 5ef8dce66..0ddb984e9 100644
--- a/modoboa/extensions/radicale/views.py
+++ b/modoboa/extensions/radicale/views.py
@@ -5,13 +5,14 @@ from itertools import chain
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.lib.webutils import render_to_json_response
+from modoboa.lib.webutils import (
+ _render_to_string, render_to_json_response
+)
from modoboa.lib.exceptions import NotFound, PermDeniedException
from modoboa.extensions.radicale.forms import (
UserCalendarWizard, SharedCalendarForm, UserCalendarEditionForm
@@ -21,11 +22,13 @@ from modoboa.extensions.radicale.models import UserCalendar, SharedCalendar
@login_required
def index(request):
- return HttpResponseRedirect(reverse(calendars))
+ return render(request, "radicale/calendars.html", {
+ "selection": "radicale"
+ })
@login_required
-def calendars(request, tplname="radicale/calendars.html"):
+def calendars(request, tplname="radicale/calendar_list.html"):
if request.user.group == "SimpleUsers":
cals = UserCalendar.objects.filter(
mailbox=request.user.mailbox_set.all()[0]
@@ -35,9 +38,10 @@ def calendars(request, tplname="radicale/calendars.html"):
UserCalendar.objects.get_for_admin(request.user),
SharedCalendar.objects.get_for_admin(request.user)
)
- return render(request, tplname, {
- "selection": "radicale",
- "calendars": cals
+ return render_to_json_response({
+ "table": _render_to_string(request, tplname, {
+ "calendars": cals
+ })
})
@@ -54,18 +58,12 @@ def user_calendar(request, pk):
ucal = UserCalendar.objects.get(pk=pk)
except UserCalendar.DoesNotExist:
raise NotFound
- instances = {"general": ucal}
+ instances = {"general": ucal, "rights": None}
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
diff --git a/modoboa/lib/formutils.py b/modoboa/lib/formutils.py
index 25a1421fe..aff2409ba 100644
--- a/modoboa/lib/formutils.py
+++ b/modoboa/lib/formutils.py
@@ -159,8 +159,15 @@ class WizardForm(object):
class DynamicForm(object):
+ """A form which accepts dynamic fields.
+ We consider a field to be dynamic when it can appear multiple
+ times within the same request.
+
+ """
def _create_field(self, typ, name, value=None, pos=None):
+ """Create a new form field.
+ """
self.fields[name] = typ(label="", required=False)
if value is not None:
self.fields[name].initial = value
@@ -169,6 +176,12 @@ class DynamicForm(object):
self.fields.keyOrder.insert(pos, name)
def _load_from_qdict(self, qdict, pattern, typ):
+ """Load all instances of a field from a ``QueryDict`` object.
+
+ :param ``QueryDict`` qdict: a QueryDict object
+ :param string pattern: pattern used to find field instances
+ :param typ: a form field class
+ """
expr = re.compile(r'%s_\d+' % pattern)
values = []
for k, v in qdict.iteritems():