Add team model, team node and a mutation for creating a team
This commit is contained in:
parent
ea3a404ae7
commit
4e1ab68a52
|
|
@ -10,6 +10,8 @@ from faker import Faker
|
||||||
from wagtail.documents.models import get_document_model
|
from wagtail.documents.models import get_document_model
|
||||||
from wagtail.images import get_image_model
|
from wagtail.images import get_image_model
|
||||||
|
|
||||||
|
from users.models import Role, UserRole
|
||||||
|
|
||||||
fake = Faker('de_CH')
|
fake = Faker('de_CH')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -48,7 +50,7 @@ class DummyImageFactory(factory.DjangoModelFactory):
|
||||||
class UserFactory(factory.django.DjangoModelFactory):
|
class UserFactory(factory.django.DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = get_user_model()
|
model = get_user_model()
|
||||||
django_get_or_create = ('username', )
|
django_get_or_create = ('username',)
|
||||||
|
|
||||||
first_name = factory.LazyAttribute(lambda x: fake.first_name())
|
first_name = factory.LazyAttribute(lambda x: fake.first_name())
|
||||||
last_name = factory.LazyAttribute(lambda x: fake.last_name())
|
last_name = factory.LazyAttribute(lambda x: fake.last_name())
|
||||||
|
|
@ -58,3 +60,11 @@ class UserFactory(factory.django.DjangoModelFactory):
|
||||||
def post(self, create, extracted, **kwargs):
|
def post(self, create, extracted, **kwargs):
|
||||||
self.set_password('test')
|
self.set_password('test')
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
class TeacherFactory(UserFactory):
|
||||||
|
@factory.post_generation
|
||||||
|
def post(self, create, extracted, **kwargs):
|
||||||
|
Role.objects.create_default_roles()
|
||||||
|
teacher_role = Role.objects.get_default_teacher_role()
|
||||||
|
UserRole.objects.create(user=self, role=teacher_role)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,6 @@ class Command(BaseCommand):
|
||||||
with open(schema_path, 'w') as o:
|
with open(schema_path, 'w') as o:
|
||||||
o.write(str(schema))
|
o.write(str(schema))
|
||||||
|
|
||||||
with open(public_schema_path, 'w') as o:
|
# with open(public_schema_path, 'w') as o:
|
||||||
o.write(str(schema_public))
|
# o.write(str(schema_public))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import random
|
import random
|
||||||
|
|
||||||
import factory
|
import factory
|
||||||
from users.models import SchoolClass, SchoolClassMember, License
|
|
||||||
|
from users.models import SchoolClass, SchoolClassMember, License, Team
|
||||||
|
|
||||||
class_types = ['DA', 'KV', 'INF', 'EE']
|
class_types = ['DA', 'KV', 'INF', 'EE']
|
||||||
class_suffix = ['A', 'B', 'C', 'D', 'E']
|
class_suffix = ['A', 'B', 'C', 'D', 'E']
|
||||||
|
|
@ -31,6 +32,14 @@ class SchoolClassFactory(factory.django.DjangoModelFactory):
|
||||||
SchoolClassMember.objects.create(user=user, school_class=self, active=True)
|
SchoolClassMember.objects.create(user=user, school_class=self, active=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TeamFactory(factory.django.DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = Team
|
||||||
|
|
||||||
|
name = factory.Faker('name')
|
||||||
|
is_deleted = False
|
||||||
|
|
||||||
|
|
||||||
class LicenseFactory(factory.django.DjangoModelFactory):
|
class LicenseFactory(factory.django.DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = License
|
model = License
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Generated by Django 2.2.19 on 2021-03-24 21:26
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0025_auto_20210126_1343'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Team',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100, unique=True)),
|
||||||
|
('is_deleted', models.BooleanField(default=False)),
|
||||||
|
('code', models.CharField(blank=True, default=None, max_length=10, null=True, unique=True, verbose_name='Code zum Beitreten')),
|
||||||
|
('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='team',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='members', to='users.Team'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import re
|
|
||||||
from datetime import datetime
|
|
||||||
import string
|
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import AbstractUser, Permission
|
from django.contrib.auth.models import AbstractUser, Permission
|
||||||
|
|
@ -25,6 +25,7 @@ class User(AbstractUser):
|
||||||
hep_group_id = models.PositiveIntegerField(null=True, blank=False)
|
hep_group_id = models.PositiveIntegerField(null=True, blank=False)
|
||||||
license_expiry_date = models.DateField(blank=False, null=True, default=None)
|
license_expiry_date = models.DateField(blank=False, null=True, default=None)
|
||||||
onboarding_visited = models.BooleanField(default=False)
|
onboarding_visited = models.BooleanField(default=False)
|
||||||
|
team = models.ForeignKey('users.Team', on_delete=models.SET_NULL, blank=True, null=True, related_name='members')
|
||||||
|
|
||||||
# for wagtail autocomplete
|
# for wagtail autocomplete
|
||||||
autocomplete_search_field = 'username'
|
autocomplete_search_field = 'username'
|
||||||
|
|
@ -110,14 +111,43 @@ class User(AbstractUser):
|
||||||
return self.get_full_name()
|
return self.get_full_name()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['pk',]
|
ordering = ['pk', ]
|
||||||
|
|
||||||
|
|
||||||
|
class GroupWithCode(models.Model):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
class SchoolClass(models.Model):
|
|
||||||
name = models.CharField(max_length=100, blank=False, null=False, unique=True)
|
name = models.CharField(max_length=100, blank=False, null=False, unique=True)
|
||||||
is_deleted = models.BooleanField(blank=False, null=False, default=False)
|
is_deleted = models.BooleanField(blank=False, null=False, default=False)
|
||||||
|
code = models.CharField('Code zum Beitreten', blank=True, null=True, max_length=10, unique=True, default=None)
|
||||||
|
|
||||||
|
def generate_code(self):
|
||||||
|
letters = string.ascii_lowercase
|
||||||
|
digits = string.digits
|
||||||
|
code = ''.join(random.choice(letters) for i in range(4)) + ''.join(random.choice(digits) for i in range(2))
|
||||||
|
try:
|
||||||
|
self.__class__.objects.get(code=code)
|
||||||
|
self.generate_code()
|
||||||
|
except self.__class__.DoesNotExist:
|
||||||
|
self.code = code.upper()
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Team(GroupWithCode):
|
||||||
|
creator = models.ForeignKey(get_user_model(), null=True, on_delete=models.SET_NULL, blank=True, related_name='+')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Team'
|
||||||
|
verbose_name_plural = 'Teams'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class SchoolClass(GroupWithCode):
|
||||||
users = models.ManyToManyField(get_user_model(), related_name='school_classes', blank=True,
|
users = models.ManyToManyField(get_user_model(), related_name='school_classes', blank=True,
|
||||||
through='users.SchoolClassMember')
|
through='users.SchoolClassMember')
|
||||||
code = models.CharField('Code zum Beitreten', blank=True, null=True, max_length=10, unique=True, default=None)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Schulklasse'
|
verbose_name = 'Schulklasse'
|
||||||
|
|
@ -162,17 +192,6 @@ class SchoolClass(models.Model):
|
||||||
def get_teacher(self):
|
def get_teacher(self):
|
||||||
return self.users.filter(user_roles__role__key='teacher').first()
|
return self.users.filter(user_roles__role__key='teacher').first()
|
||||||
|
|
||||||
def generate_code(self):
|
|
||||||
letters = string.ascii_lowercase
|
|
||||||
digits = string.digits
|
|
||||||
code = ''.join(random.choice(letters) for i in range(4)) + ''.join(random.choice(digits) for i in range(2))
|
|
||||||
try:
|
|
||||||
SchoolClass.objects.get(code=code)
|
|
||||||
self.generate_code()
|
|
||||||
except SchoolClass.DoesNotExist:
|
|
||||||
self.code = code.upper()
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.code == '': # '' can't be unique, so we null it
|
if self.code == '': # '' can't be unique, so we null it
|
||||||
self.code = None
|
self.code = None
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ from graphql_relay import from_global_id
|
||||||
|
|
||||||
from api.utils import get_object
|
from api.utils import get_object
|
||||||
from users.inputs import PasswordUpdateInput
|
from users.inputs import PasswordUpdateInput
|
||||||
from users.models import SchoolClass, UserSetting, User, SchoolClassMember
|
from users.models import SchoolClass, SchoolClassMember, Team
|
||||||
from users.schema import SchoolClassNode
|
from users.schema import SchoolClassNode, TeamNode
|
||||||
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
|
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -209,6 +209,28 @@ class CreateSchoolClass(relay.ClientIDMutation):
|
||||||
return cls(success=True, school_class=school_class)
|
return cls(success=True, school_class=school_class)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateTeam(relay.ClientIDMutation):
|
||||||
|
class Input:
|
||||||
|
name = graphene.String()
|
||||||
|
|
||||||
|
success = graphene.Boolean()
|
||||||
|
team = graphene.Field(TeamNode)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
name = kwargs.get('name')
|
||||||
|
user = info.context.user
|
||||||
|
|
||||||
|
if 'users.can_manage_school_class_content' not in user.get_role_permissions():
|
||||||
|
raise PermissionError()
|
||||||
|
|
||||||
|
team = Team.objects.create(name=name, creator=user)
|
||||||
|
team.generate_code()
|
||||||
|
user.team = team
|
||||||
|
user.save()
|
||||||
|
return cls(success=True, team=team)
|
||||||
|
|
||||||
|
|
||||||
class UpdateOnboardingProgress(graphene.Mutation):
|
class UpdateOnboardingProgress(graphene.Mutation):
|
||||||
success = graphene.Boolean()
|
success = graphene.Boolean()
|
||||||
|
|
||||||
|
|
@ -231,3 +253,4 @@ class ProfileMutations:
|
||||||
update_school_class = UpdateSchoolClass.Field()
|
update_school_class = UpdateSchoolClass.Field()
|
||||||
create_school_class = CreateSchoolClass.Field()
|
create_school_class = CreateSchoolClass.Field()
|
||||||
update_onboarding_progress = UpdateOnboardingProgress.Field()
|
update_onboarding_progress = UpdateOnboardingProgress.Field()
|
||||||
|
create_team = CreateTeam.Field()
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,18 @@ from datetime import datetime
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils.dateformat import format
|
||||||
from django_filters import FilterSet, OrderingFilter
|
from django_filters import FilterSet, OrderingFilter
|
||||||
from graphene import relay, ObjectType
|
from graphene import relay, ObjectType
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
from graphene_django.filter import DjangoFilterConnectionField
|
from graphene_django.filter import DjangoFilterConnectionField
|
||||||
from django.utils.dateformat import format
|
|
||||||
from graphql_relay import to_global_id
|
from graphql_relay import to_global_id
|
||||||
|
|
||||||
from basicknowledge.models import BasicKnowledge
|
from basicknowledge.models import BasicKnowledge
|
||||||
from basicknowledge.queries import InstrumentNode
|
from basicknowledge.queries import InstrumentNode
|
||||||
from books.models import Module, RecentModule
|
from books.models import Module
|
||||||
from books.schema.queries import ModuleNode
|
from books.schema.queries import ModuleNode
|
||||||
from users.models import User, SchoolClass, SchoolClassMember
|
from users.models import User, SchoolClass, SchoolClassMember, Team
|
||||||
|
|
||||||
|
|
||||||
class SchoolClassNode(DjangoObjectType):
|
class SchoolClassNode(DjangoObjectType):
|
||||||
|
|
@ -40,6 +40,19 @@ class SchoolClassNode(DjangoObjectType):
|
||||||
return self.code
|
return self.code
|
||||||
|
|
||||||
|
|
||||||
|
class TeamNode(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = Team
|
||||||
|
filter_fields = ['name']
|
||||||
|
interfaces = (relay.Node,)
|
||||||
|
|
||||||
|
pk = graphene.Int()
|
||||||
|
members = graphene.List('users.schema.UserNode')
|
||||||
|
|
||||||
|
def resolve_pk(self, *args, **kwargs):
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
|
||||||
class RecentModuleFilter(FilterSet):
|
class RecentModuleFilter(FilterSet):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = Module
|
||||||
|
|
@ -60,13 +73,14 @@ class UserNode(DjangoObjectType):
|
||||||
is_teacher = graphene.Boolean()
|
is_teacher = graphene.Boolean()
|
||||||
old_classes = DjangoFilterConnectionField(SchoolClassNode)
|
old_classes = DjangoFilterConnectionField(SchoolClassNode)
|
||||||
recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter)
|
recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter)
|
||||||
|
team = graphene.Field(TeamNode)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
filter_fields = ['username', 'email']
|
filter_fields = ['username', 'email']
|
||||||
only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module',
|
only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module',
|
||||||
'last_topic', 'avatar_url',
|
'last_topic', 'avatar_url',
|
||||||
'selected_class', 'expiry_date', 'onboarding_visited']
|
'selected_class', 'expiry_date', 'onboarding_visited', 'team']
|
||||||
interfaces = (relay.Node,)
|
interfaces = (relay.Node,)
|
||||||
|
|
||||||
def resolve_pk(self, info, **kwargs):
|
def resolve_pk(self, info, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
from graphene import Context
|
||||||
|
from graphene.test import Client
|
||||||
|
|
||||||
|
from api.schema import schema
|
||||||
|
from core.factories import UserFactory, TeacherFactory
|
||||||
|
from users.factories import TeamFactory
|
||||||
|
from users.models import Role
|
||||||
|
from users.services import create_teacher
|
||||||
|
|
||||||
|
ME_QUERY = """
|
||||||
|
query MeQuery {
|
||||||
|
me {
|
||||||
|
team {
|
||||||
|
name
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_MUTATION = """
|
||||||
|
mutation CreateTeamMutation($input: CreateTeamInput!) {
|
||||||
|
createTeam(input: $input) {
|
||||||
|
success
|
||||||
|
team {
|
||||||
|
name
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TeamTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client(schema=schema)
|
||||||
|
self.team_name = 'Fiterativ'
|
||||||
|
self.code = 'AAAA'
|
||||||
|
self.team = TeamFactory(name=self.team_name, code=self.code)
|
||||||
|
self.user = TeacherFactory(username='ueli', team=self.team)
|
||||||
|
self.context = Context(user=self.user)
|
||||||
|
|
||||||
|
def test_team_query(self):
|
||||||
|
result = self.client.execute(ME_QUERY, context=self.context)
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
team = result.get('data').get('me').get('team')
|
||||||
|
self.assertEqual(team.get('name'), self.team_name)
|
||||||
|
self.assertEqual(team.get('code'), self.code)
|
||||||
|
|
||||||
|
def test_join_team_mutation(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def test_create_team_mutation(self):
|
||||||
|
team_name = "Dunder Mifflin"
|
||||||
|
variables = {
|
||||||
|
"input": {
|
||||||
|
"name": team_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.client.execute(CREATE_MUTATION, context=self.context, variables=variables)
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
create_team = result.get('data').get('createTeam')
|
||||||
|
team = create_team.get('team')
|
||||||
|
success = create_team.get('success')
|
||||||
|
self.assertTrue(success)
|
||||||
|
self.assertEqual(team.get('name'), team_name)
|
||||||
|
self.assertIsNotNone(team.get('code'))
|
||||||
Loading…
Reference in New Issue