Merge pull request #1642 from normanjaeckel/CleanupUsers
Cleaned up users app.
This commit is contained in:
commit
9599e4c022
@ -91,13 +91,6 @@ INSTALLED_APPS = (
|
|||||||
'openslides.mediafiles',
|
'openslides.mediafiles',
|
||||||
)
|
)
|
||||||
|
|
||||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
|
||||||
'openslides.users.auth.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
'django.core.context_processors.request',
|
|
||||||
'django.core.context_processors.i18n',
|
|
||||||
'django.core.context_processors.static',
|
|
||||||
)
|
|
||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
'default': {
|
||||||
@ -175,6 +168,6 @@ TEST_RUNNER = 'openslides.utils.test.OpenSlidesDiscoverRunner'
|
|||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
'openslides.users.auth.AnonymousAuthentication',
|
'openslides.users.auth.RESTFrameworkAnonymousAuthentication',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
from .models import Permission
|
|
||||||
|
|
||||||
|
|
||||||
def get_protected_perm():
|
|
||||||
"""
|
|
||||||
Returns the permission to manage users. This function is a helper
|
|
||||||
function used to protect manager users from locking out themselves.
|
|
||||||
"""
|
|
||||||
return Permission.objects.get_by_natural_key(
|
|
||||||
app_label='users', model='user', codename='can_manage')
|
|
@ -10,12 +10,12 @@ class UsersAppConfig(AppConfig):
|
|||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# Load projector elements.
|
# Load projector elements.
|
||||||
# Do this by just importing all from these files.
|
# Just import this file.
|
||||||
from . import projector # noqa
|
from . import projector # noqa
|
||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.core.signals import config_signal, post_permission_creation
|
from ..core.signals import config_signal, post_permission_creation
|
||||||
from openslides.utils.rest_api import router
|
from ..utils.rest_api import router
|
||||||
from .signals import create_builtin_groups_and_admin, setup_users_config
|
from .signals import create_builtin_groups_and_admin, setup_users_config
|
||||||
from .views import GroupViewSet, UserViewSet
|
from .views import GroupViewSet, UserViewSet
|
||||||
|
|
||||||
|
@ -1,63 +1,30 @@
|
|||||||
from django.contrib.auth import get_user as _get_user
|
from django.contrib.auth import get_user as _get_user
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.backends import ModelBackend
|
from django.contrib.auth.backends import ModelBackend
|
||||||
from django.contrib.auth.context_processors import auth as _auth
|
|
||||||
from django.contrib.auth.models import AnonymousUser as DjangoAnonymousUser
|
from django.contrib.auth.models import AnonymousUser as DjangoAnonymousUser
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.functional import SimpleLazyObject
|
from django.utils.functional import SimpleLazyObject
|
||||||
from rest_framework.authentication import BaseAuthentication
|
from rest_framework.authentication import BaseAuthentication
|
||||||
|
|
||||||
from openslides.core.config import config
|
from ..core.config import config
|
||||||
|
|
||||||
|
|
||||||
class AnonymousUser(DjangoAnonymousUser):
|
# Registered users
|
||||||
"""
|
|
||||||
Class for anonymous user instances, which have the permissions from the
|
|
||||||
Group 'Anonymous' (pk=1).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_all_permissions(self, obj=None):
|
|
||||||
"""
|
|
||||||
Return the permissions a user is granted by his group membership(s).
|
|
||||||
|
|
||||||
Try to return the permissions for the 'Anonymous' group (pk=1).
|
|
||||||
"""
|
|
||||||
perms = Permission.objects.filter(group__pk=1)
|
|
||||||
if perms is None:
|
|
||||||
return set()
|
|
||||||
# TODO: test without order_by()
|
|
||||||
perms = perms.values_list('content_type__app_label', 'codename').order_by()
|
|
||||||
return set(['%s.%s' % (content_type, codename) for content_type, codename in perms])
|
|
||||||
|
|
||||||
def has_perm(self, perm, obj=None):
|
|
||||||
"""
|
|
||||||
Check if the user has a specific permission
|
|
||||||
"""
|
|
||||||
return (perm in self.get_all_permissions())
|
|
||||||
|
|
||||||
def has_module_perms(self, app_label):
|
|
||||||
"""
|
|
||||||
Check if the user has permissions on the module app_label
|
|
||||||
"""
|
|
||||||
for perm in self.get_all_permissions():
|
|
||||||
if perm[:perm.index('.')] == app_label:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class CustomizedModelBackend(ModelBackend):
|
class CustomizedModelBackend(ModelBackend):
|
||||||
"""
|
"""
|
||||||
Customized backend for authentication. Ensures that registered users have
|
Customized backend for authentication. Ensures that registered users
|
||||||
all permission of the group 'Registered' (pk=2).
|
have all permissions of the group 'Registered' (pk=2). See
|
||||||
|
AUTHENTICATION_BACKENDS settings.
|
||||||
"""
|
"""
|
||||||
def get_group_permissions(self, user_obj, obj=None):
|
def get_group_permissions(self, user_obj, obj=None):
|
||||||
"""
|
"""
|
||||||
Returns a set of permission strings that this user has through his/her
|
Returns a set of permission strings that this user has through his/her
|
||||||
groups.
|
groups.
|
||||||
"""
|
"""
|
||||||
# TODO: Refactor this after Django 1.8 release. Add also anonymous
|
# TODO: Refactor this after Django 1.8 is minimum requirement. Add
|
||||||
# permission check to this backend.
|
# also anonymous permission check to this backend.
|
||||||
if user_obj.is_anonymous() or obj is not None:
|
if user_obj.is_anonymous() or obj is not None:
|
||||||
return set()
|
return set()
|
||||||
if not hasattr(user_obj, '_group_perm_cache'):
|
if not hasattr(user_obj, '_group_perm_cache'):
|
||||||
@ -74,11 +41,61 @@ class CustomizedModelBackend(ModelBackend):
|
|||||||
return user_obj._group_perm_cache
|
return user_obj._group_perm_cache
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationMiddleware(object):
|
# Anonymous users
|
||||||
|
|
||||||
|
class AnonymousUser(DjangoAnonymousUser):
|
||||||
|
"""
|
||||||
|
Class for anonymous user instances which have the permissions from the
|
||||||
|
group 'Anonymous' (pk=1).
|
||||||
|
"""
|
||||||
|
def get_all_permissions(self, obj=None):
|
||||||
|
"""
|
||||||
|
Returns the permissions a user is granted by his group membership(s).
|
||||||
|
|
||||||
|
Try to return the permissions for the 'Anonymous' group (pk=1).
|
||||||
|
"""
|
||||||
|
perms = Permission.objects.filter(group__pk=1)
|
||||||
|
if perms is None:
|
||||||
|
return set()
|
||||||
|
# TODO: Test without order_by()
|
||||||
|
perms = perms.values_list('content_type__app_label', 'codename').order_by()
|
||||||
|
return set(['%s.%s' % (content_type, codename) for content_type, codename in perms])
|
||||||
|
|
||||||
|
def has_perm(self, perm, obj=None):
|
||||||
|
"""
|
||||||
|
Checks if the user has a specific permission.
|
||||||
|
"""
|
||||||
|
return (perm in self.get_all_permissions())
|
||||||
|
|
||||||
|
def has_module_perms(self, app_label):
|
||||||
|
"""
|
||||||
|
Checks if the user has permissions on the module app_label.
|
||||||
|
"""
|
||||||
|
for perm in self.get_all_permissions():
|
||||||
|
if perm[:perm.index('.')] == app_label:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class RESTFrameworkAnonymousAuthentication(BaseAuthentication):
|
||||||
|
"""
|
||||||
|
Authentication class for the Django REST framework.
|
||||||
|
|
||||||
|
Sets the user to the our AnonymousUser but only if
|
||||||
|
general_system_enable_anonymous is set to True in the config.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def authenticate(self, request):
|
||||||
|
if config['general_system_enable_anonymous']:
|
||||||
|
return (AnonymousUser(), None)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticationMiddleware:
|
||||||
"""
|
"""
|
||||||
Middleware to get the logged in user in users.
|
Middleware to get the logged in user in users.
|
||||||
|
|
||||||
Uses AnonymousUser instead of the django anonymous user.
|
Uses AnonymousUser instead of Django's anonymous user.
|
||||||
"""
|
"""
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
"""
|
"""
|
||||||
@ -94,20 +111,6 @@ class AuthenticationMiddleware(object):
|
|||||||
request.user = SimpleLazyObject(lambda: get_user(request))
|
request.user = SimpleLazyObject(lambda: get_user(request))
|
||||||
|
|
||||||
|
|
||||||
class AnonymousAuthentication(BaseAuthentication):
|
|
||||||
"""
|
|
||||||
Authentication class for the Django REST framework.
|
|
||||||
|
|
||||||
Sets the user to the our AnonymousUser but only if
|
|
||||||
general_system_enable_anonymous is set to True in the config.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def authenticate(self, request):
|
|
||||||
if config['general_system_enable_anonymous']:
|
|
||||||
return (AnonymousUser(), None)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_user(request):
|
def get_user(request):
|
||||||
"""
|
"""
|
||||||
Gets the user from the request.
|
Gets the user from the request.
|
||||||
@ -124,22 +127,3 @@ def get_user(request):
|
|||||||
return_user = AnonymousUser()
|
return_user = AnonymousUser()
|
||||||
request._cached_user = return_user
|
request._cached_user = return_user
|
||||||
return return_user
|
return return_user
|
||||||
|
|
||||||
|
|
||||||
def auth(request):
|
|
||||||
"""
|
|
||||||
Contextmanager to handle auth.
|
|
||||||
|
|
||||||
Uses the django auth context manager to fill the context.
|
|
||||||
|
|
||||||
Alters the attribute user if the user is not authenticated.
|
|
||||||
"""
|
|
||||||
# Call the django standard auth function, like 'super()'
|
|
||||||
context = _auth(request)
|
|
||||||
|
|
||||||
# Change the django anonymous user with our anonymous user if anonymous auth
|
|
||||||
# is enabled
|
|
||||||
if config['general_system_enable_anonymous'] and isinstance(context['user'], DjangoAnonymousUser):
|
|
||||||
context['user'] = AnonymousUser()
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from openslides.utils.exceptions import OpenSlidesError
|
from ..utils.exceptions import OpenSlidesError
|
||||||
|
|
||||||
|
|
||||||
class UserError(OpenSlidesError):
|
class UsersError(OpenSlidesError):
|
||||||
pass
|
pass
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.core.management.base import NoArgsCommand
|
from django.core.management.base import NoArgsCommand
|
||||||
|
|
||||||
from openslides.users.models import User
|
from ...models import User
|
||||||
|
|
||||||
|
|
||||||
class Command(NoArgsCommand):
|
class Command(NoArgsCommand):
|
||||||
|
@ -1,27 +1,29 @@
|
|||||||
from random import choice
|
from random import choice
|
||||||
|
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
from django.contrib.auth.models import ( # noqa
|
from django.contrib.auth.models import (
|
||||||
AbstractBaseUser,
|
AbstractBaseUser,
|
||||||
BaseUserManager,
|
BaseUserManager,
|
||||||
Group,
|
Group,
|
||||||
Permission,
|
|
||||||
PermissionsMixin,
|
PermissionsMixin,
|
||||||
)
|
)
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
from openslides.core.config import config
|
from ..core.config import config
|
||||||
from openslides.utils.models import RESTModelMixin
|
from ..utils.models import RESTModelMixin
|
||||||
|
from .exceptions import UsersError
|
||||||
from .exceptions import UserError
|
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
"""
|
"""
|
||||||
UserManager that creates new users only with a password and a username.
|
Customized manager that creates new users only with a password and a
|
||||||
|
username.
|
||||||
"""
|
"""
|
||||||
def create_user(self, username, password, **kwargs):
|
def create_user(self, username, password, **kwargs):
|
||||||
|
"""
|
||||||
|
Creates a new user only with a password and a username.
|
||||||
|
"""
|
||||||
user = self.model(username=username, **kwargs)
|
user = self.model(username=username, **kwargs)
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save(using=self._db)
|
user.save(using=self._db)
|
||||||
@ -29,15 +31,15 @@ class UserManager(BaseUserManager):
|
|||||||
|
|
||||||
def create_or_reset_admin_user(self):
|
def create_or_reset_admin_user(self):
|
||||||
"""
|
"""
|
||||||
Creates an user with the username admin. If such a user exists, resets
|
Creates an user with the username 'admin'. If such a user already
|
||||||
it. The password is (re)set to 'admin'. The user becomes member of the
|
exists, resets it. The password is (re)set to 'admin'. The user
|
||||||
group 'Staff' (pk=4).
|
becomes member of the group 'Staff' (pk=4).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
staff = Group.objects.get(pk=4)
|
staff = Group.objects.get(pk=4)
|
||||||
except Group.DoesNotExist:
|
except Group.DoesNotExist:
|
||||||
raise UserError("Admin user can not be created or reset because "
|
raise UsersError("Admin user can not be created or reset because "
|
||||||
"the group 'Staff' is not available.")
|
"the group 'Staff' (pk=4) is not available.")
|
||||||
admin, created = self.get_or_create(
|
admin, created = self.get_or_create(
|
||||||
username='admin',
|
username='admin',
|
||||||
defaults={'last_name': 'Administrator'})
|
defaults={'last_name': 'Administrator'})
|
||||||
@ -60,7 +62,7 @@ class UserManager(BaseUserManager):
|
|||||||
base_name = first_name or last_name
|
base_name = first_name or last_name
|
||||||
if not base_name:
|
if not base_name:
|
||||||
raise ValueError("Either 'first_name' or 'last_name' must not be "
|
raise ValueError("Either 'first_name' or 'last_name' must not be "
|
||||||
"empty")
|
"empty.")
|
||||||
|
|
||||||
if not self.filter(username=base_name).exists():
|
if not self.filter(username=base_name).exists():
|
||||||
generated_username = base_name
|
generated_username = base_name
|
||||||
@ -77,7 +79,7 @@ class UserManager(BaseUserManager):
|
|||||||
|
|
||||||
def generate_password(self):
|
def generate_password(self):
|
||||||
"""
|
"""
|
||||||
Generates a random passwort.
|
Generates a random passwort. Do not use l, o, I, O, 1 or 0.
|
||||||
"""
|
"""
|
||||||
chars = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'
|
chars = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'
|
||||||
size = 8
|
size = 8
|
||||||
@ -86,56 +88,75 @@ class UserManager(BaseUserManager):
|
|||||||
|
|
||||||
class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
|
class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
|
||||||
"""
|
"""
|
||||||
Model for users in OpenSlides. A client can login as a user with
|
Model for users in OpenSlides. A client can login as an user with
|
||||||
credentials. A user can also just be used as representation for a person
|
credentials. An user can also just be used as representation for a person
|
||||||
in other OpenSlides app like motion submitter or (assignment) election
|
in other OpenSlides apps like motion submitter or (assignment) election
|
||||||
candidates.
|
candidates.
|
||||||
"""
|
"""
|
||||||
USERNAME_FIELD = 'username'
|
USERNAME_FIELD = 'username'
|
||||||
slide_callback_name = 'user'
|
|
||||||
|
|
||||||
username = models.CharField(
|
username = models.CharField(
|
||||||
ugettext_lazy('Username'), max_length=255, unique=True, blank=True)
|
ugettext_lazy('Username'),
|
||||||
|
max_length=255,
|
||||||
|
unique=True,
|
||||||
|
blank=True)
|
||||||
|
|
||||||
first_name = models.CharField(
|
first_name = models.CharField(
|
||||||
ugettext_lazy('First name'), max_length=255, blank=True)
|
ugettext_lazy('First name'),
|
||||||
|
max_length=255,
|
||||||
|
blank=True)
|
||||||
|
|
||||||
last_name = models.CharField(
|
last_name = models.CharField(
|
||||||
ugettext_lazy('Last name'), max_length=255, blank=True)
|
ugettext_lazy('Last name'),
|
||||||
|
max_length=255,
|
||||||
|
blank=True)
|
||||||
|
|
||||||
|
# TODO: Try to remove the default argument in the following fields.
|
||||||
|
|
||||||
# TODO: try to remove the default argument in the following fields
|
|
||||||
structure_level = models.CharField(
|
structure_level = models.CharField(
|
||||||
max_length=255, blank=True, default='',
|
ugettext_lazy('Structure level'),
|
||||||
verbose_name=ugettext_lazy('Structure level'),
|
max_length=255,
|
||||||
|
blank=True,
|
||||||
|
default='',
|
||||||
help_text=ugettext_lazy('Will be shown after the name.'))
|
help_text=ugettext_lazy('Will be shown after the name.'))
|
||||||
|
|
||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
max_length=50, blank=True, default='',
|
ugettext_lazy('Title'),
|
||||||
verbose_name=ugettext_lazy('Title'),
|
max_length=50,
|
||||||
|
blank=True,
|
||||||
|
default='',
|
||||||
help_text=ugettext_lazy('Will be shown before the name.'))
|
help_text=ugettext_lazy('Will be shown before the name.'))
|
||||||
|
|
||||||
about_me = models.TextField(
|
about_me = models.TextField(
|
||||||
blank=True, default='', verbose_name=ugettext_lazy('About me'),
|
ugettext_lazy('About me'),
|
||||||
help_text=ugettext_lazy('Your profile text'))
|
blank=True,
|
||||||
|
default='',
|
||||||
|
help_text=ugettext_lazy('Profile text.'))
|
||||||
|
|
||||||
comment = models.TextField(
|
comment = models.TextField(
|
||||||
blank=True, default='', verbose_name=ugettext_lazy('Comment'),
|
ugettext_lazy('Comment'),
|
||||||
|
blank=True,
|
||||||
|
default='',
|
||||||
help_text=ugettext_lazy('Only for notes.'))
|
help_text=ugettext_lazy('Only for notes.'))
|
||||||
|
|
||||||
default_password = models.CharField(
|
default_password = models.CharField(
|
||||||
max_length=100, blank=True, default='',
|
ugettext_lazy('Default password'),
|
||||||
verbose_name=ugettext_lazy('Default password'))
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
default='')
|
||||||
|
|
||||||
is_active = models.BooleanField(
|
is_active = models.BooleanField(
|
||||||
ugettext_lazy('active'), default=True,
|
ugettext_lazy('Active'),
|
||||||
|
default=True,
|
||||||
help_text=ugettext_lazy(
|
help_text=ugettext_lazy(
|
||||||
'Designates whether this user should be treated as '
|
'Designates whether this user should be treated as '
|
||||||
'active. Unselect this instead of deleting accounts.'))
|
'active. Unselect this instead of deleting the account.'))
|
||||||
|
|
||||||
is_present = models.BooleanField(
|
is_present = models.BooleanField(
|
||||||
ugettext_lazy('present'), default=False,
|
ugettext_lazy('Present'),
|
||||||
help_text=ugettext_lazy('Designates whether this user is in the room '
|
default=False,
|
||||||
'or not.'))
|
help_text=ugettext_lazy(
|
||||||
|
'Designates whether this user is in the room or not.'))
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
@ -145,19 +166,11 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
|
|||||||
('can_see_extra_data', ugettext_noop('Can see extra data of users')),
|
('can_see_extra_data', ugettext_noop('Can see extra data of users')),
|
||||||
('can_manage', ugettext_noop('Can manage users')),
|
('can_manage', ugettext_noop('Can manage users')),
|
||||||
)
|
)
|
||||||
ordering = ('last_name',)
|
ordering = ('last_name', 'first_name', 'username', )
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.get_full_name()
|
return self.get_full_name()
|
||||||
|
|
||||||
def get_slide_context(self, **context):
|
|
||||||
"""
|
|
||||||
Returns the context for the user slide.
|
|
||||||
"""
|
|
||||||
# Does not call super. In this case the context would override the name
|
|
||||||
# 'user'.
|
|
||||||
return {'shown_user': self}
|
|
||||||
|
|
||||||
def get_full_name(self):
|
def get_full_name(self):
|
||||||
"""
|
"""
|
||||||
Returns a long form of the name.
|
Returns a long form of the name.
|
||||||
@ -166,7 +179,6 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
|
|||||||
* Professor Dr. Enders, Christoph (Leipzig)
|
* Professor Dr. Enders, Christoph (Leipzig)
|
||||||
"""
|
"""
|
||||||
structure = '(%s)' % self.structure_level if self.structure_level else ''
|
structure = '(%s)' % self.structure_level if self.structure_level else ''
|
||||||
|
|
||||||
return ' '.join((self.title, self.get_short_name(), structure)).strip()
|
return ' '.join((self.title, self.get_short_name(), structure)).strip()
|
||||||
|
|
||||||
def get_short_name(self):
|
def get_short_name(self):
|
||||||
@ -190,15 +202,9 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
|
|||||||
# The user has only a first_name or a last_name or no name
|
# The user has only a first_name or a last_name or no name
|
||||||
else:
|
else:
|
||||||
name = first_name or last_name or self.username
|
name = first_name or last_name or self.username
|
||||||
return name
|
|
||||||
|
|
||||||
def reset_password(self, password=None):
|
# Return result
|
||||||
"""
|
return name
|
||||||
Reset the password for the user to his default-password.
|
|
||||||
"""
|
|
||||||
if password is None:
|
|
||||||
password = self.default_password
|
|
||||||
self.set_password(password)
|
|
||||||
|
|
||||||
def get_view_class(self):
|
def get_view_class(self):
|
||||||
"""
|
"""
|
||||||
|
@ -14,9 +14,8 @@ from reportlab.platypus import (
|
|||||||
TableStyle,
|
TableStyle,
|
||||||
)
|
)
|
||||||
|
|
||||||
from openslides.core.config import config
|
from ..core.config import config
|
||||||
from openslides.utils.pdf import stylesheet
|
from ..utils.pdf import stylesheet
|
||||||
|
|
||||||
from .models import User
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from openslides.core.exceptions import ProjectorException
|
from ..core.exceptions import ProjectorException
|
||||||
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
from ..utils.projector import ProjectorElement, ProjectorRequirement
|
||||||
|
|
||||||
from .models import User
|
from .models import User
|
||||||
from .views import GroupViewSet, UserViewSet
|
from .views import GroupViewSet, UserViewSet
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ from .models import User
|
|||||||
class Index(indexes.SearchIndex, indexes.Indexable):
|
class Index(indexes.SearchIndex, indexes.Indexable):
|
||||||
text = indexes.EdgeNgramField(document=True, use_template=True)
|
text = indexes.EdgeNgramField(document=True, use_template=True)
|
||||||
text = indexes.EdgeNgramField(document=True, use_template=True)
|
text = indexes.EdgeNgramField(document=True, use_template=True)
|
||||||
modelfilter_name = "Users" # verbose_name of model
|
modelfilter_name = 'Users' # verbose_name of model
|
||||||
modelfilter_value = "users.user" # 'app_name.model_name'
|
modelfilter_value = 'users.user' # 'app_name.model_name'
|
||||||
|
|
||||||
def get_model(self):
|
def get_model(self):
|
||||||
return User
|
return User
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
|
|
||||||
from openslides.utils.rest_api import (
|
from ..utils.rest_api import (
|
||||||
ModelSerializer,
|
ModelSerializer,
|
||||||
PrimaryKeyRelatedField,
|
PrimaryKeyRelatedField,
|
||||||
RelatedField,
|
RelatedField,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
)
|
)
|
||||||
|
from .models import Group, User
|
||||||
from .models import Group, Permission, User
|
|
||||||
|
|
||||||
|
|
||||||
class UserShortSerializer(ModelSerializer):
|
class UserShortSerializer(ModelSerializer):
|
||||||
@ -28,7 +28,8 @@ class UserShortSerializer(ModelSerializer):
|
|||||||
'last_name',
|
'last_name',
|
||||||
'structure_level',
|
'structure_level',
|
||||||
'about_me',
|
'about_me',
|
||||||
'groups',)
|
'groups',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserFullSerializer(ModelSerializer):
|
class UserFullSerializer(ModelSerializer):
|
||||||
@ -58,7 +59,8 @@ class UserFullSerializer(ModelSerializer):
|
|||||||
'comment',
|
'comment',
|
||||||
'groups',
|
'groups',
|
||||||
'default_password',
|
'default_password',
|
||||||
'is_active',)
|
'is_active',
|
||||||
|
)
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
"""
|
"""
|
||||||
@ -70,7 +72,7 @@ class UserFullSerializer(ModelSerializer):
|
|||||||
raise ValidationError(_('Username, first name and last name can not all be empty.'))
|
raise ValidationError(_('Username, first name and last name can not all be empty.'))
|
||||||
|
|
||||||
# Generate username. But only if it is not set and the serializer is not
|
# Generate username. But only if it is not set and the serializer is not
|
||||||
# called in a patch-context.
|
# called in a PATCH context (partial_update).
|
||||||
try:
|
try:
|
||||||
action = self.context['view'].action
|
action = self.context['view'].action
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
@ -84,7 +86,7 @@ class UserFullSerializer(ModelSerializer):
|
|||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
"""
|
"""
|
||||||
Creates the user. Sets the default_password. Adds the new user to the
|
Creates the user. Sets the default password. Adds the new user to the
|
||||||
registered group.
|
registered group.
|
||||||
"""
|
"""
|
||||||
# Prepare setup password.
|
# Prepare setup password.
|
||||||
@ -139,4 +141,5 @@ class GroupSerializer(ModelSerializer):
|
|||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
'permissions',)
|
'permissions',
|
||||||
|
)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
from django.contrib.auth.models import Permission
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
from openslides.core.config import ConfigVariable
|
from ..core.config import ConfigVariable
|
||||||
|
from .models import Group, User
|
||||||
from .models import Group, Permission, User
|
|
||||||
|
|
||||||
|
|
||||||
def setup_users_config(sender, **kwargs):
|
def setup_users_config(sender, **kwargs):
|
||||||
@ -126,9 +126,10 @@ def create_builtin_groups_and_admin(**kwargs):
|
|||||||
'users.can_manage',
|
'users.can_manage',
|
||||||
'users.can_see_extra_data',
|
'users.can_see_extra_data',
|
||||||
'users.can_see_name', )
|
'users.can_see_name', )
|
||||||
permission_dict = {}
|
|
||||||
permission_query = Q()
|
permission_query = Q()
|
||||||
|
permission_dict = {}
|
||||||
|
|
||||||
|
# Load all permissions
|
||||||
for permission_string in permission_strings:
|
for permission_string in permission_strings:
|
||||||
app_label, codename = permission_string.split('.')
|
app_label, codename = permission_string.split('.')
|
||||||
query_part = Q(content_type__app_label=app_label) & Q(codename=codename)
|
query_part = Q(content_type__app_label=app_label) & Q(codename=codename)
|
||||||
@ -183,11 +184,12 @@ def create_builtin_groups_and_admin(**kwargs):
|
|||||||
group_staff = Group.objects.create(name=ugettext_noop('Staff'), pk=4)
|
group_staff = Group.objects.create(name=ugettext_noop('Staff'), pk=4)
|
||||||
group_staff.permissions.add(*staff_permissions)
|
group_staff.permissions.add(*staff_permissions)
|
||||||
|
|
||||||
# Add users.can_see_name and users.can_see_extra_data permissions
|
# Add users.can_see_name and users.can_see_extra_data permissions to staff
|
||||||
|
# group to ensure proper management possibilities
|
||||||
# TODO: Remove this redundancy after cleanup of the permission system.
|
# TODO: Remove this redundancy after cleanup of the permission system.
|
||||||
group_staff.permissions.add(
|
group_staff.permissions.add(
|
||||||
permission_dict['users.can_see_extra_data'],
|
permission_dict['users.can_see_extra_data'],
|
||||||
permission_dict['users.can_see_name'])
|
permission_dict['users.can_see_name'])
|
||||||
|
|
||||||
# Admin user
|
# Create or reset admin user
|
||||||
User.objects.create_or_reset_admin_user()
|
User.objects.create_or_reset_admin_user()
|
||||||
|
@ -5,16 +5,7 @@ from . import views
|
|||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
'',
|
'',
|
||||||
|
|
||||||
# PDF
|
# Auth
|
||||||
url(r'^print/$',
|
|
||||||
views.UsersListPDF.as_view(),
|
|
||||||
name='user_listpdf'),
|
|
||||||
|
|
||||||
url(r'^passwords/print/$',
|
|
||||||
views.UsersPasswordsPDF.as_view(),
|
|
||||||
name='user_passwordspdf'),
|
|
||||||
|
|
||||||
# auth
|
|
||||||
url(r'^login/$',
|
url(r'^login/$',
|
||||||
views.UserLoginView.as_view(),
|
views.UserLoginView.as_view(),
|
||||||
name='user_login'),
|
name='user_login'),
|
||||||
@ -26,4 +17,13 @@ urlpatterns = patterns(
|
|||||||
url(r'^whoami/$',
|
url(r'^whoami/$',
|
||||||
views.WhoAmIView.as_view(),
|
views.WhoAmIView.as_view(),
|
||||||
name='user_whoami'),
|
name='user_whoami'),
|
||||||
|
|
||||||
|
# PDF
|
||||||
|
url(r'^print/$',
|
||||||
|
views.UsersListPDF.as_view(),
|
||||||
|
name='user_listpdf'),
|
||||||
|
|
||||||
|
url(r'^passwords/print/$',
|
||||||
|
views.UsersPasswordsPDF.as_view(),
|
||||||
|
name='user_passwordspdf'),
|
||||||
)
|
)
|
||||||
|
@ -3,12 +3,10 @@ from django.contrib.auth import logout as auth_logout
|
|||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from openslides.core.config import config
|
|
||||||
from openslides.utils.rest_api import ModelViewSet, Response, detail_route
|
|
||||||
from openslides.utils.views import APIView, PDFView
|
|
||||||
|
|
||||||
|
from ..core.config import config
|
||||||
|
from ..utils.rest_api import ModelViewSet, Response, detail_route, status
|
||||||
|
from ..utils.views import APIView, PDFView
|
||||||
from .models import Group, User
|
from .models import Group, User
|
||||||
from .pdf import users_passwords_to_pdf, users_to_pdf
|
from .pdf import users_passwords_to_pdf, users_to_pdf
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
@ -185,7 +183,7 @@ class GroupViewSet(ModelViewSet):
|
|||||||
|
|
||||||
class UserLoginView(APIView):
|
class UserLoginView(APIView):
|
||||||
"""
|
"""
|
||||||
Login the user via Ajax.
|
Login the user.
|
||||||
"""
|
"""
|
||||||
http_method_names = ['post']
|
http_method_names = ['post']
|
||||||
|
|
||||||
@ -208,7 +206,7 @@ class UserLoginView(APIView):
|
|||||||
|
|
||||||
class UserLogoutView(APIView):
|
class UserLogoutView(APIView):
|
||||||
"""
|
"""
|
||||||
Logout the user via Ajax.
|
Logout the user.
|
||||||
"""
|
"""
|
||||||
http_method_names = ['post']
|
http_method_names = ['post']
|
||||||
|
|
||||||
@ -225,7 +223,7 @@ class WhoAmIView(APIView):
|
|||||||
|
|
||||||
def get_context_data(self, **context):
|
def get_context_data(self, **context):
|
||||||
"""
|
"""
|
||||||
Appends the user id into the context.
|
Appends the user id to the context.
|
||||||
|
|
||||||
Uses None for the anonymous user.
|
Uses None for the anonymous user.
|
||||||
"""
|
"""
|
||||||
@ -238,11 +236,11 @@ class WhoAmIView(APIView):
|
|||||||
|
|
||||||
class UsersListPDF(PDFView):
|
class UsersListPDF(PDFView):
|
||||||
"""
|
"""
|
||||||
Generate the userliste as PDF.
|
Generate a list of all users as PDF.
|
||||||
"""
|
"""
|
||||||
required_permission = 'users.can_see_extra_data'
|
required_permission = 'users.can_see_extra_data'
|
||||||
filename = ugettext_lazy("user-list")
|
filename = ugettext_lazy('user-list')
|
||||||
document_title = ugettext_lazy('List of Users')
|
document_title = ugettext_lazy('List of users')
|
||||||
|
|
||||||
def append_to_pdf(self, pdf):
|
def append_to_pdf(self, pdf):
|
||||||
"""
|
"""
|
||||||
@ -256,7 +254,7 @@ class UsersPasswordsPDF(PDFView):
|
|||||||
Generate the access data welcome paper for all users as PDF.
|
Generate the access data welcome paper for all users as PDF.
|
||||||
"""
|
"""
|
||||||
required_permission = 'users.can_manage'
|
required_permission = 'users.can_manage'
|
||||||
filename = ugettext_lazy("User-access-data")
|
filename = ugettext_lazy('user-access-data')
|
||||||
top_space = 0
|
top_space = 0
|
||||||
|
|
||||||
def build_document(self, pdf_document, story):
|
def build_document(self, pdf_document, story):
|
||||||
|
@ -2,6 +2,7 @@ import re
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from rest_framework import status # noqa
|
||||||
from rest_framework.decorators import detail_route, list_route # noqa
|
from rest_framework.decorators import detail_route, list_route # noqa
|
||||||
from rest_framework.metadata import SimpleMetadata # noqa
|
from rest_framework.metadata import SimpleMetadata # noqa
|
||||||
from rest_framework.mixins import DestroyModelMixin, UpdateModelMixin # noqa
|
from rest_framework.mixins import DestroyModelMixin, UpdateModelMixin # noqa
|
||||||
|
@ -1,25 +1,10 @@
|
|||||||
import roman
|
import roman
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
|
|
||||||
|
|
||||||
def delete_default_permissions(**kwargs):
|
|
||||||
"""
|
|
||||||
Deletes the permissions, django creates by default for the admin.
|
|
||||||
"""
|
|
||||||
# TODO: Find a way not to create the permissions in the first place.
|
|
||||||
# Meta.default_permissions does not work, because django will
|
|
||||||
# nevertheless create permissions for its own models like "group"
|
|
||||||
for p in Permission.objects.all():
|
|
||||||
if (p.codename.startswith('add') or
|
|
||||||
p.codename.startswith('delete') or
|
|
||||||
p.codename.startswith('change')):
|
|
||||||
p.delete()
|
|
||||||
|
|
||||||
|
|
||||||
def to_roman(number):
|
def to_roman(number):
|
||||||
"""
|
"""
|
||||||
Converts an arabic number within range from 1 to 4999 to the corresponding roman number.
|
Converts an arabic number within range from 1 to 4999 to the
|
||||||
Returns None on error conditions.
|
corresponding roman number. Returns None on error conditions.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return roman.toRoman(number)
|
return roman.toRoman(number)
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
from unittest import TestCase
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from openslides.users.api import get_protected_perm
|
|
||||||
|
|
||||||
|
|
||||||
@patch('openslides.users.api.Permission')
|
|
||||||
class GetProtectedPerm(TestCase):
|
|
||||||
def test_normal(self, mock_permission):
|
|
||||||
mock_permission.objects.get_by_natural_key.return_value = 'test_permission'
|
|
||||||
|
|
||||||
value = get_protected_perm()
|
|
||||||
|
|
||||||
mock_permission.objects.get_by_natural_key.assert_called_once_with(
|
|
||||||
app_label='users', model='user', codename='can_manage')
|
|
||||||
self.assertEqual(
|
|
||||||
value,
|
|
||||||
'test_permission',
|
|
||||||
"The function should return the user.can_manage permission")
|
|
@ -1,7 +1,7 @@
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from openslides.users.auth import AnonymousUser, auth, get_user
|
from openslides.users.auth import AnonymousUser, get_user
|
||||||
|
|
||||||
|
|
||||||
class TestAnonymousUser(TestCase):
|
class TestAnonymousUser(TestCase):
|
||||||
@ -98,40 +98,3 @@ class TestGetUser(TestCase):
|
|||||||
request._cached_user,
|
request._cached_user,
|
||||||
'django_anonymous_user',
|
'django_anonymous_user',
|
||||||
"The django user should be cached")
|
"The django user should be cached")
|
||||||
|
|
||||||
|
|
||||||
@patch('openslides.users.auth.config')
|
|
||||||
@patch('openslides.users.auth._auth')
|
|
||||||
class TestAuth(TestCase):
|
|
||||||
def test_anonymous_enabled(self, mock_auth, mock_config):
|
|
||||||
mock_config.__getitem__.return_value = True
|
|
||||||
request = MagicMock()
|
|
||||||
mock_auth.return_value = {'user': AnonymousUser()}
|
|
||||||
|
|
||||||
context = auth(request)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
context,
|
|
||||||
{'user': AnonymousUser()})
|
|
||||||
|
|
||||||
def test_anonymous_disabled(self, mock_auth, mock_config):
|
|
||||||
mock_config.__getitem__.return_value = False
|
|
||||||
request = MagicMock()
|
|
||||||
mock_auth.return_value = {'user': AnonymousUser()}
|
|
||||||
|
|
||||||
context = auth(request)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
context,
|
|
||||||
{'user': AnonymousUser()})
|
|
||||||
|
|
||||||
def test_logged_in_user_in_request(self, mock_auth, mock_config):
|
|
||||||
mock_config.__getitem__.return_value = True
|
|
||||||
request = MagicMock()
|
|
||||||
mock_auth.return_value = {'user': 'logged_in_user'}
|
|
||||||
|
|
||||||
context = auth(request)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
context,
|
|
||||||
{'user': 'logged_in_user'})
|
|
||||||
|
@ -18,19 +18,6 @@ class UserTest(TestCase):
|
|||||||
'Test Value IJee1yoet1ooGhesh5li',
|
'Test Value IJee1yoet1ooGhesh5li',
|
||||||
"The str representation of User is not user.get_full_name().")
|
"The str representation of User is not user.get_full_name().")
|
||||||
|
|
||||||
def test_get_slide_context(self):
|
|
||||||
"""
|
|
||||||
Tests, that get_slide_context returns:
|
|
||||||
|
|
||||||
{'shown_user': self}
|
|
||||||
"""
|
|
||||||
user = User()
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
user.get_slide_context(),
|
|
||||||
{'shown_user': user},
|
|
||||||
"User.get_slide_context returns a wrong context.")
|
|
||||||
|
|
||||||
|
|
||||||
class UserGetFullName(TestCase):
|
class UserGetFullName(TestCase):
|
||||||
def test_get_full_name_with_structure_level_and_title(self):
|
def test_get_full_name_with_structure_level_and_title(self):
|
||||||
@ -185,30 +172,6 @@ class UserGetShortName(TestCase):
|
|||||||
"User.get_short_name() has to strip whitespaces from the name parts.")
|
"User.get_short_name() has to strip whitespaces from the name parts.")
|
||||||
|
|
||||||
|
|
||||||
class UserResetPassword(TestCase):
|
|
||||||
def test_reset_password_no_attribute(self):
|
|
||||||
"""
|
|
||||||
Tests reset_password with no attribute.
|
|
||||||
"""
|
|
||||||
user = User(default_password='test_default_password')
|
|
||||||
user.set_password = MagicMock()
|
|
||||||
|
|
||||||
user.reset_password()
|
|
||||||
|
|
||||||
user.set_password.assert_called_once_with('test_default_password')
|
|
||||||
|
|
||||||
def test_reset_password_with_attribute(self):
|
|
||||||
"""
|
|
||||||
Tests reset_password with no attribute.
|
|
||||||
"""
|
|
||||||
user = User(default_password='test_default_password')
|
|
||||||
user.set_password = MagicMock()
|
|
||||||
|
|
||||||
user.reset_password('test_password')
|
|
||||||
|
|
||||||
user.set_password.assert_called_once_with('test_password')
|
|
||||||
|
|
||||||
|
|
||||||
class UserManagerTest(TestCase):
|
class UserManagerTest(TestCase):
|
||||||
def test_create_user(self):
|
def test_create_user(self):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user