|
4 | 4 | """
|
5 | 5 | from __future__ import unicode_literals
|
6 | 6 |
|
| 7 | +import logging |
7 | 8 | import uuid
|
8 | 9 |
|
| 10 | +from opaque_keys import InvalidKeyError |
| 11 | +from opaque_keys.edx.keys import CourseKey |
9 | 12 | from solo.models import SingletonModel
|
10 | 13 |
|
11 | 14 | from django.core.exceptions import ValidationError
|
12 | 15 | from django.db import models
|
| 16 | +from django.db.models import Q |
13 | 17 | from django.utils.translation import gettext_lazy as _
|
14 | 18 |
|
15 | 19 | from model_utils.models import TimeStampedModel
|
16 | 20 |
|
17 | 21 | from taxonomy.choices import UserGoal
|
| 22 | +from taxonomy.providers.utils import get_course_metadata_provider |
| 23 | + |
| 24 | +LOGGER = logging.getLogger(__name__) |
18 | 25 |
|
19 | 26 |
|
20 | 27 | class Skill(TimeStampedModel):
|
@@ -1144,3 +1151,132 @@ class Meta:
|
1144 | 1151 | app_label = 'taxonomy'
|
1145 | 1152 | verbose_name = 'B2C Job Allow List entry'
|
1146 | 1153 | verbose_name_plural = 'B2C Job Allow List entries'
|
| 1154 | + |
| 1155 | + |
| 1156 | +class SkillValidationConfiguration(TimeStampedModel): |
| 1157 | + """ |
| 1158 | + Model to store the configuration for disabling skill validation for a course or organization. |
| 1159 | + """ |
| 1160 | + |
| 1161 | + course_key = models.CharField( |
| 1162 | + max_length=255, |
| 1163 | + null=True, |
| 1164 | + blank=True, |
| 1165 | + unique=True, |
| 1166 | + help_text=_('The course, for which skill validation is disabled.'), |
| 1167 | + ) |
| 1168 | + organization = models.CharField( |
| 1169 | + max_length=255, |
| 1170 | + null=True, |
| 1171 | + blank=True, |
| 1172 | + unique=True, |
| 1173 | + help_text=_('The organization, for which skill validation is disabled.'), |
| 1174 | + ) |
| 1175 | + |
| 1176 | + def __str__(self): |
| 1177 | + """ |
| 1178 | + Create a human-readable string representation of the object. |
| 1179 | + """ |
| 1180 | + message = '' |
| 1181 | + |
| 1182 | + if self.course_key: |
| 1183 | + message = f'Skill validation disabled for course: {self.course_key}' |
| 1184 | + elif self.organization: |
| 1185 | + message = f'Skill validation disabled for organization: {self.organization}' |
| 1186 | + |
| 1187 | + return message |
| 1188 | + |
| 1189 | + class Meta: |
| 1190 | + """ |
| 1191 | + Meta configuration for SkillValidationConfiguration model. |
| 1192 | + """ |
| 1193 | + |
| 1194 | + constraints = [ |
| 1195 | + models.CheckConstraint( |
| 1196 | + check=( |
| 1197 | + Q(course_key__isnull=False) & |
| 1198 | + Q(organization__isnull=True) |
| 1199 | + ) | ( |
| 1200 | + Q(course_key__isnull=True) & |
| 1201 | + Q(organization__isnull=False) |
| 1202 | + ), |
| 1203 | + name='either_course_or_org', |
| 1204 | + # This only work on django >= 4.1 |
| 1205 | + # violation_error_message='Select either course or organization.' |
| 1206 | + ), |
| 1207 | + ] |
| 1208 | + |
| 1209 | + verbose_name = 'Skill Validation Configuration' |
| 1210 | + verbose_name_plural = 'Skill Validation Configurations' |
| 1211 | + |
| 1212 | + def clean(self): |
| 1213 | + """Override to add custom validation for course and organization fields.""" |
| 1214 | + if self.course_key: |
| 1215 | + if not get_course_metadata_provider().is_valid_course(self.course_key): |
| 1216 | + raise ValidationError({ |
| 1217 | + 'course_key': f'Course with key {self.course_key} does not exist.' |
| 1218 | + }) |
| 1219 | + |
| 1220 | + if self.organization: |
| 1221 | + if not get_course_metadata_provider().is_valid_organization(self.organization): |
| 1222 | + raise ValidationError({ |
| 1223 | + 'organization': f'Organization with key {self.organization} does not exist.' |
| 1224 | + }) |
| 1225 | + |
| 1226 | + # pylint: disable=no-member |
| 1227 | + def validate_constraints(self, exclude=None): |
| 1228 | + """ |
| 1229 | + Validate all constraints defined in Meta.constraints. |
| 1230 | +
|
| 1231 | + NOTE: We override this method only to return a human readable message. |
| 1232 | + We should remove this override once taxonomy-connector is updated to django 4.1 |
| 1233 | + On django >= 4.1, add violation_error_message in models.CheckConstraint with an appropriate message. |
| 1234 | + """ |
| 1235 | + try: |
| 1236 | + super().validate_constraints(exclude=exclude) |
| 1237 | + except ValidationError as ex: |
| 1238 | + raise ValidationError({'__all__': 'Add either course key or organization.'}) from ex |
| 1239 | + |
| 1240 | + def save(self, *args, **kwargs): |
| 1241 | + """Override to ensure that custom validation is always called.""" |
| 1242 | + self.full_clean() |
| 1243 | + return super().save(*args, **kwargs) |
| 1244 | + |
| 1245 | + @staticmethod |
| 1246 | + def is_valid_course_run_key(course_run_key): |
| 1247 | + """ |
| 1248 | + Check if the given course run key is in valid format. |
| 1249 | +
|
| 1250 | + Arguments: |
| 1251 | + course_run_key (str): Course run key |
| 1252 | + """ |
| 1253 | + try: |
| 1254 | + return True, CourseKey.from_string(course_run_key) |
| 1255 | + except InvalidKeyError: |
| 1256 | + LOGGER.error('[TAXONOMY_SKILL_VALIDATION_CONFIGURATION] Invalid course_run key: [%s]', course_run_key) |
| 1257 | + |
| 1258 | + return False, None |
| 1259 | + |
| 1260 | + @classmethod |
| 1261 | + def is_disabled(cls, course_run_key) -> bool: |
| 1262 | + """ |
| 1263 | + Check if skill validation is disabled for the given course run key. |
| 1264 | +
|
| 1265 | + Arguments: |
| 1266 | + course_run_key (str): Course run key |
| 1267 | +
|
| 1268 | + Returns: |
| 1269 | + bool: True if skill validation is disabled for the given course run key. |
| 1270 | + """ |
| 1271 | + is_valid_course_run_key, course_run_locator = cls.is_valid_course_run_key(course_run_key) |
| 1272 | + if not is_valid_course_run_key: |
| 1273 | + return False |
| 1274 | + |
| 1275 | + if cls.objects.filter(organization=course_run_locator.org).exists(): |
| 1276 | + return True |
| 1277 | + |
| 1278 | + course_key = get_course_metadata_provider().get_course_key(course_run_key) |
| 1279 | + if course_key and cls.objects.filter(course_key=course_key).exists(): |
| 1280 | + return True |
| 1281 | + |
| 1282 | + return False |
0 commit comments