wip: Form to define calendar access rules.

This commit is contained in:
Antoine Nguyen
2014-03-29 17:28:20 +01:00
parent a2aeabcf62
commit 8219ef3fe9
11 changed files with 261 additions and 53 deletions
+1 -1
View File
@@ -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
+31 -3
View File
@@ -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):
+14
View File
@@ -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")
@@ -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);
@@ -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);
@@ -0,0 +1,23 @@
{% load i18n lib_tags radicale_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>
@@ -1,10 +1,14 @@
{% extends "twocols.html" %}{% load i18n radicale_tags %}
{% block html_head %}
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.sortable.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/history.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/creation_wizard.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/listing.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}radicale/js/jquery.dynamicrule.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}radicale/js/radicale.js"></script>
<script type="text/javascript">
$(document).ready(function() {
radicale = new Radicale();
radicale = new Radicale({});
});
</script>
{% endblock %}
@@ -18,27 +22,4 @@ $(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 %}
{% block apparea %}<div id="listing"></div>{% endblock %}
@@ -0,0 +1,23 @@
{% load lib_tags %}
{% if error %}<div class="alert alert-error">{{ error }}</div>{% endif %}
<form action="{{ action }}" method="POST" id="{{ formid }}">
{% csrf_token %}
<table class="table table-condensed">
<thead>
<tr>
<th>{{ form.username.label }}</th>
<th>{{ form.read_access.label }}</th>
<th>{{ form.write_access.label }}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr id="original_row">
<td>{{ form.username }}</td>
<td>{{ form.read_access }}</td>
<td>{{ form.write_access }}</td>
<td><a href="#" class="addrule"><i class="icon-plus"></i></a></td>
</tr>
</tbody>
</table>
</form>
@@ -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]
+12 -14
View File
@@ -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
+13
View File
@@ -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():