Skip to content
Snippets Groups Projects
Unverified Commit ecd72c08 authored by jan.bednarik's avatar jan.bednarik Committed by GitHub
Browse files

Merge pull request #15 from wotaen/author_sort

Sort authors by first_name and total_reports
parents 87f4331f 2a113baa
No related branches found
No related tags found
No related merge requests found
import graphene import graphene
from graphene import relay
from django.db.models import Count, Q from django.db.models import Count, Q
from graphene import relay
from . import types from . import types
from ..models import OpenIdClient
from .paginator import Paginator from .paginator import Paginator
from .sanitizers import extract_text from .sanitizers import extract_text
from .. import search from .. import search
from ..models import OpenIdClient
from ..models import User, Report 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): class AuthorsConnection(relay.Connection):
total_count = graphene.Int() total_count = graphene.Int()
...@@ -38,6 +47,8 @@ class Query: ...@@ -38,6 +47,8 @@ class Query:
authors = relay.ConnectionField( authors = relay.ConnectionField(
AuthorsConnection, AuthorsConnection,
description='List of Authors. Returns first 10 nodes if pagination is not specified.', 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( search_reports = relay.ConnectionField(
SearchReportsConnection, SearchReportsConnection,
...@@ -59,10 +70,10 @@ class Query: ...@@ -59,10 +70,10 @@ class Query:
paginator = Paginator(**kwargs) paginator = Paginator(**kwargs)
total = User.objects.filter(is_author=True).count() 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)))\ authors = User.objects\
.order_by('last_name', 'first_name')[ .sorted(**kwargs)\
paginator.slice_from:paginator.slice_to] .filter(is_author=True)[paginator.slice_from:paginator.slice_to]
page_info = paginator.get_page_info(total) page_info = paginator.get_page_info(total)
......
import time
from django.conf import settings from django.conf import settings
from django.db import models from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.db import models
from django.db.models import Count
from django.db.models import Q
from django.utils import timezone 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().annotate(total_reports=Count('report', filter=Q(report__is_draft=False)))
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): class User(AbstractUser):
...@@ -14,12 +31,13 @@ class User(AbstractUser): ...@@ -14,12 +31,13 @@ class User(AbstractUser):
extra = JSONField(null=True, blank=True) extra = JSONField(null=True, blank=True)
is_author = models.BooleanField(default=False) is_author = models.BooleanField(default=False)
has_colliding_name = models.BooleanField(default=False) has_colliding_name = models.BooleanField(default=False)
objects = UserManager()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# deal with first name and last name collisions # deal with first name and last name collisions
if self.is_author: if self.is_author:
collisions = User.objects.filter(first_name=self.first_name, last_name=self.last_name, collisions = User.objects.filter(first_name=self.first_name, last_name=self.last_name,
is_author=True).exclude(id=self.id) is_author=True).exclude(id=self.id)
if collisions.count() > 0: if collisions.count() > 0:
self.has_colliding_name = True self.has_colliding_name = True
collisions.update(has_colliding_name=True) collisions.update(has_colliding_name=True)
......
...@@ -266,3 +266,248 @@ snapshots['test_with_reports 1'] = { ...@@ -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
}
}
}
...@@ -199,3 +199,148 @@ def test_with_reports(client, snapshot): ...@@ -199,3 +199,148 @@ def test_with_reports(client, snapshot):
""" """
response = call_api(client, query) response = call_api(client, query)
snapshot.assert_match(response) 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
import pytest
import arrow
from django.conf import settings
from unittest.mock import patch from unittest.mock import patch
from openlobby.core.models import Report, User, OpenIdClient, LoginAttempt import arrow
import pytest
from django.conf import settings
from openlobby.core.api.schema import AUTHOR_SORT_LAST_NAME_ID, AUTHOR_SORT_TOTAL_REPORTS_ID
from openlobby.core.documents import ReportDoc from openlobby.core.documents import ReportDoc
from openlobby.core.models import Report, User, OpenIdClient, LoginAttempt
pytestmark = [pytest.mark.django_db, pytest.mark.usefixtures('django_es')] pytestmark = [pytest.mark.django_db, pytest.mark.usefixtures('django_es')]
...@@ -82,7 +82,7 @@ def test_login_attempt__default_expiration(): ...@@ -82,7 +82,7 @@ def test_login_attempt__default_expiration():
client = OpenIdClient.objects.create(name='a', client_id='b', client_secret='c') client = OpenIdClient.objects.create(name='a', client_id='b', client_secret='c')
with patch('openlobby.core.models.time.time', return_value=10000): with patch('openlobby.core.models.time.time', return_value=10000):
attempt = LoginAttempt.objects.create(openid_client=client, state='foo', attempt = LoginAttempt.objects.create(openid_client=client, state='foo',
app_redirect_uri='http://openlobby/app') app_redirect_uri='http://openlobby/app')
assert attempt.expiration == 10000 + settings.LOGIN_ATTEMPT_EXPIRATION assert attempt.expiration == 10000 + settings.LOGIN_ATTEMPT_EXPIRATION
...@@ -118,3 +118,42 @@ def test_user__name_collision_excludes_self_on_update(): ...@@ -118,3 +118,42 @@ def test_user__name_collision_excludes_self_on_update():
u = User.objects.create(username='a', is_author=True, first_name='Ryan', last_name='Gosling') u = User.objects.create(username='a', is_author=True, first_name='Ryan', last_name='Gosling')
u.save() u.save()
assert User.objects.get(username='a').has_colliding_name is False assert User.objects.get(username='a').has_colliding_name is False
def test_user__sorted_default():
User.objects.create(username='a', is_author=False, first_name='Ryan', last_name='AGosling')
User.objects.create(username='b', is_author=True, first_name='Ryan', last_name='BGosling')
User.objects.create(username='c', is_author=False, first_name='Ryan', last_name='CGosling')
assert User.objects.sorted()[0].username == 'a'
def test_user__sorted_default_reversed():
User.objects.create(username='a', is_author=False, first_name='Ryan', last_name='AGosling')
User.objects.create(username='b', is_author=True, first_name='Ryan', last_name='BGosling')
User.objects.create(username='c', is_author=False, first_name='Ryan', last_name='CGosling')
assert User.objects.sorted(reversed=True)[0].username == 'c'
def test_user__sorted_last_name():
User.objects.create(username='a', is_author=False, first_name='Ryan', last_name='AGosling')
User.objects.create(username='b', is_author=True, first_name='Ryan', last_name='BGosling')
User.objects.create(username='c', is_author=False, first_name='Ryan', last_name='CGosling')
assert User.objects.sorted(sort=AUTHOR_SORT_LAST_NAME_ID)[0].username == 'a'
assert User.objects.sorted(sort=AUTHOR_SORT_LAST_NAME_ID, reversed=False)[0].username == 'a'
assert User.objects.sorted(sort=AUTHOR_SORT_LAST_NAME_ID, reversed=True)[0].username == 'c'
def test_user__sorted_total_reports():
author = User.objects.create(username='a', is_author=True, first_name='Ryan', last_name='AGosling')
User.objects.create(username='b', is_author=True, first_name='Ryan', last_name='BGosling')
date = arrow.get(2018, 1, 1).datetime
Report.objects.create(
id=7,
author=author,
date=date,
published=date,
body='Lorem ipsum.',
)
assert User.objects.sorted(sort=AUTHOR_SORT_TOTAL_REPORTS_ID)[0].username == 'a'
assert User.objects.sorted(sort=AUTHOR_SORT_TOTAL_REPORTS_ID, reversed=False)[0].username == 'a'
assert User.objects.sorted(sort=AUTHOR_SORT_TOTAL_REPORTS_ID, reversed=True)[0].username == 'b'
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment