diff --git a/openlobby/core/api/schema.py b/openlobby/core/api/schema.py index 4bd463243cc9c44ea140f4a45419967e6475e65e..cc99dac02427a68f12e8ec93a5492be90e34cf09 100644 --- a/openlobby/core/api/schema.py +++ b/openlobby/core/api/schema.py @@ -9,6 +9,15 @@ from .sanitizers import extract_text from .. import search from ..models import User, Report +AUTHOR_SORT_LAST_NAME_ID = 1 +AUTHOR_SORT_TOTAL_REPORTS_ID = 2 + +class AuthorSortEnum(graphene.Enum): + LAST_NAME = AUTHOR_SORT_LAST_NAME_ID + TOTAL_REPORTS = AUTHOR_SORT_TOTAL_REPORTS_ID + + class Meta: + description = 'Sort by field.' class AuthorsConnection(relay.Connection): total_count = graphene.Int() @@ -38,6 +47,8 @@ class Query: authors = relay.ConnectionField( AuthorsConnection, description='List of Authors. Returns first 10 nodes if pagination is not specified.', + sort=AuthorSortEnum(), + reversed=graphene.Boolean(default_value=False, description="Reverse order of sort") ) search_reports = relay.ConnectionField( SearchReportsConnection, @@ -59,10 +70,12 @@ class Query: paginator = Paginator(**kwargs) total = User.objects.filter(is_author=True).count() - authors = User.objects.filter(is_author=True)\ - .annotate(total_reports=Count('report', filter=Q(report__is_draft=False)))\ - .order_by('last_name', 'first_name')[ - paginator.slice_from:paginator.slice_to] + + authors = User.objects\ + .sorted(**kwargs)\ + .filter(is_author=True)\ + .annotate(total_reports=Count('report', filter=Q(report__is_draft=False)))[ + paginator.slice_from:paginator.slice_to] page_info = paginator.get_page_info(total) diff --git a/openlobby/core/models.py b/openlobby/core/models.py index 470de4c2f75960aba1845fb0006b208c7e3d9210..7a4ccb87c711f604b31d181e5554d6dfc7467861 100644 --- a/openlobby/core/models.py +++ b/openlobby/core/models.py @@ -1,10 +1,24 @@ from django.conf import settings +from django.contrib.auth.base_user import BaseUserManager from django.db import models from django.contrib.auth.models import AbstractUser from django.contrib.postgres.fields import JSONField from django.utils import timezone import time +class UserManager(BaseUserManager): + + def sorted(self, **kwargs): + # inline import intentionally + from openlobby.core.api.schema import AUTHOR_SORT_LAST_NAME_ID, AUTHOR_SORT_TOTAL_REPORTS_ID + qs = self.get_queryset() + sort_field = kwargs.get('sort', AUTHOR_SORT_LAST_NAME_ID) + if sort_field == AUTHOR_SORT_LAST_NAME_ID: + return qs.order_by('{}last_name'.format('-' if kwargs.get('reversed', False) else ''), 'first_name') + elif sort_field == AUTHOR_SORT_TOTAL_REPORTS_ID: + return qs.order_by('{}total_reports'.format('' if kwargs.get('reversed', False) else '-'), 'last_name') + raise NotImplemented("Other sort types are not implemented") + class User(AbstractUser): """Custom user model. For simplicity we store OpenID 'sub' identifier in @@ -14,6 +28,7 @@ class User(AbstractUser): extra = JSONField(null=True, blank=True) is_author = models.BooleanField(default=False) has_colliding_name = models.BooleanField(default=False) + objects = UserManager() def save(self, *args, **kwargs): # deal with first name and last name collisions diff --git a/tests/schema/snapshots/snap_test_authors.py b/tests/schema/snapshots/snap_test_authors.py index af1983302e23288e0d90904421bb8aa855bb14f8..cf9fba0e09487210dbca4670b052d2d65f9c3fd3 100644 --- a/tests/schema/snapshots/snap_test_authors.py +++ b/tests/schema/snapshots/snap_test_authors.py @@ -266,3 +266,248 @@ snapshots['test_with_reports 1'] = { } } } + +snapshots['test_sort_by_last_name 1'] = { + 'data': { + 'authors': { + 'edges': [ + { + 'cursor': 'MQ==', + 'node': { + 'extra': None, + 'firstName': 'Shaun', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjM=', + 'lastName': 'Sheep', + 'totalReports': 0 + } + }, + { + 'cursor': 'Mg==', + 'node': { + 'extra': None, + 'firstName': 'Spongebob', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjI=', + 'lastName': 'Squarepants', + 'totalReports': 1 + } + }, + { + 'cursor': 'Mw==', + 'node': { + 'extra': '{"movies": 1}', + 'firstName': 'Winston', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjE=', + 'lastName': 'Wolfe', + 'totalReports': 2 + } + } + ], + 'pageInfo': { + 'endCursor': 'Mw==', + 'hasNextPage': False, + 'hasPreviousPage': False, + 'startCursor': 'MQ==' + }, + 'totalCount': 3 + } + } +} + +snapshots['test_sort_by_last_name_reversed 1'] = { + 'data': { + 'authors': { + 'edges': [ + { + 'cursor': 'MQ==', + 'node': { + 'extra': '{"movies": 1}', + 'firstName': 'Winston', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjE=', + 'lastName': 'Wolfe', + 'totalReports': 2 + } + }, + { + 'cursor': 'Mg==', + 'node': { + 'extra': None, + 'firstName': 'Spongebob', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjI=', + 'lastName': 'Squarepants', + 'totalReports': 1 + } + }, + { + 'cursor': 'Mw==', + 'node': { + 'extra': None, + 'firstName': 'Shaun', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjM=', + 'lastName': 'Sheep', + 'totalReports': 0 + } + } + ], + 'pageInfo': { + 'endCursor': 'Mw==', + 'hasNextPage': False, + 'hasPreviousPage': False, + 'startCursor': 'MQ==' + }, + 'totalCount': 3 + } + } +} + +snapshots['test_sort_by_total_reports 1'] = { + 'data': { + 'authors': { + 'edges': [ + { + 'cursor': 'MQ==', + 'node': { + 'extra': '{"movies": 1}', + 'firstName': 'Winston', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjE=', + 'lastName': 'Wolfe', + 'totalReports': 2 + } + }, + { + 'cursor': 'Mg==', + 'node': { + 'extra': None, + 'firstName': 'Spongebob', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjI=', + 'lastName': 'Squarepants', + 'totalReports': 1 + } + }, + { + 'cursor': 'Mw==', + 'node': { + 'extra': None, + 'firstName': 'Shaun', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjM=', + 'lastName': 'Sheep', + 'totalReports': 0 + } + } + ], + 'pageInfo': { + 'endCursor': 'Mw==', + 'hasNextPage': False, + 'hasPreviousPage': False, + 'startCursor': 'MQ==' + }, + 'totalCount': 3 + } + } +} + +snapshots['test_sort_by_total_reports_reversed 1'] = { + 'data': { + 'authors': { + 'edges': [ + { + 'cursor': 'MQ==', + 'node': { + 'extra': None, + 'firstName': 'Shaun', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjM=', + 'lastName': 'Sheep', + 'totalReports': 0 + } + }, + { + 'cursor': 'Mg==', + 'node': { + 'extra': None, + 'firstName': 'Spongebob', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjI=', + 'lastName': 'Squarepants', + 'totalReports': 1 + } + }, + { + 'cursor': 'Mw==', + 'node': { + 'extra': '{"movies": 1}', + 'firstName': 'Winston', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjE=', + 'lastName': 'Wolfe', + 'totalReports': 2 + } + } + ], + 'pageInfo': { + 'endCursor': 'Mw==', + 'hasNextPage': False, + 'hasPreviousPage': False, + 'startCursor': 'MQ==' + }, + 'totalCount': 3 + } + } +} + +snapshots['test_sort_by_default_reversed 1'] = { + 'data': { + 'authors': { + 'edges': [ + { + 'cursor': 'MQ==', + 'node': { + 'extra': '{"movies": 1}', + 'firstName': 'Winston', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjE=', + 'lastName': 'Wolfe', + 'totalReports': 2 + } + }, + { + 'cursor': 'Mg==', + 'node': { + 'extra': None, + 'firstName': 'Spongebob', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjI=', + 'lastName': 'Squarepants', + 'totalReports': 1 + } + }, + { + 'cursor': 'Mw==', + 'node': { + 'extra': None, + 'firstName': 'Shaun', + 'hasCollidingName': False, + 'id': 'QXV0aG9yOjM=', + 'lastName': 'Sheep', + 'totalReports': 0 + } + } + ], + 'pageInfo': { + 'endCursor': 'Mw==', + 'hasNextPage': False, + 'hasPreviousPage': False, + 'startCursor': 'MQ==' + }, + 'totalCount': 3 + } + } +} diff --git a/tests/schema/test_authors.py b/tests/schema/test_authors.py index 0e83176eb619f6d23935f667e1eb9bd0e4a50734..5c59a37386187dff0949ef7bb8dedee57d827897 100644 --- a/tests/schema/test_authors.py +++ b/tests/schema/test_authors.py @@ -199,3 +199,148 @@ def test_with_reports(client, snapshot): """ response = call_api(client, query) snapshot.assert_match(response) + +def test_sort_by_last_name(client, snapshot): + prepare_reports() + query = """ + query { + authors(sort: LAST_NAME) { + totalCount + edges { + cursor + node { + id + firstName + lastName + hasCollidingName + totalReports + extra + } + } + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor + } + } + } + """ + response = call_api(client, query) + snapshot.assert_match(response) + +def test_sort_by_last_name_reversed(client, snapshot): + prepare_reports() + query = """ + query { + authors(sort: LAST_NAME, reversed: true) { + totalCount + edges { + cursor + node { + id + firstName + lastName + hasCollidingName + totalReports + extra + } + } + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor + } + } + } + """ + response = call_api(client, query) + snapshot.assert_match(response) + +def test_sort_by_total_reports(client, snapshot): + prepare_reports() + query = """ + query { + authors(sort: TOTAL_REPORTS, reversed: false) { + totalCount + edges { + cursor + node { + id + firstName + lastName + hasCollidingName + totalReports + extra + } + } + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor + } + } + } + """ + response = call_api(client, query) + snapshot.assert_match(response) + +def test_sort_by_total_reports_reversed(client, snapshot): + prepare_reports() + query = """ + query { + authors(sort: TOTAL_REPORTS, reversed: true) { + totalCount + edges { + cursor + node { + id + firstName + lastName + hasCollidingName + totalReports + extra + } + } + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor + } + } + } + """ + response = call_api(client, query) + snapshot.assert_match(response) + +def test_sort_by_default_reversed(client, snapshot): + prepare_reports() + query = """ + query { + authors(reversed: true) { + totalCount + edges { + cursor + node { + id + firstName + lastName + hasCollidingName + totalReports + extra + } + } + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor + } + } + } + """ + response = call_api(client, query) + snapshot.assert_match(response) \ No newline at end of file