Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Create necessary fields for CSE #2310

Merged
merged 2 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions cfl_common/common/migrations/0052_add_cse_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Generated by Django 3.2.25 on 2024-05-22 11:30

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('common', '0051_verify_returning_users'),
]

operations = [
migrations.AddField(
model_name='schoolteacherinvitation',
name='_invited_teacher_email',
field=models.BinaryField(blank=True, null=True),
),
migrations.AddField(
model_name='schoolteacherinvitation',
name='_invited_teacher_first_name',
field=models.BinaryField(blank=True, null=True),
),
migrations.AddField(
model_name='schoolteacherinvitation',
name='_invited_teacher_last_name',
field=models.BinaryField(blank=True, null=True),
),
migrations.AddField(
model_name='userprofile',
name='_email',
field=models.BinaryField(blank=True, null=True),
),
migrations.AddField(
model_name='userprofile',
name='_first_name',
field=models.BinaryField(blank=True, null=True),
),
migrations.AddField(
model_name='userprofile',
name='_last_name',
field=models.BinaryField(blank=True, null=True),
),
migrations.AddField(
model_name='userprofile',
name='_username',
field=models.BinaryField(blank=True, null=True),
),
migrations.AddField(
model_name='userprofile',
name='email',
field=models.CharField(blank=True, max_length=200, null=True),
),
migrations.AddField(
model_name='userprofile',
name='first_name',
field=models.CharField(blank=True, max_length=200, null=True),
),
migrations.AddField(
model_name='userprofile',
name='last_name',
field=models.CharField(blank=True, max_length=200, null=True),
),
migrations.AddField(
model_name='userprofile',
name='username',
field=models.CharField(blank=True, max_length=200, null=True),
),
]
184 changes: 148 additions & 36 deletions cfl_common/common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,25 @@ class UserProfile(models.Model):
developer = models.BooleanField(default=False)
is_verified = models.BooleanField(default=False)

# Holds the user's earned kurono badges. This information has to be on the UserProfile as the Avatar objects are
# deleted every time the Game gets deleted.
# This is a string showing which badges in which worksheets have been earned. The format is "X:Y" where X is the
# worksheet ID and Y is the badge ID. This repeats for all badges and each pair is comma-separated.
# Holds the user's earned kurono badges. This information has to be on the
# UserProfile as the Avatar objects are deleted every time the Game gets
# deleted.
# This is a string showing which badges in which worksheets have been
# earned. The format is "X:Y" where X is the worksheet ID and Y is the
# badge ID. This repeats for all badges and each pair is comma-separated.
aimmo_badges = models.CharField(max_length=200, null=True, blank=True)

# TODO: Make not nullable once data has been transferred
first_name = models.CharField(max_length=200, null=True, blank=True)
_first_name = models.BinaryField(null=True, blank=True)
last_name = models.CharField(max_length=200, null=True, blank=True)
_last_name = models.BinaryField(null=True, blank=True)
email = models.CharField(max_length=200, null=True, blank=True)
_email = models.BinaryField(null=True, blank=True)
# TODO: Make not nullable once data has been transferred
username = models.CharField(max_length=200, null=True, blank=True)
_username = models.BinaryField(null=True, blank=True)

def __str__(self):
return f"{self.user.first_name} {self.user.last_name}"

Expand All @@ -38,7 +51,9 @@ def get_queryset(self):

class School(models.Model):
name = models.CharField(max_length=200, unique=True)
country = CountryField(blank_label="(select country)", null=True, blank=True)
country = CountryField(
blank_label="(select country)", null=True, blank=True
)
# TODO: Create an Address model to house address details
county = models.CharField(max_length=50, blank=True, null=True)
creation_time = models.DateTimeField(default=timezone.now, null=True)
Expand All @@ -61,7 +76,11 @@ def classes(self):

def admins(self):
teachers = self.teacher_school.all()
return [teacher for teacher in teachers if teacher.is_admin] if teachers else None
return (
[teacher for teacher in teachers if teacher.is_admin]
if teachers
else None
)

def anonymise(self):
self.name = uuid4().hex
Expand All @@ -72,7 +91,11 @@ def anonymise(self):
class TeacherModelManager(models.Manager):
def factory(self, first_name, last_name, email, password):
user = User.objects.create_user(
username=email, email=email, password=password, first_name=first_name, last_name=last_name
username=email,
email=email,
password=password,
first_name=first_name,
last_name=last_name,
)

user_profile = UserProfile.objects.create(user=user)
Expand All @@ -86,20 +109,39 @@ def get_queryset(self):

