Implemented auth via AngularJS
Also added the derective osPerms to check if the current user has permissions. Removed old Django views and urls for user. Created utils.views.APIView which should be used instead of the AjaxView. Fixes: #1470 Fixes: #1454
This commit is contained in:
parent
a6d0c88730
commit
1969416e64
@ -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