Skip to content
Snippets Groups Projects
Commit 99827505 authored by florimondmanca's avatar florimondmanca
Browse files

update registration: remove tutoring group, add school, add grade, update tests and api

parent 4164d251
No related branches found
No related tags found
No related merge requests found
"""App signals.""" """Profile signals."""
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from .models import Tutor from .models import Tutor
# Create your signals here.
@receiver(post_save, sender=Tutor) @receiver(post_save, sender=Tutor)
def add_created_tutor_to_staff(sender, instance, created, **kwargs): def add_created_tutor_to_staff(sender, instance, created, **kwargs):
"""When creating a tutor profile, automatically assign it as staff.""" """When creating a tutor profile, automatically assign it as staff.
This way they will be able to connect in the administration site.
Note that they will not have any permission so their view of the admin
site will be empty.
"""
if created: if created:
instance.user.is_staff = True instance.user.is_staff = True
instance.user.save() instance.user.save()
from django.test import TestCase
# Create your tests here.
...@@ -12,10 +12,10 @@ from .models import Registration, EmergencyContact ...@@ -12,10 +12,10 @@ from .models import Registration, EmergencyContact
class RegistrationAdmin(AutocompleteAddressMixin, admin.ModelAdmin): class RegistrationAdmin(AutocompleteAddressMixin, admin.ModelAdmin):
"""Admin panel for registrations.""" """Admin panel for registrations."""
list_display = ('last_name', 'first_name', 'submitted') list_display = ('last_name', 'first_name', 'school', 'grade', 'submitted')
readonly_fields = ('submitted',) readonly_fields = ('submitted',)
list_filter = ('submitted',) list_filter = ('submitted', 'school', 'grade',)
autocomplete_fields = ('emergency_contact',) autocomplete_fields = ('emergency_contact', 'school',)
@admin.register(EmergencyContact) @admin.register(EmergencyContact)
...@@ -23,17 +23,14 @@ class EmergencyContactAdmin(admin.ModelAdmin): ...@@ -23,17 +23,14 @@ class EmergencyContactAdmin(admin.ModelAdmin):
"""Admin panel for emergency contacts.""" """Admin panel for emergency contacts."""
list_display = ('last_name', 'first_name', list_display = ('last_name', 'first_name',
'email', 'home_phone', 'mobile_phone', 'email', 'home_phone', 'mobile_phone', 'related_student',)
'registration_link',)
list_display_links = ('registration_link',)
# necessary to use emergency contact in Registration's admin autocomplete
search_fields = ('last_name', 'first_name',) search_fields = ('last_name', 'first_name',)
def registration_link(self, obj): def related_student(self, obj):
"""Link to the contact's registration object.""" """Link to the contact's registration object."""
url = '/admin/register/registration/{}'.format(obj.registration.pk) url = '/admin/register/registration/{}'.format(obj.registration.pk)
return format_html( return format_html(
'<a href="{}">{}</a>', '<a href="{}">{}</a>',
url, str(obj.registration) url, str(obj.registration)
) )
related_student.short_description = "Inscription administrative associée"
"""Register factories.""" """Register factories."""
import random
import factory import factory
import factory.django import factory.django
from core.factory import AddressFactory
from utils import printable_only from utils import printable_only
from core.factory import AddressFactory
from . import models from . import models
...@@ -48,3 +50,9 @@ class RegistrationFactory(factory.DjangoModelFactory): ...@@ -48,3 +50,9 @@ class RegistrationFactory(factory.DjangoModelFactory):
date_of_birth = factory.Faker('past_date', start_date='-20y') date_of_birth = factory.Faker('past_date', start_date='-20y')
address = factory.SubFactory(AddressFactory) address = factory.SubFactory(AddressFactory)
emergency_contact = factory.SubFactory(EmergencyContactFactory) emergency_contact = factory.SubFactory(EmergencyContactFactory)
@factory.lazy_attribute
def grade(self):
level = random.choice(['Seconde', 'Première', 'Terminale'])
section = random.choice(['S', 'L', 'ES', 'Pro'])
return f'{level} {section}'
# Generated by Django 2.0.4 on 2018-05-12 08:47
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tutoring', '0004_auto_20180429_1159'),
('register', '0015_auto_20180505_1403'),
]
operations = [
migrations.AddField(
model_name='registration',
name='level',
field=models.CharField(blank=True, help_text='Classe du lycée (texte libre)', max_length=200, null=True),
),
migrations.AddField(
model_name='registration',
name='school',
field=models.ForeignKey(blank=True, help_text='Lycée du lycéen', null=True, on_delete=django.db.models.deletion.SET_NULL, to='tutoring.School', verbose_name='lycée'),
),
]
# Generated by Django 2.0.4 on 2018-05-12 08:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('register', '0016_auto_20180512_1047'),
]
operations = [
migrations.RemoveField(
model_name='registration',
name='level',
),
migrations.AddField(
model_name='registration',
name='grade',
field=models.CharField(blank=True, help_text='Classe/filière du lycéen (texte libre)', max_length=200, null=True),
),
]
# Generated by Django 2.0.4 on 2018-05-12 09:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('register', '0017_auto_20180512_1050'),
]
operations = [
migrations.AlterField(
model_name='registration',
name='grade',
field=models.CharField(blank=True, help_text='Classe/filière du lycéen (texte libre)', max_length=200, null=True, verbose_name='classe'),
),
]
...@@ -35,6 +35,18 @@ class Registration(models.Model): ...@@ -35,6 +35,18 @@ class Registration(models.Model):
'core.Address', on_delete=models.CASCADE, blank=True, null=True, 'core.Address', on_delete=models.CASCADE, blank=True, null=True,
verbose_name='adresse', verbose_name='adresse',
help_text="Adresse du lycéen") help_text="Adresse du lycéen")
school = models.ForeignKey(
'tutoring.School', on_delete=models.SET_NULL, null=True, blank=True,
verbose_name='lycée',
help_text='Lycée du lycéen',
)
grade = models.CharField(
max_length=200,
verbose_name='classe',
help_text='Classe/filière du lycéen (texte libre)',
blank=True,
null=True,
)
emergency_contact = models.OneToOneField( emergency_contact = models.OneToOneField(
'EmergencyContact', 'EmergencyContact',
on_delete=models.CASCADE, blank=True, null=True, on_delete=models.CASCADE, blank=True, null=True,
...@@ -68,8 +80,12 @@ class Registration(models.Model): ...@@ -68,8 +80,12 @@ class Registration(models.Model):
def has_create_permission(request): def has_create_permission(request):
return True return True
@property
def full_name(self):
return ' '.join([self.first_name, self.last_name])
def __str__(self): def __str__(self):
return '{o.first_name} {o.last_name} ({o.submitted})'.format(o=self) return '{o.full_name} ({o.submitted})'.format(o=self)
class EmergencyContact(models.Model): class EmergencyContact(models.Model):
......
...@@ -6,7 +6,7 @@ from rest_framework import serializers ...@@ -6,7 +6,7 @@ from rest_framework import serializers
from core.models import Address from core.models import Address
from core.serializers import AddressSerializer from core.serializers import AddressSerializer
from tutoring.models import School, TutoringGroup from tutoring.models import School
from profiles.models import Student from profiles.models import Student
from .models import EmergencyContact, Registration from .models import EmergencyContact, Registration
...@@ -32,19 +32,12 @@ class RegistrationSerializer(serializers.ModelSerializer): ...@@ -32,19 +32,12 @@ class RegistrationSerializer(serializers.ModelSerializer):
write_only=True, write_only=True,
style={'input_type': 'password'}, style={'input_type': 'password'},
) )
tutoring_group = serializers.PrimaryKeyRelatedField(
label='Groupe de tutorat',
help_text='Identifiant du groupe de tutorat du lycéen',
queryset=TutoringGroup.objects.all(),
required=False,
write_only=True,
)
school = serializers.PrimaryKeyRelatedField( school = serializers.PrimaryKeyRelatedField(
label='Lycée', label='Lycée',
help_text='Identifiant du lycée du lycéen', help_text='Lycée du lycéen',
queryset=School.objects.all(), queryset=School.objects.all(),
required=False, required=False,
write_only=True, allow_null=True,
) )
address = AddressSerializer( address = AddressSerializer(
required=False, required=False,
...@@ -58,7 +51,7 @@ class RegistrationSerializer(serializers.ModelSerializer): ...@@ -58,7 +51,7 @@ class RegistrationSerializer(serializers.ModelSerializer):
model = Registration model = Registration
fields = ('id', 'email', 'password', fields = ('id', 'email', 'password',
'first_name', 'last_name', 'date_of_birth', 'phone', 'first_name', 'last_name', 'date_of_birth', 'phone',
'tutoring_group', 'school', 'school', 'grade',
'submitted', 'validated', 'submitted', 'validated',
'address', 'emergency_contact',) 'address', 'emergency_contact',)
...@@ -73,28 +66,6 @@ class RegistrationSerializer(serializers.ModelSerializer): ...@@ -73,28 +66,6 @@ class RegistrationSerializer(serializers.ModelSerializer):
'User with this email already exists') 'User with this email already exists')
return email return email
def validate(self, data):
"""Validate object-level input data.
Tutoring group and school:
- If the group is given, check the school is also given
- The group must belong to the school
"""
data = super().validate(data)
tutoring_group = data.get('tutoring_group', None)
school = data.get('school', None)
if tutoring_group:
if not school:
raise serializers.ValidationError(
'tutoring_group is set: expected school too, none given')
if tutoring_group.school != school:
raise serializers.ValidationError(
'tutoring group must belong to school')
return data
def create(self, validated_data): def create(self, validated_data):
"""Create the registration from validated data. """Create the registration from validated data.
...@@ -104,8 +75,7 @@ class RegistrationSerializer(serializers.ModelSerializer): ...@@ -104,8 +75,7 @@ class RegistrationSerializer(serializers.ModelSerializer):
password = validated_data.pop('password') password = validated_data.pop('password')
address_data = validated_data.pop('address', None) address_data = validated_data.pop('address', None)
emergency_contact_data = validated_data.pop('emergency_contact', None) emergency_contact_data = validated_data.pop('emergency_contact', None)
tutoring_group = validated_data.pop('tutoring_group', None) school = validated_data.get('school', None)
school = validated_data.pop('school', None)
# The following block will create a bunch of objects and save them # The following block will create a bunch of objects and save them
# in the database. We don't want them to be saved separately. # in the database. We don't want them to be saved separately.
...@@ -150,7 +120,6 @@ class RegistrationSerializer(serializers.ModelSerializer): ...@@ -150,7 +120,6 @@ class RegistrationSerializer(serializers.ModelSerializer):
Student.objects.create( Student.objects.create(
user=user, user=user,
school=school, school=school,
tutoring_group=tutoring_group,
registration=registration, registration=registration,
) )
...@@ -160,7 +129,7 @@ class RegistrationSerializer(serializers.ModelSerializer): ...@@ -160,7 +129,7 @@ class RegistrationSerializer(serializers.ModelSerializer):
class StudentRegistrationSerializer(serializers.ModelSerializer): class StudentRegistrationSerializer(serializers.ModelSerializer):
"""Serializer for registration objects, suited to attach to a student.""" """Serializer for registration objects, suited to attach to a student."""
class Meta: class Meta: # noqa
model = Registration model = Registration
fields = ('id', 'submitted', 'validated',) fields = ('id', 'submitted', 'validated',)
......
...@@ -2,17 +2,17 @@ ...@@ -2,17 +2,17 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from rest_framework import status from rest_framework import status
from tests.utils import SimpleAPITestCase
from core.factory import AddressFactory from core.factory import AddressFactory
from core.serializers import AddressSerializer from core.serializers import AddressSerializer
from profiles.models import Student
from register.factory import EmergencyContactFactory, RegistrationFactory from register.factory import EmergencyContactFactory, RegistrationFactory
from register.models import Registration from register.models import Registration
from register.serializers import (EmergencyContactSerializer, from register.serializers import (EmergencyContactSerializer,
RegistrationSerializer) RegistrationSerializer)
from tests.utils import SimpleAPITestCase
from tutoring.factory import SchoolFactory, TutoringGroupFactory from tutoring.factory import SchoolFactory, TutoringGroupFactory
from users.factory import UserFactory from users.factory import UserFactory
from profiles.models import Student
User = get_user_model() User = get_user_model()
...@@ -64,16 +64,17 @@ class RegistrationCreateTest(SimpleAPITestCase): ...@@ -64,16 +64,17 @@ class RegistrationCreateTest(SimpleAPITestCase):
def test_create_simple(self): def test_create_simple(self):
data = self.get_create_data() data = self.get_create_data()
response = self._create(data) response = self._create(data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED,
response.data)
def test_user_and_student_created(self): def test_user_and_student_created(self):
data = self.get_create_data() data = self.get_create_data()
email = data['email'] email = data['email']
response = self._create(data) response = self._create(data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED,
response.data)
obj = Registration.objects.get(pk=response.data['id']) obj = Registration.objects.get(pk=response.data['id'])
user = User.objects.get(email=email) user = User.objects.get(email=email)
...@@ -88,7 +89,8 @@ class RegistrationCreateTest(SimpleAPITestCase): ...@@ -88,7 +89,8 @@ class RegistrationCreateTest(SimpleAPITestCase):
emergency_contact).data emergency_contact).data
response = self._create(data) response = self._create(data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED,
response.data)
# Verify that address and emergency contact were set on registration # Verify that address and emergency contact were set on registration
pk = response.data['id'] pk = response.data['id']
...@@ -97,46 +99,27 @@ class RegistrationCreateTest(SimpleAPITestCase): ...@@ -97,46 +99,27 @@ class RegistrationCreateTest(SimpleAPITestCase):
self.assertEqual(obj.emergency_contact.first_name, self.assertEqual(obj.emergency_contact.first_name,
emergency_contact.first_name) emergency_contact.first_name)
def test_if_email_of_existing_user_returns_bad_request(self): def test_create_with_school(self):
user = UserFactory.create()
data = self.get_create_data()
data['email'] = user.email
response = self._create(data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('email', response.data)
def test_if_tutoring_group_then_school_required(self):
tutoring_group = TutoringGroupFactory.create()
data = self.get_create_data()
data['tutoring_group'] = tutoring_group.pk
response = self._create(data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('non_field_errors', response.data)
def test_if_tutoring_group_belongs_to_school_ok(self):
school = SchoolFactory.create() school = SchoolFactory.create()
tutoring_group = TutoringGroupFactory.create(school=school)
data = self.get_create_data() data = self.get_create_data()
data['tutoring_group'] = tutoring_group.pk
data['school'] = school.pk data['school'] = school.pk
response = self._create(data) response = self._create(data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED,
response.data)
def test_tutoring_group_must_belong_to_school(self): # Verify that school was set on registration and student
tutoring_group = TutoringGroupFactory.create() pk = response.data['id']
school = SchoolFactory.create() obj = Registration.objects.get(pk=pk)
self.assertNotEqual(tutoring_group.school, school) self.assertEqual(obj.school.pk, school.pk)
self.assertEqual(obj.student.school.pk, school.pk)
def test_if_email_of_existing_user_returns_bad_request(self):
user = UserFactory.create()
data = self.get_create_data() data = self.get_create_data()
data['tutoring_group'] = tutoring_group.pk data['email'] = user.email
data['school'] = school.pk
response = self._create(data) response = self._create(data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code,
self.assertIn('non_field_errors', response.data) status.HTTP_400_BAD_REQUEST, response.data)
self.assertIn('email', response.data)
...@@ -47,16 +47,20 @@ class SchoolAdmin(AutocompleteAddressMixin, admin.ModelAdmin): ...@@ -47,16 +47,20 @@ class SchoolAdmin(AutocompleteAddressMixin, admin.ModelAdmin):
list_display = ('__str__', 'uai_code', list_display = ('__str__', 'uai_code',
'get_student_count', 'get_groups_count') 'get_student_count', 'get_groups_count')
search_fields = ('name',)
def get_student_count(self, obj): def get_student_count(self, obj):
"""Display number of students."""
return obj.students.count() return obj.students.count()
get_student_count.short_description = 'Nombre de lycéens' get_student_count.short_description = 'Nombre de lycéens'
def get_groups_count(self, obj): def get_groups_count(self, obj):
"""Display number of tutoring groups."""
return obj.tutoring_groups.count() return obj.tutoring_groups.count()
get_groups_count.short_description = 'Nombre de groupes de tutorat' get_groups_count.short_description = 'Nombre de groupes de tutorat'
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
"""Make the UAI code (school's ID) read-only when editing."""
if obj is not None: if obj is not None:
return ['uai_code'] return ['uai_code']
return [] return []
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment