mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-19 05:58:34 +02:00
wip: Radicale extension.
Graphical improvements.
This commit is contained in:
@@ -2,11 +2,10 @@
|
||||
from functools import wraps
|
||||
from itertools import chain
|
||||
from django.db.models import Q
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext as _
|
||||
from modoboa.core.models import User
|
||||
from modoboa.lib import parameters, events
|
||||
from modoboa.lib import events
|
||||
from modoboa.lib.exceptions import PermDeniedException
|
||||
from modoboa.lib.emailutils import split_mailbox
|
||||
from modoboa.extensions.admin.models import (
|
||||
@@ -30,46 +29,6 @@ def needs_mailbox():
|
||||
return decorator
|
||||
|
||||
|
||||
def get_sort_order(qdict, default, allowed_values=None):
|
||||
"""Return a sort order from a querydict object
|
||||
|
||||
:param QueryDict qdict: the object to analyse
|
||||
:param string default: the default sort order if no one is found
|
||||
:param list allowed_values: an optional list of allowed values
|
||||
:return: a 2uple of strings
|
||||
"""
|
||||
sort_order = qdict.get("sort_order", default)
|
||||
if sort_order.startswith("-"):
|
||||
sort_dir = "-"
|
||||
sort_order = sort_order[1:]
|
||||
else:
|
||||
sort_dir = ""
|
||||
if allowed_values is not None and not sort_order in allowed_values:
|
||||
return (default, "")
|
||||
return (sort_order, sort_dir)
|
||||
|
||||
|
||||
def get_listing_page(objects, pagenum):
|
||||
"""Return specific a listing page.
|
||||
|
||||
A page contains a limited number of elements (see
|
||||
ITEMS_PER_PAGE). If the given page number is wrong, the first page
|
||||
will be always returned.
|
||||
|
||||
:param list objects: object list to paginate
|
||||
:param int pagenum: page number
|
||||
:return: a ``Page`` object
|
||||
"""
|
||||
paginator = Paginator(
|
||||
objects, int(parameters.get_admin("ITEMS_PER_PAGE", app="core"))
|
||||
)
|
||||
try:
|
||||
page = paginator.page(int(pagenum))
|
||||
except (EmptyPage, PageNotAnInteger, ValueError):
|
||||
page = paginator.page(paginator.num_pages)
|
||||
return page
|
||||
|
||||
|
||||
def get_identities(user, searchquery=None, idtfilter=None, grpfilter=None):
|
||||
"""Return all the identities owned by a user.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
var Admin = function(options) {
|
||||
Listing.call(this, options);
|
||||
};
|
||||
|
||||
|
||||
Admin.prototype = {
|
||||
defaults: {
|
||||
deflocation: "list/",
|
||||
@@ -17,7 +17,6 @@ Admin.prototype = {
|
||||
Listing.prototype.initialize.call(this, options);
|
||||
this.options = $.extend({}, this.defaults, this.options);
|
||||
this.options.defcallback = $.proxy(this.list_cb, this);
|
||||
this.tag_handlers = {};
|
||||
|
||||
this.navobj = new History(this.options);
|
||||
|
||||
@@ -50,54 +49,6 @@ Admin.prototype = {
|
||||
$("#searchform").submit($.proxy(this.do_search, this));
|
||||
},
|
||||
|
||||
register_tag_handler: function(name, handler) {
|
||||
this.tag_handlers[name] = handler;
|
||||
if (this.navobj.getparam(name + "filter") !== undefined) {
|
||||
var text = this.navobj.getparam(name + "filter");
|
||||
$("#searchform").parent().after(this.make_tag(text, name));
|
||||
}
|
||||
},
|
||||
|
||||
generic_tag_handler: function(tag, $link) {
|
||||
if (this.navobj.getparam(tag + "filter") === undefined && $link.hasClass(tag)) {
|
||||
var text = $link.attr("name");
|
||||
this.navobj.setparam(tag + "filter", text).update();
|
||||
$("#searchform").parent().after(this.make_tag(text, tag));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
make_tag: function(text, type) {
|
||||
var $tag = $("<a />", {"name": type, "class" : "btn btn-mini", "html": text});
|
||||
var $i = $("<i />", {"class" : "icon-remove"}).prependTo($tag);
|
||||
|
||||
$tag.click($.proxy(this.remove_tag, this));
|
||||
return $tag;
|
||||
},
|
||||
|
||||
remove_tag: function(e) {
|
||||
var $tag = $(e.target);
|
||||
|
||||
if ($tag.is("i")) {
|
||||
$tag = $tag.parent();
|
||||
}
|
||||
e.preventDefault();
|
||||
this.navobj.delparam($tag.attr("name") + "filter").update();
|
||||
$tag.remove();
|
||||
},
|
||||
|
||||
filter_by_tag: function(e) {
|
||||
var $link = $(e.target);
|
||||
e.preventDefault();
|
||||
|
||||
for (var name in this.tag_handlers) {
|
||||
if (this.tag_handlers[name].apply(this, [name, $link])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
do_search: function(e) {
|
||||
e.preventDefault();
|
||||
var squery = $("#searchquery").val();
|
||||
|
||||
@@ -16,7 +16,7 @@ from modoboa.lib.exceptions import (
|
||||
PermDeniedException
|
||||
)
|
||||
from modoboa.lib.templatetags.lib_tags import pagination_bar
|
||||
from modoboa.extensions.admin.lib import get_sort_order, get_listing_page
|
||||
from modoboa.lib.listing import get_sort_order, get_listing_page
|
||||
from modoboa.extensions.admin.models import Domain, Mailbox
|
||||
from modoboa.extensions.admin.forms import (
|
||||
DomainForm
|
||||
|
||||
@@ -16,9 +16,10 @@ from modoboa.lib.webutils import (
|
||||
from modoboa.lib.templatetags.lib_tags import pagination_bar
|
||||
from modoboa.core.models import User
|
||||
from modoboa.extensions.admin.models import Mailbox, Domain
|
||||
from modoboa.extensions.admin.lib import (
|
||||
get_sort_order, get_listing_page, get_identities
|
||||
from modoboa.lib.listing import (
|
||||
get_sort_order, get_listing_page
|
||||
)
|
||||
from modoboa.extensions.admin.lib import get_identities
|
||||
from modoboa.extensions.admin.forms import (
|
||||
AccountForm
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.core.urlresolvers import reverse
|
||||
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.admin.models import Domain
|
||||
from modoboa.extensions.admin.models import Domain, Mailbox
|
||||
from modoboa.extensions.radicale.models import UserCalendar, SharedCalendar
|
||||
|
||||
|
||||
@@ -16,7 +16,24 @@ class UserCalendarForm(forms.ModelForm):
|
||||
"""
|
||||
class Meta:
|
||||
model = UserCalendar
|
||||
fields = ('name', )
|
||||
fields = ('name', 'mailbox', )
|
||||
widgets = {
|
||||
'mailbox': forms.widgets.Select(
|
||||
attrs={"class": "selectpicker", "data-live-search": "true"}
|
||||
)
|
||||
}
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
"""Custom constructor.
|
||||
|
||||
We need the current user to filter the mailbox list.
|
||||
"""
|
||||
super(UserCalendarForm, self).__init__(*args, **kwargs)
|
||||
if user.group == 'SimpleUsers':
|
||||
del self.fields['mailbox']
|
||||
else:
|
||||
self.fields['mailbox'].queryset = \
|
||||
Mailbox.objects.get_for_admin(user)
|
||||
|
||||
|
||||
class SharedCalendarForm(forms.ModelForm):
|
||||
@@ -25,6 +42,11 @@ class SharedCalendarForm(forms.ModelForm):
|
||||
"""
|
||||
class Meta:
|
||||
model = SharedCalendar
|
||||
widgets = {
|
||||
"domain": forms.widgets.Select(
|
||||
attrs={"class": "selectpicker", "data-live-search": "true"}
|
||||
)
|
||||
}
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
"""Custom constructor.
|
||||
@@ -127,9 +149,9 @@ class UserCalendarWizard(WizardForm):
|
||||
"""
|
||||
def __init__(self, request):
|
||||
super(UserCalendarWizard, self).__init__(request)
|
||||
self.add_step(UserCalendarForm, _("General"))
|
||||
self.add_step(UserCalendarForm, _("General"), new_args=[request.user])
|
||||
self.add_step(
|
||||
RightsForm, _("Rights"), formtpl="radicale/rightsform.html"
|
||||
RightsForm, _("Rights"), formtpl="radicale/rightsform.html",
|
||||
)
|
||||
|
||||
def extra_context(self, context):
|
||||
@@ -141,7 +163,8 @@ class UserCalendarWizard(WizardForm):
|
||||
|
||||
def done(self):
|
||||
calendar = self.first_step.form.save(commit=False)
|
||||
calendar.mailbox = self.request.user.mailbox_set.all()[0]
|
||||
if self.request.user.group == 'SimpleUsers':
|
||||
calendar.mailbox = self.request.user.mailbox_set.all()[0]
|
||||
calendar.save()
|
||||
self.steps[1].form.calendar = calendar
|
||||
self.steps[1].form.save()
|
||||
@@ -151,13 +174,14 @@ class UserCalendarWizard(WizardForm):
|
||||
class UserCalendarEditionForm(TabForms):
|
||||
"""User calendar edition form.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
self.forms = []
|
||||
self.forms.append({
|
||||
"id": "general",
|
||||
"title": _("General"),
|
||||
"cls": UserCalendarForm,
|
||||
"mandatory": True
|
||||
"mandatory": True,
|
||||
"new_args": [request.user]
|
||||
})
|
||||
self.forms.append({
|
||||
"id": "rights",
|
||||
@@ -166,7 +190,7 @@ class UserCalendarEditionForm(TabForms):
|
||||
"formtpl": "radicale/rightsform.html",
|
||||
"mandatory": True
|
||||
})
|
||||
super(UserCalendarEditionForm, self).__init__(*args, **kwargs)
|
||||
super(UserCalendarEditionForm, self).__init__(request, *args, **kwargs)
|
||||
|
||||
def extra_context(self, context):
|
||||
calendar = self.instances["general"]
|
||||
|
||||
@@ -27,8 +27,8 @@ class Calendar(models.Model):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""Return calendar type"""
|
||||
def tags(self):
|
||||
"""Return calendar tags"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@@ -72,8 +72,8 @@ class UserCalendar(Calendar):
|
||||
)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return _("user")
|
||||
def tags(self):
|
||||
return [{"name": "user", "label": _("User"), "type": "cal"}]
|
||||
|
||||
@property
|
||||
def owner(self):
|
||||
@@ -115,8 +115,9 @@ class SharedCalendar(Calendar):
|
||||
)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return _("shared")
|
||||
def tags(self):
|
||||
return [{"name": "shared", "label": _("Shared"), "type": "cal"}]
|
||||
|
||||
|
||||
@property
|
||||
def owner(self):
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#searchform {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#original_row {
|
||||
margin-left: 100px;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ Radicale.prototype = {
|
||||
this.options = $.extend({}, this.defaults, this.options);
|
||||
this.options.defcallback = $.proxy(this.list_cb, this);
|
||||
this.navobj = new History(this.options);
|
||||
this.register_tag_handler("cal", this.generic_tag_handler);
|
||||
$(document).on(
|
||||
"click", "a[name=delcalendar]", $.proxy(this.del_calendar, this)
|
||||
);
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="name" class="sortable" data-sort_order="name" width="">{% trans "Name" %}</th>
|
||||
{% if with_owner %}<th id="owner" class="sortable" data-sort_order="mailbox">{% trans "Owner" %}</th>{% endif %}
|
||||
<th id="type">{% trans "Type" %}</th>
|
||||
{% if with_owner %}<th id="owner" class="sortable" data-sort_order="owner">{% trans "Owner" %}</th>{% endif %}
|
||||
<th id="tags">{% trans "Tags" %}</th>
|
||||
<th id="url">{% trans "Address" %}</th>
|
||||
<th id="actions" width="">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
@@ -14,9 +14,7 @@
|
||||
<tr id="{{ cal.id }}" class="" name="{{ forloop.counter }}">
|
||||
<td name="name">{% calendar_modify_link cal %}</td>
|
||||
{% if with_owner %}<td name="owner">{{ cal.owner }}</td>{% endif %}
|
||||
<td name="type">
|
||||
<span class="label label-info"><a href="#" class="filter">{{ cal.type }}</a></span>
|
||||
</td>
|
||||
<td name="tags">{% render_tags cal.tags %}</td>
|
||||
<td name="url">{{ cal.url }}</td>
|
||||
<td name="actions">{% calendar_actions cal %}</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "twocols.html" %}{% load i18n radicale_tags %}
|
||||
{% block html_head %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/jquery.sortable.css" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}radicale/css/radicale.css" />
|
||||
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.sortable.js"></script>
|
||||
<script type="text/javascript" src="{{ STATIC_URL }}js/history.js"></script>
|
||||
@@ -20,7 +21,6 @@ $(document).ready(function() {
|
||||
<ul class="nav nav-list">
|
||||
<li class="nav-header">{% trans "Filter" %}</li>
|
||||
<li>{% include "admin/searchform.html" %}</li>
|
||||
<li id="taglist"></li>
|
||||
<li class="nav-header">{% trans "Actions" %}</li>
|
||||
{% radicale_left_menu user %}
|
||||
</ul>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{% extends "common/generic_modal_form.html" %}{% load crispy_forms_tags %}
|
||||
{% block formbody %}
|
||||
{{ form|crispy }}
|
||||
{% endblock %}
|
||||
@@ -10,10 +10,12 @@ from django.contrib.auth.decorators import (
|
||||
)
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from modoboa.lib.listing import get_sort_order
|
||||
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
|
||||
)
|
||||
@@ -33,16 +35,24 @@ def calendars(request, tplname="radicale/calendar_list.html"):
|
||||
|
||||
The content depends on current user's role.
|
||||
"""
|
||||
sort_order, sort_dir = get_sort_order(request.GET, "name")
|
||||
calfilter = request.GET.get("calfilter", None)
|
||||
if request.user.group == "SimpleUsers":
|
||||
mbox = request.user.mailbox_set.all()[0]
|
||||
cals = UserCalendar.objects.filter(mailbox=mbox).select_related().all()
|
||||
with_owner = False
|
||||
else:
|
||||
cals = chain(
|
||||
UserCalendar.objects.get_for_admin(request.user),
|
||||
SharedCalendar.objects.get_for_admin(request.user)
|
||||
)
|
||||
ucals = []
|
||||
if calfilter is None or calfilter == "user":
|
||||
ucals = UserCalendar.objects.get_for_admin(request.user)
|
||||
scals = []
|
||||
if calfilter is None or calfilter == "shared":
|
||||
scals = SharedCalendar.objects.get_for_admin(request.user)
|
||||
cals = chain(ucals, scals)
|
||||
with_owner = True
|
||||
cals = sorted(
|
||||
cals, key=lambda c: getattr(c, sort_order), reverse=sort_dir == '-'
|
||||
)
|
||||
return render_to_json_response({
|
||||
"table": _render_to_string(request, tplname, {
|
||||
"calendars": cals, "with_owner": with_owner
|
||||
|
||||
+13
-1
@@ -22,7 +22,19 @@ def get_sort_order(qdict, default, allowed_values=None):
|
||||
|
||||
|
||||
def get_listing_page(objects, pagenum):
|
||||
paginator = Paginator(objects, int(parameters.get_admin("ITEMS_PER_PAGE", app="core")))
|
||||
"""Return specific a listing page.
|
||||
|
||||
A page contains a limited number of elements (see
|
||||
ITEMS_PER_PAGE). If the given page number is wrong, the first page
|
||||
will be always returned.
|
||||
|
||||
:param list objects: object list to paginate
|
||||
:param int pagenum: page number
|
||||
:return: a ``Page`` object
|
||||
"""
|
||||
paginator = Paginator(
|
||||
objects, int(parameters.get_admin("ITEMS_PER_PAGE", app="core"))
|
||||
)
|
||||
try:
|
||||
page = paginator.page(int(pagenum))
|
||||
except (EmptyPage, PageNotAnInteger, ValueError):
|
||||
|
||||
+7
File diff suppressed because one or more lines are too long
+8
File diff suppressed because one or more lines are too long
@@ -23,11 +23,11 @@ function get_static_url(value) {
|
||||
function modalbox(e, css, defhref, defcb, defclosecb) {
|
||||
e.preventDefault();
|
||||
var $this = $(this);
|
||||
var href = (defhref != undefined) ? defhref : $this.attr('href');
|
||||
var modalcb = (defcb != undefined) ? defcb : $this.attr('modalcb');
|
||||
var closecb = (defclosecb != undefined) ? defclosecb : $this.attr("closecb");
|
||||
var href = (defhref !== undefined) ? defhref : $this.attr('href');
|
||||
var modalcb = (defcb !== undefined) ? defcb : $this.attr('modalcb');
|
||||
var closecb = (defclosecb !== undefined) ? defclosecb : $this.attr("closecb");
|
||||
|
||||
if (href.indexOf('#') == 0) {
|
||||
if (href.indexOf('#') === 0) {
|
||||
$(href).modal('open');
|
||||
return;
|
||||
}
|
||||
@@ -41,6 +41,9 @@ function modalbox(e, css, defhref, defcb, defclosecb) {
|
||||
|
||||
$div.modal({show: false});
|
||||
$div.one('shown', function() {
|
||||
$(".selectpicker").selectpicker({
|
||||
container: 'body'
|
||||
});
|
||||
$(".help").popover({
|
||||
container: "#modalbox",
|
||||
trigger: "hover"
|
||||
@@ -323,4 +326,4 @@ $(document).ready(function() {
|
||||
$(document).on('click', 'a[data-toggle="ajaxmodal-autowidth"]', modalbox_autowidth);
|
||||
$(document).on('click', '.activator', activate_widget);
|
||||
$(".help").popover({trigger: 'hover'}).click(function(e) {e.preventDefault();});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ Listing.prototype = {
|
||||
|
||||
initialize: function(options) {
|
||||
this.options = $.extend({}, this.listing_defaults, options);
|
||||
this.tag_handlers = {};
|
||||
$(document).on("click", this.options.pbar_container + " a",
|
||||
$.proxy(this.load_page, this));
|
||||
},
|
||||
@@ -63,5 +64,53 @@ Listing.prototype = {
|
||||
sort_order = "-" + sort_order;
|
||||
}
|
||||
this.navobj.setparam("sort_order", sort_order).update();
|
||||
},
|
||||
|
||||
register_tag_handler: function(name, handler) {
|
||||
this.tag_handlers[name] = handler;
|
||||
if (this.navobj.getparam(name + "filter") !== undefined) {
|
||||
var text = this.navobj.getparam(name + "filter");
|
||||
$("#searchform").parent().after(this.make_tag(text, name));
|
||||
}
|
||||
},
|
||||
|
||||
generic_tag_handler: function(tag, $link) {
|
||||
if (this.navobj.getparam(tag + "filter") === undefined && $link.hasClass(tag)) {
|
||||
var text = $link.attr("name");
|
||||
this.navobj.setparam(tag + "filter", text).update();
|
||||
$("#searchform").parent().after(this.make_tag(text, tag));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
make_tag: function(text, type) {
|
||||
var $tag = $("<a />", {"name": type, "class" : "btn btn-mini", "html": text});
|
||||
var $i = $("<i />", {"class" : "icon-remove"}).prependTo($tag);
|
||||
|
||||
$tag.click($.proxy(this.remove_tag, this));
|
||||
return $tag;
|
||||
},
|
||||
|
||||
remove_tag: function(e) {
|
||||
var $tag = $(e.target);
|
||||
|
||||
if ($tag.is("i")) {
|
||||
$tag = $tag.parent();
|
||||
}
|
||||
e.preventDefault();
|
||||
this.navobj.delparam($tag.attr("name") + "filter").update();
|
||||
$tag.remove();
|
||||
},
|
||||
|
||||
filter_by_tag: function(e) {
|
||||
var $link = $(e.target);
|
||||
e.preventDefault();
|
||||
|
||||
for (var name in this.tag_handlers) {
|
||||
if (this.tag_handlers[name].apply(this, [name, $link])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<a class="close" data-dismiss="modal">x</a>
|
||||
<h3>{{ title }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body" style="overflow: visible;">
|
||||
{% block modalbody %}{% endblock %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<a class="close" data-dismiss="modal">x</a>
|
||||
<h3>{{ title }} / <small>{{ wizard.first_step.title }}</small></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body" style="overflow: visible;">
|
||||
<form class="form-horizontal" action="{{ action }}" enctype="{{ enctype }}"
|
||||
method="POST" id="{{ formid }}" name="{{ formid }}">{% csrf_token %}
|
||||
<div id="wizard" class="carousel">
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/bootstrap.css" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/bootstrap-responsive.css" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/bootstrap-datetimepicker.css" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/bootstrap-select.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/custom.css" />
|
||||
<script src="{{ STATIC_URL }}js/jquery.js" type="text/javascript"></script>
|
||||
<script src="{{ STATIC_URL }}js/bootstrap.js" type="text/javascript"></script>
|
||||
<script src="{{ STATIC_URL }}js/bootstrap-datetimepicker.js" type="text/javascript"></script>
|
||||
<script src="{{ STATIC_URL }}js/bootstrap-select.min.js" type="text/javascript"></script>
|
||||
{% if LANGUAGE_CODE != "en" %}<script src="{{ STATIC_URL }}js/locales/bootstrap-datetimepicker.{{ LANGUAGE_CODE }}.js" type="text/javascript"></script>{% endif %}
|
||||
<script src="{{ STATIC_URL }}js/global.js" type="text/javascript"></script>
|
||||
<script src="{{ STATIC_URL }}js/csrf.js" type="text/javascript"></script>
|
||||
|
||||
Reference in New Issue
Block a user