Merge pull request #1458 from normanjaeckel/feature/rest-api/users
Refactoring users app.
This commit is contained in:
commit
411213b1ce
@ -2,28 +2,26 @@ import os
|
|||||||
|
|
||||||
from django.core.management.commands.migrate import Command as _Command
|
from django.core.management.commands.migrate import Command as _Command
|
||||||
|
|
||||||
from openslides.users.api import create_builtin_groups_and_admin
|
from ...signals import post_permission_creation
|
||||||
|
|
||||||
|
|
||||||
class Command(_Command):
|
class Command(_Command):
|
||||||
"""
|
"""
|
||||||
Migration command that does the same like the django migration command but
|
Migration command that does nearly the same as Django's migration command
|
||||||
calles also creates the default groups
|
but also calls the post_permission_creation signal.
|
||||||
"""
|
"""
|
||||||
# TODO: Try to get rid of this code. The problem are the ContentType
|
|
||||||
# and Permission objects, which are created in the post_migrate signal, but
|
|
||||||
# we need to things later.
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
# Creates the folder for a sqlite database if necessary
|
# Creates the folder for a SQLite3 database if necessary.
|
||||||
if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3':
|
if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3':
|
||||||
try:
|
try:
|
||||||
os.makedirs(settings.OPENSLIDES_USER_DATA_PATH)
|
os.makedirs(settings.OPENSLIDES_USER_DATA_PATH)
|
||||||
except (FileExistsError, AttributeError):
|
except (FileExistsError, AttributeError):
|
||||||
# If the folder already exist or the settings OPENSLIDES_USER_DATA_PATH
|
# If the folder already exists or the settings
|
||||||
# is unknown, then do nothing
|
# OPENSLIDES_USER_DATA_PATH is unknown, just do nothing.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
super().handle(*args, **options)
|
super().handle(*args, **options)
|
||||||
create_builtin_groups_and_admin()
|
|
||||||
|
# Send this signal after sending post_migrate (inside super()) so that
|
||||||
|
# all Permission objects are created previously.
|
||||||
|
post_permission_creation.send(self)
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
from django.dispatch import Signal
|
||||||
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.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
||||||
|
|
||||||
|
# This signal is sent when the migrate command is done. That means it is sent
|
||||||
|
# after post_migrate sending and creating all Permission objects. Don't use it
|
||||||
|
# for other things than dealing with Permission objects.
|
||||||
|
post_permission_creation = Signal()
|
||||||
|
|
||||||
|
|
||||||
def setup_general_config(sender, **kwargs):
|
def setup_general_config(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -9,6 +9,8 @@ SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
|
|||||||
|
|
||||||
AUTH_USER_MODEL = 'users.User'
|
AUTH_USER_MODEL = 'users.User'
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = ('openslides.users.auth.CustomizedModelBackend',)
|
||||||
|
|
||||||
LOGIN_URL = '/login/'
|
LOGIN_URL = '/login/'
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
|
|
||||||
@ -76,6 +78,7 @@ ROOT_URLCONF = 'openslides.urls'
|
|||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'openslides.core',
|
'openslides.core',
|
||||||
|
'openslides.users',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
@ -92,7 +95,6 @@ INSTALLED_APPS = (
|
|||||||
'openslides.agenda',
|
'openslides.agenda',
|
||||||
'openslides.motion',
|
'openslides.motion',
|
||||||
'openslides.assignment',
|
'openslides.assignment',
|
||||||
'openslides.users',
|
|
||||||
'openslides.mediafile',
|
'openslides.mediafile',
|
||||||
'openslides.config',
|
'openslides.config',
|
||||||
)
|
)
|
||||||
|
@ -1,160 +1,4 @@
|
|||||||
from random import choice
|
from .models import Permission
|
||||||
|
|
||||||
from django.contrib.auth.models import Permission, Group
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.utils.translation import ugettext_noop
|
|
||||||
|
|
||||||
from .models import User
|
|
||||||
|
|
||||||
|
|
||||||
def gen_password():
|
|
||||||
"""
|
|
||||||
Generates a random passwort.
|
|
||||||
"""
|
|
||||||
chars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
|
||||||
size = 8
|
|
||||||
|
|
||||||
return ''.join([choice(chars) for i in range(size)])
|
|
||||||
|
|
||||||
|
|
||||||
def gen_username(first_name, last_name):
|
|
||||||
"""
|
|
||||||
Generates a username from a first- and lastname.
|
|
||||||
"""
|
|
||||||
first_name = first_name.strip()
|
|
||||||
last_name = last_name.strip()
|
|
||||||
|
|
||||||
if first_name and last_name:
|
|
||||||
base_name = " ".join((first_name, last_name))
|
|
||||||
else:
|
|
||||||
base_name = first_name or last_name
|
|
||||||
if not base_name:
|
|
||||||
raise ValueError('Either \'first_name\' or \'last_name\' can not be '
|
|
||||||
'empty')
|
|
||||||
|
|
||||||
if not User.objects.filter(username=base_name).exists():
|
|
||||||
return base_name
|
|
||||||
|
|
||||||
counter = 0
|
|
||||||
while True:
|
|
||||||
counter += 1
|
|
||||||
test_name = "%s %d" % (base_name, counter)
|
|
||||||
if not User.objects.filter(username=test_name).exists():
|
|
||||||
return test_name
|
|
||||||
|
|
||||||
|
|
||||||
def get_registered_group():
|
|
||||||
"""
|
|
||||||
Returns the group 'Registered' (pk=2).
|
|
||||||
"""
|
|
||||||
return Group.objects.get(pk=2)
|
|
||||||
|
|
||||||
|
|
||||||
def create_builtin_groups_and_admin():
|
|
||||||
"""
|
|
||||||
Creates the builtin groups: Anonymous, Registered, Delegates and Staff.
|
|
||||||
|
|
||||||
Creates the builtin user: admin.
|
|
||||||
"""
|
|
||||||
# Check whether the group pks 1 to 4 are free
|
|
||||||
if Group.objects.filter(pk__in=range(1, 5)).exists():
|
|
||||||
# Do completely nothing if there are already some of our groups in the database.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Anonymous (pk 1) and Registered (pk 2)
|
|
||||||
ct_core = ContentType.objects.get(app_label='core', model='customslide')
|
|
||||||
perm_11 = Permission.objects.get(content_type=ct_core, codename='can_see_projector')
|
|
||||||
perm_12 = Permission.objects.get(content_type=ct_core, codename='can_see_dashboard')
|
|
||||||
|
|
||||||
ct_agenda = ContentType.objects.get(app_label='agenda', model='item')
|
|
||||||
ct_speaker = ContentType.objects.get(app_label='agenda', model='speaker')
|
|
||||||
perm_13 = Permission.objects.get(content_type=ct_agenda, codename='can_see_agenda')
|
|
||||||
perm_14 = Permission.objects.get(content_type=ct_agenda, codename='can_see_orga_items')
|
|
||||||
can_speak = Permission.objects.get(content_type=ct_speaker, codename='can_be_speaker')
|
|
||||||
|
|
||||||
ct_motion = ContentType.objects.get(app_label='motion', model='motion')
|
|
||||||
perm_15 = Permission.objects.get(content_type=ct_motion, codename='can_see_motion')
|
|
||||||
|
|
||||||
ct_assignment = ContentType.objects.get(app_label='assignment', model='assignment')
|
|
||||||
perm_16 = Permission.objects.get(content_type=ct_assignment, codename='can_see_assignments')
|
|
||||||
|
|
||||||
ct_users = ContentType.objects.get(app_label='users', model='user')
|
|
||||||
perm_users_can_see_name = Permission.objects.get(content_type=ct_users, codename='can_see_name')
|
|
||||||
perm_users_can_see_extra_data = Permission.objects.get(content_type=ct_users, codename='can_see_extra_data')
|
|
||||||
|
|
||||||
ct_mediafile = ContentType.objects.get(app_label='mediafile', model='mediafile')
|
|
||||||
perm_18 = Permission.objects.get(content_type=ct_mediafile, codename='can_see')
|
|
||||||
|
|
||||||
base_permission_list = (
|
|
||||||
perm_11,
|
|
||||||
perm_12,
|
|
||||||
perm_13,
|
|
||||||
perm_14,
|
|
||||||
perm_15,
|
|
||||||
perm_16,
|
|
||||||
perm_users_can_see_name,
|
|
||||||
perm_users_can_see_extra_data,
|
|
||||||
perm_18)
|
|
||||||
|
|
||||||
group_anonymous = Group.objects.create(name=ugettext_noop('Anonymous'), pk=1)
|
|
||||||
group_anonymous.permissions.add(*base_permission_list)
|
|
||||||
group_registered = Group.objects.create(name=ugettext_noop('Registered'), pk=2)
|
|
||||||
group_registered.permissions.add(can_speak, *base_permission_list)
|
|
||||||
|
|
||||||
# Delegates (pk 3)
|
|
||||||
perm_31 = Permission.objects.get(content_type=ct_motion, codename='can_create_motion')
|
|
||||||
perm_32 = Permission.objects.get(content_type=ct_motion, codename='can_support_motion')
|
|
||||||
perm_33 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_other')
|
|
||||||
perm_34 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_self')
|
|
||||||
perm_35 = Permission.objects.get(content_type=ct_mediafile, codename='can_upload')
|
|
||||||
|
|
||||||
group_delegates = Group.objects.create(name=ugettext_noop('Delegates'), pk=3)
|
|
||||||
group_delegates.permissions.add(perm_31, perm_32, perm_33, perm_34, perm_35)
|
|
||||||
|
|
||||||
# Staff (pk 4)
|
|
||||||
perm_41 = Permission.objects.get(content_type=ct_agenda, codename='can_manage_agenda')
|
|
||||||
perm_42 = Permission.objects.get(content_type=ct_motion, codename='can_manage_motion')
|
|
||||||
perm_43 = Permission.objects.get(content_type=ct_assignment, codename='can_manage_assignments')
|
|
||||||
perm_44 = Permission.objects.get(content_type=ct_users, codename='can_manage')
|
|
||||||
perm_45 = Permission.objects.get(content_type=ct_core, codename='can_manage_projector')
|
|
||||||
perm_46 = Permission.objects.get(content_type=ct_core, codename='can_use_chat')
|
|
||||||
perm_47 = Permission.objects.get(content_type=ct_mediafile, codename='can_manage')
|
|
||||||
|
|
||||||
ct_config = ContentType.objects.get(app_label='config', model='configstore')
|
|
||||||
perm_48 = Permission.objects.get(content_type=ct_config, codename='can_manage')
|
|
||||||
|
|
||||||
ct_tag = ContentType.objects.get(app_label='core', model='tag')
|
|
||||||
can_manage_tags = Permission.objects.get(content_type=ct_tag, codename='can_manage_tags')
|
|
||||||
|
|
||||||
group_staff = Group.objects.create(name=ugettext_noop('Staff'), pk=4)
|
|
||||||
# add delegate permissions (without can_support_motion)
|
|
||||||
group_staff.permissions.add(perm_31, perm_33, perm_34, perm_35)
|
|
||||||
# add staff permissions
|
|
||||||
group_staff.permissions.add(perm_41, perm_42, perm_43, perm_44, perm_45, perm_46, perm_47, perm_48, can_manage_tags)
|
|
||||||
# add can_see_name and can_see_extra_data permissions
|
|
||||||
# TODO: Remove this redundancy after cleanup of the permission system.
|
|
||||||
group_staff.permissions.add(perm_users_can_see_name, perm_users_can_see_extra_data)
|
|
||||||
|
|
||||||
# Admin user
|
|
||||||
create_or_reset_admin_user()
|
|
||||||
|
|
||||||
|
|
||||||
def create_or_reset_admin_user():
|
|
||||||
group_staff = Group.objects.get(pk=4)
|
|
||||||
try:
|
|
||||||
admin = User.objects.get(username="admin")
|
|
||||||
except User.DoesNotExist:
|
|
||||||
admin = User()
|
|
||||||
admin.username = 'admin'
|
|
||||||
admin.last_name = 'Administrator'
|
|
||||||
created = True
|
|
||||||
else:
|
|
||||||
created = False
|
|
||||||
admin.default_password = 'admin'
|
|
||||||
admin.set_password(admin.default_password)
|
|
||||||
admin.save()
|
|
||||||
admin.groups.add(group_staff)
|
|
||||||
return created
|
|
||||||
|
|
||||||
|
|
||||||
def get_protected_perm():
|
def get_protected_perm():
|
||||||
|
@ -11,11 +11,11 @@ class UsersAppConfig(AppConfig):
|
|||||||
from . import main_menu, widgets # noqa
|
from . import main_menu, widgets # noqa
|
||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from django.db.models.signals import post_save
|
|
||||||
from openslides.config.signals import config_signal
|
from openslides.config.signals import config_signal
|
||||||
|
from openslides.core.signals import post_permission_creation
|
||||||
from openslides.projector.api import register_slide_model
|
from openslides.projector.api import register_slide_model
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from .signals import setup_users_config, user_post_save
|
from .signals import create_builtin_groups_and_admin, setup_users_config
|
||||||
from .views import GroupViewSet, UserViewSet
|
from .views import GroupViewSet, UserViewSet
|
||||||
|
|
||||||
# Load User model.
|
# Load User model.
|
||||||
@ -23,7 +23,9 @@ class UsersAppConfig(AppConfig):
|
|||||||
|
|
||||||
# Connect signals.
|
# Connect signals.
|
||||||
config_signal.connect(setup_users_config, dispatch_uid='setup_users_config')
|
config_signal.connect(setup_users_config, dispatch_uid='setup_users_config')
|
||||||
post_save.connect(user_post_save, sender=User, dispatch_uid='users_user_post_save')
|
post_permission_creation.connect(
|
||||||
|
create_builtin_groups_and_admin,
|
||||||
|
dispatch_uid='create_builtin_groups_and_admin')
|
||||||
|
|
||||||
# Register slides.
|
# Register slides.
|
||||||
register_slide_model(User, 'participant/user_slide.html')
|
register_slide_model(User, 'participant/user_slide.html')
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
from django.contrib.auth.models import AnonymousUser as DjangoAnonymousUser, Permission
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.backends import ModelBackend
|
||||||
|
from django.contrib.auth.models import AnonymousUser as DjangoAnonymousUser
|
||||||
from django.contrib.auth.context_processors import auth as _auth
|
from django.contrib.auth.context_processors import auth as _auth
|
||||||
from django.contrib.auth import get_user as _get_user
|
from django.contrib.auth import get_user as _get_user
|
||||||
|
from django.db.models import Q
|
||||||
from django.utils.functional import SimpleLazyObject
|
from django.utils.functional import SimpleLazyObject
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
|
|
||||||
|
from .models import Permission
|
||||||
|
|
||||||
|
|
||||||
class AnonymousUser(DjangoAnonymousUser):
|
class AnonymousUser(DjangoAnonymousUser):
|
||||||
"""
|
"""
|
||||||
@ -41,6 +46,34 @@ class AnonymousUser(DjangoAnonymousUser):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class CustomizedModelBackend(ModelBackend):
|
||||||
|
"""
|
||||||
|
Customized backend for authentication. Ensures that registered users have
|
||||||
|
all permission of the group 'Registered' (pk=2).
|
||||||
|
"""
|
||||||
|
def get_group_permissions(self, user_obj, obj=None):
|
||||||
|
"""
|
||||||
|
Returns a set of permission strings that this user has through his/her
|
||||||
|
groups.
|
||||||
|
"""
|
||||||
|
# TODO: Refactor this after Django 1.8 release. Add also anonymous
|
||||||
|
# permission check to this backend.
|
||||||
|
if user_obj.is_anonymous() or obj is not None:
|
||||||
|
return set()
|
||||||
|
if not hasattr(user_obj, '_group_perm_cache'):
|
||||||
|
if user_obj.is_superuser:
|
||||||
|
perms = Permission.objects.all()
|
||||||
|
else:
|
||||||
|
user_groups_field = get_user_model()._meta.get_field('groups')
|
||||||
|
user_groups_query = 'group__%s' % user_groups_field.related_query_name()
|
||||||
|
# The next two lines are the customization.
|
||||||
|
query = Q(**{user_groups_query: user_obj}) | Q(group__pk=2)
|
||||||
|
perms = Permission.objects.filter(query)
|
||||||
|
perms = perms.values_list('content_type__app_label', 'codename').order_by()
|
||||||
|
user_obj._group_perm_cache = set("%s.%s" % (ct, name) for ct, name in perms)
|
||||||
|
return user_obj._group_perm_cache
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationMiddleware(object):
|
class AuthenticationMiddleware(object):
|
||||||
"""
|
"""
|
||||||
Middleware to get the logged in user in users.
|
Middleware to get the logged in user in users.
|
||||||
|
@ -6,7 +6,6 @@ from django.utils.translation import ugettext as _
|
|||||||
from openslides.utils import csv_ext
|
from openslides.utils import csv_ext
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
|
|
||||||
from .api import gen_password, gen_username
|
|
||||||
from .models import Group, User
|
from .models import Group, User
|
||||||
|
|
||||||
|
|
||||||
@ -39,7 +38,7 @@ def import_users(csvfile):
|
|||||||
user.title = title
|
user.title = title
|
||||||
user.last_name = last_name
|
user.last_name = last_name
|
||||||
user.first_name = first_name
|
user.first_name = first_name
|
||||||
user.username = gen_username(first_name, last_name)
|
user.username = User.objects.generate_username(first_name, last_name)
|
||||||
user.gender = gender
|
user.gender = gender
|
||||||
user.email = email
|
user.email = email
|
||||||
user.structure_level = structure_level
|
user.structure_level = structure_level
|
||||||
@ -50,7 +49,7 @@ def import_users(csvfile):
|
|||||||
user.is_active = True
|
user.is_active = True
|
||||||
else:
|
else:
|
||||||
user.is_active = False
|
user.is_active = False
|
||||||
user.default_password = gen_password()
|
user.default_password = User.objects.generate_password()
|
||||||
user.save()
|
user.save()
|
||||||
for groupid in groups.split(','):
|
for groupid in groups.split(','):
|
||||||
try:
|
try:
|
||||||
|
5
openslides/users/exceptions.py
Normal file
5
openslides/users/exceptions.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
|
|
||||||
|
|
||||||
|
class UserError(OpenSlidesError):
|
||||||
|
pass
|
@ -1,13 +1,12 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.utils.forms import (CssClassMixin,
|
from openslides.utils.forms import (CssClassMixin,
|
||||||
LocalizedModelMultipleChoiceField)
|
LocalizedModelMultipleChoiceField)
|
||||||
|
|
||||||
from .models import Group, User
|
from .models import Group, Permission, User
|
||||||
from .api import get_protected_perm
|
from .api import get_protected_perm
|
||||||
|
|
||||||
|
|
||||||
|
0
openslides/users/management/__init__.py
Normal file
0
openslides/users/management/__init__.py
Normal file
0
openslides/users/management/commands/__init__.py
Normal file
0
openslides/users/management/commands/__init__.py
Normal file
@ -1,15 +1,15 @@
|
|||||||
from django.core.management.base import NoArgsCommand
|
from django.core.management.base import NoArgsCommand
|
||||||
|
|
||||||
from openslides.users.api import create_or_reset_admin_user
|
from openslides.users.models import User
|
||||||
|
|
||||||
|
|
||||||
class Command(NoArgsCommand):
|
class Command(NoArgsCommand):
|
||||||
"""
|
"""
|
||||||
Commands to create or reset the adminuser
|
Command to create or reset the admin user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def handle_noargs(self, **options):
|
def handle_noargs(self, **options):
|
||||||
if create_or_reset_admin_user():
|
created = User.objects.create_or_reset_admin_user()
|
||||||
|
if created:
|
||||||
self.stdout.write('Admin user successfully created.')
|
self.stdout.write('Admin user successfully created.')
|
||||||
else:
|
else:
|
||||||
self.stdout.write('Admin user successfully reset.')
|
self.stdout.write('Admin user successfully reset.')
|
@ -1,10 +1,13 @@
|
|||||||
# TODO: Check every app, that they do not import Group or User from here.
|
# TODO: Check every app, that they do not import Group or User from here.
|
||||||
|
|
||||||
from django.contrib.auth.models import (PermissionsMixin, AbstractBaseUser,
|
from random import choice
|
||||||
BaseUserManager)
|
|
||||||
|
|
||||||
# TODO: Do not import the Group in here, but in core.models (if necessary)
|
from django.contrib.auth.hashers import make_password
|
||||||
from django.contrib.auth.models import Group # noqa
|
from django.contrib.auth.models import (
|
||||||
|
AbstractBaseUser,
|
||||||
|
BaseUserManager,
|
||||||
|
PermissionsMixin)
|
||||||
|
from django.contrib.auth.models import Group, Permission # noqa
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
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
|
||||||
@ -14,20 +17,83 @@ from openslides.projector.models import SlideMixin
|
|||||||
from openslides.utils.models import AbsoluteUrlMixin
|
from openslides.utils.models import AbsoluteUrlMixin
|
||||||
from openslides.utils.rest_api import RESTModelMixin
|
from openslides.utils.rest_api import RESTModelMixin
|
||||||
|
|
||||||
|
from .exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
"""
|
"""
|
||||||
UserManager that creates new users only with a password and a username.
|
UserManager 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):
|
||||||
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)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def create_or_reset_admin_user(self):
|
||||||
|
"""
|
||||||
|
Creates an user with the username admin. If such a user exists, resets
|
||||||
|
it. The password is (re)set to 'admin'. The user becomes member of the
|
||||||
|
group 'Staff' (pk=4).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
staff = Group.objects.get(pk=4)
|
||||||
|
except Group.DoesNotExist:
|
||||||
|
raise UserError("Admin user can not be created or reset because "
|
||||||
|
"the group 'Staff' is not available.")
|
||||||
|
admin, created = self.get_or_create(
|
||||||
|
username='admin',
|
||||||
|
defaults={'last_name': 'Administrator'})
|
||||||
|
admin.default_password = 'admin'
|
||||||
|
admin.password = make_password(admin.default_password, '', 'md5')
|
||||||
|
admin.save()
|
||||||
|
admin.groups.add(staff)
|
||||||
|
return created
|
||||||
|
|
||||||
|
def generate_username(self, first_name, last_name):
|
||||||
|
"""
|
||||||
|
Generates a username from first name and last name.
|
||||||
|
"""
|
||||||
|
first_name = first_name.strip()
|
||||||
|
last_name = last_name.strip()
|
||||||
|
|
||||||
|
if first_name and last_name:
|
||||||
|
base_name = ' '.join((first_name, last_name))
|
||||||
|
else:
|
||||||
|
base_name = first_name or last_name
|
||||||
|
if not base_name:
|
||||||
|
raise ValueError("Either 'first_name' or 'last_name' must not be "
|
||||||
|
"empty")
|
||||||
|
|
||||||
|
if not self.filter(username=base_name).exists():
|
||||||
|
generated_username = base_name
|
||||||
|
else:
|
||||||
|
counter = 0
|
||||||
|
while True:
|
||||||
|
counter += 1
|
||||||
|
test_name = '%s %d' % (base_name, counter)
|
||||||
|
if not self.filter(username=test_name).exists():
|
||||||
|
generated_username = test_name
|
||||||
|
break
|
||||||
|
|
||||||
|
return generated_username
|
||||||
|
|
||||||
|
def generate_password(self):
|
||||||
|
"""
|
||||||
|
Generates a random passwort.
|
||||||
|
"""
|
||||||
|
chars = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'
|
||||||
|
size = 8
|
||||||
|
return ''.join([choice(chars) for i in range(size)])
|
||||||
|
|
||||||
|
|
||||||
class User(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, PermissionsMixin, AbstractBaseUser):
|
class User(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, PermissionsMixin, AbstractBaseUser):
|
||||||
|
"""
|
||||||
|
Model for users in OpenSlides. A client can login as a user with
|
||||||
|
credentials. A user can also just be used as representation for a person
|
||||||
|
in other OpenSlides app like motion submitter or (assignment) election
|
||||||
|
candidates.
|
||||||
|
"""
|
||||||
USERNAME_FIELD = 'username'
|
USERNAME_FIELD = 'username'
|
||||||
slide_callback_name = 'user'
|
slide_callback_name = 'user'
|
||||||
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
from openslides.utils.rest_api import ModelSerializer, RelatedField
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.contrib.auth.hashers import make_password
|
||||||
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
|
||||||
from .models import Group, User # TODO: Don't import Group from models but from core.models.
|
from openslides.utils.rest_api import ModelSerializer, PrimaryKeyRelatedField, RelatedField, ValidationError
|
||||||
|
|
||||||
|
from .models import Group, User
|
||||||
|
|
||||||
|
|
||||||
class UserShortSerializer(ModelSerializer):
|
class UserShortSerializer(ModelSerializer):
|
||||||
@ -45,6 +49,75 @@ class UserFullSerializer(ModelSerializer):
|
|||||||
'is_active',)
|
'is_active',)
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreateUpdateSerializer(ModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for users.models.User objects.
|
||||||
|
|
||||||
|
Serializes data to create new users or update users.
|
||||||
|
|
||||||
|
Do not use this for list or retrieve requests.
|
||||||
|
"""
|
||||||
|
groups = PrimaryKeyRelatedField(
|
||||||
|
many=True,
|
||||||
|
queryset=Group.objects.exclude(pk__in=(1, 2)),
|
||||||
|
help_text=ugettext_lazy('The groups this user belongs to. A user will '
|
||||||
|
'get all permissions granted to each of '
|
||||||
|
'his/her groups.'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = (
|
||||||
|
'is_present',
|
||||||
|
'username',
|
||||||
|
'title',
|
||||||
|
'first_name',
|
||||||
|
'last_name',
|
||||||
|
'structure_level',
|
||||||
|
'about_me',
|
||||||
|
'comment',
|
||||||
|
'groups',
|
||||||
|
'default_password',
|
||||||
|
'is_active',)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Overridden to add read_only flag to username field in create requests.
|
||||||
|
"""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
if self.context['view'].action == 'create':
|
||||||
|
self.fields['username'].read_only = True
|
||||||
|
elif self.context['view'].action == 'update':
|
||||||
|
# Everything is fine. Do nothing.
|
||||||
|
pass
|
||||||
|
else: # Other action than 'create' or 'update'.
|
||||||
|
raise ImproperlyConfigured('This serializer can only be used in create and update requests.')
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
"""
|
||||||
|
Checks that first_name or last_name is given.
|
||||||
|
"""
|
||||||
|
if not (data.get('username') or data.get('first_name') or data.get('last_name')):
|
||||||
|
raise ValidationError(_('Username, first name and last name can not all be empty.'))
|
||||||
|
return data
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""
|
||||||
|
Creates user with generated username and sets the default_password.
|
||||||
|
Adds the new user to the registered group.
|
||||||
|
"""
|
||||||
|
# Generate username if neccessary.
|
||||||
|
if not validated_data.get('username'):
|
||||||
|
validated_data['username'] = User.objects.generate_username(
|
||||||
|
validated_data.get('first_name', ''),
|
||||||
|
validated_data.get('last_name', ''))
|
||||||
|
# Prepare setup password.
|
||||||
|
if not validated_data.get('default_password'):
|
||||||
|
validated_data['default_password'] = User.objects.generate_password()
|
||||||
|
validated_data['password'] = make_password(validated_data['default_password'], '', 'md5')
|
||||||
|
# Perform creation in the database and return new user.
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
class PermissionRelatedField(RelatedField):
|
class PermissionRelatedField(RelatedField):
|
||||||
"""
|
"""
|
||||||
A custom field to use for the permission relationship.
|
A custom field to use for the permission relationship.
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
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.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
||||||
|
|
||||||
|
from .models import Group, Permission, User
|
||||||
|
|
||||||
|
|
||||||
def setup_users_config(sender, **kwargs):
|
def setup_users_config(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -99,16 +102,90 @@ def setup_users_config(sender, **kwargs):
|
|||||||
groups=(group_general, group_pdf))
|
groups=(group_general, group_pdf))
|
||||||
|
|
||||||
|
|
||||||
def user_post_save(sender, instance, *args, **kwargs):
|
def create_builtin_groups_and_admin(**kwargs):
|
||||||
"""
|
"""
|
||||||
Receiver function to add a new user to the registered group. It is
|
Creates the builtin groups: Anonymous, Registered, Delegates and Staff.
|
||||||
connected to the signal django.db.models.signals.post_save during app
|
|
||||||
loading.
|
Creates the builtin user: admin.
|
||||||
"""
|
"""
|
||||||
if not kwargs['created']:
|
# Check whether the group pks 1 to 4 are free
|
||||||
|
if Group.objects.filter(pk__in=range(1, 5)).exists():
|
||||||
|
# Do completely nothing if there are already some of our groups in the database.
|
||||||
return
|
return
|
||||||
|
|
||||||
from openslides.users.api import get_registered_group # TODO: Test, if global import is possible
|
# Anonymous (pk 1) and Registered (pk 2)
|
||||||
registered = get_registered_group()
|
ct_core = ContentType.objects.get(app_label='core', model='customslide')
|
||||||
instance.groups.add(registered)
|
perm_11 = Permission.objects.get(content_type=ct_core, codename='can_see_projector')
|
||||||
instance.save()
|
perm_12 = Permission.objects.get(content_type=ct_core, codename='can_see_dashboard')
|
||||||
|
|
||||||
|
ct_agenda = ContentType.objects.get(app_label='agenda', model='item')
|
||||||
|
ct_speaker = ContentType.objects.get(app_label='agenda', model='speaker')
|
||||||
|
perm_13 = Permission.objects.get(content_type=ct_agenda, codename='can_see_agenda')
|
||||||
|
perm_14 = Permission.objects.get(content_type=ct_agenda, codename='can_see_orga_items')
|
||||||
|
can_speak = Permission.objects.get(content_type=ct_speaker, codename='can_be_speaker')
|
||||||
|
|
||||||
|
ct_motion = ContentType.objects.get(app_label='motion', model='motion')
|
||||||
|
perm_15 = Permission.objects.get(content_type=ct_motion, codename='can_see_motion')
|
||||||
|
|
||||||
|
ct_assignment = ContentType.objects.get(app_label='assignment', model='assignment')
|
||||||
|
perm_16 = Permission.objects.get(content_type=ct_assignment, codename='can_see_assignments')
|
||||||
|
|
||||||
|
ct_users = ContentType.objects.get(app_label='users', model='user')
|
||||||
|
perm_users_can_see_name = Permission.objects.get(content_type=ct_users, codename='can_see_name')
|
||||||
|
perm_users_can_see_extra_data = Permission.objects.get(content_type=ct_users, codename='can_see_extra_data')
|
||||||
|
|
||||||
|
ct_mediafile = ContentType.objects.get(app_label='mediafile', model='mediafile')
|
||||||
|
perm_18 = Permission.objects.get(content_type=ct_mediafile, codename='can_see')
|
||||||
|
|
||||||
|
base_permission_list = (
|
||||||
|
perm_11,
|
||||||
|
perm_12,
|
||||||
|
perm_13,
|
||||||
|
perm_14,
|
||||||
|
perm_15,
|
||||||
|
perm_16,
|
||||||
|
perm_users_can_see_name,
|
||||||
|
perm_users_can_see_extra_data,
|
||||||
|
perm_18)
|
||||||
|
|
||||||
|
group_anonymous = Group.objects.create(name=ugettext_noop('Anonymous'), pk=1)
|
||||||
|
group_anonymous.permissions.add(*base_permission_list)
|
||||||
|
group_registered = Group.objects.create(name=ugettext_noop('Registered'), pk=2)
|
||||||
|
group_registered.permissions.add(can_speak, *base_permission_list)
|
||||||
|
|
||||||
|
# Delegates (pk 3)
|
||||||
|
perm_31 = Permission.objects.get(content_type=ct_motion, codename='can_create_motion')
|
||||||
|
perm_32 = Permission.objects.get(content_type=ct_motion, codename='can_support_motion')
|
||||||
|
perm_33 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_other')
|
||||||
|
perm_34 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_self')
|
||||||
|
perm_35 = Permission.objects.get(content_type=ct_mediafile, codename='can_upload')
|
||||||
|
|
||||||
|
group_delegates = Group.objects.create(name=ugettext_noop('Delegates'), pk=3)
|
||||||
|
group_delegates.permissions.add(perm_31, perm_32, perm_33, perm_34, perm_35)
|
||||||
|
|
||||||
|
# Staff (pk 4)
|
||||||
|
perm_41 = Permission.objects.get(content_type=ct_agenda, codename='can_manage_agenda')
|
||||||
|
perm_42 = Permission.objects.get(content_type=ct_motion, codename='can_manage_motion')
|
||||||
|
perm_43 = Permission.objects.get(content_type=ct_assignment, codename='can_manage_assignments')
|
||||||
|
perm_44 = Permission.objects.get(content_type=ct_users, codename='can_manage')
|
||||||
|
perm_45 = Permission.objects.get(content_type=ct_core, codename='can_manage_projector')
|
||||||
|
perm_46 = Permission.objects.get(content_type=ct_core, codename='can_use_chat')
|
||||||
|
perm_47 = Permission.objects.get(content_type=ct_mediafile, codename='can_manage')
|
||||||
|
|
||||||
|
ct_config = ContentType.objects.get(app_label='config', model='configstore')
|
||||||
|
perm_48 = Permission.objects.get(content_type=ct_config, codename='can_manage')
|
||||||
|
|
||||||
|
ct_tag = ContentType.objects.get(app_label='core', model='tag')
|
||||||
|
can_manage_tags = Permission.objects.get(content_type=ct_tag, codename='can_manage_tags')
|
||||||
|
|
||||||
|
group_staff = Group.objects.create(name=ugettext_noop('Staff'), pk=4)
|
||||||
|
# add delegate permissions (without can_support_motion)
|
||||||
|
group_staff.permissions.add(perm_31, perm_33, perm_34, perm_35)
|
||||||
|
# add staff permissions
|
||||||
|
group_staff.permissions.add(perm_41, perm_42, perm_43, perm_44, perm_45, perm_46, perm_47, perm_48, can_manage_tags)
|
||||||
|
# add can_see_name and can_see_extra_data permissions
|
||||||
|
# TODO: Remove this redundancy after cleanup of the permission system.
|
||||||
|
group_staff.permissions.add(perm_users_can_see_name, perm_users_can_see_extra_data)
|
||||||
|
|
||||||
|
# Admin user
|
||||||
|
User.objects.create_or_reset_admin_user()
|
||||||
|
@ -14,13 +14,13 @@ from openslides.utils.views import (
|
|||||||
UpdateView, LoginMixin)
|
UpdateView, LoginMixin)
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
|
|
||||||
from .api import gen_password, gen_username, get_protected_perm
|
from .api import get_protected_perm
|
||||||
from .csv_import import import_users
|
from .csv_import import import_users
|
||||||
from .forms import (GroupForm, UserCreateForm, UserMultipleCreateForm,
|
from .forms import (GroupForm, UserCreateForm, UserMultipleCreateForm,
|
||||||
UsersettingsForm, UserUpdateForm)
|
UsersettingsForm, UserUpdateForm)
|
||||||
from .models import Group, User
|
from .models import Group, User
|
||||||
from .pdf import users_to_pdf, users_passwords_to_pdf
|
from .pdf import users_to_pdf, users_passwords_to_pdf
|
||||||
from .serializers import GroupSerializer, UserFullSerializer, UserShortSerializer
|
from .serializers import GroupSerializer, UserCreateUpdateSerializer, UserFullSerializer, UserShortSerializer
|
||||||
|
|
||||||
|
|
||||||
class UserListView(ListView):
|
class UserListView(ListView):
|
||||||
@ -69,25 +69,14 @@ class UserCreateView(CreateView):
|
|||||||
url_name_args = []
|
url_name_args = []
|
||||||
|
|
||||||
def manipulate_object(self, form):
|
def manipulate_object(self, form):
|
||||||
self.object.username = gen_username(
|
self.object.username = User.objects.generate_username(
|
||||||
form.cleaned_data['first_name'], form.cleaned_data['last_name'])
|
form.cleaned_data['first_name'], form.cleaned_data['last_name'])
|
||||||
|
|
||||||
if not self.object.default_password:
|
if not self.object.default_password:
|
||||||
self.object.default_password = gen_password()
|
self.object.default_password = User.objects.generate_password()
|
||||||
|
|
||||||
self.object.set_password(self.object.default_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):
|
class UserMultipleCreateView(FormView):
|
||||||
"""
|
"""
|
||||||
@ -108,8 +97,8 @@ class UserMultipleCreateView(FormView):
|
|||||||
names_list = line.split()
|
names_list = line.split()
|
||||||
first_name = ' '.join(names_list[:-1])
|
first_name = ' '.join(names_list[:-1])
|
||||||
last_name = names_list[-1]
|
last_name = names_list[-1]
|
||||||
username = gen_username(first_name, last_name)
|
username = User.objects.generate_username(first_name, last_name)
|
||||||
default_password = gen_password()
|
default_password = User.objects.generate_password()
|
||||||
User.objects.create(
|
User.objects.create(
|
||||||
username=username,
|
username=username,
|
||||||
first_name=first_name,
|
first_name=first_name,
|
||||||
@ -136,17 +125,6 @@ class UserUpdateView(UpdateView):
|
|||||||
form_kwargs.update({'request': self.request})
|
form_kwargs.update({'request': self.request})
|
||||||
return form_kwargs
|
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):
|
class UserDeleteView(DeleteView):
|
||||||
"""
|
"""
|
||||||
@ -281,9 +259,12 @@ class UserViewSet(ModelViewSet):
|
|||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
"""
|
"""
|
||||||
Returns different serializer classes with respect to users permissions.
|
Returns different serializer classes with respect to action and user's
|
||||||
|
permissions.
|
||||||
"""
|
"""
|
||||||
if self.request.user.has_perm('users.can_see_extra_data'):
|
if self.action in ('create', 'update'):
|
||||||
|
serializer_class = UserCreateUpdateSerializer
|
||||||
|
elif self.request.user.has_perm('users.can_see_extra_data'):
|
||||||
serializer_class = UserFullSerializer
|
serializer_class = UserFullSerializer
|
||||||
else:
|
else:
|
||||||
serializer_class = UserShortSerializer
|
serializer_class = UserShortSerializer
|
||||||
|
@ -9,7 +9,8 @@ from rest_framework.serializers import ( # noqa
|
|||||||
ModelSerializer,
|
ModelSerializer,
|
||||||
PrimaryKeyRelatedField,
|
PrimaryKeyRelatedField,
|
||||||
RelatedField,
|
RelatedField,
|
||||||
SerializerMethodField)
|
SerializerMethodField,
|
||||||
|
ValidationError)
|
||||||
from rest_framework.response import Response # noqa
|
from rest_framework.response import Response # noqa
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from rest_framework.viewsets import ModelViewSet, ViewSet # noqa
|
from rest_framework.viewsets import ModelViewSet, ViewSet # noqa
|
||||||
|
0
tests/integration/users/__init__.py
Normal file
0
tests/integration/users/__init__.py
Normal file
89
tests/integration/users/test_viewset.py
Normal file
89
tests/integration/users/test_viewset.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from openslides.users.models import User
|
||||||
|
from openslides.utils.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreation(TestCase):
|
||||||
|
"""
|
||||||
|
Tests creation of users via REST API.
|
||||||
|
"""
|
||||||
|
def test_simple_creation(self):
|
||||||
|
self.client.login(username='admin', password='admin')
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse('user-list'),
|
||||||
|
{'last_name': 'Test name keimeiShieX4Aekoe3do'})
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
self.assertTrue(User.objects.filter(username='Test name keimeiShieX4Aekoe3do').exists())
|
||||||
|
|
||||||
|
def test_creation_with_group(self):
|
||||||
|
self.client.login(username='admin', password='admin')
|
||||||
|
|
||||||
|
self.client.post(
|
||||||
|
reverse('user-list'),
|
||||||
|
{'last_name': 'Test name aedah1iequoof0Ashed4',
|
||||||
|
'groups': ['3', '4']})
|
||||||
|
|
||||||
|
user = User.objects.get(username='Test name aedah1iequoof0Ashed4')
|
||||||
|
self.assertTrue(user.groups.filter(pk=3).exists())
|
||||||
|
self.assertTrue(user.groups.filter(pk=4).exists())
|
||||||
|
|
||||||
|
def test_creation_with_anonymous_or_registered_group(self):
|
||||||
|
self.client.login(username='admin', password='admin')
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse('user-list'),
|
||||||
|
{'last_name': 'Test name aedah1iequoof0Ashed4',
|
||||||
|
'groups': ['1', '2']})
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertEqual(response.data, {'groups': ["Invalid pk '1' - object does not exist."]})
|
||||||
|
|
||||||
|
|
||||||
|
class UserUpdate(TestCase):
|
||||||
|
"""
|
||||||
|
Tests update of users via REST API.
|
||||||
|
"""
|
||||||
|
def test_simple_update_via_patch(self):
|
||||||
|
admin_client = APIClient()
|
||||||
|
admin_client.login(username='admin', password='admin')
|
||||||
|
|
||||||
|
response = admin_client.patch(
|
||||||
|
reverse('user-detail', args=['1']),
|
||||||
|
{'last_name': 'New name tu3ooh5Iez5Aec2laefo'})
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
user = User.objects.get(pk=1)
|
||||||
|
self.assertEqual(user.last_name, 'New name tu3ooh5Iez5Aec2laefo')
|
||||||
|
self.assertEqual(user.username, 'admin')
|
||||||
|
|
||||||
|
def test_simple_update_via_put(self):
|
||||||
|
admin_client = APIClient()
|
||||||
|
admin_client.login(username='admin', password='admin')
|
||||||
|
|
||||||
|
response = admin_client.put(
|
||||||
|
reverse('user-detail', args=['1']),
|
||||||
|
{'last_name': 'New name Ohy4eeyei5Sahzah0Os2'})
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertEqual(response.data, {'username': ['This field is required.']})
|
||||||
|
|
||||||
|
|
||||||
|
class UserDelete(TestCase):
|
||||||
|
"""
|
||||||
|
Tests delete of users via REST API.
|
||||||
|
"""
|
||||||
|
def test_delete(self):
|
||||||
|
admin_client = APIClient()
|
||||||
|
admin_client.login(username='admin', password='admin')
|
||||||
|
User.objects.create(username='Test name bo3zieT3iefahng0ahqu')
|
||||||
|
self.assertTrue(User.objects.filter(username='Test name bo3zieT3iefahng0ahqu').exists())
|
||||||
|
|
||||||
|
response = admin_client.delete(reverse('user-detail', args=['2']))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
self.assertFalse(User.objects.filter(username='Test name bo3zieT3iefahng0ahqu').exists())
|
@ -246,7 +246,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
|||||||
self.assertMessage(response, 'You were successfully added to the list of speakers.')
|
self.assertMessage(response, 'You were successfully added to the list of speakers.')
|
||||||
|
|
||||||
perm = Permission.objects.filter(name='Can see agenda').get()
|
perm = Permission.objects.filter(name='Can see agenda').get()
|
||||||
self.speaker2.groups.get(name='Registered').permissions.remove(perm)
|
self.speaker2.groups.model.objects.get(name='Registered').permissions.remove(perm)
|
||||||
response = self.speaker2_client.get('/agenda/list_of_speakers/add/')
|
response = self.speaker2_client.get('/agenda/list_of_speakers/add/')
|
||||||
self.assertMessage(response, 'You were successfully added to the list of speakers.')
|
self.assertMessage(response, 'You were successfully added to the list of speakers.')
|
||||||
|
|
||||||
|
@ -208,7 +208,7 @@ class ViewTest(TestCase):
|
|||||||
orga_perm = Permission.objects.get(
|
orga_perm = Permission.objects.get(
|
||||||
content_type=ContentType.objects.get_for_model(Item),
|
content_type=ContentType.objects.get_for_model(Item),
|
||||||
codename='can_see_orga_items')
|
codename='can_see_orga_items')
|
||||||
user.groups.get(name='Registered').permissions.remove(orga_perm)
|
user.groups.model.objects.get(name='Registered').permissions.remove(orga_perm)
|
||||||
# Reload user
|
# Reload user
|
||||||
user = User.objects.get(username=user.username)
|
user = User.objects.get(username=user.username)
|
||||||
# Test view without permission
|
# Test view without permission
|
||||||
|
@ -1,172 +1,7 @@
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import patch, call, MagicMock
|
from unittest.mock import patch
|
||||||
|
|
||||||
from openslides.users.api import (
|
from openslides.users.api import get_protected_perm
|
||||||
gen_username,
|
|
||||||
gen_password,
|
|
||||||
get_registered_group,
|
|
||||||
create_or_reset_admin_user,
|
|
||||||
get_protected_perm)
|
|
||||||
|
|
||||||
|
|
||||||
@patch('openslides.users.api.User')
|
|
||||||
class UserGenUsername(TestCase):
|
|
||||||
"""
|
|
||||||
Tests for the function gen_username.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_clear_strings(self, mock_user):
|
|
||||||
mock_user.objects.filter().exists.return_value = False
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
gen_username('foo', 'bar'),
|
|
||||||
'foo bar')
|
|
||||||
|
|
||||||
def test_unstripped_strings(self, mock_user):
|
|
||||||
mock_user.objects.filter().exists.return_value = False
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
gen_username('foo ', ' bar\n'),
|
|
||||||
'foo bar',
|
|
||||||
"The retuned value should only have one whitespace between the names")
|
|
||||||
|
|
||||||
def test_empty_second_string(self, mock_user):
|
|
||||||
mock_user.objects.filter().exists.return_value = False
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
gen_username('foobar', ''),
|
|
||||||
'foobar',
|
|
||||||
"The returned value should not have whitespaces at the end")
|
|
||||||
|
|
||||||
def test_empty_first_string(self, mock_user):
|
|
||||||
mock_user.objects.filter().exists.return_value = False
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
gen_username('', 'foobar'),
|
|
||||||
'foobar',
|
|
||||||
"The returned value should not have whitespaces at the beginning")
|
|
||||||
|
|
||||||
def test_two_empty_strings(self, mock_user):
|
|
||||||
mock_user.objects.filter().exists.return_value = False
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError,
|
|
||||||
msg="A ValueError should be raised"):
|
|
||||||
gen_username('', '')
|
|
||||||
|
|
||||||
def test_used_username(self, mock_user):
|
|
||||||
mock_user.objects.filter().exists.side_effect = (True, False)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
gen_username('user', 'name'),
|
|
||||||
'user name 1',
|
|
||||||
"If the username already exist, a number should be added to the name")
|
|
||||||
|
|
||||||
def test_two_used_username(self, mock_user):
|
|
||||||
mock_user.objects.filter().exists.side_effect = (True, True, False)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
gen_username('user', 'name'),
|
|
||||||
'user name 2',
|
|
||||||
"If the username with a number already exist, a higher number should "
|
|
||||||
"be added to the name")
|
|
||||||
|
|
||||||
def test_umlauts(self, mock_user):
|
|
||||||
mock_user.objects.filter().exists.return_value = False
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
gen_username('äöü', 'ßüäö'),
|
|
||||||
'äöü ßüäö',
|
|
||||||
"gen_username has also to work with umlauts")
|
|
||||||
|
|
||||||
|
|
||||||
@patch('openslides.users.api.choice')
|
|
||||||
class GenPassword(TestCase):
|
|
||||||
def test_normal(self, mock_choice):
|
|
||||||
"""
|
|
||||||
Test normal run of the function
|
|
||||||
"""
|
|
||||||
mock_choice.side_effect = tuple('test_password')
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
gen_password(),
|
|
||||||
'test_pas')
|
|
||||||
# choice has to be called 8 times
|
|
||||||
mock_choice.assert_has_calls(
|
|
||||||
[call("abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789")
|
|
||||||
for _ in range(8)])
|
|
||||||
|
|
||||||
|
|
||||||
@patch('openslides.users.api.Group')
|
|
||||||
class GetRegisteredGroup(TestCase):
|
|
||||||
def test_normal(self, mock_group):
|
|
||||||
mock_group.objects.get.return_value = 'test_group'
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
get_registered_group(),
|
|
||||||
'test_group')
|
|
||||||
mock_group.objects.getassert_called_once_with(pk=2)
|
|
||||||
|
|
||||||
|
|
||||||
@patch('openslides.users.api.Group')
|
|
||||||
@patch('openslides.users.api.User')
|
|
||||||
class CreateOrResetAdminUser(TestCase):
|
|
||||||
def test_get_admin_group(self, mock_user, mock_group):
|
|
||||||
"""
|
|
||||||
Tests, that the Group with pk4 is added to the admin
|
|
||||||
"""
|
|
||||||
admin_user = MagicMock(name='admin_user')
|
|
||||||
mock_user.objects.get.return_value = admin_user
|
|
||||||
mock_group.objects.get.return_value = 'admin_group'
|
|
||||||
|
|
||||||
create_or_reset_admin_user()
|
|
||||||
|
|
||||||
mock_group.objects.get.assert_called_once_with(pk=4)
|
|
||||||
admin_user.groups.add.assert_called_once_with('admin_group')
|
|
||||||
|
|
||||||
def test_password_set_to_admin(self, mock_user, mock_group):
|
|
||||||
"""
|
|
||||||
Tests, that the password of the admin is set to 'admin'.
|
|
||||||
"""
|
|
||||||
admin_user = MagicMock(name='admin_user')
|
|
||||||
mock_user.objects.get.return_value = admin_user
|
|
||||||
|
|
||||||
create_or_reset_admin_user()
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
admin_user.default_password,
|
|
||||||
'admin')
|
|
||||||
admin_user.set_password.assert_called_once_with('admin')
|
|
||||||
admin_user.save.assert_called_once_with()
|
|
||||||
|
|
||||||
def test_return_value(self, mock_user, mock_group):
|
|
||||||
"""
|
|
||||||
Test, that the function retruns True, when a user is created.
|
|
||||||
"""
|
|
||||||
mock_user.DoesNotExist = Exception
|
|
||||||
mock_user.objects.get.side_effect = Exception
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
create_or_reset_admin_user(),
|
|
||||||
True,
|
|
||||||
"create_or_reset_admin_user should return True when a new user is "
|
|
||||||
" created")
|
|
||||||
|
|
||||||
def test_attributes_of_created_user(self, mock_user, mock_group):
|
|
||||||
admin_user = MagicMock(name='admin_user')
|
|
||||||
mock_user.return_value = admin_user
|
|
||||||
mock_user.DoesNotExist = Exception
|
|
||||||
mock_user.objects.get.side_effect = Exception
|
|
||||||
|
|
||||||
create_or_reset_admin_user()
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
admin_user.username,
|
|
||||||
'admin',
|
|
||||||
"The username of a new created admin should be 'admin'")
|
|
||||||
self.assertEqual(
|
|
||||||
admin_user.last_name,
|
|
||||||
'Administrator',
|
|
||||||
"The last_name of a new created admin should be 'Administrator'")
|
|
||||||
|
|
||||||
|
|
||||||
@patch('openslides.users.api.Permission')
|
@patch('openslides.users.api.Permission')
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, call, patch
|
||||||
|
|
||||||
from openslides.users.models import User, UserManager
|
from openslides.users.models import User, UserManager
|
||||||
|
|
||||||
@ -11,12 +11,12 @@ class UserTest(TestCase):
|
|||||||
User.get_full_name().
|
User.get_full_name().
|
||||||
"""
|
"""
|
||||||
user = User()
|
user = User()
|
||||||
user.get_full_name = MagicMock(return_value='Test Value')
|
user.get_full_name = MagicMock(return_value='Test Value IJee1yoet1ooGhesh5li')
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
str(user),
|
str(user),
|
||||||
'Test Value',
|
'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):
|
def test_get_slide_context(self):
|
||||||
"""
|
"""
|
||||||
@ -29,7 +29,7 @@ class UserTest(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
user.get_slide_context(),
|
user.get_slide_context(),
|
||||||
{'shown_user': user},
|
{'shown_user': user},
|
||||||
"User.get_slide_context returns a wrong context")
|
"User.get_slide_context returns a wrong context.")
|
||||||
|
|
||||||
|
|
||||||
class UserGetAbsoluteUrlTest(TestCase):
|
class UserGetAbsoluteUrlTest(TestCase):
|
||||||
@ -37,7 +37,7 @@ class UserGetAbsoluteUrlTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
Tests get_absolute_url() with no argument.
|
Tests get_absolute_url() with no argument.
|
||||||
|
|
||||||
It should return the url for the url-pattern of user_detail
|
It should return the url for the url-pattern of user_detail.
|
||||||
"""
|
"""
|
||||||
user = User(pk=5)
|
user = User(pk=5)
|
||||||
|
|
||||||
@ -48,12 +48,12 @@ class UserGetAbsoluteUrlTest(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
url,
|
url,
|
||||||
'test url',
|
'test url',
|
||||||
"User.get_absolute_url() does not return the result of reverse")
|
"User.get_absolute_url() does not return the result of reverse.")
|
||||||
mock_reverse.assert_called_once_with('user_detail', args=['5'])
|
mock_reverse.assert_called_once_with('user_detail', args=['5'])
|
||||||
|
|
||||||
def test_get_absolute_url_detail(self):
|
def test_get_absolute_url_detail(self):
|
||||||
"""
|
"""
|
||||||
Tests get_absolute_url() with 'detail' as argument
|
Tests get_absolute_url() with 'detail' as argument.
|
||||||
"""
|
"""
|
||||||
user = User(pk=5)
|
user = User(pk=5)
|
||||||
|
|
||||||
@ -64,12 +64,12 @@ class UserGetAbsoluteUrlTest(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
url,
|
url,
|
||||||
'test url',
|
'test url',
|
||||||
"User.get_absolute_url('detail') does not return the result of reverse")
|
"User.get_absolute_url('detail') does not return the result of reverse.")
|
||||||
mock_reverse.assert_called_once_with('user_detail', args=['5'])
|
mock_reverse.assert_called_once_with('user_detail', args=['5'])
|
||||||
|
|
||||||
def test_get_absolute_url_update(self):
|
def test_get_absolute_url_update(self):
|
||||||
"""
|
"""
|
||||||
Tests get_absolute_url() with 'update' as argument
|
Tests get_absolute_url() with 'update' as argument.
|
||||||
"""
|
"""
|
||||||
user = User(pk=5)
|
user = User(pk=5)
|
||||||
|
|
||||||
@ -80,12 +80,12 @@ class UserGetAbsoluteUrlTest(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
url,
|
url,
|
||||||
'test url',
|
'test url',
|
||||||
"User.get_absolute_url('update') does not return the result of reverse")
|
"User.get_absolute_url('update') does not return the result of reverse.")
|
||||||
mock_reverse.assert_called_once_with('user_update', args=['5'])
|
mock_reverse.assert_called_once_with('user_update', args=['5'])
|
||||||
|
|
||||||
def test_get_absolute_url_delete(self):
|
def test_get_absolute_url_delete(self):
|
||||||
"""
|
"""
|
||||||
Tests get_absolute_url() with 'delete' as argument
|
Tests get_absolute_url() with 'delete' as argument.
|
||||||
"""
|
"""
|
||||||
user = User(pk=5)
|
user = User(pk=5)
|
||||||
|
|
||||||
@ -96,12 +96,12 @@ class UserGetAbsoluteUrlTest(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
url,
|
url,
|
||||||
'test url',
|
'test url',
|
||||||
"User.get_absolute_url('delete') does not return the result of reverse")
|
"User.get_absolute_url('delete') does not return the result of reverse.")
|
||||||
mock_reverse.assert_called_once_with('user_delete', args=['5'])
|
mock_reverse.assert_called_once_with('user_delete', args=['5'])
|
||||||
|
|
||||||
def test_get_absolute_url_other(self):
|
def test_get_absolute_url_other(self):
|
||||||
"""
|
"""
|
||||||
Tests get_absolute_url() with any other argument
|
Tests get_absolute_url() with any other argument.
|
||||||
"""
|
"""
|
||||||
user = User(pk=5)
|
user = User(pk=5)
|
||||||
dummy_argument = MagicMock()
|
dummy_argument = MagicMock()
|
||||||
@ -113,14 +113,14 @@ class UserGetAbsoluteUrlTest(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
url,
|
url,
|
||||||
'test url',
|
'test url',
|
||||||
"User.get_absolute_url(OTHER) does not return the result of reverse")
|
"User.get_absolute_url(OTHER) does not return the result of reverse.")
|
||||||
mock_super().get_absolute_url.assert_called_once_with(dummy_argument)
|
mock_super().get_absolute_url.assert_called_once_with(dummy_argument)
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
Tests, that get_full_name returns the write string.
|
Tests that get_full_name returns the write string.
|
||||||
"""
|
"""
|
||||||
user = User()
|
user = User()
|
||||||
user.title = 'test_title'
|
user.title = 'test_title'
|
||||||
@ -130,11 +130,12 @@ class UserGetFullName(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
user.get_full_name(),
|
user.get_full_name(),
|
||||||
'test_title test_short_name (test_structure_level)',
|
'test_title test_short_name (test_structure_level)',
|
||||||
"User.get_full_name() returns wrong string when it has a structure_level and title")
|
"User.get_full_name() returns wrong string when it has a "
|
||||||
|
"structure_level and title.")
|
||||||
|
|
||||||
def test_get_full_name_without_structure_level_and_with_title(self):
|
def test_get_full_name_without_structure_level_and_with_title(self):
|
||||||
"""
|
"""
|
||||||
Tests, that get_full_name returns the write string.
|
Tests that get_full_name returns the write string.
|
||||||
"""
|
"""
|
||||||
user = User()
|
user = User()
|
||||||
user.title = 'test_title'
|
user.title = 'test_title'
|
||||||
@ -144,11 +145,12 @@ class UserGetFullName(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
user.get_full_name(),
|
user.get_full_name(),
|
||||||
'test_title test_short_name',
|
'test_title test_short_name',
|
||||||
"User.get_full_name() returns wrong string when it has no structure_level but a title")
|
"User.get_full_name() returns wrong string when it has no "
|
||||||
|
"structure_level but a title.")
|
||||||
|
|
||||||
def test_get_full_name_without_structure_level_and_without_title(self):
|
def test_get_full_name_without_structure_level_and_without_title(self):
|
||||||
"""
|
"""
|
||||||
Tests, that get_full_name returns the write string.
|
Tests that get_full_name returns the write string.
|
||||||
"""
|
"""
|
||||||
user = User()
|
user = User()
|
||||||
user.title = ''
|
user.title = ''
|
||||||
@ -158,7 +160,8 @@ class UserGetFullName(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
user.get_full_name(),
|
user.get_full_name(),
|
||||||
'test_short_name',
|
'test_short_name',
|
||||||
"User.get_full_name() returns wrong string when it has no structure_level and no title")
|
"User.get_full_name() returns wrong string when it has no "
|
||||||
|
"structure_level and no title.")
|
||||||
|
|
||||||
|
|
||||||
class UserGetShortName(TestCase):
|
class UserGetShortName(TestCase):
|
||||||
@ -177,7 +180,7 @@ class UserGetShortName(TestCase):
|
|||||||
short_name,
|
short_name,
|
||||||
'test_first_name',
|
'test_first_name',
|
||||||
"User.get_short_name() returns wrong string when it has only a "
|
"User.get_short_name() returns wrong string when it has only a "
|
||||||
"first_name and is sorted by first_name")
|
"first_name and is sorted by first_name.")
|
||||||
|
|
||||||
def test_get_short_name_sort_first_name_both_names(self):
|
def test_get_short_name_sort_first_name_both_names(self):
|
||||||
"""
|
"""
|
||||||
@ -195,7 +198,7 @@ class UserGetShortName(TestCase):
|
|||||||
short_name,
|
short_name,
|
||||||
'test_first_name test_last_name',
|
'test_first_name test_last_name',
|
||||||
"User.get_short_name() returns wrong string when it has a fist_name "
|
"User.get_short_name() returns wrong string when it has a fist_name "
|
||||||
"and a last_name and is sorted by first_name")
|
"and a last_name and is sorted by first_name.")
|
||||||
|
|
||||||
def test_get_short_name_sort_last_name_only_first_name(self):
|
def test_get_short_name_sort_last_name_only_first_name(self):
|
||||||
"""
|
"""
|
||||||
@ -212,7 +215,7 @@ class UserGetShortName(TestCase):
|
|||||||
short_name,
|
short_name,
|
||||||
'test_first_name',
|
'test_first_name',
|
||||||
"User.get_short_name() returns wrong string when it has only a "
|
"User.get_short_name() returns wrong string when it has only a "
|
||||||
"first_name and is sorted by last_name")
|
"first_name and is sorted by last_name.")
|
||||||
|
|
||||||
def test_get_short_name_sort_last_name_both_names(self):
|
def test_get_short_name_sort_last_name_both_names(self):
|
||||||
"""
|
"""
|
||||||
@ -230,7 +233,7 @@ class UserGetShortName(TestCase):
|
|||||||
short_name,
|
short_name,
|
||||||
'test_last_name, test_first_name',
|
'test_last_name, test_first_name',
|
||||||
"User.get_short_name() returns wrong string when it has a fist_name "
|
"User.get_short_name() returns wrong string when it has a fist_name "
|
||||||
"and a last_name and is sorted by last_name")
|
"and a last_name and is sorted by last_name.")
|
||||||
|
|
||||||
def test_get_short_name_no_names(self):
|
def test_get_short_name_no_names(self):
|
||||||
"""
|
"""
|
||||||
@ -246,7 +249,7 @@ class UserGetShortName(TestCase):
|
|||||||
short_name,
|
short_name,
|
||||||
'test_username',
|
'test_username',
|
||||||
"User.get_short_name() returns wrong string when it has no fist_name "
|
"User.get_short_name() returns wrong string when it has no fist_name "
|
||||||
"and no last_name and is sorted by last_name")
|
"and no last_name and is sorted by last_name.")
|
||||||
|
|
||||||
def test_while_spaces_in_name_parts(self):
|
def test_while_spaces_in_name_parts(self):
|
||||||
"""
|
"""
|
||||||
@ -264,7 +267,7 @@ class UserGetShortName(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
short_name,
|
short_name,
|
||||||
'test_first_name test_last_name',
|
'test_first_name test_last_name',
|
||||||
"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):
|
class UserResetPassword(TestCase):
|
||||||
@ -294,7 +297,7 @@ class UserResetPassword(TestCase):
|
|||||||
class UserManagerTest(TestCase):
|
class UserManagerTest(TestCase):
|
||||||
def test_create_user(self):
|
def test_create_user(self):
|
||||||
"""
|
"""
|
||||||
Tests, that create_user saves a new user with a set password.
|
Tests that create_user saves a new user with a set password.
|
||||||
"""
|
"""
|
||||||
user = MagicMock()
|
user = MagicMock()
|
||||||
user_manager = UserManager()
|
user_manager = UserManager()
|
||||||
@ -309,4 +312,175 @@ class UserManagerTest(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
return_user,
|
return_user,
|
||||||
user,
|
user,
|
||||||
"The returned user is not the created user")
|
"The returned user is not the created user.")
|
||||||
|
|
||||||
|
|
||||||
|
class UserManagerGenerateUsername(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for the manager method generate_username.
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
self.exists_mock = MagicMock()
|
||||||
|
self.filter_mock = MagicMock(return_value=self.exists_mock)
|
||||||
|
self.manager = UserManager()
|
||||||
|
self.manager.filter = self.filter_mock
|
||||||
|
|
||||||
|
def test_clear_strings(self):
|
||||||
|
self.exists_mock.exists.return_value = False
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.manager.generate_username('wiaf9eecu9mooJiZ3Lah', 'ieHaVe9ci7mooPhe0AuY'),
|
||||||
|
'wiaf9eecu9mooJiZ3Lah ieHaVe9ci7mooPhe0AuY')
|
||||||
|
|
||||||
|
def test_unstripped_strings(self):
|
||||||
|
self.exists_mock.exists.return_value = False
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.manager.generate_username('ouYeuwai0pheukeeShah ', ' Waefa8gahj8ohRaeroca\n'),
|
||||||
|
'ouYeuwai0pheukeeShah Waefa8gahj8ohRaeroca',
|
||||||
|
"The returned value should only have one whitespace between the "
|
||||||
|
"names.")
|
||||||
|
|
||||||
|
def test_empty_second_string(self):
|
||||||
|
self.exists_mock.exists.return_value = False
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.manager.generate_username('foobar', ''),
|
||||||
|
'foobar',
|
||||||
|
"The returned value should not have whitespaces at the end.")
|
||||||
|
|
||||||
|
def test_empty_first_string(self):
|
||||||
|
self.exists_mock.exists.return_value = False
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.manager.generate_username('', 'foobar'),
|
||||||
|
'foobar',
|
||||||
|
"The returned value should not have whitespaces at the beginning.")
|
||||||
|
|
||||||
|
def test_two_empty_strings(self):
|
||||||
|
self.exists_mock.exists.return_value = False
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError,
|
||||||
|
msg="A ValueError should be raised."):
|
||||||
|
self.manager.generate_username('', '')
|
||||||
|
|
||||||
|
def test_used_username(self):
|
||||||
|
self.exists_mock.exists.side_effect = (True, False)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.manager.generate_username('user', 'name'),
|
||||||
|
'user name 1',
|
||||||
|
"If the username already exists, a number should be added to the "
|
||||||
|
"name.")
|
||||||
|
|
||||||
|
def test_two_used_username(self):
|
||||||
|
self.exists_mock.exists.side_effect = (True, True, False)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.manager.generate_username('user', 'name'),
|
||||||
|
'user name 2',
|
||||||
|
"If the username with a number already exists, a higher number "
|
||||||
|
"should be added to the name.")
|
||||||
|
|
||||||
|
def test_umlauts(self):
|
||||||
|
self.exists_mock.exists.return_value = False
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.manager.generate_username('äöü', 'ßüäö'),
|
||||||
|
'äöü ßüäö',
|
||||||
|
"The method gen_username has also to work with umlauts.")
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openslides.users.models.choice')
|
||||||
|
class UserManagerGeneratePassword(TestCase):
|
||||||
|
def test_normal(self, mock_choice):
|
||||||
|
"""
|
||||||
|
Test normal run of the method.
|
||||||
|
"""
|
||||||
|
mock_choice.side_effect = tuple('test_password')
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
UserManager().generate_password(),
|
||||||
|
'test_pas')
|
||||||
|
# choice has to be called 8 times
|
||||||
|
mock_choice.assert_has_calls(
|
||||||
|
[call("abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789")
|
||||||
|
for _ in range(8)])
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openslides.users.models.Group')
|
||||||
|
class UserManagerCreateOrResetAdminUser(TestCase):
|
||||||
|
def test_get_admin_group(self, mock_group):
|
||||||
|
"""
|
||||||
|
Tests that the Group with pk=4 is added to the admin.
|
||||||
|
"""
|
||||||
|
def mock_side_effect(pk):
|
||||||
|
if pk == 2:
|
||||||
|
result = 'mock_registered'
|
||||||
|
elif pk == 4:
|
||||||
|
result = 'mock_staff'
|
||||||
|
else:
|
||||||
|
result = ''
|
||||||
|
return result
|
||||||
|
|
||||||
|
admin_user = MagicMock()
|
||||||
|
manager = UserManager()
|
||||||
|
manager.get_or_create = MagicMock(return_value=(admin_user, False))
|
||||||
|
mock_group.objects.get.side_effect = mock_side_effect
|
||||||
|
|
||||||
|
manager.create_or_reset_admin_user()
|
||||||
|
|
||||||
|
mock_group.objects.get.assert_called_once(pk=2)
|
||||||
|
admin_user.groups.add.assert_called_once('mock_staff')
|
||||||
|
|
||||||
|
def test_password_set_to_admin(self, mock_group):
|
||||||
|
"""
|
||||||
|
Tests that the password of the admin is set to 'admin'.
|
||||||
|
"""
|
||||||
|
admin_user = MagicMock()
|
||||||
|
manager = UserManager()
|
||||||
|
manager.get_or_create = MagicMock(return_value=(admin_user, False))
|
||||||
|
|
||||||
|
manager.create_or_reset_admin_user()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
admin_user.default_password,
|
||||||
|
'admin')
|
||||||
|
admin_user.save.assert_called_once_with()
|
||||||
|
|
||||||
|
@patch('openslides.users.models.User')
|
||||||
|
def test_return_value(self, mock_user, mock_group):
|
||||||
|
"""
|
||||||
|
Tests that the method returns True when a user is created.
|
||||||
|
"""
|
||||||
|
admin_user = MagicMock()
|
||||||
|
manager = UserManager()
|
||||||
|
manager.get_or_create = MagicMock(return_value=(admin_user, True))
|
||||||
|
manager.model = mock_user
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
manager.create_or_reset_admin_user(),
|
||||||
|
True,
|
||||||
|
"The method create_or_reset_admin_user should return True when a "
|
||||||
|
"new user is created.")
|
||||||
|
|
||||||
|
@patch('openslides.users.models.User')
|
||||||
|
def test_attributes_of_created_user(self, mock_user, mock_group):
|
||||||
|
"""
|
||||||
|
Tests username and last_name of the created admin user.
|
||||||
|
"""
|
||||||
|
admin_user = MagicMock(username='admin', last_name='Administrator')
|
||||||
|
manager = UserManager()
|
||||||
|
manager.get_or_create = MagicMock(return_value=(admin_user, True))
|
||||||
|
manager.model = mock_user
|
||||||
|
|
||||||
|
manager.create_or_reset_admin_user()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
admin_user.username,
|
||||||
|
'admin',
|
||||||
|
"The username of a new created admin should be 'admin'.")
|
||||||
|
self.assertEqual(
|
||||||
|
admin_user.last_name,
|
||||||
|
'Administrator',
|
||||||
|
"The last_name of a new created admin should be 'Administrator'.")
|
||||||
|
83
tests/unit/users/test_serializers.py
Normal file
83
tests/unit/users/test_serializers.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
from rest_framework.test import APIRequestFactory
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreateUpdateSerializer(TestCase):
|
||||||
|
def test_improperly_configured_exception_list_request(self):
|
||||||
|
"""
|
||||||
|
Tests that ImproperlyConfigured is raised if one tries to use the
|
||||||
|
UserCreateUpdateSerializer with a list request.
|
||||||
|
"""
|
||||||
|
# Global import is not possible for some unknown magic.
|
||||||
|
from openslides.users.serializers import UserCreateUpdateSerializer
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
request = factory.get('/')
|
||||||
|
view_class = ModelViewSet
|
||||||
|
view_class.queryset = MagicMock()
|
||||||
|
view_class.serializer_class = UserCreateUpdateSerializer
|
||||||
|
view = view_class.as_view({'get': 'list'})
|
||||||
|
|
||||||
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
|
view(request)
|
||||||
|
|
||||||
|
@patch('rest_framework.generics.get_object_or_404')
|
||||||
|
def test_improperly_configured_exception_retrieve_request(self, mock_get_object_or_404):
|
||||||
|
"""
|
||||||
|
Tests that ImproperlyConfigured is raised if one tries to use the
|
||||||
|
UserCreateUpdateSerializer with a retrieve request.
|
||||||
|
"""
|
||||||
|
# Global import is not possible for some unknown magic.
|
||||||
|
from openslides.users.serializers import UserCreateUpdateSerializer
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
request = factory.get('/')
|
||||||
|
view_class = ModelViewSet
|
||||||
|
view_class.queryset = MagicMock()
|
||||||
|
view_class.serializer_class = UserCreateUpdateSerializer
|
||||||
|
view = view_class.as_view({'get': 'retrieve'})
|
||||||
|
mock_get_object_or_404.return_value = MagicMock()
|
||||||
|
|
||||||
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
|
view(request, pk='1')
|
||||||
|
|
||||||
|
def test_no_improperly_configured_exception_create_request(self):
|
||||||
|
"""
|
||||||
|
Tests that ImproperlyConfigured is not raised if one tries to use the
|
||||||
|
UserCreateUpdateSerializer with a create request.
|
||||||
|
"""
|
||||||
|
# Global import is not possible for some unknown magic.
|
||||||
|
from openslides.users.serializers import UserCreateUpdateSerializer
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
request = factory.get('/')
|
||||||
|
view_class = ModelViewSet
|
||||||
|
view_class.queryset = MagicMock()
|
||||||
|
view_class.serializer_class = UserCreateUpdateSerializer
|
||||||
|
view = view_class.as_view({'get': 'create'})
|
||||||
|
|
||||||
|
response = view(request)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@patch('rest_framework.generics.get_object_or_404')
|
||||||
|
def test_no_improperly_configured_exception_update_request(self, mock_get_object_or_404):
|
||||||
|
"""
|
||||||
|
Tests that ImproperlyConfigured is not raised if one tries to use the
|
||||||
|
UserCreateUpdateSerializer with a update request.
|
||||||
|
"""
|
||||||
|
# Global import is not possible for some unknown magic.
|
||||||
|
from openslides.users.serializers import UserCreateUpdateSerializer
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
request = factory.get('/')
|
||||||
|
view_class = ModelViewSet
|
||||||
|
view_class.queryset = MagicMock()
|
||||||
|
view_class.serializer_class = UserCreateUpdateSerializer
|
||||||
|
view = view_class.as_view({'get': 'update'})
|
||||||
|
mock_get_object_or_404.return_value = MagicMock()
|
||||||
|
|
||||||
|
response = view(request, pk='1')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
Loading…
Reference in New Issue
Block a user