Add team model, team node and a mutation for creating a team

This commit is contained in:
Ramon Wenger 2021-03-24 23:43:36 +01:00
parent ea3a404ae7
commit 4e1ab68a52
8 changed files with 203 additions and 27 deletions

View File

@ -10,6 +10,8 @@ from faker import Faker
from wagtail.documents.models import get_document_model
from wagtail.images import get_image_model
from users.models import Role, UserRole
fake = Faker('de_CH')
@ -48,7 +50,7 @@ class DummyImageFactory(factory.DjangoModelFactory):
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = get_user_model()
django_get_or_create = ('username', )
django_get_or_create = ('username',)
first_name = factory.LazyAttribute(lambda x: fake.first_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):
self.set_password('test')
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)

View File

@ -16,6 +16,6 @@ class Command(BaseCommand):
with open(schema_path, 'w') as o:
o.write(str(schema))
with open(public_schema_path, 'w') as o:
o.write(str(schema_public))
# with open(public_schema_path, 'w') as o:
# o.write(str(schema_public))

View File

@ -1,7 +1,8 @@
import random
import factory
from users.models import SchoolClass, SchoolClassMember, License
from users.models import SchoolClass, SchoolClassMember, License, Team
class_types = ['DA', 'KV', 'INF', 'EE']
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)
class TeamFactory(factory.django.DjangoModelFactory):
class Meta:
model = Team
name = factory.Faker('name')
is_deleted = False
class LicenseFactory(factory.django.DjangoModelFactory):
class Meta:
model = License

View File

@ -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'),
),
]

View File

@ -1,7 +1,7 @@
import re
from datetime import datetime
import string
import random
import re
import string
from datetime import datetime
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser, Permission
@ -25,6 +25,7 @@ class User(AbstractUser):
hep_group_id = models.PositiveIntegerField(null=True, blank=False)
license_expiry_date = models.DateField(blank=False, null=True, default=None)
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
autocomplete_search_field = 'username'
@ -110,14 +111,43 @@ class User(AbstractUser):
return self.get_full_name()
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)
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,
through='users.SchoolClassMember')
code = models.CharField('Code zum Beitreten', blank=True, null=True, max_length=10, unique=True, default=None)
class Meta:
verbose_name = 'Schulklasse'
@ -162,17 +192,6 @@ class SchoolClass(models.Model):
def get_teacher(self):
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):
if self.code == '': # '' can't be unique, so we null it
self.code = None

View File

@ -7,8 +7,8 @@ from graphql_relay import from_global_id
from api.utils import get_object
from users.inputs import PasswordUpdateInput
from users.models import SchoolClass, UserSetting, User, SchoolClassMember
from users.schema import SchoolClassNode
from users.models import SchoolClass, SchoolClassMember, Team
from users.schema import SchoolClassNode, TeamNode
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
@ -209,6 +209,28 @@ class CreateSchoolClass(relay.ClientIDMutation):
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):
success = graphene.Boolean()
@ -231,3 +253,4 @@ class ProfileMutations:
update_school_class = UpdateSchoolClass.Field()
create_school_class = CreateSchoolClass.Field()
update_onboarding_progress = UpdateOnboardingProgress.Field()
create_team = CreateTeam.Field()

View File

@ -2,18 +2,18 @@ from datetime import datetime
import graphene
from django.db.models import Q
from django.utils.dateformat import format
from django_filters import FilterSet, OrderingFilter
from graphene import relay, ObjectType
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from django.utils.dateformat import format
from graphql_relay import to_global_id
from basicknowledge.models import BasicKnowledge
from basicknowledge.queries import InstrumentNode
from books.models import Module, RecentModule
from books.models import Module
from books.schema.queries import ModuleNode
from users.models import User, SchoolClass, SchoolClassMember
from users.models import User, SchoolClass, SchoolClassMember, Team
class SchoolClassNode(DjangoObjectType):
@ -40,6 +40,19 @@ class SchoolClassNode(DjangoObjectType):
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 Meta:
model = Module
@ -60,13 +73,14 @@ class UserNode(DjangoObjectType):
is_teacher = graphene.Boolean()
old_classes = DjangoFilterConnectionField(SchoolClassNode)
recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter)
team = graphene.Field(TeamNode)
class Meta:
model = User
filter_fields = ['username', 'email']
only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module',
'last_topic', 'avatar_url',
'selected_class', 'expiry_date', 'onboarding_visited']
'selected_class', 'expiry_date', 'onboarding_visited', 'team']
interfaces = (relay.Node,)
def resolve_pk(self, info, **kwargs):

View File

@ -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'))