mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
wip: Form to define calendar access rules.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user