class Teacher(models.Model):
user = models.OneToOneField(UserProfile, on_delete=models.CASCADE)
new_user = models.OneToOneField(User, related_name="new_teacher", null=True, blank=True, on_delete=models.CASCADE)
school = models.ForeignKey(School, related_name="teacher_school", null=True, blank=True, on_delete=models.SET_NULL)
new_user = models.OneToOneField(
User,
related_name="new_teacher",
null=True,
blank=True,
on_delete=models.CASCADE,
)
school = models.ForeignKey(
School,
related_name="teacher_school",
null=True,
blank=True,
on_delete=models.SET_NULL,
)
is_admin = models.BooleanField(default=False)
blocked_time = models.DateTimeField(null=True, blank=True)
invited_by = models.ForeignKey(
"self", related_name="invited_teachers", null=True, blank=True, on_delete=models.SET_NULL
"self",
related_name="invited_teachers",
null=True,
blank=True,
on_delete=models.SET_NULL,
)

objects = TeacherModelManager()

def teaches(self, userprofile):
if hasattr(userprofile, "student"):
student = userprofile.student
return not student.is_independent() and student.class_field.teacher == self
return (
not student.is_independent()
and student.class_field.teacher == self
)

def has_school(self):
return self.school is not (None or "")
Expand All @@ -119,11 +161,32 @@ def get_queryset(self):

class SchoolTeacherInvitation(models.Model):
token = models.CharField(max_length=32)
school = models.ForeignKey(School, related_name="teacher_invitations", null=True, on_delete=models.SET_NULL)
from_teacher = models.ForeignKey(Teacher, related_name="school_invitations", null=True, on_delete=models.SET_NULL)
invited_teacher_first_name = models.CharField(max_length=150) # Same as User model
invited_teacher_last_name = models.CharField(max_length=150) # Same as User model
school = models.ForeignKey(
School,
related_name="teacher_invitations",
null=True,
on_delete=models.SET_NULL,
)
from_teacher = models.ForeignKey(
Teacher,
related_name="school_invitations",
null=True,
on_delete=models.SET_NULL,
)
invited_teacher_first_name = models.CharField(
max_length=150
) # Same as User model
# TODO: Make not nullable once data has been transferred
_invited_teacher_first_name = models.BinaryField(null=True, blank=True)
invited_teacher_last_name = models.CharField(
max_length=150
) # Same as User model
# TODO: Make not nullable once data has been transferred
_invited_teacher_last_name = models.BinaryField(null=True, blank=True)
# TODO: Switch to a CharField to be able to hold hashed value
invited_teacher_email = models.EmailField() # Same as User model
# TODO: Make not nullable once data has been transferred
_invited_teacher_email = models.BinaryField(null=True, blank=True)
invited_teacher_is_admin = models.BooleanField(default=False)
expiry = models.DateTimeField()
creation_time = models.DateTimeField(default=timezone.now, null=True)
Expand Down Expand Up @@ -168,15 +231,21 @@ def get_queryset(self):

class Class(models.Model):
name = models.CharField(max_length=200)
teacher = models.ForeignKey(Teacher, related_name="class_teacher", on_delete=models.CASCADE)
teacher = models.ForeignKey(
Teacher, related_name="class_teacher", on_delete=models.CASCADE
)
access_code = models.CharField(max_length=5, null=True)
classmates_data_viewable = models.BooleanField(default=False)
always_accept_requests = models.BooleanField(default=False)
accept_requests_until = models.DateTimeField(null=True)
creation_time = models.DateTimeField(default=timezone.now, null=True)
is_active = models.BooleanField(default=True)
created_by = models.ForeignKey(
Teacher, null=True, blank=True, related_name="created_classes", on_delete=models.SET_NULL
Teacher,
null=True,
blank=True,
related_name="created_classes",
on_delete=models.SET_NULL,
)

objects = ClassModelManager()
Expand All @@ -188,7 +257,9 @@ def __str__(self):
def active_game(self):
games = self.game_set.filter(game_class=self, is_archived=False)
if len(games) >= 1:
assert len(games) == 1 # there should NOT be more than one active game
assert (
len(games) == 1
) # there should NOT be more than one active game
return games[0]
return None

Expand All @@ -198,16 +269,23 @@ def has_students(self):

