Performance improvements
* Add caching support to users/group * Add a function has_perm that works with the cache. * Removed our session backend so other session backends (without the database) can be used
This commit is contained in:
parent
fb24ca0aba
commit
728576d514
@ -33,6 +33,9 @@ Core:
|
||||
backend.
|
||||
- Use a separate dialog with CKEditor for editing projector messages.
|
||||
- Use CKEditor in settings for text markup.
|
||||
- Add a version of has_perm that can work with cached users.
|
||||
- Cache the group with there permissions.
|
||||
- Removed our db-session backend and added possibility to use any django session backend.
|
||||
|
||||
Motions:
|
||||
- Added adjustable line numbering mode (outside, inside, none) for each
|
||||
|
@ -122,7 +122,7 @@ TODO: Configure postgresql
|
||||
|
||||
2. Install python dependencies
|
||||
|
||||
pip install django-redis asgi-redis psycopg2
|
||||
pip install django-redis asgi-redis django-redis-sessions psycopg2
|
||||
|
||||
3. Change settings.py
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
from ..utils.access_permissions import BaseAccessPermissions
|
||||
from ..utils.auth import has_perm
|
||||
|
||||
|
||||
class ItemAccessPermissions(BaseAccessPermissions):
|
||||
@ -9,7 +10,7 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('agenda.can_see')
|
||||
return has_perm(user, 'agenda.can_see')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -26,9 +27,9 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
||||
Returns the restricted serialized data for the instance prepared
|
||||
for the user.
|
||||
"""
|
||||
if (user.has_perm('agenda.can_see') and
|
||||
if (has_perm(user, 'agenda.can_see') and
|
||||
(not full_data['is_hidden'] or
|
||||
user.has_perm('agenda.can_see_hidden_items'))):
|
||||
has_perm(user, 'agenda.can_see_hidden_items'))):
|
||||
data = full_data
|
||||
else:
|
||||
data = None
|
||||
|
@ -1,7 +1,6 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models, transaction
|
||||
@ -15,6 +14,7 @@ from openslides.utils.exceptions import OpenSlidesError
|
||||
from openslides.utils.models import RESTModelMixin
|
||||
from openslides.utils.utils import to_roman
|
||||
|
||||
from ..utils.auth import DjangoAnonymousUser
|
||||
from .access_permissions import ItemAccessPermissions
|
||||
|
||||
|
||||
@ -340,7 +340,7 @@ class SpeakerManager(models.Manager):
|
||||
if self.filter(user=user, item=item, begin_time=None).exists():
|
||||
raise OpenSlidesError(
|
||||
_('{user} is already on the list of speakers.').format(user=user))
|
||||
if isinstance(user, AnonymousUser):
|
||||
if isinstance(user, DjangoAnonymousUser):
|
||||
raise OpenSlidesError(
|
||||
_('An anonymous user can not be on lists of speakers.'))
|
||||
weight = (self.filter(item=item).aggregate(
|
||||
|
@ -1,4 +1,5 @@
|
||||
from ..utils.access_permissions import BaseAccessPermissions
|
||||
from ..utils.auth import has_perm
|
||||
|
||||
|
||||
class AssignmentAccessPermissions(BaseAccessPermissions):
|
||||
@ -9,7 +10,7 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('assignments.can_see')
|
||||
return has_perm(user, 'assignments.can_see')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -17,7 +18,7 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
from .serializers import AssignmentFullSerializer, AssignmentShortSerializer
|
||||
|
||||
if user is None or (user.has_perm('assignments.can_see') and user.has_perm('assignments.can_manage')):
|
||||
if user is None or (has_perm(user, 'assignments.can_see') and has_perm(user, 'assignments.can_manage')):
|
||||
serializer_class = AssignmentFullSerializer
|
||||
else:
|
||||
serializer_class = AssignmentShortSerializer
|
||||
@ -29,9 +30,9 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
|
||||
for the user. Removes unpublished polls for non admins so that they
|
||||
only get a result like the AssignmentShortSerializer would give them.
|
||||
"""
|
||||
if user.has_perm('assignments.can_see') and user.has_perm('assignments.can_manage'):
|
||||
if has_perm(user, 'assignments.can_see') and has_perm(user, 'assignments.can_manage'):
|
||||
data = full_data
|
||||
elif user.has_perm('assignments.can_see'):
|
||||
elif has_perm(user, 'assignments.can_see'):
|
||||
data = full_data.copy()
|
||||
data['polls'] = [poll for poll in data['polls'] if poll['published']]
|
||||
else:
|
||||
|
@ -81,7 +81,8 @@ class AssignmentManager(models.Manager):
|
||||
return self.get_queryset().prefetch_related(
|
||||
'related_users',
|
||||
'agenda_items',
|
||||
'polls')
|
||||
'polls',
|
||||
'tags')
|
||||
|
||||
|
||||
class Assignment(RESTModelMixin, models.Model):
|
||||
|
@ -1,4 +1,5 @@
|
||||
from ..utils.access_permissions import BaseAccessPermissions
|
||||
from ..utils.auth import DjangoAnonymousUser, has_perm
|
||||
|
||||
|
||||
class ProjectorAccessPermissions(BaseAccessPermissions):
|
||||
@ -9,7 +10,7 @@ class ProjectorAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('core.can_see_projector')
|
||||
return has_perm(user, 'core.can_see_projector')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -32,7 +33,7 @@ class TagAccessPermissions(BaseAccessPermissions):
|
||||
|
||||
# Every authenticated user can retrieve tags. Anonymous users can do
|
||||
# so if they are enabled.
|
||||
return user.is_authenticated() or config['general_system_enable_anonymous']
|
||||
return not isinstance(user, DjangoAnonymousUser) or config['general_system_enable_anonymous']
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -53,7 +54,7 @@ class ChatMessageAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
# Anonymous users can see the chat if the anonymous group has the
|
||||
# permission core.can_use_chat. But they can not use it. See views.py.
|
||||
return user.has_perm('core.can_use_chat')
|
||||
return has_perm(user, 'core.can_use_chat')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -72,7 +73,7 @@ class ProjectorMessageAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('core.can_see_projector')
|
||||
return has_perm(user, 'core.can_see_projector')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -91,7 +92,7 @@ class CountdownAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('core.can_see_projector')
|
||||
return has_perm(user, 'core.can_see_projector')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -115,7 +116,7 @@ class ConfigAccessPermissions(BaseAccessPermissions):
|
||||
|
||||
# Every authenticated user can see the metadata and list or retrieve
|
||||
# the config. Anonymous users can do so if they are enabled.
|
||||
return user.is_authenticated() or config['general_system_enable_anonymous']
|
||||
return not isinstance(user, DjangoAnonymousUser) or config['general_system_enable_anonymous']
|
||||
|
||||
def get_full_data(self, instance):
|
||||
"""
|
||||
|
32
openslides/core/migrations/0003_auto_20161217_1158.py
Normal file
32
openslides/core/migrations/0003_auto_20161217_1158.py
Normal file
@ -0,0 +1,32 @@
|
||||
# Generated by Django 1.10.4 on 2016-12-17 10:58
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def remove_session_content_type(apps, schema_editor):
|
||||
"""
|
||||
Remove session content_type because we want to delete the session model in
|
||||
the next step.
|
||||
"""
|
||||
# We get the model from the versioned app registry;
|
||||
# if we directly import it, it will be the wrong version.
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
Session = apps.get_model('core', 'Session')
|
||||
ContentType.objects.get_for_model(Session).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0002_misc_features'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
remove_session_content_type
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Session',
|
||||
),
|
||||
]
|
@ -1,5 +1,4 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.sessions.models import Session as DjangoSession
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
from jsonfield import JSONField
|
||||
@ -340,22 +339,3 @@ class Countdown(RESTModelMixin, models.Model):
|
||||
self.running = False
|
||||
self.countdown_time = self.default_time
|
||||
self.save()
|
||||
|
||||
|
||||
class Session(DjangoSession):
|
||||
"""
|
||||
Model like the Django db session, which saves the user as ForeignKey instead
|
||||
of an encoded value.
|
||||
"""
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
null=True)
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
||||
@classmethod
|
||||
def get_session_store_class(cls):
|
||||
from .session_backend import SessionStore
|
||||
return SessionStore
|
||||
|
@ -1,23 +0,0 @@
|
||||
from django.contrib.sessions.backends.db import \
|
||||
SessionStore as DjangoSessionStore
|
||||
|
||||
|
||||
class SessionStore(DjangoSessionStore):
|
||||
"""
|
||||
Like the Django db Session store, but saves the user into the db field.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_model_class(cls):
|
||||
# Avoids a circular import
|
||||
from .models import Session
|
||||
return Session
|
||||
|
||||
def create_model_instance(self, data):
|
||||
"""
|
||||
Set the user from data to the db field. Set to None, if its a session
|
||||
from an anonymous user.
|
||||
"""
|
||||
model = super().create_model_instance(data)
|
||||
model.user_id = data['_auth_user_id'] if '_auth_user_id' in data else None
|
||||
return model
|
@ -571,7 +571,7 @@ class TagViewSet(ModelViewSet):
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action == 'metadata':
|
||||
# Every authenticated user can see the metadata and list tags.
|
||||
# Every authenticated user can see the metadata.
|
||||
# Anonymous users can do so if they are enabled.
|
||||
result = self.request.user.is_authenticated() or config['general_system_enable_anonymous']
|
||||
elif self.action in ('create', 'update', 'destroy'):
|
||||
|
@ -94,11 +94,9 @@ STATICFILES_DIRS = [
|
||||
AUTH_USER_MODEL = 'users.User'
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'openslides.users.auth.CustomizedModelBackend',
|
||||
'openslides.utils.auth.CustomizedModelBackend',
|
||||
]
|
||||
|
||||
SESSION_ENGINE = 'openslides.core.session_backend'
|
||||
|
||||
SESSION_COOKIE_NAME = 'OpenSlidesSessionID'
|
||||
|
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
||||
@ -140,7 +138,7 @@ CACHES = {
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'openslides.users.auth.RESTFrameworkAnonymousAuthentication',
|
||||
'openslides.utils.auth.RESTFrameworkAnonymousAuthentication',
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
from ..utils.access_permissions import BaseAccessPermissions
|
||||
from ..utils.auth import has_perm
|
||||
|
||||
|
||||
class MediafileAccessPermissions(BaseAccessPermissions):
|
||||
@ -9,7 +10,7 @@ class MediafileAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('mediafiles.can_see')
|
||||
return has_perm(user, 'mediafiles.can_see')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -24,7 +25,7 @@ class MediafileAccessPermissions(BaseAccessPermissions):
|
||||
Returns the restricted serialized data for the instance prepared
|
||||
for the user.
|
||||
"""
|
||||
if (not full_data['hidden'] or user.has_perm('mediafiles.can_see_hidden')):
|
||||
if (not full_data['hidden'] or has_perm(user, 'mediafiles.can_see_hidden')):
|
||||
data = full_data
|
||||
else:
|
||||
data = None
|
||||
|
@ -1,7 +1,11 @@
|
||||
from copy import deepcopy
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from ..core.config import config
|
||||
from ..utils.access_permissions import BaseAccessPermissions
|
||||
from ..utils.auth import has_perm
|
||||
from ..utils.collection import CollectionElement
|
||||
|
||||
|
||||
class MotionAccessPermissions(BaseAccessPermissions):
|
||||
@ -12,7 +16,7 @@ class MotionAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('motions.can_see')
|
||||
return has_perm(user, 'motions.can_see')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -29,12 +33,25 @@ class MotionAccessPermissions(BaseAccessPermissions):
|
||||
the motion in this state. Removes non public comment fields for
|
||||
some unauthorized users.
|
||||
"""
|
||||
required_permission_to_see = full_data.get('state_required_permission_to_see')
|
||||
if isinstance(user, get_user_model()):
|
||||
# Converts a user object to a collection element.
|
||||
# from_instance can not be used because the user serializer loads
|
||||
# the group from the db. So each call to from_instance(user) consts
|
||||
# one db query.
|
||||
user = CollectionElement.from_values('users/user', user.id)
|
||||
|
||||
if isinstance(user, CollectionElement):
|
||||
is_submitter = user.get_full_data()['id'] in full_data.get('submitters_id', [])
|
||||
else:
|
||||
# Anonymous users can not be submitters
|
||||
is_submitter = False
|
||||
|
||||
required_permission_to_see = full_data['state_required_permission_to_see']
|
||||
if (not required_permission_to_see or
|
||||
user.has_perm(required_permission_to_see) or
|
||||
user.has_perm('motions.can_manage') or
|
||||
user.pk in full_data.get('submitters_id', [])):
|
||||
if user.has_perm('motions.can_see_and_manage_comments') or not full_data.get('comments'):
|
||||
has_perm(user, required_permission_to_see) or
|
||||
has_perm(user, 'motions.can_manage') or
|
||||
is_submitter):
|
||||
if has_perm(user, 'motions.can_see_and_manage_comments') or not full_data.get('comments'):
|
||||
data = full_data
|
||||
else:
|
||||
data = deepcopy(full_data)
|
||||
@ -74,7 +91,7 @@ class MotionChangeRecommendationAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('motions.can_see')
|
||||
return has_perm(user, 'motions.can_see')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -93,7 +110,7 @@ class CategoryAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('motions.can_see')
|
||||
return has_perm(user, 'motions.can_see')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -112,7 +129,7 @@ class MotionBlockAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('motions.can_see')
|
||||
return has_perm(user, 'motions.can_see')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -131,7 +148,7 @@ class WorkflowAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('motions.can_see')
|
||||
return has_perm(user, 'motions.can_see')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
|
@ -1,4 +1,5 @@
|
||||
from ..utils.access_permissions import BaseAccessPermissions
|
||||
from ..utils.auth import has_perm
|
||||
|
||||
|
||||
class TopicAccessPermissions(BaseAccessPermissions):
|
||||
@ -9,7 +10,7 @@ class TopicAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('agenda.can_see')
|
||||
return has_perm(user, 'agenda.can_see')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
|
@ -1,4 +1,5 @@
|
||||
from ..utils.access_permissions import BaseAccessPermissions
|
||||
from ..utils.auth import DjangoAnonymousUser, has_perm
|
||||
|
||||
|
||||
class UserAccessPermissions(BaseAccessPermissions):
|
||||
@ -9,7 +10,7 @@ class UserAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('users.can_see_name')
|
||||
return has_perm(user, 'users.can_see_name')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
@ -33,9 +34,9 @@ class UserAccessPermissions(BaseAccessPermissions):
|
||||
FULL_DATA = 3
|
||||
|
||||
# Check user permissions.
|
||||
if user.has_perm('users.can_see_name'):
|
||||
if user.has_perm('users.can_see_extra_data'):
|
||||
if user.has_perm('users.can_manage'):
|
||||
if has_perm(user, 'users.can_see_name'):
|
||||
if has_perm(user, 'users.can_see_extra_data'):
|
||||
if has_perm(user, 'users.can_manage'):
|
||||
case = FULL_DATA
|
||||
else:
|
||||
case = MANY_DATA
|
||||
@ -77,3 +78,29 @@ class UserAccessPermissions(BaseAccessPermissions):
|
||||
if key in USERCANSEESERIALIZER_FIELDS:
|
||||
data[key] = full_data[key]
|
||||
return data
|
||||
|
||||
|
||||
class GroupAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Access permissions container for Groups. Everyone can see them
|
||||
"""
|
||||
def check_permissions(self, user):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
from ..core.config import config
|
||||
|
||||
# Every authenticated user can retrieve groups. Anonymous users can do
|
||||
# so if they are enabled.
|
||||
# Our AnonymousUser is a subclass of the DjangoAnonymousUser. Normaly, a
|
||||
# DjangoAnonymousUser means, that AnonymousUser is disabled. But this is
|
||||
# no garanty. send_data uses the AnonymousUser in any case.
|
||||
return not isinstance(user, DjangoAnonymousUser) or config['general_system_enable_anonymous']
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
Returns serializer class.
|
||||
"""
|
||||
from .serializers import GroupSerializer
|
||||
|
||||
return GroupSerializer
|
||||
|
38
openslides/users/migrations/0003_group.py
Normal file
38
openslides/users/migrations/0003_group.py
Normal file
@ -0,0 +1,38 @@
|
||||
# Generated by Django 1.10.5 on 2017-01-11 21:45
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import openslides.users.models
|
||||
import openslides.utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0008_alter_user_username_max_length'),
|
||||
('users', '0002_user_misc_default_groups'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Group',
|
||||
fields=[(
|
||||
'group_ptr',
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to='auth.Group'))],
|
||||
options={
|
||||
'default_permissions': (),
|
||||
},
|
||||
bases=(openslides.utils.models.RESTModelMixin, 'auth.group'),
|
||||
managers=[
|
||||
('objects', openslides.users.models.GroupManager()),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,20 +1,21 @@
|
||||
from random import choice
|
||||
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.contrib.auth.models import Group as DjangoGroup
|
||||
from django.contrib.auth.models import (
|
||||
AbstractBaseUser,
|
||||
BaseUserManager,
|
||||
Group,
|
||||
GroupManager,
|
||||
Permission,
|
||||
PermissionsMixin,
|
||||
)
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.db.models import Prefetch, Q
|
||||
|
||||
from openslides.utils.search import user_name_helper
|
||||
|
||||
from ..utils.models import RESTModelMixin
|
||||
from .access_permissions import UserAccessPermissions
|
||||
from .access_permissions import GroupAccessPermissions, UserAccessPermissions
|
||||
|
||||
|
||||
class UserManager(BaseUserManager):
|
||||
@ -25,9 +26,16 @@ class UserManager(BaseUserManager):
|
||||
def get_full_queryset(self):
|
||||
"""
|
||||
Returns the normal queryset with all users. In the background all
|
||||
groups are prefetched from the database.
|
||||
groups are prefetched from the database together with all permissions
|
||||
and content types.
|
||||
"""
|
||||
return self.get_queryset().prefetch_related('groups')
|
||||
return self.get_queryset().prefetch_related(Prefetch(
|
||||
'groups',
|
||||
queryset=Group.objects
|
||||
.select_related('group_ptr')
|
||||
.prefetch_related(Prefetch(
|
||||
'permissions',
|
||||
queryset=Permission.objects.select_related('content_type')))))
|
||||
|
||||
def create_user(self, username, password, **kwargs):
|
||||
"""
|
||||
@ -196,3 +204,30 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
|
||||
user_name_helper(self),
|
||||
self.structure_level,
|
||||
self.about_me))
|
||||
|
||||
|
||||
class GroupManager(GroupManager):
|
||||
"""
|
||||
Customized manager that supports our get_full_queryset method.
|
||||
"""
|
||||
def get_full_queryset(self):
|
||||
"""
|
||||
Returns the normal queryset with all groups. In the background all
|
||||
permissions with the content types are prefetched from the database.
|
||||
"""
|
||||
return (self.get_queryset()
|
||||
.select_related('group_ptr')
|
||||
.prefetch_related(Prefetch(
|
||||
'permissions',
|
||||
queryset=Permission.objects.select_related('content_type'))))
|
||||
|
||||
|
||||
class Group(RESTModelMixin, DjangoGroup):
|
||||
"""
|
||||
Extend the django group with support of our REST and caching system.
|
||||
"""
|
||||
access_permissions = GroupAccessPermissions()
|
||||
objects = GroupManager()
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
@ -1,6 +1,7 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db.models import Q
|
||||
|
||||
from ..utils.autoupdate import inform_changed_data
|
||||
from .models import Group, User
|
||||
|
||||
|
||||
@ -143,3 +144,8 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
|
||||
# Create or reset admin user
|
||||
User.objects.create_or_reset_admin_user()
|
||||
|
||||
# After each group was created, the permissions (many to many fields) where
|
||||
# added to the group. So we have to update the cache by calling
|
||||
# inform_changed_data().
|
||||
inform_changed_data((group_default, group_delegates, group_staff, group_committee))
|
||||
|
@ -14,7 +14,7 @@ from ..utils.rest_api import (
|
||||
status,
|
||||
)
|
||||
from ..utils.views import APIView
|
||||
from .access_permissions import UserAccessPermissions
|
||||
from .access_permissions import GroupAccessPermissions, UserAccessPermissions
|
||||
from .models import Group, User
|
||||
from .serializers import GroupSerializer
|
||||
|
||||
@ -123,16 +123,19 @@ class GroupViewSet(ModelViewSet):
|
||||
partial_update, update and destroy.
|
||||
"""
|
||||
metadata_class = GroupViewSetMetadata
|
||||
queryset = Group.objects.prefetch_related('permissions', 'permissions__content_type')
|
||||
queryset = Group.objects.all()
|
||||
serializer_class = GroupSerializer
|
||||
access_permissions = GroupAccessPermissions()
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action in ('metadata', 'list', 'retrieve'):
|
||||
# Every authenticated user can see the metadata and list or
|
||||
# retrieve groups. Anonymous users can do so if they are enabled.
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action == 'metadata':
|
||||
# Every authenticated user can see the metadata.
|
||||
# Anonymous users can do so if they are enabled.
|
||||
result = self.request.user.is_authenticated() or config['general_system_enable_anonymous']
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy'):
|
||||
# Users with all app permissions can edit groups.
|
||||
|
@ -6,7 +6,7 @@ from django.contrib.auth.models import Permission
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
from rest_framework.authentication import BaseAuthentication
|
||||
|
||||
from ..core.config import config
|
||||
from .collection import CollectionElement
|
||||
|
||||
|
||||
# Registered users
|
||||
@ -48,33 +48,13 @@ 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
|
||||
default_group = CollectionElement.from_values('users/group', 1)
|
||||
return perm in default_group.get_full_data()['permissions']
|
||||
|
||||
|
||||
class RESTFrameworkAnonymousAuthentication(BaseAuthentication):
|
||||
@ -86,6 +66,7 @@ class RESTFrameworkAnonymousAuthentication(BaseAuthentication):
|
||||
"""
|
||||
|
||||
def authenticate(self, request):
|
||||
from ..core.config import config
|
||||
if config['general_system_enable_anonymous']:
|
||||
return (AnonymousUser(), None)
|
||||
return None
|
||||
@ -106,7 +87,7 @@ class AuthenticationMiddleware:
|
||||
"The authentication middleware requires session middleware "
|
||||
"to be installed. Edit your MIDDLEWARE_CLASSES setting to insert "
|
||||
"'django.contrib.sessions.middleware.SessionMiddleware' before "
|
||||
"'openslides.users.auth.AuthenticationMiddleware'."
|
||||
"'openslides.utils.auth.AuthenticationMiddleware'."
|
||||
)
|
||||
request.user = SimpleLazyObject(lambda: get_user(request))
|
||||
|
||||
@ -118,6 +99,7 @@ def get_user(request):
|
||||
This is a mix of django.contrib.auth.get_user and
|
||||
django.contrib.auth.middleware.get_user which uses our anonymous user.
|
||||
"""
|
||||
from ..core.config import config
|
||||
try:
|
||||
return_user = request._cached_user
|
||||
except AttributeError:
|
||||
@ -127,3 +109,36 @@ def get_user(request):
|
||||
return_user = AnonymousUser()
|
||||
request._cached_user = return_user
|
||||
return return_user
|
||||
|
||||
|
||||
def has_perm(user, perm):
|
||||
"""
|
||||
Checks that user has a specific permission.
|
||||
"""
|
||||
if isinstance(user, AnonymousUser):
|
||||
# Our anonymous user has a has_perm-method that works with the cache
|
||||
# system. So we can use it here.
|
||||
has_perm = user.has_perm(perm)
|
||||
elif isinstance(user, DjangoAnonymousUser):
|
||||
# The django anonymous user is only used when anonymous user is disabled
|
||||
# So he never has permissions to see anything.
|
||||
has_perm = False
|
||||
else:
|
||||
if isinstance(user, get_user_model()):
|
||||
# Converts a user object to a collection element.
|
||||
# from_instance can not be used because the user serializer loads
|
||||
# the group from the db. So each call to from_instance(user) consts
|
||||
# one db query.
|
||||
user = CollectionElement.from_values('users/user', user.id)
|
||||
|
||||
# Get all groups of the user and then see, if one group has the required
|
||||
# permission. If the user has no groups, then use group 1.
|
||||
group_ids = user.get_full_data()['groups_id'] or [1]
|
||||
for group_id in group_ids:
|
||||
group = CollectionElement.from_values('users/group', group_id)
|
||||
if perm in group.get_full_data()['permissions']:
|
||||
has_perm = True
|
||||
break
|
||||
else:
|
||||
has_perm = False
|
||||
return has_perm
|
@ -1,4 +1,3 @@
|
||||
import itertools
|
||||
import json
|
||||
from collections import Iterable
|
||||
|
||||
@ -6,24 +5,14 @@ from asgiref.inmemory import ChannelLayer
|
||||
from channels import Channel, Group
|
||||
from channels.auth import channel_session_user, channel_session_user_from_http
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
|
||||
from ..core.config import config
|
||||
from ..core.models import Projector
|
||||
from ..users.auth import AnonymousUser
|
||||
from ..users.models import User
|
||||
from .auth import AnonymousUser
|
||||
from .cache import websocket_user_cache
|
||||
from .collection import Collection, CollectionElement, CollectionElementList
|
||||
|
||||
|
||||
def get_logged_in_users():
|
||||
"""
|
||||
Helper to get all logged in users.
|
||||
|
||||
Only works with the OpenSlides session backend.
|
||||
"""
|
||||
return User.objects.exclude(session=None).filter(session__expire_date__gte=timezone.now()).distinct()
|
||||
|
||||
|
||||
@channel_session_user_from_http
|
||||
def ws_add_site(message):
|
||||
"""
|
||||
@ -31,7 +20,10 @@ def ws_add_site(message):
|
||||
|
||||
The group with the name 'user-None' stands for all anonymous users.
|
||||
"""
|
||||
Group('user-{}'.format(message.user.id)).add(message.reply_channel)
|
||||
Group('site').add(message.reply_channel)
|
||||
message.channel_session['user_id'] = message.user.id
|
||||
# Saves the reply channel to the user. Uses 0 for anonymous users.
|
||||
websocket_user_cache.add(message.user.id or 0, message.reply_channel.name)
|
||||
|
||||
|
||||
@channel_session_user
|
||||
@ -39,7 +31,8 @@ def ws_disconnect_site(message):
|
||||
"""
|
||||
This function is called, when a client on the site disconnects.
|
||||
"""
|
||||
Group('user-{}'.format(message.user.id)).discard(message.reply_channel)
|
||||
Group('site').discard(message.reply_channel)
|
||||
websocket_user_cache.remove(message.user.id or 0, message.reply_channel.name)
|
||||
|
||||
|
||||
@channel_session_user_from_http
|
||||
@ -104,12 +97,15 @@ def send_data(message):
|
||||
Informs all site users and projector clients about changed data.
|
||||
"""
|
||||
collection_elements = CollectionElementList.from_channels_message(message)
|
||||
|
||||
# Loop over all logged in site users and the anonymous user and send changed data.
|
||||
for user in itertools.chain(get_logged_in_users(), [AnonymousUser()]):
|
||||
channel = Group('user-{}'.format(user.id))
|
||||
for user_id, channel_names in websocket_user_cache.get_all().items():
|
||||
if not user_id:
|
||||
# Anonymous user
|
||||
user = AnonymousUser()
|
||||
else:
|
||||
user = CollectionElement.from_values('users/user', user_id)
|
||||
output = collection_elements.as_autoupdate_for_user(user)
|
||||
channel.send({'text': json.dumps(output)})
|
||||
for channel_name in channel_names:
|
||||
Channel(channel_name).send({'text': json.dumps(output)})
|
||||
|
||||
# Check whether broadcast is active at the moment and set the local
|
||||
# projector queryset.
|
||||
|
209
openslides/utils/cache.py
Normal file
209
openslides/utils/cache.py
Normal file
@ -0,0 +1,209 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from channels import Group
|
||||
from channels.sessions import session_for_reply_channel
|
||||
from django.core.cache import cache, caches
|
||||
|
||||
|
||||
class BaseWebsocketUserCache:
|
||||
"""
|
||||
Caches the reply channel names of all open websocket connections. The id of
|
||||
the user that that opened the connection is used as reference.
|
||||
|
||||
This is the Base cache that has to be overriden.
|
||||
"""
|
||||
cache_key = 'current_websocket_users'
|
||||
|
||||
def add(self, user_id, channel_name):
|
||||
"""
|
||||
Adds a channel name to an user id.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove(self, user_id, channel_name):
|
||||
"""
|
||||
Removes one channel name from the cache.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_all(self):
|
||||
"""
|
||||
Returns all data using a dict where the key is a user id and the value
|
||||
is a set of channel_names.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def save_data(self, data):
|
||||
"""
|
||||
Saves the full data set (like created with build_data) to the cache.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def build_data(self):
|
||||
"""
|
||||
Creates all the data, saves it to the cache and returns it.
|
||||
"""
|
||||
websocket_user_ids = defaultdict(set)
|
||||
for channel_name in Group('site').channel_layer.group_channels('site'):
|
||||
session = session_for_reply_channel(channel_name)
|
||||
user_id = session.get('user_id', None)
|
||||
websocket_user_ids[user_id or 0].add(channel_name)
|
||||
self.save_data(websocket_user_ids)
|
||||
return websocket_user_ids
|
||||
|
||||
def get_cache_key(self):
|
||||
"""
|
||||
Returns the cache key.
|
||||
"""
|
||||
return self.cache_key
|
||||
|
||||
|
||||
class RedisWebsocketUserCache(BaseWebsocketUserCache):
|
||||
"""
|
||||
Implementation of the WebsocketUserCache that uses redis.
|
||||
|
||||
This uses one cache key to store all connected user ids in a set and
|
||||
for each user another set to save the channel names.
|
||||
"""
|
||||
|
||||
def add(self, user_id, channel_name):
|
||||
"""
|
||||
Adds a channel name to an user id.
|
||||
"""
|
||||
redis = get_redis_connection()
|
||||
pipe = redis.pipeline()
|
||||
pipe.sadd(self.get_cache_key(), user_id)
|
||||
pipe.sadd(self.get_user_cache_key(user_id), channel_name)
|
||||
pipe.execute()
|
||||
|
||||
def remove(self, user_id, channel_name):
|
||||
"""
|
||||
Removes one channel name from the cache.
|
||||
"""
|
||||
redis = get_redis_connection()
|
||||
redis.srem(self.get_user_cache_key(user_id), channel_name)
|
||||
|
||||
def get_all(self):
|
||||
"""
|
||||
Returns all data using a dict where the key is a user id and the value
|
||||
is a set of channel_names.
|
||||
"""
|
||||
redis = get_redis_connection()
|
||||
user_ids = redis.smembers(self.get_cache_key())
|
||||
if user_ids is None:
|
||||
websocket_user_ids = self.build_data()
|
||||
else:
|
||||
websocket_user_ids = dict()
|
||||
for user_id in user_ids:
|
||||
# Redis returns the id as string. So we have to convert it
|
||||
user_id = int(user_id)
|
||||
channel_names = redis.smembers(self.get_user_cache_key(user_id))
|
||||
if channel_names is not None:
|
||||
# If channel name is empty, then we can assume, that the user
|
||||
# has no active connection.
|
||||
websocket_user_ids[user_id] = set(channel_names)
|
||||
return websocket_user_ids
|
||||
|
||||
def save_data(self, data):
|
||||
"""
|
||||
Saves the full data set (like created with the method build_data()) to
|
||||
the cache.
|
||||
"""
|
||||
redis = get_redis_connection()
|
||||
pipe = redis.pipeline()
|
||||
|
||||
# Save all user ids
|
||||
pipe.delete(self.get_cache_key())
|
||||
pipe.sadd(self.get_cache_key(), *data.keys())
|
||||
|
||||
for user_id, channel_names in data.items():
|
||||
pipe.delete(self.get_user_cache_key(user_id))
|
||||
pipe.sadd(self.get_user_cache_key(user_id), *channel_names)
|
||||
pipe.execute()
|
||||
|
||||
def get_cache_key(self):
|
||||
"""
|
||||
Returns the cache key.
|
||||
"""
|
||||
return cache.make_key(self.cache_key)
|
||||
|
||||
def get_user_cache_key(self, user_id):
|
||||
"""
|
||||
Returns a cache key to save the channel names for a specific user.
|
||||
"""
|
||||
return cache.make_key('{}:{}'.format(self.cache_key, user_id))
|
||||
|
||||
|
||||
class DjangoCacheWebsocketUserCache(BaseWebsocketUserCache):
|
||||
"""
|
||||
Implementation of the WebsocketUserCache that uses the django cache.
|
||||
|
||||
If you use this with the inmemory cache, then you should only use one
|
||||
worker.
|
||||
|
||||
This uses only one cache key to save a dict where the key is the user id and
|
||||
the value is a set of channel names.
|
||||
"""
|
||||
|
||||
def add(self, user_id, channel_name):
|
||||
"""
|
||||
Adds a channel name for a user using the django cache.
|
||||
"""
|
||||
websocket_user_ids = cache.get(self.get_cache_key())
|
||||
if websocket_user_ids is None:
|
||||
websocket_user_ids = dict()
|
||||
|
||||
if user_id in websocket_user_ids:
|
||||
websocket_user_ids[user_id].add(channel_name)
|
||||
else:
|
||||
websocket_user_ids[user_id] = set([channel_name])
|
||||
cache.set(self.get_cache_key(), websocket_user_ids)
|
||||
|
||||
def remove(self, user_id, channel_name):
|
||||
"""
|
||||
Removes one channel name from the django cache.
|
||||
"""
|
||||
websocket_user_ids = cache.get(self.get_cache_key())
|
||||
if websocket_user_ids is not None and user_id in websocket_user_ids:
|
||||
websocket_user_ids[user_id].discard(channel_name)
|
||||
cache.set(self.get_cache_key(), websocket_user_ids)
|
||||
|
||||
def get_all(self):
|
||||
"""
|
||||
Returns the data using the django cache.
|
||||
"""
|
||||
websocket_user_ids = cache.get(self.get_cache_key())
|
||||
if websocket_user_ids is None:
|
||||
return self.build_data()
|
||||
return websocket_user_ids
|
||||
|
||||
def save_data(self, data):
|
||||
"""
|
||||
Saves the data using the django cache.
|
||||
"""
|
||||
cache.set(self.get_cache_key(), data)
|
||||
|
||||
|
||||
def use_redis_cache():
|
||||
"""
|
||||
Returns True if Redis is used als caching backend.
|
||||
"""
|
||||
try:
|
||||
from django_redis.cache import RedisCache
|
||||
except ImportError:
|
||||
return False
|
||||
return isinstance(caches['default'], RedisCache)
|
||||
|
||||
|
||||
def get_redis_connection():
|
||||
"""
|
||||
Returns an object that can be used to talk directly to redis.
|
||||
"""
|
||||
from django_redis import get_redis_connection
|
||||
return get_redis_connection("default")
|
||||
|
||||
|
||||
if use_redis_cache():
|
||||
websocket_user_cache = RedisWebsocketUserCache()
|
||||
else:
|
||||
websocket_user_cache = DjangoCacheWebsocketUserCache()
|
@ -1,5 +1,7 @@
|
||||
from django.apps import apps
|
||||
from django.core.cache import cache, caches
|
||||
from django.core.cache import cache
|
||||
|
||||
from .cache import get_redis_connection, use_redis_cache
|
||||
|
||||
|
||||
class CollectionElement:
|
||||
@ -37,6 +39,7 @@ class CollectionElement:
|
||||
self.full_data = full_data
|
||||
self.information = information or {}
|
||||
if instance is not None:
|
||||
# Collection element is created via instance
|
||||
self.collection_string = instance.get_collection_string()
|
||||
from openslides.core.config import config
|
||||
if self.collection_string == config.get_collection_string():
|
||||
@ -45,6 +48,7 @@ class CollectionElement:
|
||||
else:
|
||||
self.id = instance.pk
|
||||
elif collection_string is not None and id is not None:
|
||||
# Collection element is created via values
|
||||
self.collection_string = collection_string
|
||||
self.id = id
|
||||
else:
|
||||
@ -511,19 +515,3 @@ def get_collection_id_from_cache_key(cache_key):
|
||||
# The id is no integer. This can happen on config elements
|
||||
pass
|
||||
return (collection_string, id)
|
||||
|
||||
|
||||
def use_redis_cache():
|
||||
"""
|
||||
Returns True if Redis is used als caching backend.
|
||||
"""
|
||||
try:
|
||||
from django_redis.cache import RedisCache
|
||||
except ImportError:
|
||||
return False
|
||||
return isinstance(caches['default'], RedisCache)
|
||||
|
||||
|
||||
def get_redis_connection():
|
||||
from django_redis import get_redis_connection
|
||||
return get_redis_connection("default")
|
||||
|
@ -65,32 +65,49 @@ DATABASES = {
|
||||
}
|
||||
|
||||
|
||||
# Django Channels
|
||||
|
||||
# Unless you have only a small assembly uncomment the following lines to
|
||||
# activate Redis as backend for Django Channels and Cache. You have to install
|
||||
# a Redis server and the python packages asgi_redis and django-redis.
|
||||
|
||||
# https://channels.readthedocs.io/en/latest/backends.html#redis
|
||||
|
||||
# CHANNEL_LAYERS['default']['BACKEND'] = 'asgi_redis.RedisChannelLayer'
|
||||
# Set use_redis to True to activate redis as cache-, asgi- and session backend.
|
||||
use_redis = False
|
||||
|
||||
|
||||
# Caching
|
||||
if use_redis:
|
||||
|
||||
# Django uses a inmemory cache at default. This supports only one thread. If
|
||||
# you use more then one thread another caching backend is required. We recommand
|
||||
# django-redis: https://niwinz.github.io/django-redis/latest/#_user_guide
|
||||
# Django Channels
|
||||
|
||||
# Unless you have only a small assembly uncomment the following lines to
|
||||
# activate Redis as backend for Django Channels and Cache. You have to install
|
||||
# a Redis server and the python packages asgi_redis and django-redis.
|
||||
|
||||
# https://channels.readthedocs.io/en/latest/backends.html#redis
|
||||
|
||||
CHANNEL_LAYERS['default']['BACKEND'] = 'asgi_redis.RedisChannelLayer'
|
||||
|
||||
|
||||
# Caching
|
||||
|
||||
# Django uses a inmemory cache at default. This supports only one thread. If
|
||||
# you use more then one thread another caching backend is required. We recommand
|
||||
# django-redis: https://niwinz.github.io/django-redis/latest/#_user_guide
|
||||
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": "redis://127.0.0.1:6379/0",
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Session backend
|
||||
|
||||
# Per default django uses the database as session backend. This can be slow.
|
||||
# One possibility is to use the cache session backend with redis as cache backend
|
||||
# Another possibility is to use a native redis session backend. For example:
|
||||
# https://github.com/martinrusev/django-redis-sessions
|
||||
|
||||
# SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
|
||||
SESSION_ENGINE = 'redis_sessions.session'
|
||||
|
||||
# CACHES = {
|
||||
# "default": {
|
||||
# "BACKEND": "django_redis.cache.RedisCache",
|
||||
# "LOCATION": "redis://127.0.0.1:6379/0",
|
||||
# "OPTIONS": {
|
||||
# "CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
|
||||
# Internationalization
|
||||
|
@ -1,3 +1,7 @@
|
||||
from contextlib import ContextDecorator
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.core.cache import caches
|
||||
from django.test import TestCase as _TestCase
|
||||
from django.test.runner import DiscoverRunner
|
||||
|
||||
@ -31,3 +35,22 @@ class TestCase(_TestCase):
|
||||
Could be used in the future. Use this this for the integration test suit.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class use_cache(ContextDecorator):
|
||||
"""
|
||||
Contextmanager that changes the code to use the local memory cache.
|
||||
|
||||
Can also be used as decorator for a function.
|
||||
|
||||
The code inside the contextmananger starts with an empty cache.
|
||||
"""
|
||||
|
||||
def __enter__(self):
|
||||
cache = caches['locmem']
|
||||
cache.clear()
|
||||
self.patch = patch('openslides.utils.collection.cache', cache)
|
||||
self.patch.start()
|
||||
|
||||
def __exit__(self, *exc):
|
||||
self.patch.stop()
|
||||
|
@ -10,7 +10,7 @@ from openslides.core.models import Countdown
|
||||
from openslides.motions.models import Motion
|
||||
from openslides.topics.models import Topic
|
||||
from openslides.users.models import User
|
||||
from openslides.utils.test import TestCase
|
||||
from openslides.utils.test import TestCase, use_cache
|
||||
|
||||
|
||||
class RetrieveItem(TestCase):
|
||||
@ -64,36 +64,37 @@ class TestDBQueries(TestCase):
|
||||
Motion.objects.create(title='motion2')
|
||||
Assignment.objects.create(title='assignment', open_posts=5)
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 5 requests to get the session an the request user with its permissions,
|
||||
* 4 requests to get the session an the request user with its permissions,
|
||||
* 2 requests to get the list of all agenda items,
|
||||
* 1 request to get all speakers,
|
||||
* 3 requests to get the assignments, motions and topics and
|
||||
|
||||
* 2 requests for the motionsversions.
|
||||
|
||||
TODO: There could be less requests to get the session and the request user.
|
||||
The last two request for the motionsversions are a bug.
|
||||
TODO: The last two request for the motionsversions are a bug.
|
||||
"""
|
||||
self.client.force_login(User.objects.get(pk=1))
|
||||
with self.assertNumQueries(13):
|
||||
with self.assertNumQueries(12):
|
||||
self.client.get(reverse('item-list'))
|
||||
|
||||
@use_cache()
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 2 requests to get the permission for anonymous (config and permissions)
|
||||
* 3 requests to get the permission for anonymous,
|
||||
* 2 requests to get the list of all agenda items,
|
||||
* 1 request to get all speakers,
|
||||
* 3 requests to get the assignments, motions and topics and
|
||||
|
||||
* 32 requests for the motionsversions.
|
||||
* 2 requests for the motionsversions.
|
||||
|
||||
TODO: The last 32 requests are a bug.
|
||||
TODO: The last two request for the motionsversions are a bug.
|
||||
"""
|
||||
with self.assertNumQueries(40):
|
||||
with self.assertNumQueries(11):
|
||||
self.client.get(reverse('item-list'))
|
||||
|
||||
|
@ -6,7 +6,7 @@ from rest_framework.test import APIClient
|
||||
from openslides.assignments.models import Assignment
|
||||
from openslides.core.config import config
|
||||
from openslides.users.models import User
|
||||
from openslides.utils.test import TestCase
|
||||
from openslides.utils.test import TestCase, use_cache
|
||||
|
||||
|
||||
class TestDBQueries(TestCase):
|
||||
@ -23,39 +23,41 @@ class TestDBQueries(TestCase):
|
||||
for index in range(10):
|
||||
Assignment.objects.create(title='motion{}'.format(index), open_posts=1)
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 5 requests to get the session an the request user with its permissions,
|
||||
* 4 requests to get the session an the request user with its permissions,
|
||||
* 2 requests to get the list of all assignments,
|
||||
* 1 request to get all related users,
|
||||
* 1 request to get the agenda item,
|
||||
* 1 request to get the polls,
|
||||
* 1 request to get the tags and
|
||||
|
||||
* 10 request to featch each related user again.
|
||||
* 10 request to fetch each related user again.
|
||||
|
||||
TODO: There could be less requests to get the session and the request user.
|
||||
The eleven request are a bug.
|
||||
TODO: The last request are a bug.
|
||||
"""
|
||||
self.client.force_login(User.objects.get(pk=1))
|
||||
with self.assertNumQueries(30):
|
||||
with self.assertNumQueries(20):
|
||||
self.client.get(reverse('assignment-list'))
|
||||
|
||||
@use_cache()
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 2 requests to get the permission for anonymous (config and permissions)
|
||||
* 3 requests to get the permission for anonymous,
|
||||
* 2 requests to get the list of all assignments,
|
||||
* 1 request to get all related users,
|
||||
* 1 request to get the agenda item,
|
||||
* 1 request to get the polls,
|
||||
* 1 request to get the tags,
|
||||
* 1 request to get the tags and
|
||||
|
||||
* lots of permissions requests.
|
||||
* 10 request to fetch each related user again.
|
||||
|
||||
TODO: The last requests are a bug.
|
||||
TODO: The last 10 requests are an bug.
|
||||
"""
|
||||
with self.assertNumQueries(57):
|
||||
with self.assertNumQueries(19):
|
||||
self.client.get(reverse('assignment-list'))
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@ from rest_framework.test import APIClient
|
||||
from openslides.core.config import config
|
||||
from openslides.core.models import ChatMessage, Projector, Tag
|
||||
from openslides.users.models import User
|
||||
from openslides.utils.test import TestCase
|
||||
from openslides.utils.test import TestCase, use_cache
|
||||
|
||||
|
||||
class TestProjectorDBQueries(TestCase):
|
||||
@ -22,29 +22,27 @@ class TestProjectorDBQueries(TestCase):
|
||||
for index in range(10):
|
||||
Projector.objects.create(name="Projector{}".format(index))
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 5 requests to get the session an the request user with its permissions,
|
||||
* 4 requests to get the session an the request user with its permissions,
|
||||
* 2 requests to get the list of all projectors,
|
||||
* 1 request to get the list of the projector defaults.
|
||||
"""
|
||||
self.client.force_login(User.objects.get(pk=1))
|
||||
with self.assertNumQueries(8):
|
||||
with self.assertNumQueries(7):
|
||||
self.client.get(reverse('projector-list'))
|
||||
|
||||
@use_cache()
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 2 requests to get the permission for anonymous (config and permissions)
|
||||
* 3 requests to get the permission for anonymous,
|
||||
* 2 requests to get the list of all projectors,
|
||||
* 1 request to get the list of the projector defaults and
|
||||
|
||||
* 11 requests for permissions.
|
||||
|
||||
TODO: The last 11 requests are a bug.
|
||||
"""
|
||||
with self.assertNumQueries(16):
|
||||
with self.assertNumQueries(6):
|
||||
self.client.get(reverse('projector-list'))
|
||||
|
||||
|
||||
@ -63,14 +61,15 @@ class TestCharmessageDBQueries(TestCase):
|
||||
for index in range(10):
|
||||
ChatMessage.objects.create(user=user)
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 5 requests to get the session an the request user with its permissions,
|
||||
* 4 requests to get the session an the request user with its permissions,
|
||||
* 2 requests to get the list of all chatmessages,
|
||||
"""
|
||||
self.client.force_login(User.objects.get(pk=1))
|
||||
with self.assertNumQueries(7):
|
||||
with self.assertNumQueries(6):
|
||||
self.client.get(reverse('chatmessage-list'))
|
||||
|
||||
|
||||
@ -88,6 +87,7 @@ class TestTagDBQueries(TestCase):
|
||||
for index in range(10):
|
||||
Tag.objects.create(name='tag{}'.format(index))
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
@ -98,6 +98,7 @@ class TestTagDBQueries(TestCase):
|
||||
with self.assertNumQueries(4):
|
||||
self.client.get(reverse('tag-list'))
|
||||
|
||||
@use_cache()
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
@ -124,6 +125,7 @@ class TestConfigDBQueries(TestCase):
|
||||
self.client = APIClient()
|
||||
config['general_system_enable_anonymous'] = True
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
@ -134,6 +136,7 @@ class TestConfigDBQueries(TestCase):
|
||||
with self.assertNumQueries(3):
|
||||
self.client.get(reverse('config-list'))
|
||||
|
||||
@use_cache()
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
|
@ -5,7 +5,7 @@ from rest_framework.test import APIClient
|
||||
from openslides.core.config import config
|
||||
from openslides.mediafiles.models import Mediafile
|
||||
from openslides.users.models import User
|
||||
from openslides.utils.test import TestCase
|
||||
from openslides.utils.test import TestCase, use_cache
|
||||
|
||||
|
||||
class TestDBQueries(TestCase):
|
||||
@ -26,21 +26,23 @@ class TestDBQueries(TestCase):
|
||||
'some_file{}'.format(index),
|
||||
b'some content.'))
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 5 requests to get the session an the request user with its permissions and
|
||||
* 4 requests to get the session an the request user with its permissions and
|
||||
* 2 requests to get the list of all files.
|
||||
"""
|
||||
self.client.force_login(User.objects.get(pk=1))
|
||||
with self.assertNumQueries(7):
|
||||
with self.assertNumQueries(6):
|
||||
self.client.get(reverse('mediafile-list'))
|
||||
|
||||
@use_cache()
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 2 requests to get the permission for anonymous (config and permissions) and
|
||||
* 3 requests to get the permission for anonymous and
|
||||
* 2 requests to get the list of all projectors.
|
||||
"""
|
||||
with self.assertNumQueries(4):
|
||||
with self.assertNumQueries(5):
|
||||
self.client.get(reverse('mediafile-list'))
|
||||
|
@ -9,7 +9,7 @@ from openslides.core.config import config
|
||||
from openslides.core.models import Tag
|
||||
from openslides.motions.models import Category, Motion, MotionBlock, State
|
||||
from openslides.users.models import User
|
||||
from openslides.utils.test import TestCase
|
||||
from openslides.utils.test import TestCase, use_cache
|
||||
|
||||
|
||||
class TestMotionDBQueries(TestCase):
|
||||
@ -27,10 +27,11 @@ class TestMotionDBQueries(TestCase):
|
||||
Motion.objects.create(title='motion{}'.format(index))
|
||||
# TODO: Create some polls etc.
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 5 requests to get the session an the request user with its permissions,
|
||||
* 4 requests to get the session an the request user with its permissions,
|
||||
* 2 requests to get the list of all motions,
|
||||
* 1 request to get the motion versions,
|
||||
* 1 request to get the agenda item,
|
||||
@ -38,16 +39,17 @@ class TestMotionDBQueries(TestCase):
|
||||
* 1 request to get the polls,
|
||||
* 1 request to get the attachments,
|
||||
* 1 request to get the tags,
|
||||
* 2 requests to get the submitters and supporters
|
||||
* 2 requests to get the submitters and supporters and
|
||||
"""
|
||||
self.client.force_login(User.objects.get(pk=1))
|
||||
with self.assertNumQueries(15):
|
||||
with self.assertNumQueries(14):
|
||||
self.client.get(reverse('motion-list'))
|
||||
|
||||
@use_cache()
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 2 requests to get the permission for anonymous (config and permissions)
|
||||
* 3 requests to get the permission for anonymous,
|
||||
* 2 requests to get the list of all motions,
|
||||
* 1 request to get the motion versions,
|
||||
* 1 request to get the agenda item,
|
||||
@ -56,12 +58,8 @@ class TestMotionDBQueries(TestCase):
|
||||
* 1 request to get the attachments,
|
||||
* 1 request to get the tags,
|
||||
* 2 requests to get the submitters and supporters
|
||||
|
||||
* 10 requests for permissions.
|
||||
|
||||
TODO: The last 10 requests are a bug.
|
||||
"""
|
||||
with self.assertNumQueries(22):
|
||||
with self.assertNumQueries(13):
|
||||
self.client.get(reverse('motion-list'))
|
||||
|
||||
|
||||
@ -79,27 +77,25 @@ class TestCategoryDBQueries(TestCase):
|
||||
for index in range(10):
|
||||
Category.objects.create(name='category{}'.format(index))
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 5 requests to get the session an the request user with its permissions and
|
||||
* 4 requests to get the session an the request user with its permissions and
|
||||
* 2 requests to get the list of all categories.
|
||||
"""
|
||||
self.client.force_login(User.objects.get(pk=1))
|
||||
with self.assertNumQueries(7):
|
||||
with self.assertNumQueries(6):
|
||||
self.client.get(reverse('category-list'))
|
||||
|
||||
@use_cache()
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 2 requests to get the permission for anonymous (config and permissions)
|
||||
* 3 requests to get the permission for anonymous (config and permissions)
|
||||
* 2 requests to get the list of all motions and
|
||||
|
||||
* 10 requests for permissions.
|
||||
|
||||
TODO: The last 10 requests are a bug.
|
||||
"""
|
||||
with self.assertNumQueries(14):
|
||||
with self.assertNumQueries(5):
|
||||
self.client.get(reverse('category-list'))
|
||||
|
||||
|
||||
@ -113,31 +109,29 @@ class TestWorkflowDBQueries(TestCase):
|
||||
config['general_system_enable_anonymous'] = True
|
||||
# There do not need to be more workflows
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 5 requests to get the session an the request user with its permissions,
|
||||
* 4 requests to get the session an the request user with its permissions,
|
||||
* 2 requests to get the list of all workflows,
|
||||
* 1 request to get all states and
|
||||
* 1 request to get the next states of all states.
|
||||
"""
|
||||
self.client.force_login(User.objects.get(pk=1))
|
||||
with self.assertNumQueries(9):
|
||||
with self.assertNumQueries(8):
|
||||
self.client.get(reverse('workflow-list'))
|
||||
|
||||
@use_cache()
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 2 requests to get the permission for anonymous (config and permissions),
|
||||
* 3 requests to get the permission for anonymous,
|
||||
* 2 requests to get the list of all workflows,
|
||||
* 1 request to get all states and
|
||||
* 1 request to get the next states of all states.
|
||||
|
||||
* 2 requests for permissions.
|
||||
|
||||
TODO: The last 2 requests are a bug.
|
||||
"""
|
||||
with self.assertNumQueries(8):
|
||||
with self.assertNumQueries(7):
|
||||
self.client.get(reverse('workflow-list'))
|
||||
|
||||
|
||||
@ -294,11 +288,12 @@ class RetrieveMotion(TestCase):
|
||||
self.motion.save()
|
||||
self.motion.create_poll()
|
||||
|
||||
@use_cache()
|
||||
def test_number_of_queries(self):
|
||||
with self.assertNumQueries(16):
|
||||
with self.assertNumQueries(18):
|
||||
self.client.get(reverse('motion-detail', args=[self.motion.pk]))
|
||||
|
||||
def test__guest_state_with_required_permission_to_see(self):
|
||||
def test_guest_state_with_required_permission_to_see(self):
|
||||
config['general_system_enable_anonymous'] = True
|
||||
guest_client = APIClient()
|
||||
state = self.motion.state
|
||||
@ -323,9 +318,7 @@ class RetrieveMotion(TestCase):
|
||||
password='password_kau4eequaisheeBateef')
|
||||
self.motion.submitters.add(user)
|
||||
submitter_client = APIClient()
|
||||
submitter_client.login(
|
||||
username='username_ohS2opheikaSa5theijo',
|
||||
password='password_kau4eequaisheeBateef')
|
||||
submitter_client.force_login(user)
|
||||
response = submitter_client.get(reverse('motion-detail', args=[self.motion.pk]))
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
|
@ -1,25 +0,0 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
|
||||
class TestAnonymousRequests(TestCase):
|
||||
"""
|
||||
Test that a request with an user that is not logged in gets only the
|
||||
requested data, if the anonymous user is activated in the config.
|
||||
|
||||
Expects that the page '/rest/users/user/' needs a permission and the
|
||||
anonymous user has this permission.
|
||||
"""
|
||||
|
||||
@patch('openslides.users.auth.config', {'general_system_enable_anonymous': True})
|
||||
def test_with_anonymous_user(self):
|
||||
response = self.client.get('/rest/users/user/')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@patch('openslides.users.auth.config', {'general_system_enable_anonymous': False})
|
||||
def test_without_anonymous_user(self):
|
||||
response = self.client.get('/rest/users/user/')
|
||||
|
||||
self.assertEqual(response.status_code, 403)
|
@ -5,7 +5,7 @@ from rest_framework.test import APIClient
|
||||
from openslides.core.config import config
|
||||
from openslides.users.models import Group, User
|
||||
from openslides.users.serializers import UserFullSerializer
|
||||
from openslides.utils.test import TestCase
|
||||
from openslides.utils.test import TestCase, use_cache
|
||||
|
||||
|
||||
class TestUserDBQueries(TestCase):
|
||||
@ -22,29 +22,27 @@ class TestUserDBQueries(TestCase):
|
||||
for index in range(10):
|
||||
User.objects.create(username='user{}'.format(index))
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 5 requests to get the session an the request user with its permissions,
|
||||
* 2 requests to get the list of all assignments and
|
||||
* 1 request to get all groups.
|
||||
* 2 requests to get the session and the request user with its permissions,
|
||||
* 2 requests to get the list of all users and
|
||||
* 1 requests to get the list of all groups.
|
||||
"""
|
||||
self.client.force_login(User.objects.get(pk=1))
|
||||
with self.assertNumQueries(8):
|
||||
with self.assertNumQueries(7):
|
||||
self.client.get(reverse('user-list'))
|
||||
|
||||
@use_cache()
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 2 requests to get the permission for anonymous (config and permissions)
|
||||
* 2 requests to get the list of all users,
|
||||
* 1 request to get all groups and
|
||||
|
||||
* lots of permissions requests.
|
||||
|
||||
TODO: The last requests are a bug.
|
||||
* 3 requests to get the permission for anonymous,
|
||||
* 2 requests to get the list of all users and
|
||||
* 2 request to get all groups (needed by the user serializer).
|
||||
"""
|
||||
with self.assertNumQueries(27):
|
||||
with self.assertNumQueries(7):
|
||||
self.client.get(reverse('user-list'))
|
||||
|
||||
|
||||
@ -62,29 +60,36 @@ class TestGroupDBQueries(TestCase):
|
||||
for index in range(10):
|
||||
Group.objects.create(name='group{}'.format(index))
|
||||
|
||||
@use_cache()
|
||||
def test_admin(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 2 requests to get the session an the request user with its permissions,
|
||||
* 1 request to get the list of all groups,
|
||||
* 1 request to get the permissions and
|
||||
* 1 request to get the content_object for the permissions.
|
||||
* 4 requests to get the session an the request user with its permissions and
|
||||
* 1 request to get the list of all groups.
|
||||
|
||||
The data of the groups where loaded when the admin was authenticated. So
|
||||
only the list of all groups has be fetched from the db.
|
||||
"""
|
||||
self.client.force_login(User.objects.get(pk=1))
|
||||
with self.assertNumQueries(5):
|
||||
self.client.get(reverse('group-list'))
|
||||
|
||||
@use_cache()
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Tests that only the following db queries are done:
|
||||
* 2 requests to find out if anonymous is enabled
|
||||
* 1 request to get the list of all groups,
|
||||
* 1 request to get the permissions and
|
||||
* 1 request to get the content_object for the permissions.
|
||||
* 3 request to get the list of all groups and
|
||||
|
||||
* 14 Requests to find out if anonymous is enabled.
|
||||
|
||||
TODO: There should be only one request to find out if anonymous is enabled.
|
||||
The reason for the last 14 requests is the base get_restricted_data()
|
||||
method that is used by the group access_permissions. It calls check_permissions().
|
||||
But this is important for the autoupdate-case and can only be fixt by
|
||||
caching the config object.
|
||||
"""
|
||||
with self.assertNumQueries(5):
|
||||
with self.assertNumQueries(19):
|
||||
self.client.get(reverse('group-list'))
|
||||
|
||||
|
||||
|
@ -1,75 +1,16 @@
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from channels import DEFAULT_CHANNEL_LAYER
|
||||
from channels.asgi import channel_layers
|
||||
from channels.tests import ChannelTestCase
|
||||
from django.contrib.auth.models import Group
|
||||
from django.utils import timezone
|
||||
|
||||
from openslides.assignments.models import Assignment
|
||||
from openslides.core.models import Session
|
||||
from openslides.topics.models import Topic
|
||||
from openslides.users.models import User
|
||||
from openslides.utils.autoupdate import (
|
||||
get_logged_in_users,
|
||||
inform_changed_data,
|
||||
inform_deleted_data,
|
||||
)
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
|
||||
class TestGetLoggedInUsers(TestCase):
|
||||
def test_call(self):
|
||||
"""
|
||||
Test to call the function with:
|
||||
* A user that session has not expired
|
||||
* A user that session has expired
|
||||
* A user that has no session
|
||||
* An anonymous user that session hot not expired
|
||||
|
||||
Only the user with the session that has not expired should be returned
|
||||
"""
|
||||
user1 = User.objects.create(username='user1')
|
||||
user2 = User.objects.create(username='user2')
|
||||
User.objects.create(username='user3')
|
||||
|
||||
# Create a session with a user, that expires in 5 hours
|
||||
Session.objects.create(
|
||||
user=user1,
|
||||
expire_date=timezone.now() + timedelta(hours=5),
|
||||
session_key='1')
|
||||
|
||||
# Create a session with a user, that is expired before 5 hours
|
||||
Session.objects.create(
|
||||
user=user2,
|
||||
expire_date=timezone.now() + timedelta(hours=-5),
|
||||
session_key='2')
|
||||
|
||||
# Create a session with an anonymous user, that expires in 5 hours
|
||||
Session.objects.create(
|
||||
user=None,
|
||||
expire_date=timezone.now() + timedelta(hours=5),
|
||||
session_key='3')
|
||||
|
||||
self.assertEqual(list(get_logged_in_users()), [user1])
|
||||
|
||||
def test_unique(self):
|
||||
"""
|
||||
Test the function with a user that has two not expired session.
|
||||
The user should be returned only once.
|
||||
"""
|
||||
user1 = User.objects.create(username='user1')
|
||||
Session.objects.create(
|
||||
user=user1,
|
||||
expire_date=timezone.now() + timedelta(hours=1),
|
||||
session_key='1')
|
||||
Session.objects.create(
|
||||
user=user1,
|
||||
expire_date=timezone.now() + timedelta(hours=2),
|
||||
session_key='2')
|
||||
|
||||
self.assertEqual(list(get_logged_in_users()), [user1])
|
||||
|
||||
|
||||
@patch('openslides.utils.autoupdate.transaction.on_commit', lambda func: func())
|
||||
@ -138,7 +79,8 @@ class TestsInformChangedData(ChannelTestCase):
|
||||
def test_change_no_autoupdate_model(self):
|
||||
"""
|
||||
Tests that if inform_changed_data() is called with a model that does
|
||||
not support autoupdate, nothing happens.
|
||||
not support autoupdate, nothing happens. We use the django Group for
|
||||
this (not the OpenSlides Group)
|
||||
"""
|
||||
group = Group.objects.create(name='test_group')
|
||||
channel_layers[DEFAULT_CHANNEL_LAYER].flush()
|
||||
|
@ -1,100 +0,0 @@
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from openslides.users.auth import AnonymousUser, get_user
|
||||
|
||||
|
||||
class TestAnonymousUser(TestCase):
|
||||
def test_get_all_permissions_from_group_1(self):
|
||||
"""
|
||||
Tests, that get_all_permissions looks in the permissions of the group with
|
||||
pk=1
|
||||
"""
|
||||
anonymous = AnonymousUser()
|
||||
|
||||
with patch('openslides.users.auth.Permission') as mock_permission:
|
||||
anonymous.get_all_permissions()
|
||||
|
||||
mock_permission.objects.filter.assert_called_once_with(group__pk=1)
|
||||
|
||||
def test_has_perm_in_list(self):
|
||||
anonymous = AnonymousUser()
|
||||
anonymous.get_all_permissions = MagicMock(return_value=('p1', 'p2'))
|
||||
|
||||
self.assertTrue(
|
||||
anonymous.has_perm('p1'),
|
||||
"has_perm() should return True when the user has the permission")
|
||||
|
||||
def test_has_perm_not_in_list(self):
|
||||
anonymous = AnonymousUser()
|
||||
anonymous.get_all_permissions = MagicMock(return_value=('p1', 'p2'))
|
||||
|
||||
self.assertFalse(
|
||||
anonymous.has_perm('p3'),
|
||||
"has_perm() should return False when the user has not the permission")
|
||||
|
||||
def test_has_module_perms_in_list(self):
|
||||
anonymous = AnonymousUser()
|
||||
anonymous.get_all_permissions = MagicMock(return_value=('test_app.perm', ))
|
||||
|
||||
self.assertTrue(
|
||||
anonymous.has_module_perms('test_app'),
|
||||
"has_module_perms() should return True when the user has the "
|
||||
"permission test_app.perm")
|
||||
|
||||
def test_has_module_perms_not_in_list(self):
|
||||
anonymous = AnonymousUser()
|
||||
anonymous.get_all_permissions = MagicMock(return_value=('test_otherapp.perm', ))
|
||||
|
||||
self.assertFalse(
|
||||
anonymous.has_module_perms('test_app'),
|
||||
"has_module_perms() should return False when the user does not have "
|
||||
"the permission test_app.perm")
|
||||
|
||||
|
||||
@patch('openslides.users.auth.config')
|
||||
@patch('openslides.users.auth._get_user')
|
||||
class TestGetUser(TestCase):
|
||||
def test_not_in_cache(self, mock_get_user, mock_config):
|
||||
mock_config.__getitem__.return_value = True
|
||||
mock_get_user.return_value = AnonymousUser()
|
||||
request = MagicMock()
|
||||
del request._cached_user
|
||||
|
||||
user = get_user(request)
|
||||
|
||||
mock_get_user.assert_called_once_with(request)
|
||||
self.assertEqual(user, AnonymousUser())
|
||||
self.assertEqual(request._cached_user, AnonymousUser())
|
||||
|
||||
def test_in_cache(self, mock_get_user, mock_config):
|
||||
request = MagicMock()
|
||||
request._cached_user = 'my_user'
|
||||
|
||||
user = get_user(request)
|
||||
|
||||
self.assertFalse(
|
||||
mock_get_user.called,
|
||||
"_get_user should not have been called when the user object is in cache")
|
||||
self.assertEqual(
|
||||
user,
|
||||
'my_user',
|
||||
"The user in cache should be returned")
|
||||
|
||||
def test_disabled_anonymous_user(self, mock_get_user, mock_config):
|
||||
mock_config.__getitem__.return_value = False
|
||||
mock_get_user.return_value = 'django_anonymous_user'
|
||||
request = MagicMock()
|
||||
del request._cached_user
|
||||
|
||||
user = get_user(request)
|
||||
|
||||
mock_get_user.assert_called_once_with(request)
|
||||
self.assertEqual(
|
||||
user,
|
||||
'django_anonymous_user',
|
||||
"The django user should be returned")
|
||||
self.assertEqual(
|
||||
request._cached_user,
|
||||
'django_anonymous_user',
|
||||
"The django user should be cached")
|
53
tests/unit/utils/test_auth.py
Normal file
53
tests/unit/utils/test_auth.py
Normal file
@ -0,0 +1,53 @@
|
||||
from unittest import TestCase, skip
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from openslides.utils.auth import AnonymousUser, get_user
|
||||
|
||||
|
||||
@skip # I don't know how to patch the config if it is not imported in the global space
|
||||
@patch('openslides.utils.auth.config')
|
||||
@patch('openslides.utils.auth._get_user')
|
||||
class TestGetUser(TestCase):
|
||||
def test_not_in_cache(self, mock_get_user, mock_config):
|
||||
mock_config.__getitem__.return_value = True
|
||||
mock_get_user.return_value = AnonymousUser()
|
||||
request = MagicMock()
|
||||
del request._cached_user
|
||||
|
||||
user = get_user(request)
|
||||
|
||||
mock_get_user.assert_called_once_with(request)
|
||||
self.assertEqual(user, AnonymousUser())
|
||||
self.assertEqual(request._cached_user, AnonymousUser())
|
||||
|
||||
def test_in_cache(self, mock_get_user, mock_config):
|
||||
request = MagicMock()
|
||||
request._cached_user = 'my_user'
|
||||
|
||||
user = get_user(request)
|
||||
|
||||
self.assertFalse(
|
||||
mock_get_user.called,
|
||||
"_get_user should not have been called when the user object is in cache")
|
||||
self.assertEqual(
|
||||
user,
|
||||
'my_user',
|
||||
"The user in cache should be returned")
|
||||
|
||||
def test_disabled_anonymous_user(self, mock_get_user, mock_config):
|
||||
mock_config.__getitem__.return_value = False
|
||||
mock_get_user.return_value = 'django_anonymous_user'
|
||||
request = MagicMock()
|
||||
del request._cached_user
|
||||
|
||||
user = get_user(request)
|
||||
|
||||
mock_get_user.assert_called_once_with(request)
|
||||
self.assertEqual(
|
||||
user,
|
||||
'django_anonymous_user',
|
||||
"The django user should be returned")
|
||||
self.assertEqual(
|
||||
request._cached_user,
|
||||
'django_anonymous_user',
|
||||
"The django user should be cached")
|
Loading…
Reference in New Issue
Block a user