Merge pull request #1479 from normanjaeckel/FixCommitAngularAuth
Implemented auth via AngularJS.
This commit is contained in:
commit
00a9709bf8
@ -3,8 +3,6 @@ angular.module('OpenSlidesApp.core', [])
|
||||
.config(function($stateProvider) {
|
||||
// Use stateProvider.decorator to give default values to our states
|
||||
$stateProvider.decorator('views', function(state, parent) {
|
||||
|
||||
|
||||
var result = {},
|
||||
views = parent(state);
|
||||
|
||||
@ -69,12 +67,9 @@ angular.module('OpenSlidesApp.core', [])
|
||||
});
|
||||
})
|
||||
|
||||
.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
|
||||
.config(function($stateProvider, $locationProvider) {
|
||||
// Core urls
|
||||
$urlRouterProvider.otherwise('/');
|
||||
|
||||
$stateProvider
|
||||
.state('dashboard', {
|
||||
$stateProvider.state('dashboard', {
|
||||
url: '/',
|
||||
templateUrl: 'static/templates/dashboard.html'
|
||||
});
|
||||
@ -119,16 +114,6 @@ angular.module('OpenSlidesApp.core', [])
|
||||
});
|
||||
})
|
||||
|
||||
.run(function($rootScope, i18n) {
|
||||
// Puts the gettext methods into each scope.
|
||||
// Uses the methods that are known by xgettext by default.
|
||||
methods = ['gettext', 'dgettext', 'dcgettext', 'ngettext', 'dngettext',
|
||||
'pgettext', 'dpgettext'];
|
||||
_.forEach(methods, function(method) {
|
||||
$rootScope[method] = _.bind(i18n[method], i18n);
|
||||
});
|
||||
})
|
||||
|
||||
.run(function($rootScope, Config) {
|
||||
// Puts the config object into each scope.
|
||||
// TODO: maybe rootscope.config has to set before findAll() is finished
|
||||
|
@ -22,14 +22,13 @@
|
||||
<a href="/" class="logo"><img src="/static/img/logo.png" alt="OpenSlides" /></a>
|
||||
<span class="title optional">{{ config('event_name') }}</span>
|
||||
</div>
|
||||
{% block loginbutton %}
|
||||
<div class="navbar-right">
|
||||
<!-- login/logout button -->
|
||||
<div class="btn-group">
|
||||
{% if user.is_authenticated %}
|
||||
<div class="navbar-right" ng-controller="userMenu">
|
||||
<!-- login/logout button -->
|
||||
<div class="btn-group">
|
||||
<div ng-if="operator.isAuthenticated()">
|
||||
<a href="#" data-toggle="dropdown" class="btn btn-default dropdown-toggle">
|
||||
<span class="glyphicon glyphicon-user" aria-hidden="true"></span>
|
||||
<span class="optional-small">{{ user.username }}</span>
|
||||
<span class="optional-small">{{ operator.user.get_short_name() }}</span>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
@ -40,19 +39,29 @@
|
||||
<span class="glyphicon glyphicon-lock" aria-hidden="true"></span>
|
||||
{% trans "Change password" %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'user_logout' %}">
|
||||
<span class="glyphicon glyphicon-log-out" aria-hidden="true"></span>
|
||||
{% trans "Logout" %}</a></li>
|
||||
<li>
|
||||
<a ng-click="logout()">
|
||||
<span class="glyphicon glyphicon-log-out" aria-hidden="true"></span>
|
||||
Logout
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<a href="{% url 'user_login' %}" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-log-in" aria-hidden="true"></span>
|
||||
{{ gettext("Login") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div ng-if="!operator.isAuthenticated()">
|
||||
<a href="#" ng-click="showLoginForm = true" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-log-in" aria-hidden="true"></span>
|
||||
{{ gettext("Login") }}
|
||||
</a>
|
||||
<div ng-if="showLoginForm">
|
||||
<form>
|
||||
username: <input type="text" ng-model="username"><br>
|
||||
password: <input type="password" ng-model="password"><br>
|
||||
<input type="submit" ng-click="login(username, password)" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- Container -->
|
||||
|
@ -29,7 +29,7 @@
|
||||
<a href="/" class="logo" title="{% trans 'Home' %}"><img src="{% static 'img/logo.png' %}" alt="{% trans 'Logo' %}" /></a>
|
||||
<span class="title optional">{% get_config 'event_name' %}</span>
|
||||
</div>
|
||||
{% block loginbutton %}
|
||||
{% block loginbutton %}
|
||||
<div class="navbar-right">
|
||||
<!-- Chatbox button -->
|
||||
{% if chat_messages != None %}
|
||||
@ -61,7 +61,7 @@
|
||||
{% trans "Logout" %}</a></li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<a href="{% url 'user_login' %}" class="btn btn-default">
|
||||
<a href="" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-log-in" aria-hidden="true"></span>
|
||||
{% trans "Login" %}</a>
|
||||
{% endif %}
|
||||
|
@ -10,6 +10,7 @@ from django.utils.importlib import import_module
|
||||
from django.utils.translation import ugettext as _
|
||||
from haystack.views import SearchView as _SearchView
|
||||
from django.http import HttpResponse
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
|
||||
from openslides import get_version as get_openslides_version
|
||||
from openslides import get_git_commit_id, RELEASE
|
||||
@ -35,6 +36,14 @@ class IndexView(utils_views.View):
|
||||
to the custom staticfiles directory. See STATICFILES_DIRS in settings.py.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def as_view(cls, *args, **kwargs):
|
||||
"""
|
||||
Makes sure that the csrf cookie is send.
|
||||
"""
|
||||
view = super().as_view(*args, **kwargs)
|
||||
return ensure_csrf_cookie(view)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
with open(finders.find('templates/index.html')) as f:
|
||||
content = f.read()
|
||||
@ -341,6 +350,6 @@ class TagViewSet(ModelViewSet):
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to manage tags and it is a create, update or detroy request.
|
||||
"""
|
||||
if (self.action in ('create', 'update', 'destroy')
|
||||
and not request.user.has_perm('core.can_manage_tags')):
|
||||
if (self.action in ('create', 'update', 'destroy') and
|
||||
not request.user.has_perm('core.can_manage_tags')):
|
||||
self.permission_denied(request)
|
||||
|
@ -11,7 +11,7 @@ AUTH_USER_MODEL = 'users.User'
|
||||
|
||||
AUTHENTICATION_BACKENDS = ('openslides.users.auth.CustomizedModelBackend',)
|
||||
|
||||
LOGIN_URL = '/login/'
|
||||
LOGIN_URL = '/users/'
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
|
||||
SESSION_COOKIE_NAME = 'OpenSlidesSessionID'
|
||||
|
@ -11,14 +11,15 @@ handler500 = ErrorView.as_view(status_code=500)
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^rest/', include(router.urls)),
|
||||
# TODO: add "special" urls, for example pdf views etc.
|
||||
(r'^users/', include('openslides.users.urls')),
|
||||
|
||||
url(r'^user.*', IndexView.as_view()),
|
||||
url(r'^users.*', IndexView.as_view()),
|
||||
|
||||
# activate next line go get more angular views
|
||||
# url(r'^$', IndexView.as_view()),
|
||||
# url(r'^assignment.*', IndexView.as_view()),
|
||||
# url(r'^agenda.*', IndexView.as_view()),
|
||||
|
||||
)
|
||||
|
||||
|
||||
@ -31,7 +32,6 @@ urlpatterns += patterns(
|
||||
(r'^agenda/', include('openslides.agenda.urls')),
|
||||
(r'^motion/', include('openslides.motion.urls')),
|
||||
(r'^assignment/', include('openslides.assignment.urls')),
|
||||
(r'^user/', include('openslides.users.urls')),
|
||||
(r'^mediafile/', include('openslides.mediafile.urls')),
|
||||
(r'^config/', include('openslides.config.urls')),
|
||||
(r'^projector/', include('openslides.projector.urls')),
|
||||
@ -44,14 +44,6 @@ urlpatterns += patterns(
|
||||
'',
|
||||
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
|
||||
|
||||
url(r'^login/$',
|
||||
'openslides.users.views.login',
|
||||
name='user_login'),
|
||||
|
||||
url(r'^logout/$',
|
||||
'django.contrib.auth.views.logout_then_login',
|
||||
name='user_logout'),
|
||||
|
||||
url(r'^myusersettings/$',
|
||||
UserSettingsView.as_view(),
|
||||
name='user_settings'),
|
||||
|
@ -1,147 +1,10 @@
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
from openslides.config.api import config
|
||||
from openslides.utils.forms import (CssClassMixin,
|
||||
LocalizedModelMultipleChoiceField)
|
||||
from openslides.utils.forms import CssClassMixin
|
||||
|
||||
from .models import Group, Permission, User
|
||||
from .api import get_protected_perm
|
||||
|
||||
|
||||
class UserCreateForm(CssClassMixin, forms.ModelForm):
|
||||
groups = LocalizedModelMultipleChoiceField(
|
||||
# Hide the built-in groups 'Anonymous' (pk=1) and 'Registered' (pk=2)
|
||||
queryset=Group.objects.exclude(pk__in=[1, 2]),
|
||||
label=ugettext_lazy('Groups'), required=False)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('title', 'first_name', 'last_name', 'groups',
|
||||
'structure_level', 'about_me', 'comment', 'is_active',
|
||||
'default_password')
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
"""
|
||||
Ensures that a user has either a first name or a last name.
|
||||
"""
|
||||
cleaned_data = super(UserCreateForm, self).clean(*args, **kwargs)
|
||||
if not (cleaned_data['first_name'] or cleaned_data['last_name']):
|
||||
error_msg = _('First name and last name can not both be empty.')
|
||||
raise forms.ValidationError(error_msg)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class UserMultipleCreateForm(forms.Form):
|
||||
users_block = forms.CharField(
|
||||
widget=forms.Textarea,
|
||||
label=ugettext_lazy('Users'),
|
||||
help_text=ugettext_lazy('Use one line per user for its name '
|
||||
'(first name and last name).'))
|
||||
|
||||
|
||||
class UserUpdateForm(UserCreateForm):
|
||||
"""
|
||||
Form to update an user. It raises a validation error, if a non-superuser
|
||||
user edits himself and removes the last group containing the permission
|
||||
to manage users.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'title', 'first_name', 'last_name',
|
||||
'groups', 'structure_level', 'about_me', 'comment',
|
||||
'is_active', 'default_password')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.request = kwargs.pop('request')
|
||||
return super().__init__(*args, **kwargs)
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
"""
|
||||
Raises a validation error if a non-superuser user edits himself
|
||||
and removes the last group containing the permission to manage users.
|
||||
"""
|
||||
# TODO: Check this in clean_groups
|
||||
if (self.request.user == self.instance and
|
||||
not self.instance.is_superuser and
|
||||
not self.cleaned_data['groups'].filter(permissions__in=[get_protected_perm()]).exists()):
|
||||
error_msg = _('You can not remove the last group containing the permission to manage users.')
|
||||
raise forms.ValidationError(error_msg)
|
||||
return super().clean(*args, **kwargs)
|
||||
|
||||
|
||||
class GroupForm(CssClassMixin, forms.ModelForm):
|
||||
permissions = LocalizedModelMultipleChoiceField(
|
||||
queryset=Permission.objects.all(), label=ugettext_lazy('Permissions'), required=False,
|
||||
widget=forms.SelectMultiple(attrs={'class': 'dont_use_bsmselect'}))
|
||||
users = forms.ModelMultipleChoiceField(
|
||||
queryset=User.objects.all(), label=ugettext_lazy('Participants'), required=False,
|
||||
widget=forms.SelectMultiple(attrs={'class': 'dont_use_bsmselect'}))
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Take request argument
|
||||
self.request = kwargs.pop('request', None)
|
||||
# Initial users
|
||||
if kwargs.get('instance', None) is not None:
|
||||
initial = kwargs.setdefault('initial', {})
|
||||
initial['users'] = kwargs['instance'].user_set.all()
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
if config['users_sort_users_by_first_name']:
|
||||
self.fields['users'].queryset = self.fields['users'].queryset.order_by('first_name')
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = forms.ModelForm.save(self, False)
|
||||
|
||||
old_save_m2m = self.save_m2m
|
||||
|
||||
def save_m2m():
|
||||
old_save_m2m()
|
||||
|
||||
instance.user_set.clear()
|
||||
for user in self.cleaned_data['users']:
|
||||
instance.user_set.add(user)
|
||||
self.save_m2m = save_m2m
|
||||
|
||||
if commit:
|
||||
instance.save()
|
||||
self.save_m2m()
|
||||
|
||||
return instance
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
"""
|
||||
Raises a validation error if a non-superuser user removes himself
|
||||
from the last group containing the permission to manage users.
|
||||
|
||||
Raises also a validation error if a non-superuser removes his last
|
||||
permission to manage users from the (last) group.
|
||||
"""
|
||||
# TODO: Check this in clean_users or clean_permissions
|
||||
if (self.request and
|
||||
not self.request.user.is_superuser and
|
||||
self.request.user not in self.cleaned_data['users'] and
|
||||
not Group.objects.exclude(pk=self.instance.pk).filter(
|
||||
permissions__in=[get_protected_perm()],
|
||||
user__pk=self.request.user.pk).exists()):
|
||||
error_msg = _('You can not remove yourself from the last group containing the permission to manage users.')
|
||||
raise forms.ValidationError(error_msg)
|
||||
|
||||
if (self.request and
|
||||
not self.request.user.is_superuser and
|
||||
not get_protected_perm() in self.cleaned_data['permissions'] and
|
||||
not Group.objects.exclude(pk=self.instance.pk).filter(
|
||||
permissions__in=[get_protected_perm()],
|
||||
user__pk=self.request.user.pk).exists()):
|
||||
error_msg = _('You can not remove the permission to manage users from the last group you are in.')
|
||||
raise forms.ValidationError(error_msg)
|
||||
return super(GroupForm, self).clean(*args, **kwargs)
|
||||
from .models import User
|
||||
|
||||
|
||||
class UsersettingsForm(CssClassMixin, forms.ModelForm):
|
||||
|
@ -10,5 +10,5 @@ class UserMainMenuEntry(MainMenuEntry):
|
||||
verbose_name = ugettext_lazy('Users')
|
||||
required_permission = 'users.can_see_extra_data'
|
||||
default_weight = 50
|
||||
pattern_name = 'user_list'
|
||||
pattern_name = 'core_dashboard'
|
||||
icon_css_class = 'glyphicon-user'
|
||||
|
@ -3,12 +3,13 @@
|
||||
from random import choice
|
||||
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.contrib.auth.models import (
|
||||
from django.contrib.auth.models import ( # noqa
|
||||
AbstractBaseUser,
|
||||
BaseUserManager,
|
||||
PermissionsMixin)
|
||||
from django.contrib.auth.models import Group, Permission # noqa
|
||||
from django.core.urlresolvers import reverse
|
||||
Group,
|
||||
Permission,
|
||||
PermissionsMixin
|
||||
)
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||
|
||||
@ -158,11 +159,9 @@ class User(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, PermissionsMixin, Abstr
|
||||
Returns the URL to the user.
|
||||
"""
|
||||
if link == 'detail':
|
||||
url = reverse('user_detail', args=[str(self.pk)])
|
||||
url = "/users/%s/" % self.pk
|
||||
elif link == 'update':
|
||||
url = reverse('user_update', args=[str(self.pk)])
|
||||
elif link == 'delete':
|
||||
url = reverse('user_delete', args=[str(self.pk)])
|
||||
url = "/users/%s/edit/" % self.pk
|
||||
else:
|
||||
url = super().get_absolute_url(link)
|
||||
return url
|
||||
|
@ -3,7 +3,7 @@ angular.module('OpenSlidesApp.users', [])
|
||||
.config(function($stateProvider) {
|
||||
$stateProvider
|
||||
.state('users', {
|
||||
url: '/user',
|
||||
url: '/users',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
@ -33,7 +33,48 @@ angular.module('OpenSlidesApp.users', [])
|
||||
});
|
||||
})
|
||||
|
||||
.factory('User', function(DS) {
|
||||
.factory('operator', function(User, Group) {
|
||||
var operator = {
|
||||
user: null,
|
||||
perms: [],
|
||||
isAuthenticated: function() {
|
||||
return !!this.user;
|
||||
},
|
||||
setUser: function(user_id) {
|
||||
if (user_id) {
|
||||
User.find(user_id).then(function(user) {
|
||||
operator.user = user;
|
||||
// TODO: load only the needed groups
|
||||
Group.findAll().then(function() {
|
||||
operator.perms = user.getPerms();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
operator.user = null;
|
||||
Group.find(1).then(function(group) {
|
||||
operator.perms = group.permissions;
|
||||
});
|
||||
}
|
||||
},
|
||||
hasPerms: function(perms) {
|
||||
if (typeof perms == 'string') {
|
||||
perms = perms.split(' ');
|
||||
}
|
||||
return _.intersection(perms, operator.perms).length > 0;
|
||||
},
|
||||
}
|
||||
return operator;
|
||||
})
|
||||
|
||||
.run(function(operator, $rootScope, $http) {
|
||||
// Put the operator into the root scope
|
||||
$http.get('/users/whoami/').success(function(data) {
|
||||
operator.setUser(data.user_id);
|
||||
});
|
||||
$rootScope.operator = operator;
|
||||
})
|
||||
|
||||
.factory('User', function(DS, Group) {
|
||||
return DS.defineResource({
|
||||
name: 'users/user',
|
||||
endpoint: '/rest/users/user/',
|
||||
@ -52,7 +93,23 @@ angular.module('OpenSlidesApp.users', [])
|
||||
}
|
||||
return name;
|
||||
},
|
||||
}
|
||||
getPerms: function() {
|
||||
var allPerms = [];
|
||||
_.forEach(this.groups, function(groupId) {
|
||||
// Get group from server
|
||||
Group.find(groupId);
|
||||
// But do not work with the returned promise, because in
|
||||
// this case this method can not be called in $watch
|
||||
group = Group.get(groupId);
|
||||
if (group) {
|
||||
_.forEach(group.permissions, function(perm) {
|
||||
allPerms.push(perm);
|
||||
});
|
||||
}
|
||||
});
|
||||
return _.uniq(allPerms);
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
@ -63,6 +120,73 @@ angular.module('OpenSlidesApp.users', [])
|
||||
});
|
||||
})
|
||||
|
||||
/*
|
||||
* Directive to check for permissions
|
||||
*
|
||||
* This is the Code from angular.js ngIf.
|
||||
*
|
||||
* TODO: find a way not to copy the code.
|
||||
*/
|
||||
.directive('osPerms', ['$animate', function($animate) {
|
||||
return {
|
||||
multiElement: true,
|
||||
transclude: 'element',
|
||||
priority: 600,
|
||||
terminal: true,
|
||||
restrict: 'A',
|
||||
$$tlb: true,
|
||||
link: function($scope, $element, $attr, ctrl, $transclude) {
|
||||
var block, childScope, previousElements, perms;
|
||||
if ($attr.osPerms[0] === '!') {
|
||||
perms = _.trimLeft($attr.osPerms, '!')
|
||||
} else {
|
||||
perms = $attr.osPerms;
|
||||
}
|
||||
$scope.$watch(
|
||||
function (scope) {
|
||||
return scope.operator.hasPerms(perms);
|
||||
},
|
||||
function (value) {
|
||||
if ($attr.osPerms[0] === '!') {
|
||||
value = !value;
|
||||
}
|
||||
if (value) {
|
||||
if (!childScope) {
|
||||
$transclude(function(clone, newScope) {
|
||||
childScope = newScope;
|
||||
clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
|
||||
// Note: We only need the first/last node of the cloned nodes.
|
||||
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
|
||||
// by a directive with templateUrl when its template arrives.
|
||||
block = {
|
||||
clone: clone
|
||||
};
|
||||
$animate.enter(clone, $element.parent(), $element);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (previousElements) {
|
||||
previousElements.remove();
|
||||
previousElements = null;
|
||||
}
|
||||
if (childScope) {
|
||||
childScope.$destroy();
|
||||
childScope = null;
|
||||
}
|
||||
if (block) {
|
||||
previousElements = getBlockNodes(block.clone);
|
||||
$animate.leave(previousElements).then(function() {
|
||||
previousElements = null;
|
||||
});
|
||||
block = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.controller('UserListCtrl', function($scope, User, i18n) {
|
||||
User.bindAll({}, $scope, 'users');
|
||||
})
|
||||
@ -85,4 +209,42 @@ angular.module('OpenSlidesApp.users', [])
|
||||
User.save(user);
|
||||
// TODO: redirect to list-view
|
||||
};
|
||||
})
|
||||
|
||||
.controller('userMenu', function($scope, $http, DS, User, operator) {
|
||||
$scope.logout = function() {
|
||||
$http.post('/users/logout/').success(function(data) {
|
||||
operator.setUser(null);
|
||||
// TODO: remove all data from cache and reload page
|
||||
// DS.flush();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.login = function(username, password) {
|
||||
$http.post(
|
||||
'/users/login/',
|
||||
{'username': username, 'password': password}
|
||||
).success(function(data) {
|
||||
operator.setUser(data.user_id);
|
||||
$scope.showLoginForm = false;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// this is code from angular.js. Find a way to call this function from this file
|
||||
function getBlockNodes(nodes) {
|
||||
// TODO(perf): just check if all items in `nodes` are siblings and if they are return the original
|
||||
// collection, otherwise update the original collection.
|
||||
var node = nodes[0];
|
||||
var endNode = nodes[nodes.length - 1];
|
||||
var blockNodes = [node];
|
||||
|
||||
do {
|
||||
node = node.nextSibling;
|
||||
if (!node) break;
|
||||
blockNodes.push(node);
|
||||
} while (node !== endNode);
|
||||
|
||||
return $(blockNodes);
|
||||
}
|
||||
|
@ -4,4 +4,7 @@
|
||||
<a ui-sref="users.user.detail.update({id: user.id })">{{ gettext('Edit') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<a ui-sref="users.user.create">{{ gettext('New') }}</a>
|
||||
|
||||
<a os-perms="users.can_manage" ui-sref="users.user.create">{{ gettext('New') }}</a>
|
||||
<span os-perms="!users.can_manage">No Permission to create</span>
|
||||
<a href="/users/print/" target="_self">PDF</a>
|
||||
|
@ -1,55 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load tags %}
|
||||
|
||||
{% block title %}{% trans group.name %} – {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans group.name %}
|
||||
<small class="pull-right">
|
||||
<a href="{% url 'group_list' %}" class="btn btn-mini">
|
||||
<i class="icon-chevron-left"></i>
|
||||
<span class="optional-small">{% trans "Back to overview" %}</span>
|
||||
</a>
|
||||
{% if perms.users.can_manage and group.pk != 1 and group.pk != 2 %}
|
||||
<div class="btn-group">
|
||||
<a data-toggle="dropdown" class="btn btn-mini dropdown-toggle">
|
||||
<span class="optional-small">{% trans 'More actions' %} </span>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<!-- edit -->
|
||||
<li>
|
||||
<a href="{% url 'group_update' group.id %}">
|
||||
<i class="icon-pencil"></i>
|
||||
{% trans 'Edit group' %}
|
||||
</a>
|
||||
</li>
|
||||
<!-- delete -->
|
||||
<li>
|
||||
<a href="{% url 'group_delete' group.id %}">
|
||||
<i class="icon-remove"></i>
|
||||
{% trans 'Delete group' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</small>
|
||||
</h1>
|
||||
|
||||
<p>{{ group.description }}</p>
|
||||
|
||||
<h4>{% trans "Members" %}</h4>
|
||||
|
||||
<ol>
|
||||
{% for member in group_members %}
|
||||
<li><a href="{{ member|absolute_url }}">{{ member }}</a></li>
|
||||
{% empty %}
|
||||
<p>{% trans "No members available." %}</p>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
|
||||
{% endblock %}
|
@ -1,39 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{% if group %}
|
||||
{% trans "Edit group" %}
|
||||
{% else %}
|
||||
{% trans "New group" %}
|
||||
{% endif %}
|
||||
– {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% if group %}
|
||||
{% trans "Edit group" %}
|
||||
{% else %}
|
||||
{% trans "New group" %}
|
||||
{% endif %}
|
||||
<small class="pull-right">
|
||||
<a href="{% url 'group_list' %}" class="btn btn-mini">
|
||||
<i class="icon-chevron-left"></i>
|
||||
{% trans "Back to overview" %}
|
||||
</a>
|
||||
</small>
|
||||
</h1>
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{% include "form.html" %}
|
||||
<p>
|
||||
{% include "formbuttons_saveapply.html" %}
|
||||
<a href="{% url 'group_list' %}" class="btn">
|
||||
{% trans 'Cancel' %}
|
||||
</a>
|
||||
</p>
|
||||
<small>* {% trans "required" %}</small>
|
||||
</form>
|
||||
{% endblock %}
|
@ -1,72 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load tags %}
|
||||
|
||||
{% block title %}{% trans "Groups" %} – {{ block.super }}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<link href="{% static 'css/dataTables/dataTables.bootstrap.css' %}" type="text/css" rel="stylesheet">
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'js/jquery/jquery.dataTables.min.js' %}" type="text/javascript"></script>
|
||||
<script src="{% static 'js/jquery/dataTables.bootstrap.js' %}" type="text/javascript"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% trans "Groups" %}
|
||||
<small class="pull-right">
|
||||
<a href="{% url 'group_create' %}" class="btn btn-mini btn-primary" rel="tooltip" data-original-title="{% trans 'New group' %}">
|
||||
<i class="icon-plus icon-white"></i>
|
||||
{% trans "New" %}
|
||||
</a>
|
||||
<a href="{% url 'user_list' %}" class="btn btn-mini">
|
||||
<i class="icon-chevron-left"></i>
|
||||
<span class="optional-small">
|
||||
{% trans "Back to users overview" %}
|
||||
</span>
|
||||
</a>
|
||||
</small>
|
||||
</h1>
|
||||
|
||||
<table id="dataTable" class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="mini_width">{% trans "ID" %}</th>
|
||||
<th>{% trans "Group" %}</th>
|
||||
<th>{% trans "Members" %}</th>
|
||||
<th class="mini_width">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for group in groups %}
|
||||
<tr class="{% if group.is_active_slide %}activeline{% endif %}">
|
||||
<td class="nobr">{{ group.pk }}
|
||||
{% if group.pk == 1 or group.pk == 2 %}
|
||||
<a title="{% blocktrans %}The groups 1 ('Anonymous') and 2 ('Registered') are fixed default groups which can not be deleted. Each created or imported user is a member of group 2. Use custom groups to set additional permissions for a subset of users.{% endblocktrans %}"><i class="icon-info-sign"></i></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'group_detail' group.pk %}">{% trans group.name %}</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-info">{{ group.user_set.all.count }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span style="width: 1px; white-space: nowrap;">
|
||||
<a href="{% url 'group_update' group.id %}" title="{% trans 'Edit' %}" class="btn btn-mini">
|
||||
<i class="icon-pencil"></i>
|
||||
</a>
|
||||
{% if group.pk != 1 and group.pk != 2 %}
|
||||
<a href="{% url 'group_delete' group.id %}" title="{% trans 'Delete' %}" class="btn btn-mini">
|
||||
<i class="icon-remove"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
@ -1,59 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}{{ block.super }} – {% trans "Login" %} {% endblock %}
|
||||
|
||||
{% block loginbutton %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div id="login-page" class="container">
|
||||
<h2><img src="{% static 'img/logo-login.png' %}"></h2>
|
||||
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{% for msg in form.non_field_errors %}
|
||||
<em>{{ msg }}</em>
|
||||
{% if not forloop.last %}<br />{% endif %}
|
||||
{% empty %}
|
||||
<em>{% trans "Your username and password were not accepted. Please try again." %}</em>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if first_time_message %}
|
||||
<div class="alert alert-info">
|
||||
<em>{{ first_time_message|safe }}</em>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" action="{% url 'user_login' %}{% if next %}?next={{ next }}{% endif %}" class="well">
|
||||
{% csrf_token %}
|
||||
{# username #}
|
||||
<div class="form-group input-group">
|
||||
<div class="input-group-addon">
|
||||
<span class="glyphicon glyphicon-user" aria-hidden="true"></span>
|
||||
</div>
|
||||
<input type="text" class="form-control input-lg" name="username" id="id_username" placeholder="{% trans 'Username' %}">
|
||||
</div>
|
||||
{# password #}
|
||||
<div class="form-group input-group">
|
||||
<div class="input-group-addon">
|
||||
<span class="glyphicon glyphicon-lock" aria-hidden="true"></span>
|
||||
</div>
|
||||
<input type="password" class="form-control input-lg" name="password" id="id_password" placeholder="{% trans 'Password' %}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{% trans 'Login' %}
|
||||
</button>
|
||||
{% if os_enable_anonymous_login %}
|
||||
<a id="anonymous_login" class="btn btn-default" href="{% url 'core_dashboard' %}">
|
||||
{% trans 'Continue as guest' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
@ -1,92 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load tags %}
|
||||
|
||||
{% block title %}{{ shown_user }} – {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>
|
||||
{{ shown_user.clean_name }}
|
||||
<small class="pull-right">
|
||||
<a href="{% url 'user_list' %}" class="btn btn-mini">
|
||||
<i class="icon-chevron-left"></i>
|
||||
<span class="optional-small">
|
||||
{% trans "Back to overview" %}
|
||||
</span>
|
||||
</a>
|
||||
<!-- activate projector -->
|
||||
{% if perms.core.can_manage_projector %}
|
||||
<a href="{% url 'projector_activate_slide' shown_user.sid %}" class="activate_link btn {% if shown_user.active %}btn-primary{% endif %} btn-mini" rel="tooltip" data-original-title="{% trans 'Show users' %}">
|
||||
<i class="icon-facetime-video {% if shown_user.active %}icon-white{% endif %}"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.users.can_manage %}
|
||||
<div class="btn-group">
|
||||
<a data-toggle="dropdown" class="btn btn-mini dropdown-toggle">
|
||||
<span class="optional-small">{% trans 'More actions' %} </span><span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<!-- edit -->
|
||||
<li>
|
||||
<a href="{{ user|absolute_url:'update' }}">
|
||||
<i class="icon-pencil"></i>
|
||||
{% trans 'Edit user' %}
|
||||
</a>
|
||||
</li>
|
||||
<!-- delete -->
|
||||
<li><a href="{% url 'user_delete' shown_user.id %}"><i class="icon-remove"></i> {% trans 'Delete user' %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</small>
|
||||
</h1>
|
||||
|
||||
|
||||
<div class="user_details">
|
||||
<fieldset>
|
||||
<legend>{% trans "Personal data" %}</legend>
|
||||
<label>{% trans "Gender" %}</label>
|
||||
{{ shown_user.get_gender_display }}
|
||||
<label>{% trans "Email" %}</label>
|
||||
{{ shown_user.email }}
|
||||
<label>{% trans "About me" %}</label>
|
||||
{{ shown_user.about_me|linebreaks }}
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>{% trans "Event data" %}</legend>
|
||||
<label>{% trans "Structure level" %}</label>
|
||||
{{ shown_user.structure_level }}
|
||||
<label>{% trans "Committee" %}</label>
|
||||
{{ shown_user.committee }}
|
||||
<label>{% trans "Groups" %}</label>
|
||||
{% if shown_user.groups.all %}
|
||||
{% for group in shown_user.groups.all %}
|
||||
{% if group.pk != 2 %}
|
||||
{% trans group.name %}
|
||||
{% if not forloop.last %}, {% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% trans "The user is not member of any group." %}
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
|
||||
{% if perms.users.can_manage %}
|
||||
<fieldset>
|
||||
<legend>{% trans "Administrative data" %}</legend>
|
||||
<label>{% trans "Username" %}</label>
|
||||
{{ shown_user.username }}
|
||||
<label>{% trans "Comment" %}</label>
|
||||
{{ shown_user.comment|linebreaks }}
|
||||
<label>{% trans "Last Login" %}</label>
|
||||
{% if shown_user.last_login > shown_user.date_joined %}
|
||||
{{ shown_user.last_login }}
|
||||
{% else %}
|
||||
{% trans "The user has not logged in yet." %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,49 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
|
||||
|
||||
{% block title %}
|
||||
{% if edit_user %}
|
||||
{% trans "Edit user" %}
|
||||
{% else %}
|
||||
{% trans "New user" %}
|
||||
{% endif %}
|
||||
– {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% if edit_user %}
|
||||
{% trans "Edit user" %}
|
||||
{% else %}
|
||||
{% trans "New user" %}
|
||||
{% endif %}
|
||||
<small class="pull-right">
|
||||
<a href="{% url 'user_list' %}" class="btn btn-mini">
|
||||
<i class="icon-chevron-left"></i>
|
||||
{% trans "Back to overview" %}
|
||||
</a>
|
||||
</small>
|
||||
</h1>
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{% include "form.html" %}
|
||||
{% if edit_user %}
|
||||
<p style="margin: -15px 0 25px 0;">
|
||||
<a class="btn btn-mini" href="{% url 'user_reset_password' edit_user.id %}">
|
||||
<i class="icon-exclamation-sign"></i>
|
||||
{% trans 'Reset to First Password' %}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
{% include "formbuttons_saveapply.html" %}
|
||||
<a href="{% url 'user_list' %}" class="btn">
|
||||
{% trans 'Cancel' %}
|
||||
</a>
|
||||
</p>
|
||||
<small>* {% trans "required" %}</small>
|
||||
</form>
|
||||
{% endblock %}
|
@ -1,28 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{% trans 'New multiple users' %} – {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% trans 'New multiple users' %}
|
||||
<small class="pull-right">
|
||||
<a href="{% url 'user_list' %}" class="btn btn-mini">
|
||||
<i class="icon-chevron-left"></i> {% trans 'Back to overview' %}
|
||||
</a>
|
||||
</small>
|
||||
</h1>
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{% include 'form.html' %}
|
||||
<p>
|
||||
{% include 'formbuttons_save.html' %}
|
||||
<a href="{% url 'user_list' %}" class="btn">
|
||||
{% trans 'Cancel' %}
|
||||
</a>
|
||||
</p>
|
||||
<small>* {% trans 'required' %}</small>
|
||||
</form>
|
||||
{% endblock %}
|
@ -1,158 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load tags %}
|
||||
|
||||
{% block title %}{% trans "Users" %} – {{ block.super }}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<link href="{% static 'css/dataTables/dataTables.bootstrap.css' %}" type="text/css" rel="stylesheet">
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'js/jquery/jquery.dataTables.min.js' %}" type="text/javascript"></script>
|
||||
<script src="{% static 'js/jquery/dataTables.bootstrap.js' %}" type="text/javascript"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% trans "Users" %}
|
||||
<small class="pull-right">
|
||||
{% if perms.users.can_manage %}
|
||||
<a href="{% url 'user_create' %}" class="btn btn-mini btn-primary" rel="tooltip" data-original-title="{% trans 'New user' %}">
|
||||
<i class="icon-plus icon-white"></i>
|
||||
{% trans "New" %}
|
||||
</a>
|
||||
<a href="{% url 'user_create_multiple' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'New multiple users' %}">
|
||||
<i class="icon-plus"></i>
|
||||
{% trans 'New multiple' %}
|
||||
</a>
|
||||
<a href="{% url 'group_list' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'All groups' %}">
|
||||
<i class="icon-group"></i>
|
||||
<span class="optional-small">{% trans "Groups" %}</span>
|
||||
</a>
|
||||
<a href="{% url 'user_csv_import' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Import users' %}">
|
||||
<i class="icon-import"></i>
|
||||
<span class="optional-small"> {% trans 'Import' %}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.users.can_see and perms.users.can_manage %}
|
||||
<div class="btn-group">
|
||||
<a data-toggle="dropdown" class="btn btn-mini dropdown-toggle">
|
||||
<i class="icon-print"></i>
|
||||
<span class="optional-small"> PDF</span>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
{% url 'user_settings' as url_usersettings %}
|
||||
<li>
|
||||
<a href="{% url 'user_print' %}" target="_blank">
|
||||
<i class="icon-list"></i>
|
||||
{% trans 'List of users' %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'print_passwords' %}" target="_blank">
|
||||
<i class="icon-th-large"></i>
|
||||
{% trans 'List of access data' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="{% url 'user_print' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Print list of users as PDF' %}" target="_blank"><i class="icon-print"></i> PDF</a>
|
||||
{% endif %}
|
||||
</small>
|
||||
</h1>
|
||||
|
||||
<table id="dataTable" class="table table-striped table-bordered" cellpadding="0" cellspacing="0" border="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Present" %}</th>
|
||||
<th class="optional">{% trans "Title" %}</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th class="optional">{% trans "Structure level" %}</th>
|
||||
<th class="optional">{% trans "Group" %}</th>
|
||||
<th class="optional">{% trans "Committee" %}</th>
|
||||
{% if perms.users.can_manage %}
|
||||
<th class="optional">{% trans "Comment" %}</th>
|
||||
<th class="optional">{% trans "Last Login" %}</th>
|
||||
<th class="mini_width">{% trans "Actions" %}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr class="{% if user.is_active_slide %}activeline{% endif %}">
|
||||
<td>{% if perms.users.can_manage %}
|
||||
{% if user != request_user %}
|
||||
<a href="{% url 'user_status_toggle' user.id %}"
|
||||
class="status_link btn btn-mini {% if user.is_active %}btn-success{% endif %}"
|
||||
rel="tooltip" data-original-title="{% if user.is_active %}{% trans 'present' %}{% else %}{% trans 'absent' %}{% endif %}">
|
||||
<i class="{% if user.is_active %}icon-on{% else %}icon-off{% endif %}"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="status_link">
|
||||
<i class="status_link {% if user.is_active %}icon-on{% else %}icon-off{% endif %} tooltip-bottom"
|
||||
data-original-title="{% if user.is_active %}{% trans 'present' %}{% else %}{% trans 'absent' %}{% endif %}"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="optional">{{ user.title }}</td>
|
||||
<td>
|
||||
{% if 'users_sort_users_by_first_name'|get_config %}
|
||||
<a href="{{ user|absolute_url }}">{{ user.first_name }} {{ user.last_name }}</a>
|
||||
{% else %}
|
||||
<a href="{{ user|absolute_url }}">{{ user.last_name }}{% if user.last_name and user.first_name %},{% endif %} {{ user.first_name }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="optional">{{ user.structure_level }}</td>
|
||||
<td class="optional">
|
||||
{% for group in user.groups.all %}
|
||||
{% if group.pk != 2 %}
|
||||
{% trans group.name %}
|
||||
{% if not forloop.last %}<br>{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="optional">{{ user.committee }}</td>
|
||||
{% if perms.users.can_manage %}
|
||||
<td class="optional">
|
||||
{% if user.comment %}
|
||||
<a class="btn btn-mini" rel="popover" data-content="{{ user.comment|linebreaks }}">
|
||||
<i class="icon icon-search"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="optional">
|
||||
{% if user.last_login > user.date_joined %}
|
||||
{{ user.last_login }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span style="width: 1px; white-space: nowrap;">
|
||||
{% if perms.core.can_manage_projector %}
|
||||
<a href="{{ user|absolute_url:'projector' }}" class="activate_link btn {% if user.is_active_slide %}btn-primary{% endif %} btn-mini"
|
||||
rel="tooltip" data-original-title="{% trans 'Show user' %}">
|
||||
<i class="icon-facetime-video {% if user.is_active_slide %}icon-white{% endif %}"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ user|absolute_url:'update' }}" rel="tooltip" data-original-title="{% trans 'Edit' %}"
|
||||
class="btn btn-mini">
|
||||
<i class="icon-pencil"></i>
|
||||
</a>
|
||||
{% if user != request_user %}
|
||||
<a href="{% url 'user_delete' user.id %}" rel="tooltip" data-original-title="{% trans 'Delete' %}"
|
||||
class="btn btn-mini">
|
||||
<i class="icon-remove"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
@ -4,75 +4,10 @@ from . import views
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
# User
|
||||
url(r'^$',
|
||||
views.UserListView.as_view(),
|
||||
name='user_list'),
|
||||
|
||||
url(r'^new/$',
|
||||
views.UserCreateView.as_view(),
|
||||
name='user_create'),
|
||||
|
||||
url(r'^new_multiple/$',
|
||||
views.UserMultipleCreateView.as_view(),
|
||||
name='user_create_multiple'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/$',
|
||||
views.UserDetailView.as_view(),
|
||||
name='user_detail'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/edit/$',
|
||||
views.UserUpdateView.as_view(),
|
||||
name='user_update'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/del/$',
|
||||
views.UserDeleteView.as_view(),
|
||||
name='user_delete'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/reset_password/$',
|
||||
views.ResetPasswordView.as_view(),
|
||||
name='user_reset_password'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/status/activate/$',
|
||||
views.SetUserStatusView.as_view(),
|
||||
{'action': 'activate'},
|
||||
name='user_status_activate'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/status/deactivate/$',
|
||||
views.SetUserStatusView.as_view(),
|
||||
{'action': 'deactivate'},
|
||||
name='user_status_deactivate'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/status/toggle/$',
|
||||
views.SetUserStatusView.as_view(),
|
||||
{'action': 'toggle'},
|
||||
name='user_status_toggle'),
|
||||
|
||||
url(r'^csv_import/$',
|
||||
views.UserCSVImportView.as_view(),
|
||||
name='user_csv_import'),
|
||||
|
||||
# Group
|
||||
url(r'^group/$',
|
||||
views.GroupListView.as_view(),
|
||||
name='group_list'),
|
||||
|
||||
url(r'^group/new/$',
|
||||
views.GroupCreateView.as_view(),
|
||||
name='group_create'),
|
||||
|
||||
url(r'^group/(?P<pk>\d+)/$',
|
||||
views.GroupDetailView.as_view(),
|
||||
name='group_detail'),
|
||||
|
||||
url(r'^group/(?P<pk>\d+)/edit/$',
|
||||
views.GroupUpdateView.as_view(),
|
||||
name='group_update'),
|
||||
|
||||
url(r'^group/(?P<pk>\d+)/del/$',
|
||||
views.GroupDeleteView.as_view(),
|
||||
name='group_delete'),
|
||||
|
||||
# PDF
|
||||
url(r'^print/$',
|
||||
views.UsersListPDF.as_view(),
|
||||
@ -81,4 +16,17 @@ urlpatterns = patterns(
|
||||
url(r'^passwords/print/$',
|
||||
views.UsersPasswordsPDF.as_view(),
|
||||
name='print_passwords'),
|
||||
|
||||
# auth
|
||||
url(r'^login/$',
|
||||
views.UserLoginView.as_view(),
|
||||
name='user_login'),
|
||||
|
||||
url(r'^logout/$',
|
||||
views.UserLogoutView.as_view(),
|
||||
name='user_logout'),
|
||||
|
||||
url(r'^whoami/$',
|
||||
views.WhoAmIView.as_view(),
|
||||
name='user_whoami'),
|
||||
)
|
||||
|
@ -1,179 +1,30 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.contrib.auth.views import login as django_login
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy, activate
|
||||
from django.contrib.auth import login as auth_login
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import activate, ugettext_lazy
|
||||
|
||||
from openslides.config.api import config
|
||||
from openslides.utils.rest_api import ModelViewSet
|
||||
from openslides.utils.utils import delete_default_permissions, html_strong
|
||||
from openslides.utils.views import (
|
||||
CreateView, CSVImportView, DeleteView, DetailView, FormView, ListView,
|
||||
PDFView, PermissionMixin, QuestionView, RedirectView, SingleObjectMixin,
|
||||
UpdateView, LoginMixin)
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
CSVImportView,
|
||||
FormView,
|
||||
LoginMixin,
|
||||
PDFView,
|
||||
UpdateView,
|
||||
APIView
|
||||
)
|
||||
|
||||
from .api import get_protected_perm
|
||||
from .csv_import import import_users
|
||||
from .forms import (GroupForm, UserCreateForm, UserMultipleCreateForm,
|
||||
UsersettingsForm, UserUpdateForm)
|
||||
from .forms import UsersettingsForm
|
||||
from .models import Group, User
|
||||
from .pdf import users_to_pdf, users_passwords_to_pdf
|
||||
from .serializers import GroupSerializer, UserCreateUpdateSerializer, UserFullSerializer, UserShortSerializer
|
||||
|
||||
|
||||
class UserListView(ListView):
|
||||
"""
|
||||
Show all users.
|
||||
"""
|
||||
required_permission = 'users.can_see_extra_data'
|
||||
context_object_name = 'users'
|
||||
|
||||
def get_queryset(self):
|
||||
query = User.objects
|
||||
if config['users_sort_users_by_first_name']:
|
||||
query = query.order_by('first_name')
|
||||
else:
|
||||
query = query.order_by('last_name')
|
||||
return query.all()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
all_users = User.objects.count()
|
||||
# context vars
|
||||
context.update({
|
||||
'allusers': all_users,
|
||||
'request_user': self.request.user})
|
||||
return context
|
||||
|
||||
|
||||
class UserDetailView(DetailView, PermissionMixin):
|
||||
"""
|
||||
Classed based view to show a specific user in the interface.
|
||||
"""
|
||||
required_permission = 'users.can_see_extra_data'
|
||||
model = User
|
||||
context_object_name = 'shown_user'
|
||||
|
||||
|
||||
class UserCreateView(CreateView):
|
||||
"""
|
||||
Create a new user.
|
||||
"""
|
||||
required_permission = 'users.can_manage'
|
||||
model = User
|
||||
context_object_name = 'edit_user'
|
||||
form_class = UserCreateForm
|
||||
success_url_name = 'user_list'
|
||||
url_name_args = []
|
||||
|
||||
def manipulate_object(self, form):
|
||||
self.object.username = User.objects.generate_username(
|
||||
form.cleaned_data['first_name'], form.cleaned_data['last_name'])
|
||||
|
||||
if not self.object.default_password:
|
||||
self.object.default_password = User.objects.generate_password()
|
||||
|
||||
self.object.set_password(self.object.default_password)
|
||||
|
||||
|
||||
class UserMultipleCreateView(FormView):
|
||||
"""
|
||||
View to create multiple users at once using a big text field.
|
||||
|
||||
Sets the password with md5. It is the same password as in the
|
||||
default_password field in cleartext. A stronger password hasher is used,
|
||||
when the password is changed by the user.
|
||||
"""
|
||||
required_permission = 'users.can_manage'
|
||||
template_name = 'users/user_form_multiple.html'
|
||||
form_class = UserMultipleCreateForm
|
||||
success_url_name = 'user_list'
|
||||
|
||||
def form_valid(self, form):
|
||||
# TODO: Use bulk_create
|
||||
for number, line in enumerate(form.cleaned_data['users_block'].splitlines()):
|
||||
names_list = line.split()
|
||||
first_name = ' '.join(names_list[:-1])
|
||||
last_name = names_list[-1]
|
||||
username = User.objects.generate_username(first_name, last_name)
|
||||
default_password = User.objects.generate_password()
|
||||
User.objects.create(
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
default_password=default_password,
|
||||
password=make_password(default_password, '', 'md5'))
|
||||
messages.success(self.request, _('%(number)d users successfully created.') % {'number': number + 1})
|
||||
return super(UserMultipleCreateView, self).form_valid(form)
|
||||
|
||||
|
||||
class UserUpdateView(UpdateView):
|
||||
"""
|
||||
Update an existing users.
|
||||
"""
|
||||
required_permission = 'users.can_manage'
|
||||
model = User
|
||||
context_object_name = 'edit_user'
|
||||
form_class = UserUpdateForm
|
||||
success_url_name = 'user_list'
|
||||
url_name_args = []
|
||||
|
||||
def get_form_kwargs(self, *args, **kwargs):
|
||||
form_kwargs = super(UserUpdateView, self).get_form_kwargs(*args, **kwargs)
|
||||
form_kwargs.update({'request': self.request})
|
||||
return form_kwargs
|
||||
|
||||
|
||||
class UserDeleteView(DeleteView):
|
||||
"""
|
||||
Delete a user.
|
||||
"""
|
||||
required_permission = 'users.can_manage'
|
||||
model = User
|
||||
success_url_name = 'user_list'
|
||||
url_name_args = []
|
||||
|
||||
def pre_redirect(self, request, *args, **kwargs):
|
||||
if self.get_object() == self.request.user:
|
||||
messages.error(request, _("You can not delete yourself."))
|
||||
else:
|
||||
super().pre_redirect(request, *args, **kwargs)
|
||||
|
||||
def pre_post_redirect(self, request, *args, **kwargs):
|
||||
if self.get_object() == self.request.user:
|
||||
messages.error(self.request, _("You can not delete yourself."))
|
||||
else:
|
||||
super().pre_post_redirect(request, *args, **kwargs)
|
||||
|
||||
|
||||
class SetUserStatusView(SingleObjectMixin, RedirectView):
|
||||
"""
|
||||
Activate or deactivate an user.
|
||||
"""
|
||||
required_permission = 'users.can_manage'
|
||||
allow_ajax = True
|
||||
url_name = 'user_list'
|
||||
url_name_args = []
|
||||
model = User
|
||||
|
||||
def pre_redirect(self, request, *args, **kwargs):
|
||||
action = kwargs['action']
|
||||
if action == 'activate':
|
||||
self.get_object().is_active = True
|
||||
elif action == 'deactivate':
|
||||
if self.get_object().user == self.request.user:
|
||||
messages.error(request, _("You can not deactivate yourself."))
|
||||
else:
|
||||
self.get_object().is_active = False
|
||||
self.get_object().save()
|
||||
return super(SetUserStatusView, self).pre_redirect(request, *args, **kwargs)
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
context = super(SetUserStatusView, self).get_ajax_context(**kwargs)
|
||||
context['active'] = self.get_object().is_active
|
||||
return context
|
||||
from .pdf import users_passwords_to_pdf, users_to_pdf
|
||||
from .serializers import (
|
||||
GroupSerializer,
|
||||
UserCreateUpdateSerializer,
|
||||
UserFullSerializer,
|
||||
UserShortSerializer
|
||||
)
|
||||
|
||||
|
||||
class UsersListPDF(PDFView):
|
||||
@ -213,30 +64,10 @@ class UserCSVImportView(CSVImportView):
|
||||
"""
|
||||
Import users via CSV.
|
||||
"""
|
||||
import_function = staticmethod(import_users)
|
||||
required_permission = 'users.can_manage'
|
||||
success_url_name = 'user_list'
|
||||
template_name = 'users/user_form_csv_import.html'
|
||||
|
||||
|
||||
class ResetPasswordView(SingleObjectMixin, QuestionView):
|
||||
"""
|
||||
Set the Passwort for a user to his default password.
|
||||
"""
|
||||
required_permission = 'users.can_manage'
|
||||
model = User
|
||||
allow_ajax = True
|
||||
question_message = ugettext_lazy('Do you really want to reset the password?')
|
||||
|
||||
def get_redirect_url(self, **kwargs):
|
||||
return self.get_object().get_absolute_url('update')
|
||||
|
||||
def on_clicked_yes(self):
|
||||
self.get_object().reset_password()
|
||||
self.get_object().save()
|
||||
|
||||
def get_final_message(self):
|
||||
return _('The Password for %s was successfully reset.') % html_strong(self.get_object())
|
||||
import_function = staticmethod(import_users)
|
||||
|
||||
|
||||
class UserViewSet(ModelViewSet):
|
||||
@ -291,164 +122,13 @@ class GroupViewSet(ModelViewSet):
|
||||
self.permission_denied(request)
|
||||
|
||||
|
||||
class GroupListView(ListView):
|
||||
"""
|
||||
Overview over all groups.
|
||||
"""
|
||||
required_permission = 'users.can_manage'
|
||||
template_name = 'users/group_list.html'
|
||||
context_object_name = 'groups'
|
||||
model = Group
|
||||
|
||||
|
||||
class GroupDetailView(DetailView, PermissionMixin):
|
||||
"""
|
||||
Classed based view to show a specific group in the interface.
|
||||
"""
|
||||
required_permission = 'users.can_manage'
|
||||
model = Group
|
||||
template_name = 'users/group_detail.html'
|
||||
context_object_name = 'group'
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super(GroupDetailView, self).get_context_data(*args, **kwargs)
|
||||
query = User.objects
|
||||
if config['users_sort_users_by_first_name']:
|
||||
query = query.order_by('first_name')
|
||||
else:
|
||||
query = query.order_by('last_name')
|
||||
context['group_members'] = query.filter(groups__in=[context['group']])
|
||||
return context
|
||||
|
||||
|
||||
class GroupCreateView(CreateView):
|
||||
"""
|
||||
Create a new group.
|
||||
"""
|
||||
required_permission = 'users.can_manage'
|
||||
template_name = 'users/group_form.html'
|
||||
context_object_name = 'group'
|
||||
model = Group
|
||||
form_class = GroupForm
|
||||
success_url_name = 'group_list'
|
||||
url_name_args = []
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
delete_default_permissions()
|
||||
return super(GroupCreateView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_apply_url(self):
|
||||
"""
|
||||
Returns the url when the user clicks on 'apply'.
|
||||
"""
|
||||
return self.get_url('group_update', args=[self.object.pk])
|
||||
|
||||
|
||||
class GroupUpdateView(UpdateView):
|
||||
"""
|
||||
Update an existing group.
|
||||
"""
|
||||
required_permission = 'users.can_manage'
|
||||
template_name = 'users/group_form.html'
|
||||
model = Group
|
||||
context_object_name = 'group'
|
||||
form_class = GroupForm
|
||||
url_name_args = []
|
||||
success_url_name = 'group_list'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
delete_default_permissions()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_form_kwargs(self, *args, **kwargs):
|
||||
form_kwargs = super().get_form_kwargs(*args, **kwargs)
|
||||
form_kwargs.update({'request': self.request})
|
||||
return form_kwargs
|
||||
|
||||
def get_apply_url(self):
|
||||
"""
|
||||
Returns the url when the user clicks on 'apply'.
|
||||
"""
|
||||
return self.get_url('group_update', args=[self.object.pk])
|
||||
|
||||
|
||||
class GroupDeleteView(DeleteView):
|
||||
"""
|
||||
Delete a group.
|
||||
"""
|
||||
required_permission = 'users.can_manage'
|
||||
model = Group
|
||||
success_url_name = 'group_list'
|
||||
question_url_name = 'group_detail'
|
||||
url_name_args = []
|
||||
|
||||
def pre_redirect(self, request, *args, **kwargs):
|
||||
if not self.is_protected_from_deleting():
|
||||
super().pre_redirect(request, *args, **kwargs)
|
||||
|
||||
def pre_post_redirect(self, request, *args, **kwargs):
|
||||
if not self.is_protected_from_deleting():
|
||||
super().pre_post_redirect(request, *args, **kwargs)
|
||||
|
||||
def is_protected_from_deleting(self):
|
||||
"""
|
||||
Checks whether the group is protected.
|
||||
"""
|
||||
if self.get_object().pk in [1, 2]:
|
||||
messages.error(self.request, _('You can not delete this group.'))
|
||||
return True
|
||||
if (not self.request.user.is_superuser and
|
||||
get_protected_perm() in self.get_object().permissions.all() and
|
||||
not Group.objects.exclude(pk=self.get_object().pk).filter(
|
||||
permissions__in=[get_protected_perm()],
|
||||
user__pk=self.request.user.pk).exists()):
|
||||
messages.error(
|
||||
self.request,
|
||||
_('You can not delete the last group containing the permission '
|
||||
'to manage users you are in.'))
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_url_name_args(self):
|
||||
try:
|
||||
answer = self.get_answer()
|
||||
except OpenSlidesError:
|
||||
answer = 'no'
|
||||
|
||||
if self.request.method == 'POST' and answer != 'no':
|
||||
return []
|
||||
else:
|
||||
return [self.object.pk]
|
||||
|
||||
|
||||
def login(request):
|
||||
extra_content = {}
|
||||
try:
|
||||
admin = User.objects.get(pk=1)
|
||||
if admin.check_password(admin.default_password):
|
||||
user_data = {
|
||||
'user': html_strong(admin.username),
|
||||
'password': html_strong(admin.default_password)}
|
||||
|
||||
extra_content['first_time_message'] = _(
|
||||
"Installation was successfully! Use %(user)s "
|
||||
"(password: %(password)s) for first login.<br>"
|
||||
"<strong>Important:</strong> Please change the password after "
|
||||
"first login! Otherwise this message still appears for "
|
||||
"everyone and could be a security risk.") % user_data
|
||||
|
||||
extra_content['next'] = reverse('password_change')
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
return django_login(request, template_name='users/login.html', extra_context=extra_content)
|
||||
|
||||
|
||||
class UserSettingsView(LoginMixin, UpdateView):
|
||||
required_permission = None
|
||||
template_name = 'users/settings.html'
|
||||
success_url_name = 'user_settings'
|
||||
model = User
|
||||
form_class = UsersettingsForm
|
||||
success_url_name = 'user_settings'
|
||||
url_name_args = []
|
||||
template_name = 'users/settings.html'
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
@ -465,9 +145,10 @@ class UserSettingsView(LoginMixin, UpdateView):
|
||||
|
||||
|
||||
class UserPasswordSettingsView(LoginMixin, FormView):
|
||||
form_class = PasswordChangeForm
|
||||
success_url_name = 'core_dashboard'
|
||||
required_permission = None
|
||||
template_name = 'users/password_change.html'
|
||||
success_url_name = 'core_dashboard'
|
||||
form_class = PasswordChangeForm
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save()
|
||||
@ -478,3 +159,54 @@ class UserPasswordSettingsView(LoginMixin, FormView):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['user'] = self.request.user
|
||||
return kwargs
|
||||
|
||||
|
||||
class UserLoginView(APIView):
|
||||
"""
|
||||
Login the user via ajax.
|
||||
"""
|
||||
http_method_names = ['post']
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
form = AuthenticationForm(self.request, data=self.request.data)
|
||||
if form.is_valid():
|
||||
self.user = form.get_user()
|
||||
auth_login(self.request, self.user)
|
||||
self.success = True
|
||||
else:
|
||||
self.success = False
|
||||
return super().post(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **context):
|
||||
context['success'] = self.success
|
||||
if self.success:
|
||||
context['user_id'] = self.user.pk
|
||||
return super().get_context_data(**context)
|
||||
|
||||
|
||||
class UserLogoutView(APIView):
|
||||
"""
|
||||
Logout the user via ajax.
|
||||
"""
|
||||
http_method_names = ['post']
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
auth_logout(self.request)
|
||||
return super().post(*args, **kwargs)
|
||||
|
||||
|
||||
class WhoAmIView(APIView):
|
||||
"""
|
||||
Returns the user id in the session.
|
||||
"""
|
||||
http_method_names = ['get']
|
||||
|
||||
def get_context_data(self, **context):
|
||||
"""
|
||||
Appends the user id into the context.
|
||||
|
||||
Uses None for the anonymous user.
|
||||
"""
|
||||
return super().get_context_data(
|
||||
user_id=self.request.user.pk,
|
||||
**context)
|
||||
|
@ -17,7 +17,6 @@ class UserWidget(Widget):
|
||||
default_weight = 60
|
||||
default_active = False
|
||||
template_name = 'users/widget_user.html'
|
||||
more_link_pattern_name = 'user_list'
|
||||
|
||||
def get_context_data(self, **context):
|
||||
return super(UserWidget, self).get_context_data(
|
||||
|
@ -13,6 +13,8 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.views import generic as django_views
|
||||
from reportlab.lib.units import cm
|
||||
from reportlab.platypus import SimpleDocTemplate, Spacer
|
||||
from rest_framework.views import APIView as _APIView
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .exceptions import OpenSlidesError
|
||||
from .forms import CSVImportForm
|
||||
@ -76,13 +78,19 @@ class AjaxMixin(object):
|
||||
Mixin to response to an ajax request with an json object.
|
||||
"""
|
||||
|
||||
def get_ajax_context(self, **kwargs):
|
||||
def get_ajax_context(self, **context):
|
||||
"""
|
||||
Returns a dictonary with the context for the ajax response.
|
||||
"""
|
||||
return kwargs
|
||||
return context
|
||||
|
||||
def ajax_get(self, request, *args, **kwargs):
|
||||
"""
|
||||
Deprecated. Use ajax_response instead.
|
||||
"""
|
||||
return self.ajax_response()
|
||||
|
||||
def ajax_response(self):
|
||||
"""
|
||||
Returns the HttpResponse.
|
||||
"""
|
||||
@ -289,13 +297,15 @@ class ListView(PermissionMixin, ExtraContextMixin, django_views.ListView):
|
||||
class AjaxView(PermissionMixin, AjaxMixin, View):
|
||||
"""
|
||||
View for ajax requests.
|
||||
|
||||
Deprecated. Use APIView instead.
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
# TODO: Raise an error, if the request is not an ajax-request
|
||||
return self.ajax_get(request, *args, **kwargs)
|
||||
return self.ajax_response()
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
return self.get(*args, **kwargs)
|
||||
return self.ajax_response()
|
||||
|
||||
|
||||
class RedirectView(PermissionMixin, AjaxMixin, UrlMixin, django_views.RedirectView):
|
||||
@ -640,3 +650,33 @@ class CSVImportView(FormView):
|
||||
messages.warning(self.request, warning)
|
||||
messages.error(self.request, error)
|
||||
return super(CSVImportView, self).form_valid(form)
|
||||
|
||||
|
||||
class APIView(_APIView):
|
||||
"""
|
||||
The Django Rest framework APIView with improvements for OpenSlides.
|
||||
"""
|
||||
|
||||
http_method_names = []
|
||||
"""
|
||||
The allowed actions have to be explicitly defined.
|
||||
|
||||
Django allowes the following:
|
||||
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
|
||||
"""
|
||||
|
||||
def get_context_data(self, **context):
|
||||
"""
|
||||
Returns the context for the response.
|
||||
"""
|
||||
return context
|
||||
|
||||
def method_call(self, request, *args, **kwargs):
|
||||
"""
|
||||
Http method that returns the response object with the context data.
|
||||
"""
|
||||
return Response(self.get_context_data())
|
||||
|
||||
# Add the http-methods and delete the method "method_call"
|
||||
get = post = put = patch = delete = head = options = trace = method_call
|
||||
del method_call
|
||||
|
89
tests/integration/users/test_views.py
Normal file
89
tests/integration/users/test_views.py
Normal file
@ -0,0 +1,89 @@
|
||||
import json
|
||||
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
|
||||
class TestWhoAmIView(TestCase):
|
||||
url = '/users/whoami/'
|
||||
|
||||
def test_get_anonymous(self):
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b'{"user_id":null}')
|
||||
|
||||
def test_get_authenticated_user(self):
|
||||
self.client.login(username='admin', password='admin')
|
||||
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b'{"user_id":1}')
|
||||
|
||||
def test_post(self):
|
||||
response = self.client.post(self.url)
|
||||
|
||||
self.assertEqual(response.status_code, 405)
|
||||
|
||||
|
||||
class TestUserLogoutView(TestCase):
|
||||
url = '/users/logout/'
|
||||
|
||||
def test_get(self):
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assertEqual(response.status_code, 405)
|
||||
|
||||
def test_post_anonymous(self):
|
||||
response = self.client.post(self.url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_post_authenticated_user(self):
|
||||
self.client.login(username='admin', password='admin')
|
||||
self.client.session['test_key'] = 'test_value'
|
||||
|
||||
response = self.client.post(self.url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFalse(hasattr(self.client.session, 'test_key'))
|
||||
|
||||
|
||||
class TestUserLoginView(TestCase):
|
||||
url = '/users/login/'
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
|
||||
def test_get(self):
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assertEqual(response.status_code, 405)
|
||||
|
||||
def test_post_no_data(self):
|
||||
response = self.client.post(self.url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b'{"success":false}')
|
||||
|
||||
def test_post_correct_data(self):
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
{'username': 'admin', 'password': 'admin'})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
json.loads(response.content.decode('utf-8')),
|
||||
{'success': True, 'user_id': 1})
|
||||
|
||||
def test_post_incorrect_data(self):
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
{'username': 'wrong', 'password': 'wrong'})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
json.loads(response.content.decode('utf-8')),
|
||||
{'success': False})
|
@ -120,10 +120,6 @@ class ConfigFormTest(TestCase):
|
||||
response = self.client_manager.get('/config/')
|
||||
self.assertRedirects(response=response, expected_url='/config/general/',
|
||||
status_code=302, target_status_code=200)
|
||||
bad_client = Client()
|
||||
response = bad_client.get('/config/', follow=True)
|
||||
self.assertRedirects(response=response, expected_url='/login/?next=/config/general/',
|
||||
status_code=302, target_status_code=200)
|
||||
|
||||
def test_get_config_form_testgroupedpage1_manager_client(self):
|
||||
response = self.client_manager.get('/config/testgroupedpage1/')
|
||||
@ -141,10 +137,6 @@ class ConfigFormTest(TestCase):
|
||||
def test_get_config_form_testgroupedpage1_other_clients(self):
|
||||
response = self.client_normal_user.get('/config/testgroupedpage1/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
bad_client = Client()
|
||||
response = bad_client.get('/config/testgroupedpage1/')
|
||||
self.assertRedirects(response=response, expected_url='/login/?next=/config/testgroupedpage1/',
|
||||
status_code=302, target_status_code=200)
|
||||
|
||||
def test_get_config_form_testsimplepage1_manager_client(self):
|
||||
response = self.client_manager.get('/config/testsimplepage1/')
|
||||
|
@ -64,9 +64,6 @@ class MediafileTest(TestCase):
|
||||
response = client.get('/mediafile/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'mediafile/mediafile_list.html')
|
||||
bad_client = Client()
|
||||
response = bad_client.get('/mediafile/')
|
||||
self.assertRedirects(response, expected_url='/login/?next=/mediafile/', status_code=302, target_status_code=200)
|
||||
|
||||
def test_upload_mediafile_get_request(self):
|
||||
clients = self.login_clients()
|
||||
@ -82,10 +79,6 @@ class MediafileTest(TestCase):
|
||||
response = clients['client_normal_user'].get('/mediafile/new/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
bad_client = Client()
|
||||
response = bad_client.get('/mediafile/new/')
|
||||
self.assertRedirects(response, expected_url='/login/?next=/mediafile/new/', status_code=302, target_status_code=200)
|
||||
|
||||
def test_upload_mediafile_post_request(self):
|
||||
# Test first user
|
||||
client_1 = self.login_clients()['client_manager']
|
||||
@ -139,10 +132,6 @@ class MediafileTest(TestCase):
|
||||
response = clients['client_normal_user'].get('/mediafile/1/edit/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
bad_client = Client()
|
||||
response = bad_client.get('/mediafile/1/edit/')
|
||||
self.assertRedirects(response, expected_url='/login/?next=/mediafile/1/edit/', status_code=302, target_status_code=200)
|
||||
|
||||
def test_edit_mediafile_get_request_own_file(self):
|
||||
clients = self.login_clients()
|
||||
self.object.uploader = self.vip_user
|
||||
@ -204,9 +193,6 @@ class MediafileTest(TestCase):
|
||||
self.assertEqual(response.status_code, 403)
|
||||
response = clients['client_normal_user'].get('/mediafile/1/del/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
bad_client = Client()
|
||||
response = bad_client.get('/mediafile/1/del/')
|
||||
self.assertRedirects(response, expected_url='/login/?next=/mediafile/1/del/', status_code=302, target_status_code=200)
|
||||
|
||||
def test_delete_mediafile_get_request_own_file(self):
|
||||
self.object.uploader = self.vip_user
|
||||
|
@ -41,15 +41,9 @@ class UserGetAbsoluteUrlTest(TestCase):
|
||||
"""
|
||||
user = User(pk=5)
|
||||
|
||||
with patch('openslides.users.models.reverse') as mock_reverse:
|
||||
mock_reverse.return_value = 'test url'
|
||||
url = user.get_absolute_url()
|
||||
|
||||
self.assertEqual(
|
||||
url,
|
||||
'test url',
|
||||
"User.get_absolute_url() does not return the result of reverse.")
|
||||
mock_reverse.assert_called_once_with('user_detail', args=['5'])
|
||||
user.get_absolute_url(),
|
||||
'/users/5/')
|
||||
|
||||
def test_get_absolute_url_detail(self):
|
||||
"""
|
||||
@ -57,15 +51,11 @@ class UserGetAbsoluteUrlTest(TestCase):
|
||||
"""
|
||||
user = User(pk=5)
|
||||
|
||||
with patch('openslides.users.models.reverse') as mock_reverse:
|
||||
mock_reverse.return_value = 'test url'
|
||||
url = user.get_absolute_url('detail')
|
||||
url = user.get_absolute_url('detail')
|
||||
|
||||
self.assertEqual(
|
||||
url,
|
||||
'test url',
|
||||
"User.get_absolute_url('detail') does not return the result of reverse.")
|
||||
mock_reverse.assert_called_once_with('user_detail', args=['5'])
|
||||
'/users/5/')
|
||||
|
||||
def test_get_absolute_url_update(self):
|
||||
"""
|
||||
@ -73,31 +63,11 @@ class UserGetAbsoluteUrlTest(TestCase):
|
||||
"""
|
||||
user = User(pk=5)
|
||||
|
||||
with patch('openslides.users.models.reverse') as mock_reverse:
|
||||
mock_reverse.return_value = 'test url'
|
||||
url = user.get_absolute_url('update')
|
||||
url = user.get_absolute_url('update')
|
||||
|
||||
self.assertEqual(
|
||||
url,
|
||||
'test url',
|
||||
"User.get_absolute_url('update') does not return the result of reverse.")
|
||||
mock_reverse.assert_called_once_with('user_update', args=['5'])
|
||||
|
||||
def test_get_absolute_url_delete(self):
|
||||
"""
|
||||
Tests get_absolute_url() with 'delete' as argument.
|
||||
"""
|
||||
user = User(pk=5)
|
||||
|
||||
with patch('openslides.users.models.reverse') as mock_reverse:
|
||||
mock_reverse.return_value = 'test url'
|
||||
url = user.get_absolute_url('delete')
|
||||
|
||||
self.assertEqual(
|
||||
url,
|
||||
'test url',
|
||||
"User.get_absolute_url('delete') does not return the result of reverse.")
|
||||
mock_reverse.assert_called_once_with('user_delete', args=['5'])
|
||||
'/users/5/edit/')
|
||||
|
||||
def test_get_absolute_url_other(self):
|
||||
"""
|
||||
@ -112,8 +82,7 @@ class UserGetAbsoluteUrlTest(TestCase):
|
||||
|
||||
self.assertEqual(
|
||||
url,
|
||||
'test url',
|
||||
"User.get_absolute_url(OTHER) does not return the result of reverse.")
|
||||
'test url')
|
||||
mock_super().get_absolute_url.assert_called_once_with(dummy_argument)
|
||||
|
||||
|
||||
|
@ -316,3 +316,18 @@ class SingleObjectMixinTest(TestCase):
|
||||
self.assertTrue(
|
||||
view.get_object.called,
|
||||
"view.get_object() should be called")
|
||||
|
||||
|
||||
class TestAPIView(TestCase):
|
||||
def test_class_creation(self):
|
||||
"""
|
||||
Tests that the APIView has all relevant methods
|
||||
"""
|
||||
http_methods = set(('get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'))
|
||||
|
||||
self.assertTrue(
|
||||
http_methods.issubset(views.APIView.__dict__),
|
||||
"All http methods should be defined in the APIView")
|
||||
self.assertFalse(
|
||||
hasattr(views.APIView, 'method_call'),
|
||||
"The APIView should not have the method 'method_call'")
|
||||
|
Loading…
Reference in New Issue
Block a user