Skip to content

Conversation

@Agrendalath
Copy link
Member

@Agrendalath Agrendalath commented Nov 3, 2025

Merge checklist:
Check off if complete or not applicable:

  • Version bumped
  • Changelog record added
  • Documentation updated (not only docstrings)
  • Fixup commits are squashed away
  • Unit tests added/updated
  • Manual testing instructions provided
  • Noted any: Concerns, dependencies, migration issues, deadlines, tickets

@Agrendalath Agrendalath changed the base branch from main to agrendalath/bb-9902-rest-api November 3, 2025 14:06
@Agrendalath Agrendalath force-pushed the agrendalath/bb-10170-validation branch from fb44a7c to 0e6c112 Compare November 5, 2025 21:52
This was linked to issues Nov 24, 2025
@Agrendalath Agrendalath force-pushed the agrendalath/bb-9902-rest-api branch 2 times, most recently from 1d9c576 to a483669 Compare December 15, 2025 20:23
Base automatically changed from agrendalath/bb-9902-rest-api to main December 15, 2025 21:26
@Agrendalath Agrendalath force-pushed the agrendalath/bb-10170-validation branch from 184aefd to 6c6bded Compare December 15, 2025 22:09
@Agrendalath Agrendalath force-pushed the agrendalath/bb-10170-validation branch from 6c6bded to 65de928 Compare January 29, 2026 00:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces credential verification and invalidation capabilities, alongside a schema refactor to normalize credential data and add richer metadata.

Changes:

  • Add a public API endpoint and UI page to verify credentials by a dedicated verification UUID, exposing limited, read-only metadata.
  • Refactor the Credential model to reference User and CredentialConfiguration via FKs, add verify_uuid, learning_context_name, and invalidation fields, and update generators and admin to support invalidation and reissue flows.
  • Extend and adjust the test suite (models, generators, admin, views) plus migrations, versioning, and documentation to cover the new behavior.

Reviewed changes

Copilot reviewed 21 out of 23 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
uv.lock Bumps the locked version of the local learning-credentials package to 0.5.0rc3 to match the code changes.
pyproject.toml Updates the project version to 0.5.0rc3 for the new release containing verification and invalidation features.
CHANGELOG.rst Adds a 0.5.0 section describing the new verification endpoint/UI and invalidation option.
learning_credentials/models.py Refactors Credential to use user FK and configuration FK, adds verify_uuid, learning_context_name, invalidated_at, and invalidation_reason, implements automatic invalidation logic and a reissue() method, and adjusts CredentialConfiguration.generate_credential_for_user to work with the new schema and generator signature.
learning_credentials/migrations/0008_validation.py Introduces verification and invalidation fields on Credential, backfills verify_uuid and learning_context_name, and removes the old unique_together constraint.
learning_credentials/migrations/0009_credential_user_fk.py Migrates from an integer user_id on Credential to a proper user FK to AUTH_USER_MODEL.
learning_credentials/migrations/0010_credential_configuration_fk.py Adds a FK from Credential to CredentialConfiguration and removes the redundant learning_context_key and credential_type fields from Credential.
learning_credentials/migrations/0001_squashed_0010.py Provides a squashed initial migration that encapsulates the full current schema including Credential, CredentialConfiguration, assets, and new verification/invalidation fields for fresh installs.
learning_credentials/generators.py Updates PDF generation to operate on a Credential instance, uses learning_context_name and verify_uuid, parameterizes the localized date, adds _get_credential_paths and _invalidate_credential helpers, and supports invalidation by archiving PDFs and clearing URLs.
learning_credentials/compat.py Changes get_localized_credential_date to accept a datetime argument and format that timestamp in the configured timezone.
learning_credentials/api/v1/serializers.py Adds CredentialSerializer exposing user_full_name, created, learning_context_name, status, and invalidation_reason for verification responses.
learning_credentials/api/v1/views.py Extends the API with CredentialMetadataView to fetch credential metadata by verify_uuid, returning 404 on missing credentials.
learning_credentials/api/v1/urls.py Wires the new metadata endpoint at v1/metadata/<uuid:uuid>/ under the existing API namespace.
learning_credentials/urls.py Adds a TemplateView-based route for the credential verification page at /learning_credentials/verify/.
learning_credentials/templates/learning_credentials/verify.html Implements a simple UI form that accepts a credential ID, calls the metadata API via fetch, and renders returned metadata in a details table.
learning_credentials/admin.py Enhances admin for CredentialConfiguration and Credential (docstring-based help, read-only key fields, generate/reissue actions, disabled add/delete for credentials, and customized URL display and form behavior).
tests/test_models.py Updates model tests to the new schema, adds coverage for invalidation and reissue logic, configuration-based generation using configuration and learning_context_name, and filtering logic using the new relationships.
tests/test_generators.py Aligns generator tests with the new generate_pdf_credential signature, adds coverage for verify_uuid placeholders, output paths, invalidation/archiving behavior, and S3 ACL handling.
tests/test_views.py Extends API tests to cover the new CredentialMetadataView, ensuring correct responses for valid, missing, and invalidated credentials.
tests/test_admin.py Adds comprehensive tests for admin forms and actions, including docstring-based options help, configuration inline behavior, credential admin permissions, URL display, validation error handling, and the reissue action.
tests/conftest.py Introduces reusable fixtures for mock retrieval/generation functions, mock credential types/configs, credential instances, email patching, and a temporary media root used by generator and asset tests.
.gitignore Removes the now-obsolete external_certificates/ ignore, since credential PDFs are stored under the default storage path (learning_credentials/) instead.
.coveragerc Stops excluding admin.py from coverage so the new admin tests are counted.
Comments suppressed due to low confidence (1)

