diff --git a/Pipfile.lock b/Pipfile.lock index 523b77670..f5c4e20aa 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -542,7 +542,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.9.0.post0" }, "pytz": { @@ -691,7 +691,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "sqlparse": { @@ -921,71 +921,71 @@ "toml" ], "hashes": [ - "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433", - "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529", - "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671", - "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e", - "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42", - "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99", - "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327", - "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8", - "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06", - "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874", - "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4", - "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354", - "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1", - "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab", - "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3", - "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b", - "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37", - "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd", - "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f", - "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b", - "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c", - "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b", - "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7", - "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3", - "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808", - "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a", - "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76", - "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469", - "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55", - "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289", - "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc", - "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13", - "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2", - "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30", - "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163", - "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d", - "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c", - "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1", - "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c", - "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2", - "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3", - "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314", - "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0", - "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384", - "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb", - "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c", - "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45", - "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a", - "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24", - "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8", - "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec", - "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56", - "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777", - "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b", - "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f", - "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a", - "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d", - "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9", - "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413", - "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c", - "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b", - "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c" + "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5", + "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf", + "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb", + "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638", + "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4", + "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc", + "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed", + "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a", + "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d", + "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649", + "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c", + "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b", + "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4", + "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443", + "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83", + "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee", + "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e", + "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e", + "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3", + "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0", + "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb", + "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076", + "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb", + "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787", + "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1", + "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e", + "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce", + "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801", + "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764", + "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365", + "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf", + "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6", + "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", + "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002", + "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4", + "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c", + "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8", + "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4", + "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", + "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc", + "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea", + "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4", + "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad", + "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", + "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451", + "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50", + "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779", + "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63", + "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e", + "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", + "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022", + "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d", + "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94", + "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b", + "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d", + "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331", + "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a", + "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0", + "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee", + "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92", + "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a", + "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9" ], "markers": "python_version >= '3.9'", - "version": "==7.6.7" + "version": "==7.6.8" }, "django": { "hashes": [ @@ -1143,12 +1143,12 @@ }, "pytest": { "hashes": [ - "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", - "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" + "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", + "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==8.3.3" + "version": "==8.3.4" }, "pytest-cov": { "hashes": [ @@ -1205,11 +1205,11 @@ }, "rapid-router": { "hashes": [ - "sha256:5019151d61c4873379564b59b0ede6891d9a780fc020c05dcda84681cea99595", - "sha256:d236dd3038d2545ba634625e8019399cdd5f8dcb565d69d0a8eefa8167fcf9bb" + "sha256:817c9b296af5956eb16289751706ffe6b0282be8b8f13fecd892b2cf6112467a", + "sha256:8841f2b8c9d9be81a42c64a4a2cd3a210ee59b83c041ff5feeabcb2ed4ccb9fb" ], "index": "pypi", - "version": "==7.0.1" + "version": "==7.2.0" }, "requests": { "hashes": [ diff --git a/cfl_common/common/models.py b/cfl_common/common/models.py index b589dcdf1..f0a028b86 100644 --- a/cfl_common/common/models.py +++ b/cfl_common/common/models.py @@ -1,4 +1,5 @@ import re +import typing as t from datetime import timedelta from uuid import uuid4 @@ -7,6 +8,10 @@ from django.utils import timezone from django_countries.fields import CountryField +if t.TYPE_CHECKING: + from django.db.models import ManyToManyField + from game.models import Worksheet + class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) @@ -43,9 +48,7 @@ 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) @@ -68,11 +71,7 @@ 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 @@ -130,10 +129,7 @@ class Teacher(models.Model): 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 "") @@ -165,14 +161,10 @@ class SchoolTeacherInvitation(models.Model): null=True, on_delete=models.SET_NULL, ) - invited_teacher_first_name = models.CharField( - max_length=150 - ) # Same as User model + 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 + 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 @@ -222,10 +214,10 @@ def get_queryset(self): class Class(models.Model): + locked_worksheets: "ManyToManyField[Worksheet]" + 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) @@ -249,9 +241,7 @@ 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 @@ -261,13 +251,8 @@ 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") @@ -275,9 +260,7 @@ def get_requests_message(self): + 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 @@ -299,9 +282,7 @@ 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}" @@ -330,9 +311,7 @@ def schoolFactory(self, klass, name, password, login_id=None): ) 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) @@ -391,9 +370,7 @@ 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) diff --git a/portal/forms/teach.py b/portal/forms/teach.py index 9b30310d9..ffd5123fb 100644 --- a/portal/forms/teach.py +++ b/portal/forms/teach.py @@ -3,14 +3,14 @@ from builtins import map, range, str from common.helpers.emails import send_verification_email -from common.models import Student, stripStudentName, UserSession, Teacher +from common.models import Student, Teacher, UserSession, stripStudentName from django import forms from django.contrib.auth import authenticate from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.models import User from django_recaptcha.fields import ReCaptchaField from django_recaptcha.widgets import ReCaptchaV2Invisible -from game.models import Episode +from game.models import Episode, Worksheet from portal.forms.error_messages import INVALID_LOGIN_MESSAGE from portal.helpers.password import PasswordStrength, form_clean_password @@ -18,46 +18,34 @@ class InvitedTeacherForm(forms.Form): - prefix = 'teacher_signup' + prefix = "teacher_signup" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field_name, field in self.fields.items(): - field.widget.attrs['id'] = f'id_teacher_signup-{field_name}' + field.widget.attrs["id"] = f"id_teacher_signup-{field_name}" teacher_password = forms.CharField( help_text="Enter a password", - widget=forms.PasswordInput( - attrs={"autocomplete": "off", "placeholder": "Password"} - ), + widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}), ) teacher_confirm_password = forms.CharField( help_text="Repeat password", - widget=forms.PasswordInput( - attrs={"autocomplete": "off", "placeholder": "Repeat password"} - ), + widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Repeat password"}), ) - consent_ticked = forms.BooleanField( - widget=forms.CheckboxInput(), initial=False, required=True - ) - newsletter_ticked = forms.BooleanField( - widget=forms.CheckboxInput(), initial=False, required=False - ) + consent_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=True) + newsletter_ticked = forms.BooleanField(widget=forms.CheckboxInput(), initial=False, required=False) def clean_teacher_password(self): - return form_clean_password( - self, "teacher_password", PasswordStrength.TEACHER - ) + return form_clean_password(self, "teacher_password", PasswordStrength.TEACHER) def clean(self): if any(self.errors): return password = self.cleaned_data.get("teacher_password", None) - confirm_password = self.cleaned_data.get( - "teacher_confirm_password", None - ) + confirm_password = self.cleaned_data.get("teacher_confirm_password", None) check_passwords(password, confirm_password) @@ -68,22 +56,16 @@ class TeacherSignupForm(InvitedTeacherForm): teacher_first_name = forms.CharField( help_text="Enter your first name", max_length=100, - widget=forms.TextInput( - attrs={"autocomplete": "off", "placeholder": "First name"} - ), + widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "First name"}), ) teacher_last_name = forms.CharField( help_text="Enter your last name", max_length=100, - widget=forms.TextInput( - attrs={"autocomplete": "off", "placeholder": "Last name"} - ), + widget=forms.TextInput(attrs={"autocomplete": "off", "placeholder": "Last name"}), ) teacher_email = forms.EmailField( help_text="Enter your email address", - widget=forms.EmailInput( - attrs={"autocomplete": "off", "placeholder": "Email address"} - ), + widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}), ) captcha = ReCaptchaField(widget=ReCaptchaV2Invisible) @@ -92,37 +74,27 @@ class TeacherSignupForm(InvitedTeacherForm): class TeacherEditAccountForm(forms.Form): first_name = forms.CharField( max_length=100, - widget=forms.TextInput( - attrs={"placeholder": "First name", "class": "fName"} - ), + widget=forms.TextInput(attrs={"placeholder": "First name", "class": "fName"}), help_text="First name", ) last_name = forms.CharField( max_length=100, - widget=forms.TextInput( - attrs={"placeholder": "Last name", "class": "lName"} - ), + widget=forms.TextInput(attrs={"placeholder": "Last name", "class": "lName"}), help_text="Last name", ) email = forms.EmailField( required=False, - widget=forms.EmailInput( - attrs={"placeholder": "New email address (optional)"} - ), + widget=forms.EmailInput(attrs={"placeholder": "New email address (optional)"}), help_text="New email address (optional)", ) password = forms.CharField( required=False, - widget=forms.PasswordInput( - attrs={"placeholder": "New password (optional)"} - ), + widget=forms.PasswordInput(attrs={"placeholder": "New password (optional)"}), help_text="New password (optional)", ) confirm_password = forms.CharField( required=False, - widget=forms.PasswordInput( - attrs={"placeholder": "Confirm new password (optional)"} - ), + widget=forms.PasswordInput(attrs={"placeholder": "Confirm new password (optional)"}), help_text="Confirm new password (optional)", ) current_password = forms.CharField( @@ -149,9 +121,7 @@ def clean(self): return self.cleaned_data - def check_password_errors( - self, password, confirm_password, current_password - ): + def check_password_errors(self, password, confirm_password, current_password): check_passwords(password, confirm_password) if not self.user.check_password(current_password): @@ -160,15 +130,11 @@ def check_password_errors( class TeacherLoginForm(AuthenticationForm): username = forms.EmailField( - widget=forms.EmailInput( - attrs={"autocomplete": "off", "placeholder": "Email address"} - ), + widget=forms.EmailInput(attrs={"autocomplete": "off", "placeholder": "Email address"}), help_text="Enter your email address", ) password = forms.CharField( - widget=forms.PasswordInput( - attrs={"autocomplete": "off", "placeholder": "Password"} - ), + widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Password"}), help_text="Enter your password", ) @@ -202,9 +168,7 @@ def find_user(self, email, user): users = User.objects.filter(email=email) for result in users: - if hasattr(result, "userprofile") and hasattr( - result.userprofile, "teacher" - ): + if hasattr(result, "userprofile") and hasattr(result.userprofile, "teacher"): user = result break @@ -286,9 +250,7 @@ class ClassEditForm(forms.Form): [ ( str(hours), - "Allow external requests to this class for the next " - + str(hours) - + " hours", + "Allow external requests to this class for the next " + str(hours) + " hours", ) for hours in range(4, 28, 4) ] @@ -297,9 +259,7 @@ class ClassEditForm(forms.Form): [ ( str(days * 24), - "Allow external requests to this class for the next " - + str(days) - + " days", + "Allow external requests to this class for the next " + str(days) + " days", ) for days in range(2, 5) ] @@ -333,18 +293,22 @@ class ClassLevelControlForm(forms.Form): def __init__(self, *args, **kwargs): super(ClassLevelControlForm, self).__init__(*args, **kwargs) - episodes = Episode.objects.filter(pk__in=range(1, 22)) + episodes = Episode.objects.filter(pk__in=range(1, 25)) for episode in episodes: - levels = [] - + choices = [] for level in episode.levels: - levels.append(level) + try: + choices.append((f"worksheet:{level.after_worksheet.id}", episode.name)) + except Worksheet.DoesNotExist: + pass + choices.append((f"level:{level.id}", level.name)) - levels_choices = [(level.id, level.name) for level in levels] + for worksheet in episode.worksheets.filter(before_level__isnull=True): + choices.append((f"worksheet:{worksheet.id}", episode.name)) self.fields[episode.name] = forms.MultipleChoiceField( - choices=itertools.chain(levels_choices), + choices=itertools.chain(choices), widget=forms.CheckboxSelectMultiple(), required=False, ) @@ -364,9 +328,7 @@ def __init__(self, teachers, *args, **kwargs): teacher_choices.append( ( teacher.id, - teacher.new_user.first_name - + " " - + teacher.new_user.last_name, + teacher.new_user.first_name + " " + teacher.new_user.last_name, ) ) super(ClassMoveForm, self).__init__(*args, **kwargs) @@ -389,24 +351,14 @@ def clean_name(self): name = stripStudentName(self.cleaned_data.get("name", "")) if name == "": - raise forms.ValidationError( - "'" - + self.cleaned_data.get("name", "") - + "' is not a valid name" - ) + raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name") if re.match(re.compile("^[\w -]+$"), name) is None: - raise forms.ValidationError( - "Names may only contain letters, numbers, dashes, underscores, and spaces." - ) + raise forms.ValidationError("Names may only contain letters, numbers, dashes, underscores, and spaces.") - students = Student.objects.filter( - class_field=self.klass, new_user__first_name__iexact=name - ) + students = Student.objects.filter(class_field=self.klass, new_user__first_name__iexact=name) if students.exists() and students[0] != self.student: - raise forms.ValidationError( - "There is already a student called '" + name + "' in this class" - ) + raise forms.ValidationError("There is already a student called '" + name + "' in this class") return name @@ -415,16 +367,12 @@ class TeacherSetStudentPass(forms.Form): password = forms.CharField( label="New password", help_text="Enter new password", - widget=forms.PasswordInput( - attrs={"autocomplete": "off", "placeholder": "Enter new password"} - ), + widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Enter new password"}), ) confirm_password = forms.CharField( label="Confirm new password", help_text="Confirm new password", - widget=forms.PasswordInput( - attrs={"autocomplete": "off", "placeholder": "Confirm new password"} - ), + widget=forms.PasswordInput(attrs={"autocomplete": "off", "placeholder": "Confirm new password"}), ) def clean_password(self): @@ -468,32 +416,19 @@ def validateStudentNames(klass, names): def find_clashes(names, students, clashes_found, validationErrors): for name in names: - if ( - students.filter(new_user__first_name__iexact=name).exists() - and name not in clashes_found - ): + if students.filter(new_user__first_name__iexact=name).exists() and name not in clashes_found: validationErrors.append( - forms.ValidationError( - "There is already a student called '" - + name - + "' in this class" - ) + forms.ValidationError("There is already a student called '" + name + "' in this class") ) clashes_found.append(name) def find_duplicates(names, lower_names, validationErrors): duplicates_found = [] - for duplicate in [ - name for name in names if lower_names.count(name.lower()) > 1 - ]: + for duplicate in [name for name in names if lower_names.count(name.lower()) > 1]: if duplicate not in duplicates_found: validationErrors.append( - forms.ValidationError( - "You cannot add more than one student called '" - + duplicate - + "'" - ) + forms.ValidationError("You cannot add more than one student called '" + duplicate + "'") ) duplicates_found.append(duplicate) @@ -512,9 +447,7 @@ def find_illegal_characters(names, validationErrors): def check_passwords(password, confirm_password): if password is not None and password != confirm_password: - raise forms.ValidationError( - "The password and the confirmation password do not match" - ) + raise forms.ValidationError("The password and the confirmation password do not match") class TeacherMoveStudentsDestinationForm(forms.Form): @@ -539,9 +472,7 @@ def __init__(self, classes, *args, **kwargs): + klass.teacher.new_user.last_name, ) ) - super(TeacherMoveStudentsDestinationForm, self).__init__( - *args, **kwargs - ) + super(TeacherMoveStudentsDestinationForm, self).__init__(*args, **kwargs) self.fields["new_class"].choices = class_choices @@ -558,28 +489,20 @@ class TeacherMoveStudentDisambiguationForm(forms.Form): ) name = forms.CharField( label="Name", - widget=forms.TextInput( - attrs={"placeholder": "Name", "style": "margin : 0px"} - ), + widget=forms.TextInput(attrs={"placeholder": "Name", "style": "margin : 0px"}), ) def clean_name(self): name = stripStudentName(self.cleaned_data.get("name", "")) if name == "": - raise forms.ValidationError( - "'" - + self.cleaned_data.get("name", "") - + "' is not a valid name" - ) + raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name") return name class BaseTeacherMoveStudentsDisambiguationFormSet(forms.BaseFormSet): def __init__(self, destination, *args, **kwargs): self.destination = destination - super(BaseTeacherMoveStudentsDisambiguationFormSet, self).__init__( - *args, **kwargs - ) + super(BaseTeacherMoveStudentsDisambiguationFormSet, self).__init__(*args, **kwargs) def clean(self): if any(self.errors): @@ -608,34 +531,24 @@ class TeacherDismissStudentsForm(forms.Form): ) name = forms.CharField( help_text="New student name", - widget=forms.TextInput( - attrs={"placeholder": "Enter new student name", "class": "m-0"} - ), + widget=forms.TextInput(attrs={"placeholder": "Enter new student name", "class": "m-0"}), ) email = forms.EmailField( label="Email", help_text="New email address", - widget=forms.EmailInput( - attrs={"placeholder": "Enter email address", "class": "m-0"} - ), + widget=forms.EmailInput(attrs={"placeholder": "Enter email address", "class": "m-0"}), ) confirm_email = forms.EmailField( label="Confirm Email", help_text="Confirm email address", - widget=forms.EmailInput( - attrs={"placeholder": "Confirm email address", "class": "m-0"} - ), + widget=forms.EmailInput(attrs={"placeholder": "Confirm email address", "class": "m-0"}), ) def clean_name(self): name = stripStudentName(self.cleaned_data.get("name", "")) if name == "": - raise forms.ValidationError( - "'" - + self.cleaned_data.get("name", "") - + "' is not a valid name" - ) + raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name") return name @@ -706,17 +619,9 @@ def clean_name(self): name = stripStudentName(self.cleaned_data.get("name", "")) if name == "": - raise forms.ValidationError( - "'" - + self.cleaned_data.get("name", "") - + "' is not a valid name" - ) + raise forms.ValidationError("'" + self.cleaned_data.get("name", "") + "' is not a valid name") - if Student.objects.filter( - class_field=self.klass, new_user__first_name__iexact=name - ).exists(): - raise forms.ValidationError( - "There is already a student called '" + name + "' in this class" - ) + if Student.objects.filter(class_field=self.klass, new_user__first_name__iexact=name).exists(): + raise forms.ValidationError("There is already a student called '" + name + "' in this class") return name diff --git a/portal/templates/portal/teach/teacher_edit_class.html b/portal/templates/portal/teach/teacher_edit_class.html index 912706e04..c09afe009 100644 --- a/portal/templates/portal/teach/teacher_edit_class.html +++ b/portal/templates/portal/teach/teacher_edit_class.html @@ -147,20 +147,21 @@
{{episode.name}}
- {% if episode.first_level > 1009 %} - Levels {{episode.first_level|stringformat:"i"|slice:"2:4"}}-{{episode.last_level|stringformat:"i"|slice:"2:4"}} - {% else %} - {% if episode.last_level > 1009 %} - Levels {{episode.first_level|stringformat:"i"|slice:"3:4"}}-{{episode.last_level|stringformat:"i"|slice:"2:4"}} + {% if episode.levels %} + {% if episode.first_level > 1009 %} + Levels {{episode.first_level|stringformat:"i"|slice:"2:4"}}-{{episode.last_level|stringformat:"i"|slice:"2:4"}} {% else %} - Levels {{episode.first_level|stringformat:"i"|slice:"3:4"}}-{{episode.last_level|stringformat:"i"|slice:"3:4"}} + {% if episode.last_level > 1009 %} + Levels {{episode.first_level|stringformat:"i"|slice:"3:4"}}-{{episode.last_level|stringformat:"i"|slice:"2:4"}} + {% else %} + Levels {{episode.first_level|stringformat:"i"|slice:"3:4"}}-{{episode.last_level|stringformat:"i"|slice:"3:4"}} + {% endif %} {% endif %} {% endif %}{{episode.name}}
+ {% endif %} + {% endfor %} {% if level.name < 1010 %}{{level.name|stringformat:"i"|slice:"3:4"}}: {{level.title.strip | safe}}
{% else %}{{level.name|stringformat:"i"|slice:"2:4"}}: {{level.title.strip | safe}}
{% endif %} {% endfor %} + {% for worksheet in episode.worksheets %} + {% if not worksheet.before_level %} +{{episode.name}}{% if episode.worksheets|length > 1 %} pt. {{ forloop.counter }}{% endif %}
+ {% endif %} + {% endfor %}