eed5c59013
Added api for groups. Refactored serializers now using 'id' instead of 'url'. Rework of tornado autoupdate functionality. Implemented extra data in SockJS messages.
502 lines
17 KiB
Python
502 lines
17 KiB
Python
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 openslides.config.api import config
|
|
from openslides.utils.rest_api import viewsets
|
|
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
|
|
|
|
from .api import gen_password, gen_username, get_protected_perm
|
|
from .csv_import import import_users
|
|
from .forms import (GroupForm, UserCreateForm, UserMultipleCreateForm,
|
|
UsersettingsForm, UserUpdateForm)
|
|
from .models import Group, User
|
|
from .pdf import users_to_pdf, users_passwords_to_pdf
|
|
from .serializers import GroupSerializer, 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 = gen_username(
|
|
form.cleaned_data['first_name'], form.cleaned_data['last_name'])
|
|
|
|
if not self.object.default_password:
|
|
self.object.default_password = gen_password()
|
|
|
|
self.object.set_password(self.object.default_password)
|
|
|
|
def post_save(self, form):
|
|
super(UserCreateView, self).post_save(form)
|
|
# TODO: find a better solution that makes the following lines obsolete
|
|
# Background: motion.models.use_post_save adds already the registerd group
|
|
# to new user but super(..).post_save(form) removes it and sets only the
|
|
# groups selected in the form (without 'registered')
|
|
# workaround: add registered group again manually
|
|
from openslides.users.api import get_registered_group # TODO: Test, if global import is possible
|
|
registered = get_registered_group()
|
|
self.object.groups.add(registered)
|
|
|
|
|
|
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 = gen_username(first_name, last_name)
|
|
default_password = gen_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
|
|
|
|
def post_save(self, form):
|
|
super(UserUpdateView, self).post_save(form)
|
|
# TODO: Find a better solution that makes the following lines obsolete
|
|
# Background: motion.models.use_post_save adds already the registerd group
|
|
# to new user but super(..).post_save(form) removes it and sets only the
|
|
# groups selected in the form (without 'registered')
|
|
# workaround: add registered group again manually
|
|
from openslides.users.api import get_registered_group # TODO: Test, if global import is possible
|
|
registered = get_registered_group()
|
|
self.object.groups.add(registered)
|
|
|
|
|
|
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
|
|
|
|
|
|
class UsersListPDF(PDFView):
|
|
"""
|
|
Generate the userliste as PDF.
|
|
"""
|
|
required_permission = 'users.can_see_extra_data'
|
|
filename = ugettext_lazy("user-list")
|
|
document_title = ugettext_lazy('List of Users')
|
|
|
|
def append_to_pdf(self, pdf):
|
|
"""
|
|
Append PDF objects.
|
|
"""
|
|
users_to_pdf(pdf)
|
|
|
|
|
|
class UsersPasswordsPDF(PDFView):
|
|
"""
|
|
Generate the access data welcome paper for all users as PDF.
|
|
"""
|
|
required_permission = 'users.can_manage'
|
|
filename = ugettext_lazy("User-access-data")
|
|
top_space = 0
|
|
|
|
def build_document(self, pdf_document, story):
|
|
pdf_document.build(story)
|
|
|
|
def append_to_pdf(self, pdf):
|
|
"""
|
|
Append PDF objects.
|
|
"""
|
|
users_passwords_to_pdf(pdf)
|
|
|
|
|
|
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())
|
|
|
|
|
|
class UserViewSet(viewsets.ModelViewSet):
|
|
"""
|
|
API endpoint to list, retrieve, create, update and delete users.
|
|
"""
|
|
model = User
|
|
queryset = User.objects.all()
|
|
|
|
def check_permissions(self, request):
|
|
"""
|
|
Calls self.permission_denied() if the requesting user has not the
|
|
permission to see users and in case of create, update or destroy
|
|
requests the permission to see extra user data and to manage users.
|
|
"""
|
|
if (not request.user.has_perm('users.can_see_name') or
|
|
(self.action in ('create', 'update', 'destroy') and not
|
|
(request.user.has_perm('users.can_manage') and
|
|
request.user.has_perm('users.can_see_extra_data')))):
|
|
self.permission_denied(request)
|
|
|
|
def get_serializer_class(self):
|
|
"""
|
|
Returns different serializer classes with respect to users permissions.
|
|
"""
|
|
if self.request.user.has_perm('users.can_see_extra_data'):
|
|
serializer_class = UserFullSerializer
|
|
else:
|
|
serializer_class = UserShortSerializer
|
|
return serializer_class
|
|
|
|
|
|
class GroupViewSet(viewsets.ModelViewSet):
|
|
"""
|
|
API endpoint to list, retrieve, create, update and delete groups.
|
|
"""
|
|
model = Group
|
|
queryset = Group.objects.all()
|
|
serializer_class = GroupSerializer
|
|
|
|
def check_permissions(self, request):
|
|
"""
|
|
Calls self.permission_denied() if the requesting user has not the
|
|
permission to see users and in case of create, update or destroy
|
|
requests the permission to see extra user data and to manage users.
|
|
"""
|
|
if (not request.user.has_perm('users.can_see_name') or
|
|
(self.action in ('create', 'update', 'destroy') and not
|
|
(request.user.has_perm('users.can_manage') and
|
|
request.user.has_perm('users.can_see_extra_data')))):
|
|
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):
|
|
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()
|
|
initial['language'] = self.request.session.get('django_language', self.request.LANGUAGE_CODE)
|
|
return initial
|
|
|
|
def form_valid(self, form):
|
|
self.request.LANGUAGE_CODE = self.request.session['django_language'] = form.cleaned_data['language']
|
|
activate(self.request.LANGUAGE_CODE)
|
|
return super().form_valid(form)
|
|
|
|
def get_object(self):
|
|
return self.request.user
|
|
|
|
|
|
class UserPasswordSettingsView(LoginMixin, FormView):
|
|
form_class = PasswordChangeForm
|
|
success_url_name = 'core_dashboard'
|
|
template_name = 'users/password_change.html'
|
|
|
|
def form_valid(self, form):
|
|
form.save()
|
|
messages.success(self.request, _('Password successfully changed.'))
|
|
return super().form_valid(form)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
return kwargs
|