def get_requests_message(self):
if self.always_accept_requests:
external_requests_message = "This class is currently set to always accept requests."
elif self.accept_requests_until is not None and (self.accept_requests_until - timezone.now()) >= timedelta():
external_requests_message = (
"This class is currently set to always accept requests."
)
elif (
self.accept_requests_until is not None
and (self.accept_requests_until - timezone.now()) >= timedelta()
):
external_requests_message = (
"This class is accepting external requests until "
+ self.accept_requests_until.strftime("%d-%m-%Y %H:%M")
+ " "
+ timezone.get_current_timezone_name()
)
else:
external_requests_message = "This class is not currently accepting external requests."
external_requests_message = (
"This class is not currently accepting external requests."
)

return external_requests_message

Expand All @@ -229,7 +307,9 @@ class UserSession(models.Model):
login_time = models.DateTimeField(default=timezone.now)
school = models.ForeignKey(School, null=True, on_delete=models.SET_NULL)
class_field = models.ForeignKey(Class, null=True, on_delete=models.SET_NULL)
login_type = models.CharField(max_length=100, null=True) # for student login
login_type = models.CharField(
max_length=100, null=True
) # for student login

def __str__(self):
return f"{self.user} login: {self.login_time} type: {self.login_type}"
Expand All @@ -243,27 +323,54 @@ def get_random_username(self):
return random_username

def schoolFactory(self, klass, name, password, login_id=None):
user = User.objects.create_user(username=self.get_random_username(), password=password, first_name=name)
user = User.objects.create_user(
username=self.get_random_username(),
password=password,
first_name=name,
)
user_profile = UserProfile.objects.create(user=user)

return Student.objects.create(class_field=klass, user=user_profile, new_user=user, login_id=login_id)
return Student.objects.create(
class_field=klass,
user=user_profile,
new_user=user,
login_id=login_id,
)

def independentStudentFactory(self, name, email, password):
user = User.objects.create_user(username=email, email=email, password=password, first_name=name)
user = User.objects.create_user(
username=email, email=email, password=password, first_name=name
)

user_profile = UserProfile.objects.create(user=user)

return Student.objects.create(user=user_profile, new_user=user)


class Student(models.Model):
class_field = models.ForeignKey(Class, related_name="students", null=True, blank=True, on_delete=models.CASCADE)
class_field = models.ForeignKey(
Class,
related_name="students",
null=True,
blank=True,
on_delete=models.CASCADE,
)
# hashed uuid used for the unique direct login url
login_id = models.CharField(max_length=64, null=True)
user = models.OneToOneField(UserProfile, on_delete=models.CASCADE)
new_user = models.OneToOneField(User, related_name="new_student", null=True, blank=True, on_delete=models.CASCADE)
new_user = models.OneToOneField(
User,
related_name="new_student",
null=True,
blank=True,
on_delete=models.CASCADE,
)
pending_class_request = models.ForeignKey(
Class, related_name="class_request", null=True, blank=True, on_delete=models.SET_NULL
Class,
related_name="class_request",
null=True,
blank=True,
on_delete=models.SET_NULL,
)
blocked_time = models.DateTimeField(null=True, blank=True)

Expand Down Expand Up @@ -309,16 +416,19 @@ class JoinReleaseStudent(models.Model):
JOIN = "join"
RELEASE = "release"

student = models.ForeignKey(Student, related_name="student", on_delete=models.CASCADE)
student = models.ForeignKey(
Student, related_name="student", on_delete=models.CASCADE
)
# either "release" or "join"
action_type = models.CharField(max_length=64)
action_time = models.DateTimeField(default=timezone.now)


class DailyActivity(models.Model):
"""
A model to record sets of daily activity. Currently used to record the amount of
student details download clicks, through the CSV and login cards methods, per day.
A model to record sets of daily activity. Currently used to record the
amount of student details download clicks, through the CSV and login
cards methods, per day.
"""

date = models.DateField(default=timezone.now)
Expand All @@ -342,8 +452,8 @@ def __str__(self):

class TotalActivity(models.Model):
"""
A model to record total activity. Meant to only have one entry which records all total activity.
An example of this is total ever registrations.
A model to record total activity. Meant to only have one entry which
records all total activity. An example of this is total ever registrations.
"""

teacher_registrations = models.PositiveIntegerField(default=0)
Expand All @@ -361,9 +471,11 @@ def __str__(self):

class DynamicElement(models.Model):
"""
This model is meant to allow us to quickly update some elements dynamically on the website without having to
redeploy everytime. For example, if a maintenance banner needs to be added, we check the box in the Django admin
panel, edit the text and it'll show immediately on the website.
This model is meant to allow us to quickly update some elements
dynamically on the website without having to redeploy everytime. For
example, if a maintenance banner needs to be added, we check the box in
the Django admin panel, edit the text and it'll show immediately on the
website.
"""

name = models.CharField(max_length=64, unique=True, editable=False)
Expand Down
Loading