|
24 | 24 | from common.djangoapps.student.models import CourseEnrollment |
25 | 25 | from lms.djangoapps.branding import api as branding_api |
26 | 26 | from lms.djangoapps.certificates.config import AUTO_CERTIFICATE_GENERATION as _AUTO_CERTIFICATE_GENERATION |
27 | | -from lms.djangoapps.certificates.data import CertificateStatuses |
| 27 | +from lms.djangoapps.certificates.data import CertificateStatuses, GeneratedCertificateData |
28 | 28 | from lms.djangoapps.certificates.generation_handler import generate_certificate_task as _generate_certificate_task |
29 | 29 | from lms.djangoapps.certificates.generation_handler import is_on_certificate_allowlist as _is_on_certificate_allowlist |
30 | 30 | from lms.djangoapps.certificates.models import ( |
31 | 31 | CertificateAllowlist, |
32 | 32 | CertificateDateOverride, |
33 | 33 | CertificateGenerationConfiguration, |
34 | 34 | CertificateGenerationCourseSetting, |
| 35 | + CertificateGenerationHistory, |
35 | 36 | CertificateInvalidation, |
36 | 37 | CertificateTemplate, |
37 | 38 | CertificateTemplateAsset, |
@@ -129,19 +130,16 @@ def get_certificate_for_user(username, course_key, format_results=True): |
129 | 130 | Arguments: |
130 | 131 | username (unicode): The identifier of the user. |
131 | 132 | course_key (CourseKey): A Course Key. |
| 133 | + format_results (boolean): Default True. If False, return the GeneratedCertificate object |
| 134 | + instead of the serialized dict representation. |
132 | 135 | Returns: |
133 | | - A dict containing information about the certificate or, optionally, |
| 136 | + A dict containing a serialized representation of the certificate or, optionally, |
134 | 137 | the GeneratedCertificate object itself. |
| 138 | + If there is no GeneratedCertificate object for the user and course combination, returns None. |
135 | 139 | """ |
136 | | - try: |
137 | | - cert = GeneratedCertificate.eligible_certificates.get(user__username=username, course_id=course_key) |
138 | | - except GeneratedCertificate.DoesNotExist: |
139 | | - return None |
140 | 140 |
|
141 | | - if format_results: |
142 | | - return _format_certificate_for_user(username, cert) |
143 | | - else: |
144 | | - return cert |
| 141 | + cert = GeneratedCertificate.eligible_certificates.filter(user__username=username, course_id=course_key).first() |
| 142 | + return _format_certificate_for_user(username, cert) if cert and format_results else cert |
145 | 143 |
|
146 | 144 |
|
147 | 145 | def get_certificate_for_user_id(user, course_id): |
@@ -728,16 +726,44 @@ def create_certificate_invalidation_entry(certificate, user_requesting_invalidat |
728 | 726 | return certificate_invalidation |
729 | 727 |
|
730 | 728 |
|
731 | | -def get_certificate_invalidation_entry(certificate): |
| 729 | +def get_certificate_invalidation_entry(generated_certificate, invalidated_by=None, notes=None, active=None): |
732 | 730 | """ |
733 | | - Retrieves and returns an certificate invalidation entry for a given certificate id. |
| 731 | + Retrieve a certificate invalidation entry for a specified certificate. |
| 732 | +
|
| 733 | + Optionally filter the invalidation entry by who invalidated it, notes, and whether the invalidation is active. |
| 734 | +
|
| 735 | + Args: |
| 736 | + generated_certificate (GeneratedCertificate): The certificate to find the invalidation entry for. |
| 737 | + invalidated_by (User, optional): User who invalidated the certificate. Defaults to None. |
| 738 | + notes (str, optional): Notes associated with the invalidation. Defaults to None. |
| 739 | + active (bool, optional): Whether the invalidation entry is currently active. Defaults to None. |
| 740 | +
|
| 741 | + Returns: |
| 742 | + CertificateInvalidationEntry or None: The matching invalidation entry if found, else None. |
734 | 743 | """ |
735 | | - log.info(f"Attempting to retrieve certificate invalidation entry for certificate with id {certificate.id}.") |
736 | | - try: |
737 | | - certificate_invalidation_entry = CertificateInvalidation.objects.get(generated_certificate=certificate) |
738 | | - except ObjectDoesNotExist: |
739 | | - log.warning(f"No certificate invalidation found linked to certificate with id {certificate.id}.") |
740 | | - return None |
| 744 | + log.info( |
| 745 | + f"Attempting to retrieve certificate invalidation entry " |
| 746 | + f"for certificate with id {generated_certificate.id}." |
| 747 | + ) |
| 748 | + |
| 749 | + cert_filter_args = { |
| 750 | + "generated_certificate": generated_certificate |
| 751 | + } |
| 752 | + |
| 753 | + if invalidated_by is not None: |
| 754 | + cert_filter_args["invalidated_by"] = invalidated_by |
| 755 | + |
| 756 | + if notes is not None: |
| 757 | + cert_filter_args["notes"] = notes |
| 758 | + |
| 759 | + if active is not None: |
| 760 | + cert_filter_args["active"] = active |
| 761 | + |
| 762 | + certificate_invalidation_entry = CertificateInvalidation.objects.filter(**cert_filter_args).first() |
| 763 | + |
| 764 | + # If object does not exist, add a warning to the logs |
| 765 | + if certificate_invalidation_entry is None: |
| 766 | + log.warning(f"No certificate invalidation found linked to certificate with id {generated_certificate.id}.") |
741 | 767 |
|
742 | 768 | return certificate_invalidation_entry |
743 | 769 |
|
@@ -958,3 +984,281 @@ def clear_pii_from_certificate_records_for_user(user): |
958 | 984 | None |
959 | 985 | """ |
960 | 986 | GeneratedCertificate.objects.filter(user=user).update(name="") |
| 987 | + |
| 988 | + |
| 989 | +def get_cert_history_for_course_id(course_id): |
| 990 | + """ |
| 991 | + Retrieve all certificate generation history records for the specified course. |
| 992 | +
|
| 993 | + Args: |
| 994 | + course_id (CourseLocator | CourseKey): The unique identifier for the course. |
| 995 | +
|
| 996 | + Returns: |
| 997 | + QuerySet[CertificateGenerationHistory]: A queryset of all certificate generation history records |
| 998 | + associated with the specified course. |
| 999 | + """ |
| 1000 | + return CertificateGenerationHistory.objects.filter(course_id=course_id) |
| 1001 | + |
| 1002 | + |
| 1003 | +def is_certificate_generation_enabled(): |
| 1004 | + """ |
| 1005 | + Checks if certificate generation is currently enabled. |
| 1006 | +
|
| 1007 | + This function queries the `CertificateGenerationConfiguration` model to retrieve the |
| 1008 | + current configuration and returns whether certificate generation is enabled or not. |
| 1009 | +
|
| 1010 | + Returns: |
| 1011 | + bool: True if certificate generation is enabled, False otherwise. |
| 1012 | + """ |
| 1013 | + return CertificateGenerationConfiguration.current().enabled |
| 1014 | + |
| 1015 | + |
| 1016 | +def set_certificate_generation_config(enabled=True): |
| 1017 | + """ |
| 1018 | + Configures the certificate generation settings. |
| 1019 | +
|
| 1020 | + Args: |
| 1021 | + enabled (bool): If True, enables certificate generation; if False, disables it. Default is True. |
| 1022 | +
|
| 1023 | + Returns: |
| 1024 | + CertificateGenerationConfiguration: The created or updated certificate generation configuration object. |
| 1025 | + """ |
| 1026 | + return CertificateGenerationConfiguration.objects.create(enabled=enabled) |
| 1027 | + |
| 1028 | + |
| 1029 | +def get_certificate_generation_history(course_id=None, generated_by=None, instructor_task=None, is_regeneration=None): |
| 1030 | + """ |
| 1031 | + Retrieves a queryset of `CertificateGenerationHistory` records, filtered by the provided criteria. |
| 1032 | +
|
| 1033 | + Args: |
| 1034 | + course_id (Optional[CourseKey]): The unique ID of the course (if filtering by course). |
| 1035 | + generated_by (Optional[User]): The user who generated the certificate (if filtering by user). |
| 1036 | + instructor_task (Optional[bool]): Whether the certificate generation was triggered by an instructor task. |
| 1037 | + is_regeneration (Optional[bool]): Whether the certificate was regenerated. |
| 1038 | +
|
| 1039 | + Returns: |
| 1040 | + QuerySet[CertificateGenerationHistory]: A queryset of filtered `CertificateGenerationHistory` records. |
| 1041 | + """ |
| 1042 | + cert_filter_args = {} |
| 1043 | + |
| 1044 | + # Only add the filter if the corresponding argument is provided |
| 1045 | + if course_id is not None: |
| 1046 | + cert_filter_args["course_id"] = course_id |
| 1047 | + |
| 1048 | + if generated_by is not None: |
| 1049 | + cert_filter_args["generated_by"] = generated_by |
| 1050 | + |
| 1051 | + if instructor_task is not None: |
| 1052 | + cert_filter_args["instructor_task"] = instructor_task |
| 1053 | + |
| 1054 | + if is_regeneration is not None: |
| 1055 | + cert_filter_args["is_regeneration"] = is_regeneration |
| 1056 | + |
| 1057 | + res = CertificateGenerationHistory.objects.filter(**cert_filter_args) |
| 1058 | + |
| 1059 | + return res |
| 1060 | + |
| 1061 | + |
| 1062 | +def create_or_update_certificate_generation_history(course_id, generated_by, instructor_task, is_regeneration): |
| 1063 | + """ |
| 1064 | + Creates a new certificate generation history record or updates an existing one. |
| 1065 | +
|
| 1066 | + Args: |
| 1067 | + course_id (CourseKey): The unique identifier for the course run. |
| 1068 | + generated_by (User): The user (typically an instructor or admin) who initiated the certificate generation. |
| 1069 | + instructor_task (str): A descriptor or task identifier for the instructor-related task. |
| 1070 | + is_regeneration (bool): A flag indicating whether the certificate is being |
| 1071 | + regenerated (True) or newly generated (False). |
| 1072 | +
|
| 1073 | + Returns: |
| 1074 | + CertificateGenerationHistory: The created or updated CertificateGenerationHistory instance. |
| 1075 | + """ |
| 1076 | + cert_history, created = CertificateGenerationHistory.objects.update_or_create( |
| 1077 | + course_id=course_id, |
| 1078 | + generated_by=generated_by, |
| 1079 | + instructor_task=instructor_task, |
| 1080 | + is_regeneration=is_regeneration |
| 1081 | + ) |
| 1082 | + |
| 1083 | + return cert_history |
| 1084 | + |
| 1085 | + |
| 1086 | +def create_or_update_eligible_certificate_for_user(user, course_id, status): |
| 1087 | + """ |
| 1088 | + Create or update an eligible GeneratedCertificate for a user in a specific course. |
| 1089 | +
|
| 1090 | + Args: |
| 1091 | + user (User): The user for whom the certificate is being created or updated. |
| 1092 | + course_id (CourseKey): The unique identifier for the course the certificate applies to. |
| 1093 | + status (str): The status of the certificate (e.g., "downloadable", "issued"). |
| 1094 | +
|
| 1095 | + Returns: |
| 1096 | + tuple: A tuple containing: |
| 1097 | + - `GeneratedCertificate`: The created or updated certificate. |
| 1098 | + - `bool`: A boolean indicating whether the certificate was created (True) or updated (False). |
| 1099 | + """ |
| 1100 | + |
| 1101 | + cert, created = GeneratedCertificate.eligible_certificates.update_or_create( |
| 1102 | + user=user, |
| 1103 | + course_id=course_id, |
| 1104 | + status=status |
| 1105 | + ) |
| 1106 | + |
| 1107 | + return cert, created |
| 1108 | + |
| 1109 | + |
| 1110 | +def get_cert_invalidations_for_course(course_key): |
| 1111 | + """ |
| 1112 | + Retrieves all certificate invalidations associated with the specified course. |
| 1113 | +
|
| 1114 | + Args: |
| 1115 | + course_key (CourseKey): The unique identifier for the course run. |
| 1116 | +
|
| 1117 | + Returns: |
| 1118 | + QuerySet[CertificateInvalidation]: A queryset containing all invalidations for the specified course. |
| 1119 | + """ |
| 1120 | + return CertificateInvalidation.get_certificate_invalidations(course_key) |
| 1121 | + |
| 1122 | + |
| 1123 | +def get_certificates_for_course_and_users(course_id, users): |
| 1124 | + """ |
| 1125 | + Retrieves all GeneratedCertificate records for a specific course and a list of users. |
| 1126 | +
|
| 1127 | + Args: |
| 1128 | + course_id (CourseKey): The unique identifier for the course run. |
| 1129 | + users (Iterable[User]): A list or queryset of User instances to filter certificates by. |
| 1130 | +
|
| 1131 | + Returns: |
| 1132 | + QuerySet[GeneratedCertificate]: A queryset containing the matching certificate records. |
| 1133 | + """ |
| 1134 | + return GeneratedCertificate.objects.filter(course_id=course_id, user__in=users) |
| 1135 | + |
| 1136 | + |
| 1137 | +def get_course_ids_from_certs_for_user(username): |
| 1138 | + """ |
| 1139 | + Retrieves a list of course IDs for which the given user has generated certificates. |
| 1140 | +
|
| 1141 | + Args: |
| 1142 | + username (str): The username of the user whose course IDs are being retrieved. |
| 1143 | +
|
| 1144 | + Returns: |
| 1145 | + list: A list of course IDs for which the user has generated certificates. |
| 1146 | + If no certificates are found, an empty list is returned. |
| 1147 | + """ |
| 1148 | + course_ids_with_certificates = GeneratedCertificate.objects.filter( |
| 1149 | + user__username=username |
| 1150 | + ).values_list('course_id', flat=True) |
| 1151 | + |
| 1152 | + return list(course_ids_with_certificates) |
| 1153 | + |
| 1154 | + |
| 1155 | +def get_generated_certificate(user, course_id): |
| 1156 | + """ |
| 1157 | + Retrieves the GeneratedCertificateData for the given user and course. |
| 1158 | +
|
| 1159 | + Args: |
| 1160 | + user (User): The user for whom the certificate is being fetched. |
| 1161 | + course_id (CourseKey): The unique identifier for the course. |
| 1162 | +
|
| 1163 | + Returns: |
| 1164 | + Optional[GeneratedCertificateData]: A `GeneratedCertificateData` object if found, otherwise None. |
| 1165 | + """ |
| 1166 | + try: |
| 1167 | + cert = GeneratedCertificate.objects.get(user=user, course_id=course_id) |
| 1168 | + return GeneratedCertificateData( |
| 1169 | + user=cert.user, |
| 1170 | + course_id=cert.course_id |
| 1171 | + ) |
| 1172 | + except GeneratedCertificate.DoesNotExist: |
| 1173 | + return None |
| 1174 | + |
| 1175 | + |
| 1176 | +def create_generated_certificate(cert_args): |
| 1177 | + """ |
| 1178 | + Creates a new `GeneratedCertificate` object using the provided arguments. |
| 1179 | +
|
| 1180 | + Args: |
| 1181 | + cert_args (dict): A dictionary containing the certificate's attributes, such as |
| 1182 | + "user", "course_id", "status", etc. |
| 1183 | +
|
| 1184 | + Returns: |
| 1185 | + GeneratedCertificate: The newly created `GeneratedCertificate` object. |
| 1186 | +
|
| 1187 | + Raises: |
| 1188 | + ValueError: If any required fields ("user", "course_id") are missing from `cert_args`. |
| 1189 | + """ |
| 1190 | + |
| 1191 | + required_fields = {"user", "course_id"} |
| 1192 | + |
| 1193 | + # Check if all required fields are present |
| 1194 | + if not all(field in cert_args for field in required_fields): |
| 1195 | + raise ValueError(f"Missing required fields: {required_fields - cert_args.keys()}") |
| 1196 | + |
| 1197 | + # Create and return the GeneratedCertificate object |
| 1198 | + return GeneratedCertificate.objects.create(**cert_args) |
| 1199 | + |
| 1200 | + |
| 1201 | +def get_eligible_certificate(user, course_id): |
| 1202 | + """ |
| 1203 | + Retrieves the eligible GeneratedCertificate for a given user and course. |
| 1204 | +
|
| 1205 | + Args: |
| 1206 | + user (User): The user object for whom the certificate is being retrieved. |
| 1207 | + course_id (CourseKey): The unique identifier for the course run. |
| 1208 | +
|
| 1209 | + Returns: |
| 1210 | + GeneratedCertificate or None: The eligible certificate instance if one exists; otherwise, None. |
| 1211 | + """ |
| 1212 | + try: |
| 1213 | + return GeneratedCertificate.eligible_certificates.get( |
| 1214 | + user=user.id, |
| 1215 | + course_id=course_id |
| 1216 | + ) |
| 1217 | + except GeneratedCertificate.DoesNotExist: |
| 1218 | + return None |
| 1219 | + |
| 1220 | + |
| 1221 | +def get_eligible_and_available_certificates(user): |
| 1222 | + """ |
| 1223 | + Retrieves all eligible and available certificates for the specified user. |
| 1224 | +
|
| 1225 | + Args: |
| 1226 | + user (User): The user whose eligible and available certificates are being retrieved. |
| 1227 | +
|
| 1228 | + Returns: |
| 1229 | + QuerySet[GeneratedCertificate]: A queryset containing all eligible and available certificates |
| 1230 | + for the specified user. |
| 1231 | + """ |
| 1232 | + return GeneratedCertificate.eligible_available_certificates.filter(user=user) |
| 1233 | + |
| 1234 | + |
| 1235 | +def get_certificates_by_course_and_status(course_id, status): |
| 1236 | + """ |
| 1237 | + Retrieves all eligible certificates for a specific course and status. |
| 1238 | +
|
| 1239 | + Args: |
| 1240 | + course_id (CourseKey): The unique identifier for the course run. |
| 1241 | + status (str): The status of the certificates to filter by (e.g., "downloadable", "issued"). |
| 1242 | +
|
| 1243 | + Returns: |
| 1244 | + QuerySet[GeneratedCertificate]: A queryset containing the eligible certificates |
| 1245 | + matching the provided course ID and status. |
| 1246 | + """ |
| 1247 | + |
| 1248 | + return GeneratedCertificate.eligible_certificates.filter( |
| 1249 | + course_id=course_id, |
| 1250 | + status=status |
| 1251 | + ) |
| 1252 | + |
| 1253 | + |
| 1254 | +def get_unique_certificate_statuses(course_key): |
| 1255 | + """ |
| 1256 | + Retrieves the unique certificate statuses for the specified course. |
| 1257 | +
|
| 1258 | + Args: |
| 1259 | + course_key (CourseKey): The unique identifier for the course run. |
| 1260 | +
|
| 1261 | + Returns: |
| 1262 | + QuerySet[str]: A queryset containing the unique certificate statuses for the specified course. |
| 1263 | + """ |
| 1264 | + return GeneratedCertificate.get_unique_statuses(course_key=course_key) |
0 commit comments