-
Notifications
You must be signed in to change notification settings - Fork 0
Feature kpoland public private setting on dataset #217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Feature kpoland public private setting on dataset #217
Conversation
0e4ff1c to
167ebad
Compare
167ebad to
8c75f96
Compare
05d7301 to
d43f3c6
Compare
There was a problem hiding this 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 implements a public/private setting feature for datasets, allowing dataset owners and co-owners to publish datasets and control their visibility. The feature includes comprehensive UI changes for dataset creation/editing workflows and adds backend validation to prevent editing of published or public datasets.
Key Changes
- Adds
is_publicboolean field to datasets with UI controls for public/private visibility selection - Implements a new publishing workflow with a dedicated step in the dataset creation/editing wizard
- Creates a publish modal and API endpoint for updating dataset status and visibility from the dataset list
- Prevents editing of datasets that are published (status=final) or made public
Reviewed Changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| gateway/sds_gateway/users/views.py | Adds PublishDatasetView for handling publish requests, refactors permission validation with status/public checks, and updates dataset creation to handle is_public field |
| gateway/sds_gateway/users/urls.py | Adds URL route for the new publish dataset endpoint |
| gateway/sds_gateway/users/forms.py | Adds is_public BooleanField and changes status field to HiddenInput widget |
| gateway/sds_gateway/templates/users/partials/step_4.html | New step for publishing info with toggles for status and visibility |
| gateway/sds_gateway/templates/users/partials/step_5.html | Moves review/create content to step 5 (previously step 4) |
| gateway/sds_gateway/templates/users/partials/step_1.html | Removes status field from basic info step (moved to publishing step) |
| gateway/sds_gateway/templates/users/partials/review_create_dataset.html | Removes status display and adds publishing alerts container |
| gateway/sds_gateway/templates/users/partials/publish_dataset_modal.html | New modal for publishing datasets from the dataset list |
| gateway/sds_gateway/templates/users/group_captures.html | Updates step tabs and passes dataset status/public state to JavaScript |
| gateway/sds_gateway/templates/users/dataset_list.html | Adds public/private icons, publish button in dropdown, and conditional edit button visibility |
| gateway/sds_gateway/static/js/dataset/DatasetModeManager.js | Major updates to handle 5-step workflow, publishing info UI, and submit button styling based on publish status |
| gateway/sds_gateway/static/js/dataset/DatasetCreationHandler.js | Updates step indices for 5-step workflow |
| gateway/sds_gateway/static/js/dataset/DatasetEditingHandler.js | Replaces inline opacity styles with CSS class for disabled elements |
| gateway/sds_gateway/static/js/actions/PublishActionManager.js | New manager class for handling publish modal interactions and API calls |
| gateway/sds_gateway/static/js/actions/DetailsActionManager.js | Updates status display to use badge instead of plain text |
| gateway/sds_gateway/static/js/search/AssetSearchHandler.js | Adds null check for search form (handles public datasets without search) |
| gateway/sds_gateway/static/css/components.css | Adds disabled-element and disable-events CSS classes, textarea padding fix |
| gateway/sds_gateway/api_methods/serializers/dataset_serializers.py | Adds is_public field to dataset serializer |
| gateway/sds_gateway/api_methods/migrations/0017_convert_dataset_authors_to_object_format.py | Removes blank lines (formatting only) |
| gateway/sds_gateway/templates/users/partials/files_management.html | Adds id to files section for JavaScript targeting |
| gateway/sds_gateway/templates/users/partials/captures_management.html | Adds id to captures section for JavaScript targeting |
Comments suppressed due to low confidence (1)
gateway/sds_gateway/static/css/components.css:185
- Duplicate CSS rule: The
.author-itemclass is defined twice (lines 123-125 and 177-179) with slightly different styles. The second definition will override the first one. These should be consolidated into a single rule to avoid confusion and ensure predictable styling.
/* Author management styling */
.author-item {
transition: all 0.3s ease;
}
.author-item:hover {
background-color: var(--bs-gray-100);
}
.author-item.marked-for-removal .form-control {
background-color: var(--bs-gray-100);
border-color: var(--bs-gray-300);
color: var(--bs-gray-600);
}
.author-name-input,
.author-orcid-input {
font-size: 0.9rem;
}
.author-orcid-input {
font-family: "Courier New", monospace;
}
/* Permission-based styling */
.readonly-field {
background-color: var(--bs-gray-100);
border: 1px solid var(--bs-gray-300);
color: var(--bs-gray-600);
}
.readonly-field:focus {
background-color: var(--bs-gray-100);
border-color: var(--bs-gray-300);
box-shadow: none;
}
.readonly-row {
background-color: var(--bs-gray-100);
opacity: 0.8;
}
.readonly-row td {
color: var(--bs-gray-600);
}
.readonly-row .badge {
opacity: 0.7;
}
/* Disabled state styling */
.disabled-element {
opacity: 0.5;
pointer-events: none;
}
/* Authors management styling */
.author-item {
transition: all 0.2s ease;
}
.author-item:hover {
background-color: var(--bs-gray-100);
padding: 0.5rem;
border-radius: 0.375rem;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,3 @@ | |||
| <div class="tab-pane fade" id="step4"> | |||
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The id attribute should be "step5" instead of "step4". The file is named step_5.html and is meant to be the 5th step, but uses id="step4" which conflicts with the actual step 4 (publishing info) defined in step_4.html. This will cause JavaScript step navigation to fail as both steps will have the same ID.
| <div class="tab-pane fade" id="step4"> | |
| <div class="tab-pane fade" id="step5"> |
| <div class="modal-body"> | ||
| <h5 class="card-title mb-4">Dataset Publishing</h5> | ||
| <!-- Published Indicator (only shown for final datasets) --> | ||
| {% if dataset.dataset.status == 'final' %} |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent property access pattern: dataset.dataset.status is used here but dataset.is_public is used on line 27. This suggests that dataset is a wrapper object with a nested dataset property for the actual dataset model, but is_public is accessed directly on the wrapper. This inconsistency could lead to bugs if the structure changes. Consider using dataset.dataset.is_public for consistency, or document why the different access patterns are intentional.
| * @param {HTMLElement} publishToggle - The publish toggle checkbox | ||
| * @param {HTMLElement} visibilitySection - The visibility section element | ||
| * @param {HTMLElement} statusBadge - The status badge element | ||
| * @param {string} datasetUuid - Dataset UUID |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JSDoc comment incorrectly lists datasetUuid as a parameter, but the method signature for handlePublishToggleChange doesn't include datasetUuid as a parameter. The parameter list should be updated to remove this.
| * @param {string} datasetUuid - Dataset UUID |
| if dataset.status == DatasetStatus.FINAL: | ||
| messages.error(request, "This dataset is published and cannot be edited.") | ||
| return redirect("users:dataset_list") | ||
|
|
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The backend validation only checks if the dataset status is FINAL, but the frontend (dataset_list.html line 171) also prevents editing public datasets. This inconsistency creates a potential security issue - a user could bypass the frontend restriction and still edit a public dataset through direct API calls if it's not FINAL. Add a check for dataset.is_public to be consistent with the frontend restrictions:
# Check if dataset is final (published) or public - cannot be edited
if dataset.status == DatasetStatus.FINAL:
messages.error(request, "This dataset is published and cannot be edited.")
return redirect("users:dataset_list")
if dataset.is_public:
messages.error(request, "This dataset is public and cannot be edited.")
return redirect("users:dataset_list")| # Check if dataset is public - cannot be edited | |
| if dataset.is_public: | |
| messages.error(request, "This dataset is public and cannot be edited.") | |
| return redirect("users:dataset_list") |
|
|
||
| # Get status and is_public from request | ||
| status_value = request.POST.get("status") | ||
| is_public_value = json.loads(request.POST.get("is_public")) |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The json.loads() call on line 2477 can raise a JSONDecodeError if the is_public value is not valid JSON or is None. This should be wrapped in a try-except block to handle parsing errors gracefully and return an appropriate error response.
Consider:
try:
is_public_value = json.loads(request.POST.get("is_public"))
except (json.JSONDecodeError, TypeError, ValueError):
return JsonResponse({"success": False, "error": "Invalid is_public value."}, status=400)| is_public_value = json.loads(request.POST.get("is_public")) | |
| try: | |
| is_public_value = json.loads(request.POST.get("is_public")) | |
| except (json.JSONDecodeError, TypeError, ValueError): | |
| return JsonResponse({"success": False, "error": "Invalid is_public value."}, status=400) |
| } | ||
|
|
||
| /** | ||
| * Check if this is an existing final-status datase |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in the comment: "datase" should be "dataset"
| * Check if this is an existing final-status datase | |
| * Check if this is an existing final-status dataset |
| def get_is_public(self, obj): | ||
| """Check if the dataset is public.""" | ||
| return obj.is_public |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The get_is_public method is redundant. Since is_public is already defined as a BooleanField on line 16, it will automatically serialize the model's is_public attribute. The get_is_public method (lines 48-50) simply returns the same value without any transformation. This method should be removed, and the field declaration on line 16 is sufficient.
| def get_is_public(self, obj): | |
| """Check if the dataset is public.""" | |
| return obj.is_public |
| updateStatusBadge(modal, status) { | ||
| const statusContainer = modal.querySelector(".dataset-details-status"); | ||
| const statusText = status[0].toUpperCase() + status.slice(1); | ||
| const statusClass = status === "final" ? "success" : "secondary"; | ||
| if (statusContainer) { | ||
| statusContainer.innerHTML = `<span class="badge bg-${statusClass}">${statusText}</span>`; | ||
| } | ||
| } |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The updateStatusBadge method will throw a runtime error if status is null, undefined, or an empty string, because it attempts to access status[0] and call status.slice(1) without validation. Add a check for valid status value before string manipulation.
Suggested fix:
updateStatusBadge(modal, status) {
const statusContainer = modal.querySelector(".dataset-details-status");
if (!status || typeof status !== 'string') {
if (statusContainer) {
statusContainer.innerHTML = `<span class="badge bg-secondary">Unknown</span>`;
}
return;
}
const statusText = status[0].toUpperCase() + status.slice(1);
const statusClass = status === "final" ? "success" : "secondary";
if (statusContainer) {
statusContainer.innerHTML = `<span class="badge bg-${statusClass}">${statusText}</span>`;
}
}| updateSubmitButton() { | ||
| const submitBtn = document.getElementById("submitForm"); | ||
| const publishToggle = document.getElementById("publish-dataset-toggle"); | ||
| const publicOption = document.getElementById("public-option"); |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable publicOption.
| const publicOption = document.getElementById("public-option"); |
SFDS-184