Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • arkindex/backend
1 result
Show changes
Commits on Source (4)
Showing
with 1018 additions and 757 deletions
......@@ -8,4 +8,4 @@ line_length = 120
default_section=FIRSTPARTY
known_first_party = arkindex_common,ponos,transkribus
known_third_party = boto3,botocore,corsheaders,django,django_admin_hstore_widget,django_rq,elasticsearch,elasticsearch_dsl,enumfields,gitlab,psycopg2,requests,responses,rest_framework,rq,setuptools,sqlparse,tenacity,tripoli,yaml
known_third_party = boto3,botocore,corsheaders,django,django_admin_hstore_widget,django_rq,elasticsearch,elasticsearch_dsl,enumfields,gitlab,psycopg2,requests,responses,rest_framework,rq,setuptools,sqlparse,teklia_toolbox,tenacity,tripoli,yaml
......@@ -186,9 +186,9 @@ class TestImports(FixtureAPITestCase):
self.assertEqual(response.json(), {'creator': "User with email or id 'blabla@blabla.fr' does not exist"})
self.client.force_login(self.user)
response = self.client.get(reverse('api:import-list'), {'creator': '5'})
response = self.client.get(reverse('api:import-list'), {'creator': '1337'})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.json(), {'creator': "User with email or id '5' does not exist"})
self.assertEqual(response.json(), {'creator': "User with email or id '1337' does not exist"})
def test_list_filter_id(self):
self.client.force_login(self.user)
......
This diff is collapsed.
......@@ -9,7 +9,7 @@ from django.utils import timezone as DjangoTimeZone
from arkindex.dataimport.models import RepositoryType, WorkerVersion, WorkerVersionState, Workflow
from arkindex.documents.models import Corpus, DataSource, Element, MetaData
from arkindex.images.models import Image, ImageServer, Zone
from arkindex.users.models import CorpusRight, User
from arkindex.users.models import CorpusRight, Group, User
from arkindex_common.enums import MetaType, TranscriptionType
from arkindex_common.ml_tool import MLToolType
from ponos.models import State
......@@ -61,18 +61,29 @@ class Command(BaseCommand):
line_zone = makezone(img1, 400, 500)
# Create an admin, an internal and a normal user
superuser = User.objects.create_superuser('root@root.fr', 'Pa$$w0rd')
superuser = User.objects.create_superuser('root@root.fr', 'Pa$$w0rd', display_name='Admin')
superuser.verified_email = True
superuser.save()
internal_user = User.objects.create_user('internal@internal.fr', 'Pa$$w0rd', internal=True)
internal_user = User.objects.create_user(
'internal@internal.fr',
'Pa$$w0rd',
display_name='Internal user',
internal=True,
)
internal_user.verified_email = True
internal_user.save()
user = User.objects.create_user('user@user.fr', 'Pa$$w0rd')
user = User.objects.create_user('user@user.fr', 'Pa$$w0rd', display_name='Test user')
user.verified_email = True
user.save()
# Create a group owned by the user with 2 other members
group = Group.objects.create(name="User group", public=False)
group.memberships.create(user=user, level=100)
group.users.create(email='user2@user.fr', display_name='Test user write', through_defaults={'level': 50})
group.users.create(email='user3@user.fr', display_name='Test user read', through_defaults={'level': 10})
# Create 1 data source for transcriptions
recognizer_source = DataSource.objects.create(
type=MLToolType.Recognizer,
......
......@@ -3,9 +3,9 @@ import logging
import uuid
from django.core.management.base import BaseCommand
from teklia_toolbox.time import Timer
from arkindex.documents.models import Element
from arkindex_common.tools import Timer
# Enable deletion signal logs
logging.basicConfig(
......
from django.core.management.base import BaseCommand
from django.db import connections, transaction
from arkindex_common.tools import Timer
from teklia_toolbox.time import Timer
class Command(BaseCommand):
......
......@@ -86,6 +86,7 @@ from arkindex.project.openapi import SchemaGenerator
from arkindex.users.api import (
CredentialsList,
CredentialsRetrieve,
GroupMembershipsList,
GroupsList,
JobList,
JobRetrieve,
......@@ -248,6 +249,7 @@ api = [
# Rights management
path('groups/', GroupsList.as_view(), name='groups-list'),
path('group/<uuid:pk>/members/', GroupMembershipsList.as_view(), name='group-members'),
# Asynchronous jobs
path('jobs/', JobList.as_view(), name='jobs-list'),
......
......@@ -2,7 +2,7 @@ import uuid
from enum import Enum
from pathlib import Path
from arkindex_common.config_parser import ConfigParser, ConfigurationError, dir_path
from teklia_toolbox.config import ConfigParser, ConfigurationError, dir_path
class CacheType(Enum):
......
......@@ -80,6 +80,7 @@ class FixtureMixin(object):
def setUpTestData(cls):
cls.corpus = Corpus.objects.get(name='Unit Tests')
cls.user = User.objects.get(email='user@user.fr')
cls.group = cls.user.groups.get(name='User group')
cls.superuser = User.objects.get(email='root@root.fr')
cls.imgsrv = ImageServer.objects.get(url='http://server')
......
......@@ -5,9 +5,9 @@ from unittest import TestCase
from unittest.mock import patch
import yaml
from teklia_toolbox.config import ConfigParser, ConfigurationError
from arkindex.project.config import get_settings_parser
from arkindex_common.config_parser import ConfigParser, ConfigurationError
SAMPLES = Path(__file__).resolve().parent / 'config_samples'
......@@ -42,8 +42,7 @@ class TestConfig(TestCase):
return stream.getvalue()
# Ignore non-existent paths
@patch('arkindex_common.config_parser.dir_path', new=Path)
@patch('arkindex_common.config_parser.file_path', new=Path)
@patch('teklia_toolbox.config.dir_path', new=Path)
def test_settings_defaults(self):
parser = get_settings_parser(Path('/somewhere/backend/arkindex'))
self.assertIsInstance(parser, ConfigParser)
......@@ -57,8 +56,7 @@ class TestConfig(TestCase):
self.maxDiff = None
self.assertEqual(expected, actual)
@patch('arkindex_common.config_parser.dir_path', new=Path)
@patch('arkindex_common.config_parser.file_path', new=Path)
@patch('teklia_toolbox.config.dir_path', new=Path)
def test_settings_override(self):
parser = get_settings_parser(Path('/somewhere/backend/arkindex'))
self.assertIsInstance(parser, ConfigParser)
......@@ -72,7 +70,7 @@ class TestConfig(TestCase):
self.maxDiff = None
self.assertEqual(expected, actual)
@patch('arkindex_common.config_parser._all_checks')
@patch('teklia_toolbox.config._all_checks')
def test_settings_errors(self, all_checks_mock):
all_checks_mock.return_value = True
parser = get_settings_parser(Path('/somewhere/backend/arkindex'))
......
......@@ -8,6 +8,7 @@ from django.contrib.auth.mixins import UserPassesTestMixin
from django.contrib.auth.tokens import default_token_generator
from django.core.mail import send_mail
from django.db.models import Count, Q
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.http import urlsafe_base64_encode
......@@ -35,6 +36,7 @@ from arkindex.users.models import Group, OAuthStatus, Scope, User, UserScope
from arkindex.users.providers import get_provider, oauth_providers
from arkindex.users.serializers import (
EmailLoginSerializer,
GroupMembershipsSerializer,
GroupSerializer,
JobSerializer,
NewUserSerializer,
......@@ -461,8 +463,8 @@ class GroupsList(ListCreateAPIView):
def get_queryset(self):
# List public groups and ones to which user belongs
return Group.objects \
.filter(Q(public=True) | Q(users__in=[self.request.user])) \
.annotate(members_count=Count('users')) \
.filter(Q(public=True) | Q(users__in=[self.request.user])) \
.order_by('id')
......@@ -501,3 +503,23 @@ class JobRetrieve(RetrieveDestroyAPIView):
if instance.get_status(refresh=False) == JobStatus.STARTED:
raise ValidationError(['Cannot delete a running job.'])
instance.delete()
class GroupMembershipsList(ListAPIView):
"""
List members of a group to which user belongs with their privileges
"""
serializer_class = GroupMembershipsSerializer
permission_classes = (IsVerified, )
openapi_overrides = {
'security': [],
'tags': ['users'],
}
def get_queryset(self):
group = get_object_or_404(Group.objects.filter(
# Filter groups to which user belongs
users__in=[self.request.user],
id=self.kwargs['pk']
))
return group.memberships.order_by('user__display_name', 'id').prefetch_related('user')
......@@ -10,6 +10,24 @@ from arkindex.users.managers import UserManager
from arkindex.users.providers import get_provider, oauth_providers
class LevelRight(Enum):
Admin = 'admin', 100
Write = 'write', 50
Read = 'read', 10
def __new__(cls, value, level):
right = object.__new__(cls)
right._value_ = value
right.level = level
return right
@classmethod
def from_level(cls, level):
for right in cls.__iter__():
if right.level <= level:
return right
class User(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
......
......@@ -9,7 +9,7 @@ from django.utils.http import urlsafe_base64_decode
from rest_framework import serializers
from arkindex.project.serializer_fields import EnumField
from arkindex.users.models import Group, Membership, OAuthCredentials, OAuthStatus, User
from arkindex.users.models import Group, LevelRight, Membership, OAuthCredentials, OAuthStatus, User
from transkribus import TranskribusAPI
logging.basicConfig(
......@@ -258,3 +258,23 @@ class JobSerializer(serializers.Serializer):
but the enum is just a plain object and not an Enum for Py2 compatibility.
"""
return instance.get_status(refresh=False)
class GroupMembershipsSerializer(serializers.ModelSerializer):
id = serializers.StringRelatedField(source='user.id')
display_name = serializers.StringRelatedField(source='user.display_name')
email = serializers.StringRelatedField(source='user.email')
level = serializers.SerializerMethodField()
class Meta:
model = Membership
fields = (
'id',
'display_name',
'email',
'level'
)
def get_level(self, obj):
right = LevelRight.from_level(obj.level)
return right and right.value
......@@ -64,6 +64,15 @@ class TestMembership(FixtureAPITestCase):
[(self.user.id, 100)]
)
def test_crate_group_duplicated_name(self):
"""
Assert multiple groups can have a similar name
"""
self.client.force_login(self.user)
with self.assertNumQueries(6):
response = self.client.post(reverse('api:groups-list'), {'name': self.group.name})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_list_groups(self):
"""
List groups which are public or for which the user is a member
......@@ -72,14 +81,90 @@ class TestMembership(FixtureAPITestCase):
with self.assertNumQueries(4):
response = self.client.get(reverse('api:groups-list'))
self.assertDictEqual(response.json(), {
'count': 1,
'count': 2,
'number': 1,
'next': None,
'previous': None,
'results': [
{
'id': str(self.group.id),
'members_count': 3,
'name': 'User group',
'public': False
}, {
'id': str(self.admin_group.id),
'members_count': 1,
'name': 'Admin group',
'public': True
}
]
})
def test_list_members_requires_member_user(self):
"""
Only users that belong to a group have the ability to list the members
Otherwise we return a 404 as usual
"""
self.client.force_login(self.user)
with self.assertNumQueries(3):
response = self.client.get(reverse('api:group-members', kwargs={'pk': str(self.admin_group.id)}))
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_list_members_level_right(self):
"""
List members of a group with their right corresponding to privileges level
Members are ordered by display name
"""
self.client.force_login(self.user)
with self.assertNumQueries(6):
response = self.client.get(reverse('api:group-members', kwargs={'pk': str(self.group.id)}))
read_member, write_member, admin_member = self.group.memberships.order_by('level')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json(), {
'count': 3,
'next': None,
'number': 1,
'previous': None,
'results': [
{
'display_name': 'Test user',
'email': admin_member.user.email,
'id': str(admin_member.user.id),
'level': 'admin'
}, {
'display_name': 'Test user read',
'email': read_member.user.email,
'id': str(read_member.user.id),
'level': 'read'
}, {
'display_name': 'Test user write',
'email': write_member.user.email,
'id': str(write_member.user.id),
'level': 'write'
}
]
})
def test_list_members_null_right(self):
"""
Non defined right level should return None
"""
self.client.force_login(self.user)
group = self.user.groups.create(name='Another group', through_defaults={'level': 1})
with self.assertNumQueries(6):
response = self.client.get(reverse('api:group-members', kwargs={'pk': str(group.id)}))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json(), {
'count': 1,
'next': None,
'number': 1,
'previous': None,
'results': [{
'id': str(self.admin_group.id),
'members_count': 1,
'name': 'Admin group',
'public': True
}]
'results': [
{
'display_name': self.user.display_name,
'email': self.user.email,
'id': str(self.user.id),
'level': None
}
]
})
......@@ -18,6 +18,7 @@ pytz==2020.4
PyYAML==5.3.1
requests==2.25.0
sentry-sdk==0.19.2
teklia-toolbox==0.1.0
tenacity==6.2
transkribus-client>=0.1.1
git+https://gitlab.com/arkindex/transkribus.git#egg=transkribus-client
......