Support multiple prefixes

This commit is contained in:
nikolark committed 2015-01-25 19:01:33 +01:00
1 parent c7ee107e99
commit 7902767f3b
20 files changed
+581 -167

No files matched your search

+10 -4
View File
@@ -1,12 +1,18 @@
from django.contrib import admin
from mailadmin.models import OrgUnit
from mailadmin.models import OrgUnit, Prefix
class PrefixInline(admin.TabularInline):
model = Prefix
extra = 0
class OrgUnitAdmin(admin.ModelAdmin):
list_display = ('name', 'prefix', 'is_active')
list_display = ('name', 'is_active')
list_filter = ('is_active',)
list_editable = ('prefix',)
filter_horizontal = ('admin_groups', 'member_groups')
inlines = [PrefixInline]
admin.site.register(OrgUnit, OrgUnitAdmin)
admin.site.register(OrgUnit, OrgUnitAdmin)
admin.site.register(Prefix)
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('mailadmin', '0003_auto_20141105_2153'),
]
operations = [
migrations.CreateModel(
name='Prefix',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=500, null=True, blank=True)),
('orgunit', models.ForeignKey(related_name='prefixes', to='mailadmin.OrgUnit')),
],
options={
},
bases=(models.Model,),
),
migrations.RemoveField(
model_name='orgunit',
name='prefix',
),
]
+17 -1
View File
@@ -7,7 +7,6 @@ class OrgUnit(models.Model):
return self.name
name = models.CharField(max_length=500)
prefix = models.CharField(max_length=256, blank=True, null=True)
is_active = models.BooleanField(default=True)
admin_groups = models.ManyToManyField(Group, null=True, blank=True, related_name='admin_orgunits')
member_groups = models.ManyToManyField(Group, null=True, blank=True, related_name='member_orgunits')
@@ -17,3 +16,20 @@ class OrgUnit(models.Model):
class Meta:
ordering = ['name']
class PrefixQueryset(models.QuerySet):
def as_regex(self):
prefixes = self.values_list('name', flat=True)
return '|'.join(['^{0}-|^{0}@'.format(p) for p in prefixes])
class Prefix(models.Model):
def __unicode__(self):
return self.name
name = models.CharField(max_length=500, blank=True, null=True)
orgunit = models.ForeignKey('mailadmin.OrgUnit', related_name='prefixes')
objects = PrefixQueryset.as_manager()
+1 -2
View File
@@ -27,10 +27,9 @@ class SourcePrefixOwner(permissions.BasePermission):
return False
# Prepare regular expression
regexp = '|'.join(['^{0}-|^{0}@'.format(p) for p in my_prefixes])
regexp = my_prefixes.as_regex()
# All source-adresses should match the prefix regexp
for alias in serializer.initial_data:
print alias, re.search(regexp, alias.get('source', ''))
if re.search(regexp, alias.get('source', '')) is None:
return False
+5
View File
@@ -3,6 +3,7 @@ from django.contrib.auth.models import User, Group
from rest_framework import serializers
from mailadmin.models import OrgUnit
from rest_framework.relations import SlugRelatedField
class GroupSerializer(serializers.ModelSerializer):
@@ -22,6 +23,10 @@ class UserSerializer(serializers.ModelSerializer):
class OrgUnitSerializer(serializers.ModelSerializer):
admin_groups = GroupSerializer(many=True)
member_groups = GroupSerializer(many=True)
prefixes = SlugRelatedField(
many=True,
read_only=True,
slug_field='name')
class Meta:
model = OrgUnit
+116 -67
View File
@@ -1,6 +1,6 @@
(function() {
'use strict';
/*global moment, jQuery, document, _, $, window, List */
/*global moment, jQuery, document, _, $, window, List, queryString */
var api_urls = {
aliases: '/api/aliases/',
ou_list: '/api/orgunits/',
@@ -9,13 +9,6 @@
};
var csrf_token;
function getParameterByName(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'),
results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
function parseEmails(text) {
if(text === undefined || text === "") {
return [];
@@ -37,28 +30,60 @@
return _.unique(emails);
}
function filter_lists(term, pushState, with_members) {
if(pushState) {
/* Update query string */
window.history.pushState(null, null, '/lists/?q=' + term);
}
function set_query_string(obj) {
var qs = '';
if(obj !== null) {
qs = '?' + queryString.stringify(obj);
}
window.history.pushState(null, null, '/lists/'+qs);
}
function filter_lists(query_term, with_members) {
var lists = $(".fwdlist").not('.new-list');
if($.trim(query_term) === "") {
$(lists).find('.alias-row').removeClass('highlight');
$(lists).removeClass('hidden');
return;
}
/* Filter lists */
var re_term = new RegExp(term);
$(".fwdlist").not('.new-list').each(function() {
var cur_name = $(this).attr('data-list-name');
var members = $(this).find('.member');
var hits = _.filter(members, function(el) {
return re_term.test($(el).text());
});
if(re_term.test(cur_name) || (hits.length !== 0 && with_members) ) {
var re_term = new RegExp(query_term);
lists.each(function() {
var list_name = $(this).attr('data-list-name');
var members = $(this).find('.alias');
var hits = 0;
if(with_members) {
hits = _.filter(members, function(el) {
var matches = re_term.test($(el).text());
if(matches) {
$(el).parent().addClass('highlight');
} else {
$(el).parent().removeClass('highlight');
}
return matches;
});
} else {
$('.alias-row').removeClass('highlight');
}
if(re_term.test(list_name) || (hits.length !== 0 && with_members) ) {
$(this).removeClass('hidden');
} else {
$(this).addClass('hidden');
}
});
}
function filter_lists_by_orgunit(id, prefix) {
set_active_org_unit(id);
filter_lists(prefix);
var query_params = {orgunit: id};
if(id === "") {
query_params = null;
}
set_query_string(query_params);
}
function notify(title, text, type) {
/* types: info, success, error */
new PNotify({
@@ -93,6 +118,33 @@
error: onError
});
}
function set_active_org_unit(id) {
/* Reset active */
$(".orgunit-list li").removeClass('active');
/* Set current */
var org_unit = $('.orgunit-list li a[data-id="'+ id +'"]');
org_unit.parent().toggleClass('active');
$('.orgunits-select').val(id); // <select>
/* Reset search field */
$('.js-lists-filter-field').val('');
}
function set_prefixes_regex(ous) {
ous = _.map(ous, function(ou) {
var prefixes = _.map(ou.prefixes, function(prefix) {
return '^'+ prefix +'-|^'+ prefix +'@';
});
ou.prefixes_regex = prefixes.join('|');
return ou;
});
return ous;
}
function reset_active_org_unit() {
$(".orgunit-list li").removeClass('active');
$(".orgunit-list li:first-child").toggleClass('active');
}
$(document).ready(function() {
csrf_token = $('meta[name=x-csrf-token]').attr('content');
@@ -102,16 +154,22 @@
/* Lists view (home) */
if( $('body.home').length ) {
$.getJSON(api_urls.ou_list, function(data) {
data = set_prefixes_regex(data);
/* Load orgunit list */
var ou_html = nunjucks.render('orgunits.html', { orgunits: data });
$(".orgunit-list").append(ou_html);
var ous_html = nunjucks.render('orgunits_select.html', { orgunits: data });
$(".orgunit-select-container").html(ous_html);
var q_term = getParameterByName('q');
if(q_term && q_term[0] === '^') {
var prefix = q_term.slice(1, -1);
$(".orgunit-list li").removeClass('active');
$('.orgunit-list li a[data-prefix="'+prefix+'"]').parent().toggleClass('active');
/* If searching then set q */
var query_string = queryString.parse(location.search);
if(query_string.q) {
reset_active_org_unit();
$('.js-lists-filter-field').val(query_string.q);
}
if(query_string.orgunit) {
set_active_org_unit(query_string.orgunit);
}
$.getJSON(api_urls.email_domain, function(email_domain) {
@@ -136,6 +194,7 @@
}
$('.js-new-list-preview').text(new_list_name);
});
/* Prefix select */
$('.new-list .prefix-select a').on('click', function() {
$('.new-list .prefix-select a').parent().removeClass('active');
@@ -143,6 +202,7 @@
var prefix = $(this).parent().attr('data-value');
$('.new-list .prefix-btn').html(prefix+' <span class="caret"></span>');
});
$('.new-list textarea').on('keyup', update_num_emails);
$('.js-add-list').on('click', function(e) {
@@ -163,8 +223,7 @@
var prefix = $('.new-list .prefix-select .active').attr('data-value');
var source = prefix + new_list_name + "@" + email_domain.name;
var new_aliases = {};
new_aliases = _.map(emails, function(el) {
var new_aliases = _.map(emails, function(el) {
return {
'source': source,
'destination': el,
@@ -196,26 +255,15 @@
/* Filter lists by orgunit */
$(".orgunit-list a").on('click', function(e) {
var prefix = $(this).attr('data-prefix');
e.preventDefault();
/* Toggle selection */
$(".orgunit-list li").removeClass('active');
$(this).parent().toggleClass('active');
$('.orgunits-select').val(prefix);
filter_lists('^'+prefix+'-', true);
$('.js-lists-filter-field').val('');
var id = $(this).attr('data-id');
var prefix = $(this).attr('data-prefix');
filter_lists_by_orgunit(id, prefix);
});
$(".orgunits-select").on('change', function() {
var prefix = $(this).val();
/* Toggle selection */
$(".orgunit-list li").removeClass('active');
$(".orgunit-list li a[data-prefix="+prefix+"]").parent().toggleClass('active');
filter_lists('^'+prefix+'-', true);
$('.js-lists-filter-field').val('');
var id = $(this).val();
var prefix = $(this).find(':selected').attr('data-prefix');
filter_lists_by_orgunit(id, prefix);
});
});
});
@@ -236,28 +284,16 @@
return;
}
var forwards = data;
var lists =_.groupBy(forwards, function(fwd) { return fwd.source; });
var lists = _.groupBy(data, function(alias) { return alias.source; });
var fw_html = nunjucks.render('list.html', { lists: lists });
forwards_container.html(fw_html);
/* Load filter from query string */
var q_term = getParameterByName('q');
if(q_term) {
if(q_term[0] === '^') {
filter_lists(q_term, false);
} else {
filter_lists(q_term, false, true);
}
/* Filter lists by search term from query string */
var query_string = queryString.parse(location.search);
if(query_string.q) {
filter_lists(query_string.q, true);
}
/* Filter by search query */
$('.js-lists-filter-field').on('keyup', function() {
var term = $(this).val();
filter_lists(term, true, true);
$(".orgunit-list li").removeClass('active');
$(".orgunit-list li:first-child").toggleClass('active');
});
});
/* Mark row red for deletion and show delete button if applicable */
@@ -271,7 +307,7 @@
tbody.find('.link-del').removeClass('visible');
}
});
/* Delete selected */
/* Delete selected aliases */
forwards_container.on('click', '.js-del-selected', function() {
var list_name = $(this).attr('data-delete-list-name');
var list_el = $('.fwdlist[data-list-name="'+list_name+'"]');
@@ -281,7 +317,7 @@
return {
'source': $(el).attr('data-source'),
'destination': $(el).attr('data-destination'),
'domain': parseInt($(el).attr('data-domain'), 10),
'domain': parseInt($(el).attr('data-domain'), 10)
};
});
function success(res){
@@ -308,7 +344,7 @@
$.ajax(data);
});
/* Toggles emails textarea */
/* Toggle button for add emails textarea */
forwards_container.on('click', '.js-toggle-email-textarea', function(e) {
e.preventDefault();
var row = $(this).closest('tbody').find('.textarea-row');
@@ -357,9 +393,22 @@
}
);
});
/* Update num emails for all list textarea's */
forwards_container.on('keyup', '.js-add-list-textarea', update_num_emails);
/* New list toggle display */
/* Filter by search query */
$('.js-lists-filter-field').on('keyup change', function(e) {
var term = $(this).val();
filter_lists(term, true);
reset_active_org_unit();
var query_params = {q: term};
if(term === "") {
query_params = null;
}
set_query_string(query_params);
});
/* New list toggle display button */
$('.new-list-btn').on('click', function() {
$('.new-list').toggleClass('visible');
});
+16 -1
View File
@@ -141,6 +141,17 @@ $icon-font-path: "../fonts/";
margin-bottom: 10px;
}
}
.highlight .alias:before {
width: 7px;
height: 7px;
border-radius: 50% 50%;
background-color: #00d000;
content: '';
display: inline-block;
position: absolute;
left: 20px;
margin-top: 7px;
}
/* Responsive fixes */
@media(max-width: 500px){
.panel-heading {
@@ -153,6 +164,10 @@ $icon-font-path: "../fonts/";
td {
font-size: 13px;
}
.highlight .alias:before {
margin-top: 5px;
}
}
.love {
@@ -164,4 +179,4 @@ $icon-font-path: "../fonts/";
text-align: center;
color: #666;
font-size: 12px;
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
<li class="active"><a href="/lists/" data-prefix=".*">Alle</a></li>
<li class="active"><a href="/lists/" data-id="" data-prefix=".+">Alle</a></li>
{% for ou in orgunits %}
<li><a href="/lists/?q={{ou.prefix }}" data-prefix="{{ou.prefix }}">{{ ou.name }} ({{ ou.prefix }})</a></li>
<li><a href="/lists/?orgunit={{ ou.id }}" data-prefix="{{ ou.prefixes_regex }}" data-id="{{ ou.id }}">{{ ou.name }}</a></li>
{% endfor %}
@@ -1,6 +1,6 @@
<select class="orgunits-select form-control">
<option value=".+">Alle</option>
<option value="" data-prefix=".+">Alle</option>
{% for ou in orgunits %}
<option value="{{ou.prefix }}">{{ ou.name }}</option>
<option value="{{ ou.id }}" data-prefix="{{ou.prefixes_regex }}">{{ ou.name }}</option>
{% endfor %}
</select>
+2 -1
View File
@@ -6,7 +6,8 @@
"jquery": "~1.11.0",
"underscore": "~1.7.0",
"nunjucks": "~1.1.0",
"pnotify": "~2.0.1"
"pnotify": "~2.0.1",
"query-string": "~1.0.0"
},
"devDependencies": {}
}
@@ -0,0 +1,37 @@
{
"name": "query-string",
"description": "Parse and stringify URL query strings",
"main": "query-string.js",
"keywords": [
"querystring",
"query",
"string",
"qs",
"param",
"parameter",
"url",
"uri",
"parse",
"stringify",
"encode",
"decode"
],
"ignore": [
".*",
"test.js",
"component.json",
"package.json"
],
"homepage": "https://github.com/sindresorhus/query-string",
"version": "1.0.0",
"_release": "1.0.0",
"_resolution": {
"type": "version",
"tag": "v1.0.0",
"commit": "f49a95c792543f38c76773f8c0031a488b34f1d0"
},
"_source": "git://github.com/sindresorhus/query-string.git",
"_target": "~1.0.0",
"_originalSource": "query-string",
"_direct": true
}
@@ -0,0 +1,25 @@
{
"name": "query-string",
"description": "Parse and stringify URL query strings",
"main": "query-string.js",
"keywords": [
"querystring",
"query",
"string",
"qs",
"param",
"parameter",
"url",
"uri",
"parse",
"stringify",
"encode",
"decode"
],
"ignore": [
".*",
"test.js",
"component.json",
"package.json"
]
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,66 @@
/*!
query-string
Parse and stringify URL query strings
https://github.com/sindresorhus/query-string
by Sindre Sorhus
MIT License
*/
(function () {
'use strict';
var queryString = {};
queryString.parse = function (str) {
if (typeof str !== 'string') {
return {};
}
str = str.trim().replace(/^(\?|#)/, '');
if (!str) {
return {};
}
return str.trim().split('&').reduce(function (ret, param) {
var parts = param.replace(/\+/g, ' ').split('=');
var key = parts[0];
var val = parts[1];
key = decodeURIComponent(key);
// missing `=` should be `null`:
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
val = val === undefined ? null : decodeURIComponent(val);
if (!ret.hasOwnProperty(key)) {
ret[key] = val;
} else if (Array.isArray(ret[key])) {
ret[key].push(val);
} else {
ret[key] = [ret[key], val];
}
return ret;
}, {});
};
queryString.stringify = function (obj) {
return obj ? Object.keys(obj).map(function (key) {
var val = obj[key];
if (Array.isArray(val)) {
return val.map(function (val2) {
return encodeURIComponent(key) + '=' + encodeURIComponent(val2);
}).join('&');
}
return encodeURIComponent(key) + '=' + encodeURIComponent(val);
}).join('&') : '';
};
if (typeof define === 'function' && define.amd) {
define(function() { return queryString; });
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = queryString;
} else {
window.queryString = queryString;
}
})();
@@ -0,0 +1,78 @@
# query-string [![Build Status](https://travis-ci.org/sindresorhus/query-string.svg?branch=master)](https://travis-ci.org/sindresorhus/query-string)
> Parse and stringify URL [query strings](http://en.wikipedia.org/wiki/Query_string)
## Install
```sh
$ npm install --save query-string
```
```sh
$ bower install --save query-string
```
```sh
$ component install sindresorhus/query-string
```
## Usage
```js
console.log(location.search);
// ?foo=bar
var parsed = queryString.parse(location.search);
console.log(parsed);
// {foo: 'bar'}
console.log(location.hash);
// #token=bada55cafe
var parsedHash = queryString.parse(location.hash);
console.log(parsedHash);
// {token: 'bada55cafe'}
parsed.foo = 'unicorn';
parsed.ilike = 'pizza';
location.search = queryString.stringify(parsed);
console.log(location.search);
// ?foo=unicorn&ilike=pizza
```
## API
### queryString.parse(*string*)
Parse a query string into an object. Leading `?` or `#` are ignored, so you can pass `location.search` or `location.hash` directly.
### queryString.stringify(*object*)
Stringify an object into a query string.
## Nesting
This module intentionally doesn't support nesting as it's not specced and varies between implementations, which causes a lot of [edge cases](https://github.com/visionmedia/node-querystring/issues).
You're much better off just converting the object to a JSON string:
```js
queryString.stringify({
foo: 'bar',
nested: JSON.stringify({
unicorn: 'cake'
})
});
// foo=bar&nested=%7B%22unicorn%22%3A%22cake%22%7D
```
## License
MIT © [Sindre Sorhus](http://sindresorhus.com)
+116 -67
View File
@@ -1,6 +1,6 @@
(function() {
'use strict';
/*global moment, jQuery, document, _, $, window, List */
/*global moment, jQuery, document, _, $, window, List, queryString */
var api_urls = {
aliases: '/api/aliases/',
ou_list: '/api/orgunits/',
@@ -9,13 +9,6 @@
};
var csrf_token;
function getParameterByName(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'),
results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
function parseEmails(text) {
if(text === undefined || text === "") {
return [];
@@ -37,28 +30,60 @@
return _.unique(emails);
}
function filter_lists(term, pushState, with_members) {
if(pushState) {
/* Update query string */
window.history.pushState(null, null, '/lists/?q=' + term);
}
function set_query_string(obj) {
var qs = '';
if(obj !== null) {
qs = '?' + queryString.stringify(obj);
}
window.history.pushState(null, null, '/lists/'+qs);
}
function filter_lists(query_term, with_members) {
var lists = $(".fwdlist").not('.new-list');
if($.trim(query_term) === "") {
$(lists).find('.alias-row').removeClass('highlight');
$(lists).removeClass('hidden');
return;
}
/* Filter lists */
var re_term = new RegExp(term);
$(".fwdlist").not('.new-list').each(function() {
var cur_name = $(this).attr('data-list-name');
var members = $(this).find('.member');
var hits = _.filter(members, function(el) {
return re_term.test($(el).text());
});
if(re_term.test(cur_name) || (hits.length !== 0 && with_members) ) {
var re_term = new RegExp(query_term);
lists.each(function() {
var list_name = $(this).attr('data-list-name');
var members = $(this).find('.alias');
var hits = 0;
if(with_members) {
hits = _.filter(members, function(el) {
var matches = re_term.test($(el).text());
if(matches) {
$(el).parent().addClass('highlight');
} else {
$(el).parent().removeClass('highlight');
}
return matches;
});
} else {
$('.alias-row').removeClass('highlight');
}
if(re_term.test(list_name) || (hits.length !== 0 && with_members) ) {
$(this).removeClass('hidden');
} else {
$(this).addClass('hidden');
}
});
}
function filter_lists_by_orgunit(id, prefix) {
set_active_org_unit(id);
filter_lists(prefix);
var query_params = {orgunit: id};
if(id === "") {
query_params = null;
}
set_query_string(query_params);
}
function notify(title, text, type) {
/* types: info, success, error */
new PNotify({
@@ -93,6 +118,33 @@
error: onError
});
}
function set_active_org_unit(id) {
/* Reset active */
$(".orgunit-list li").removeClass('active');
/* Set current */
var org_unit = $('.orgunit-list li a[data-id="'+ id +'"]');
org_unit.parent().toggleClass('active');
$('.orgunits-select').val(id); // <select>
/* Reset search field */
$('.js-lists-filter-field').val('');
}
function set_prefixes_regex(ous) {
ous = _.map(ous, function(ou) {
var prefixes = _.map(ou.prefixes, function(prefix) {
return '^'+ prefix +'-|^'+ prefix +'@';
});
ou.prefixes_regex = prefixes.join('|');
return ou;
});
return ous;
}
function reset_active_org_unit() {
$(".orgunit-list li").removeClass('active');
$(".orgunit-list li:first-child").toggleClass('active');
}
$(document).ready(function() {
csrf_token = $('meta[name=x-csrf-token]').attr('content');
@@ -102,16 +154,22 @@
/* Lists view (home) */
if( $('body.home').length ) {
$.getJSON(api_urls.ou_list, function(data) {
data = set_prefixes_regex(data);
/* Load orgunit list */
var ou_html = nunjucks.render('orgunits.html', { orgunits: data });
$(".orgunit-list").append(ou_html);
var ous_html = nunjucks.render('orgunits_select.html', { orgunits: data });
$(".orgunit-select-container").html(ous_html);
var q_term = getParameterByName('q');
if(q_term && q_term[0] === '^') {
var prefix = q_term.slice(1, -1);
$(".orgunit-list li").removeClass('active');
$('.orgunit-list li a[data-prefix="'+prefix+'"]').parent().toggleClass('active');
/* If searching then set q */
var query_string = queryString.parse(location.search);
if(query_string.q) {
reset_active_org_unit();
$('.js-lists-filter-field').val(query_string.q);
}
if(query_string.orgunit) {
set_active_org_unit(query_string.orgunit);
}
$.getJSON(api_urls.email_domain, function(email_domain) {
@@ -136,6 +194,7 @@
}
$('.js-new-list-preview').text(new_list_name);
});
/* Prefix select */
$('.new-list .prefix-select a').on('click', function() {
$('.new-list .prefix-select a').parent().removeClass('active');
@@ -143,6 +202,7 @@
var prefix = $(this).parent().attr('data-value');
$('.new-list .prefix-btn').html(prefix+' <span class="caret"></span>');
});
$('.new-list textarea').on('keyup', update_num_emails);
$('.js-add-list').on('click', function(e) {
@@ -163,8 +223,7 @@
var prefix = $('.new-list .prefix-select .active').attr('data-value');
var source = prefix + new_list_name + "@" + email_domain.name;
var new_aliases = {};
new_aliases = _.map(emails, function(el) {
var new_aliases = _.map(emails, function(el) {
return {
'source': source,
'destination': el,
@@ -196,26 +255,15 @@
/* Filter lists by orgunit */
$(".orgunit-list a").on('click', function(e) {
var prefix = $(this).attr('data-prefix');
e.preventDefault();
/* Toggle selection */
$(".orgunit-list li").removeClass('active');
$(this).parent().toggleClass('active');
$('.orgunits-select').val(prefix);
filter_lists('^'+prefix+'-', true);
$('.js-lists-filter-field').val('');
var id = $(this).attr('data-id');
var prefix = $(this).attr('data-prefix');
filter_lists_by_orgunit(id, prefix);
});
$(".orgunits-select").on('change', function() {
var prefix = $(this).val();
/* Toggle selection */
$(".orgunit-list li").removeClass('active');
$(".orgunit-list li a[data-prefix="+prefix+"]").parent().toggleClass('active');
filter_lists('^'+prefix+'-', true);
$('.js-lists-filter-field').val('');
var id = $(this).val();
var prefix = $(this).find(':selected').attr('data-prefix');
filter_lists_by_orgunit(id, prefix);
});
});
});
@@ -236,28 +284,16 @@
return;
}
var forwards = data;
var lists =_.groupBy(forwards, function(fwd) { return fwd.source; });
var lists = _.groupBy(data, function(alias) { return alias.source; });
var fw_html = nunjucks.render('list.html', { lists: lists });
forwards_container.html(fw_html);
/* Load filter from query string */
var q_term = getParameterByName('q');
if(q_term) {
if(q_term[0] === '^') {
filter_lists(q_term, false);
} else {
filter_lists(q_term, false, true);
}
/* Filter lists by search term from query string */
var query_string = queryString.parse(location.search);
if(query_string.q) {
filter_lists(query_string.q, true);
}
/* Filter by search query */
$('.js-lists-filter-field').on('keyup', function() {
var term = $(this).val();
filter_lists(term, true, true);
$(".orgunit-list li").removeClass('active');
$(".orgunit-list li:first-child").toggleClass('active');
});
});
/* Mark row red for deletion and show delete button if applicable */
@@ -271,7 +307,7 @@
tbody.find('.link-del').removeClass('visible');
}
});
/* Delete selected */
/* Delete selected aliases */
forwards_container.on('click', '.js-del-selected', function() {
var list_name = $(this).attr('data-delete-list-name');
var list_el = $('.fwdlist[data-list-name="'+list_name+'"]');
@@ -281,7 +317,7 @@
return {
'source': $(el).attr('data-source'),
'destination': $(el).attr('data-destination'),
'domain': parseInt($(el).attr('data-domain'), 10),
'domain': parseInt($(el).attr('data-domain'), 10)
};
});
function success(res){
@@ -308,7 +344,7 @@
$.ajax(data);
});
/* Toggles emails textarea */
/* Toggle button for add emails textarea */
forwards_container.on('click', '.js-toggle-email-textarea', function(e) {
e.preventDefault();
var row = $(this).closest('tbody').find('.textarea-row');
@@ -357,9 +393,22 @@
}
);
});
/* Update num emails for all list textarea's */
forwards_container.on('keyup', '.js-add-list-textarea', update_num_emails);
/* New list toggle display */
/* Filter by search query */
$('.js-lists-filter-field').on('keyup change', function(e) {
var term = $(this).val();
filter_lists(term, true);
reset_active_org_unit();
var query_params = {q: term};
if(term === "") {
query_params = null;
}
set_query_string(query_params);
});
/* New list toggle display button */
$('.new-list-btn').on('click', function() {
$('.new-list').toggleClass('visible');
});
+12 -10
View File
@@ -233,7 +233,7 @@ var lineno = null;
var colno = null;
var output = "";
try {
output += "<li class=\"active\"><a href=\"/lists/\" data-prefix=\".*\">Alle</a></li>\n";
output += "<li class=\"active\"><a href=\"/lists/\" data-id=\"\" data-prefix=\".+\">Alle</a></li>\n";
frame = frame.push();
var t_3 = runtime.contextOrFrameLookup(context, frame, "orgunits");
if(t_3) {var t_2 = t_3.length;
@@ -247,15 +247,15 @@ frame.set("loop.revindex0", t_2 - t_1 - 1);
frame.set("loop.first", t_1 === 0);
frame.set("loop.last", t_1 === t_2 - 1);
frame.set("loop.length", t_2);
output += "\n<li><a href=\"/lists/?q=";
output += runtime.suppressValue(runtime.memberLookup((t_4),"prefix", env.autoesc), env.autoesc);
output += "\n<li><a href=\"/lists/?orgunit=";
output += runtime.suppressValue(runtime.memberLookup((t_4),"id", env.autoesc), env.autoesc);
output += "\" data-prefix=\"";
output += runtime.suppressValue(runtime.memberLookup((t_4),"prefix", env.autoesc), env.autoesc);
output += runtime.suppressValue(runtime.memberLookup((t_4),"prefixes_regex", env.autoesc), env.autoesc);
output += "\" data-id=\"";
output += runtime.suppressValue(runtime.memberLookup((t_4),"id", env.autoesc), env.autoesc);
output += "\">";
output += runtime.suppressValue(runtime.memberLookup((t_4),"name", env.autoesc), env.autoesc);
output += " (";
output += runtime.suppressValue(runtime.memberLookup((t_4),"prefix", env.autoesc), env.autoesc);
output += ")</a></li>\n";
output += "</a></li>\n";
;
}
}
@@ -277,7 +277,7 @@ var lineno = null;
var colno = null;
var output = "";
try {
output += "<select class=\"orgunits-select form-control\">\n <option value=\".+\">Alle</option>\n\t";
output += "<select class=\"orgunits-select form-control\">\n <option value=\"\" data-prefix=\".+\">Alle</option>\n\t";
frame = frame.push();
var t_3 = runtime.contextOrFrameLookup(context, frame, "orgunits");
if(t_3) {var t_2 = t_3.length;
@@ -291,8 +291,10 @@ frame.set("loop.revindex0", t_2 - t_1 - 1);
frame.set("loop.first", t_1 === 0);
frame.set("loop.last", t_1 === t_2 - 1);
frame.set("loop.length", t_2);
output += "\n\t<option value=\"";
output += runtime.suppressValue(runtime.memberLookup((t_4),"prefix", env.autoesc), env.autoesc);
output += "\n\t <option value=\"";
output += runtime.suppressValue(runtime.memberLookup((t_4),"id", env.autoesc), env.autoesc);
output += "\" data-prefix=\"";
output += runtime.suppressValue(runtime.memberLookup((t_4),"prefixes_regex", env.autoesc), env.autoesc);
output += "\">";
output += runtime.suppressValue(runtime.memberLookup((t_4),"name", env.autoesc), env.autoesc);
output += "</option>\n\t";
+16
View File
@@ -6753,6 +6753,18 @@ html > body > .ui-pnotify {
margin-bottom: 10px;
}
.highlight .alias:before {
width: 7px;
height: 7px;
border-radius: 50% 50%;
background-color: #00d000;
content: '';
display: inline-block;
position: absolute;
left: 20px;
margin-top: 7px;
}
/* Responsive fixes */
@media (max-width: 500px) {
.panel-heading {
@@ -6767,6 +6779,10 @@ html > body > .ui-pnotify {
td {
font-size: 13px;
}
.highlight .alias:before {
margin-top: 5px;
}
}
.love {
color: #faafba;
+2 -1
View File
@@ -25,7 +25,8 @@
<script type="text/javascript" src="{% static 'bower_components/bootstrap-sass-official/vendor/assets/javascripts/bootstrap/dropdown.js' %}"></script>
<script type="text/javascript" src="{% static 'bower_components/bootstrap-sass-official/vendor/assets/javascripts/bootstrap/collapse.js' %}"></script>
<script type="text/javascript" src="{% static 'bower_components/pnotify/pnotify.core.js' %}"></script>
<script type="text/javascript" src="{% static 'bower_components/query-string/query-string.js' %}"></script>
<script type="text/javascript" src="{% static 'dist/scripts/templates.js' %}"></script>
<script type="text/javascript" src="{% static 'dist/scripts/main.js' %}"></script>
</body>
</html>
</html>
+8 -9
View File
@@ -12,7 +12,7 @@ from rest_framework.response import Response
from rest_framework import status
from mailadmin.api import DjangoPostfixDovecotAPI
from mailadmin.models import OrgUnit
from mailadmin.models import OrgUnit, Prefix
from mailadmin.serializers import UserSerializer, OrgUnitSerializer, AliasSerializer
from mailadmin.permissions import SourcePrefixOwner
@@ -69,10 +69,11 @@ class AliasesView(views.APIView):
my_groups = self.get_groups(request)
# Look for mailinglist prefixes
orgunits = OrgUnit.objects.exclude(prefix__isnull=True).exclude(is_active=False).distinct()
orgunits = OrgUnit.objects.exclude(is_active=False).distinct()
if not request.user.is_superuser:
orgunits = orgunits.filter(admin_groups__in=my_groups)
my_prefixes = orgunits.values_list('prefix', flat=True)
my_prefixes = Prefix.objects.filter(orgunit=orgunits)
return my_prefixes
@@ -80,12 +81,11 @@ class AliasesView(views.APIView):
my_prefixes = self.get_user_prefixes(request)
# Prepare regular expression
# FIXME: Move to method on orgunit manager
regexp = '|'.join(['^{0}-|^{0}@'.format(p) for p in my_prefixes])
regex = my_prefixes.as_regex()
if len(my_prefixes) == 0:
return Response({'result': 'No results'}) # Need at least one
res = self._api.list_aliases_regex(regexp)
res = self._api.list_aliases_regex(regex)
return Response(res)
@@ -123,12 +123,11 @@ class MyOrgUnitViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = [IsAuthenticated]
def get_queryset(self):
orgunits = OrgUnit.objects.exclude(prefix__isnull=True).exclude(is_active=False).distinct()
orgunits = OrgUnit.objects.exclude(prefixes__isnull=True).exclude(is_active=False).distinct()
if self.request.user.is_superuser:
return orgunits
group_ids = self.request.user.groups.values('id')
return orgunits.filter(admin_groups__in=group_ids)
return orgunits.filter(admin_groups=self.request.user.groups)
class OrgUnitViewSet(viewsets.ReadOnlyModelViewSet):