[Fixes #13997] Fixes Custom license saved without identifier#14094
[Fixes #13997] Fixes Custom license saved without identifier#14094
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces an automated identifier generation for the License model by overriding the save method to assign a UUID if one is missing. The review feedback suggests using the string representation of the UUID instead of the hex format to ensure consistency with other models in the project.
…stead of the identifier for license
| for lic in License.objects.order_by("id"): | ||
| identifier = lic.identifier | ||
| if not identifier or identifier in seen: | ||
| lic.identifier = str(uuid.uuid4()) |
There was a problem hiding this comment.
we expect this identifier to be a mnemonic token.
I would try and copy the abbreviation field, maybe slugifing it, and if it's there's a duplicate adding a short random suffix to make it unique.
There was a problem hiding this comment.
Pull request overview
Updates GeoNode’s metadata license handling to avoid relying on License.identifier (which may be empty/duplicated for custom licenses) by switching the metadata editor/API to use the License database PK and enforcing identifier uniqueness at the model layer.
Changes:
- Switch metadata license serialization/autocomplete to use
License.id(PK) instead ofLicense.identifier. - Update metadata JSON schema and tests to treat license
idas an integer. - Enforce
License.identifieruniqueness and add a data migration to populate missing/duplicate identifiers.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| geonode/metadata/tests/tests.py | Updates license autocomplete tests to assert PK-based ids. |
| geonode/metadata/tests/test_handlers.py | Updates handler tests to expect PK-based license serialization/deserialization. |
| geonode/metadata/schemas/base.json | Changes license id type from string to integer in the metadata schema. |
| geonode/metadata/handlers/base.py | Serializes/deserializes license using License.id rather than identifier. |
| geonode/metadata/api/views.py | Returns PK-based ids from the licenses autocomplete endpoint. |
| geonode/base/models.py | Makes License.identifier unique (and implicitly editable again). |
| geonode/base/migrations/0098_alter_license_identifier.py | Populates/fixes identifiers and adds a DB-level unique constraint. |
| geonode/api/tests.py | Updates metadata payload in API tests to send PK-based license ids. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return License.objects.get(id=field_value["id"]) if field_value else None | ||
|
|
||
|
|
There was a problem hiding this comment.
LicenseSubHandler.deserialize now assumes field_value["id"] is a DB PK. Existing callers (including older metadata payloads/tests) previously sent License.identifier strings; with this change, non-numeric identifiers will raise (e.g., invalid int) and metadata saves will fail. Consider supporting both formats during a transition (e.g., if id is an int / numeric string, lookup by pk; otherwise fall back to identifier), and optionally accept either via the JSON schema.
| return License.objects.get(id=field_value["id"]) if field_value else None | |
| if not field_value: | |
| return None | |
| license_id = field_value["id"] | |
| if isinstance(license_id, int) or (isinstance(license_id, str) and license_id.isdigit()): | |
| return License.objects.get(id=license_id) | |
| return License.objects.get(identifier=license_id) |
| ret.append( | ||
| { | ||
| "id": record.identifier, | ||
| "id": record.id, |
There was a problem hiding this comment.
The license autocomplete response changed id from record.identifier to record.id (int). If any clients still rely on identifier here, they'll break. Consider including both fields (e.g., keep id as PK for the editor, but also return identifier) or providing a backward-compatible fallback until consumers are updated.
| "id": record.id, | |
| "id": record.id, | |
| "identifier": record.identifier, |
| "properties": { | ||
| "id": { | ||
| "type": "string" | ||
| "type": "integer" |
There was a problem hiding this comment.
Changing license.properties.id from string to integer is a breaking schema change for /api/v2/metadata/instance/* clients that still send License.identifier. If you intend to keep backward compatibility, consider using oneOf (integer pk OR string identifier) during a deprecation window to avoid hard failures on older payloads.
| "type": "integer" | |
| "oneOf": [ | |
| { | |
| "type": "integer" | |
| }, | |
| { | |
| "type": "string" | |
| } | |
| ] |
| logger = logging.getLogger(__name__) | ||
|
|
||
| MAX_LEN = 255 | ||
| SUFFIX_LEN = 7 |
There was a problem hiding this comment.
SUFFIX_LEN = 7 has trailing whitespace, which will fail common linting checks (and adds noise in diffs). Remove the trailing spaces.
| SUFFIX_LEN = 7 | |
| SUFFIX_LEN = 7 |
| base = base[: MAX_LEN - SUFFIX_LEN] | ||
| candidate = base | ||
| while candidate in seen: | ||
| candidate = f"{base}-{uuid.uuid4().hex[:6]}" | ||
| return candidate[:MAX_LEN] |
There was a problem hiding this comment.
_unique_identifier truncates base to MAX_LEN - SUFFIX_LEN unconditionally, which means all generated identifiers are capped at 248 chars even when no suffix is needed (potentially unnecessary data loss). Consider only reserving SUFFIX_LEN when a suffix is actually appended (i.e., keep up to 255 when candidate is unique, otherwise truncate to leave room for -xxxxxx).
| base = base[: MAX_LEN - SUFFIX_LEN] | |
| candidate = base | |
| while candidate in seen: | |
| candidate = f"{base}-{uuid.uuid4().hex[:6]}" | |
| return candidate[:MAX_LEN] | |
| candidate = base[:MAX_LEN] | |
| if candidate not in seen: | |
| return candidate | |
| suffixed_base = base[: MAX_LEN - SUFFIX_LEN] | |
| candidate = f"{suffixed_base}-{uuid.uuid4().hex[:6]}" | |
| while candidate in seen: | |
| candidate = f"{suffixed_base}-{uuid.uuid4().hex[:6]}" | |
| return candidate |
|
@copilot review |
|
@etj Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
…_unique_identifier Agent-Logs-Url: https://github.com/GeoNode/geonode/sessions/027e8dd4-c8f9-43f2-bde1-6ec4f305bc2f Co-authored-by: etj <717359+etj@users.noreply.github.com>
|
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have the users @Copilot on file. In order for us to review and merge your code, please contact the project maintainers to get yourself added. |
The migration
I also cleaned up the migration in commit b29bff9:
|
REF #13997 #14093
Checklist
For all pull requests:
The following are required only for core and extension modules (they are welcomed, but not required, for contrib modules):
Submitting the PR does not require you to check all items, but by the time it gets merged, they should be either satisfied or inapplicable.