learning_credentials/generators.py:395

  • The placeholder list in this docstring is now out of date: _write_text_on_template and the options plumbing also support {verify_uuid} (via the placeholders dict), but the documentation here still only mentions {name}, {context_name}, and {issue_date}. To avoid confusing integrators configuring custom text_elements, please update this section to include {verify_uuid} in the list of available placeholders.
    Options:

      - template (required): The slug of the PDF template asset.
      - template_multiline: Alternative template for multiline context names (when using '\n').
      - defaults: Global defaults for all text elements.
          - font: Font name (asset slug). Default: Helvetica.
          - color: Hex color code. Default: #000.
          - size: Font size in points. Default: 12.
          - char_space: Character spacing. Default: 0.
          - uppercase: Convert text to uppercase. Default: false.
          - line_height: Line height multiplier for multiline text. Default: 1.1.
      - text_elements: Configuration for text elements. Standard elements (name, context, date) have
          defaults and render automatically. Set to false to hide.
          Custom elements require 'text' and 'y' properties.
          Element properties:
          - text: Text content with {placeholder} substitution. Available: {name}, {context_name}, {issue_date}.
          - y: Vertical position (PDF coordinates from bottom).
          - size: Font size (inherited from defaults.size).
          - font: Font name (inherited from defaults.font).
          - color: Hex color (inherited from defaults.color).
          - char_space: Character spacing (inherited from defaults.char_space).
          - uppercase: Convert text to uppercase (inherited from defaults.uppercase).
          - line_height: Line height multiplier for multiline text (inherited from defaults.line_height).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 31 to 60
let credentialId = document.getElementById('credentialID').value;
var url = '/api/learning_credentials/v1/metadata/' + encodeURIComponent(credentialId);

// Hide previous error messages.
formError.style.display = 'none';

await fetch(url)
.then(response => response.json())
.then(data => {
const table = document.createElement('table');
table.className = 'table table-striped table-bordered';
const tbody = document.createElement('tbody');
Object.entries(data).forEach(([key, value]) => {
if (!!value) {
const row = tbody.insertRow();
const cellKey = row.insertCell();
const cellValue = row.insertCell();
cellKey.textContent = key.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
cellValue.textContent = value;
}
});
table.appendChild(tbody);
verificationResults.textContent = '';
verificationResults.appendChild(table);
})
.catch((error) => {
console.error('Error:', error);
verificationResults.textContent = '';
formError.innerHTML = "An error occurred during verification. Please check the Credential ID and try again.";
formError.style.display = 'block';
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JavaScript currently treats any HTTP response as success: it always calls response.json() and renders whatever comes back into the table, so a 404 from the API (e.g., {"error": "Credential not found."}) will appear as a one-row table instead of using the formError area. To provide clearer feedback and align with the API’s error contract, consider checking response.ok (or the status code) and routing non-2xx responses to the error display rather than rendering them as metadata.

Copilot uses AI. Check for mistakes.
Comment on lines 26 to 32
document.getElementById('credentialVerificationForm').onsubmit = async function(event) {
event.preventDefault();
let formError = document.getElementById('formError');
let verificationResults = document.getElementById('verificationResults');

let credentialId = document.getElementById('credentialID').value;
var url = '/api/learning_credentials/v1/metadata/' + encodeURIComponent(credentialId);
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API URL is hard-coded as '/api/learning_credentials/v1/metadata/' in the client-side script, which couples this template to a specific URL structure and will silently break if the API prefix or version changes. To keep the page robust against URL refactors, it would be better to build this endpoint via Django’s URL reversing (e.g., passing a base URL into the template with {% url 'learning_credentials_api_v1:credential-metadata' '00000000-0000-0000-0000-000000000000' %} and replacing the UUID in JavaScript) instead of embedding a literal path.

Suggested change
document.getElementById('credentialVerificationForm').onsubmit = async function(event) {
event.preventDefault();
let formError = document.getElementById('formError');
let verificationResults = document.getElementById('verificationResults');
let credentialId = document.getElementById('credentialID').value;
var url = '/api/learning_credentials/v1/metadata/' + encodeURIComponent(credentialId);
const credentialMetadataUrlTemplate = "{% url 'learning_credentials_api_v1:credential-metadata' '00000000-0000-0000-0000-000000000000' %}";
const credentialMetadataBaseUrl = credentialMetadataUrlTemplate.replace('00000000-0000-0000-0000-000000000000', '');
document.getElementById('credentialVerificationForm').onsubmit = async function(event) {
event.preventDefault();
let formError = document.getElementById('formError');
let verificationResults = document.getElementById('verificationResults');
let credentialId = document.getElementById('credentialID').value;
var url = credentialMetadataBaseUrl + encodeURIComponent(credentialId);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validation Certificate invalidation

1 participant