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.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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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