Feature kpoland refactor django web views file#270
Conversation
Changed Files
|
|
makes sense at a high level; after copilot's comments, run |
There was a problem hiding this comment.
Pull request overview
This PR refactors the Django users app views by splitting a large views module into multiple focused modules, while keeping backwards-compatible imports via users/views/__init__.py and retaining a deprecated views file for verification.
Changes:
- Split user-facing views into dedicated modules (
api_keys,captures,datasets,downloads,files,sharing, etc.). - Add/maintain re-export surface in
users/views/__init__.pyto preserve existing import paths. - Introduce/retain generic HTML fragment rendering endpoint and other utility endpoints in their own module.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
gateway/sds_gateway/users/views/__init__.py |
Central re-exports to keep legacy imports working after module split. |
gateway/sds_gateway/users/views/api_keys.py |
API key management views/utilities moved into their own module. |
gateway/sds_gateway/users/views/captures.py |
Capture list + API search + keyword autocomplete views moved/refactored. |
gateway/sds_gateway/users/views/datasets.py |
Dataset creation/editing, listing, details, publish, versioning, and published-dataset search. |
gateway/sds_gateway/users/views/downloads.py |
Temporary zip download and item download request views. |
gateway/sds_gateway/users/views/files.py |
File list/detail/download/preview and files UI views. |
gateway/sds_gateway/users/views/share_groups.py |
Share group management views separated out. |
gateway/sds_gateway/users/views/sharing.py |
Generic item sharing endpoint and helpers separated out. |
gateway/sds_gateway/users/views/special_pages.py |
Home page + special competition page views separated out. |
gateway/sds_gateway/users/views/uploads.py |
Upload/capture creation flow separated out. |
gateway/sds_gateway/users/views/user_profile.py |
User profile detail/update/redirect views separated out. |
gateway/sds_gateway/users/views/utilities.py |
Generic server-side fragment rendering endpoint separated out. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| matching_dataset_ids = { | ||
| capture.dataset_id | ||
| for capture in filtered_captures | ||
| if capture.dataset_id is not None | ||
| } |
There was a problem hiding this comment.
This mapping back to datasets uses capture.dataset_id, which will be None for captures associated via the M2M Capture.datasets relationship. That will cause matching datasets to be dropped when applying frequency filters. Consider deriving dataset IDs/UUIDs from capture.datasets (and the FK for backward compatibility) instead of relying on dataset_id.
|
|
||
| def get(self, request, *args, **kwargs) -> HttpResponse: | ||
| # Get query parameters | ||
| page = int(request.GET.get("page", 1)) |
There was a problem hiding this comment.
page is coerced with int(request.GET.get('page', 1)). If a non-numeric value is provided (e.g. ?page=abc), this will raise ValueError before pagination handling and return a 500. Consider leaving it as a string and using paginator.get_page(...), or wrapping the int() conversion in a try/except ValueError and falling back to page 1.
| page = int(request.GET.get("page", 1)) | |
| page_param = request.GET.get("page", 1) | |
| try: | |
| page = int(page_param) | |
| except (TypeError, ValueError): | |
| page = 1 |
| with file_path.open("rb") as f: | ||
| file_content = f.read() | ||
| response = HttpResponse(file_content, content_type="application/zip") | ||
| response["Content-Disposition"] = ( | ||
| f'attachment; filename="{temp_zip.filename}"' | ||
| ) | ||
| response["Content-Length"] = file_size |
There was a problem hiding this comment.
This view reads the entire zip into memory (file_content = f.read()) before returning it. Temporary zip downloads can be large, so this can cause high memory usage and slow response times. Use Django’s FileResponse/streaming response to stream the file from disk instead of buffering it fully in RAM.
There was a problem hiding this comment.
bit of a tangent, but this might actually be a good one to address
| """Extract and return request parameters for HTML view.""" | ||
| return { | ||
| "page": int(request.GET.get("page", 1)), | ||
| "sort_by": request.GET.get("sort_by", "created_at"), | ||
| "sort_order": request.GET.get("sort_order", "desc"), | ||
| "search": request.GET.get("search", ""), | ||
| "date_start": request.GET.get("date_start", ""), | ||
| "date_end": request.GET.get("date_end", ""), | ||
| "cap_type": request.GET.get("capture_type", ""), | ||
| "min_freq": request.GET.get("min_freq", ""), | ||
| "max_freq": request.GET.get("max_freq", ""), | ||
| "items_per_page": min( | ||
| int(request.GET.get("items_per_page", self.default_items_per_page)), | ||
| self.max_items_per_page, | ||
| ), | ||
| } |
There was a problem hiding this comment.
page/items_per_page are coerced with int(...) directly from query params. If a non-numeric value is provided (e.g. ?page=abc or ?items_per_page=foo), this will raise ValueError before the paginator fallback logic and return a 500. Consider using paginator.get_page(...) and validating ints with try/except ValueError (clamping to defaults on failure).
| # SECURITY MODEL: | ||
| # - Only templates in users/components/ directory are allowed (enforced by prefix check) | ||
| # - Context data is provided by the client, not pulled from the database | ||
| # - All data is rendered through Django templates with automatic HTML escaping | ||
| # - CSRF protection is still enforced by Django middleware | ||
| # - No sensitive server-side data is exposed - only client-provided data is rendered |
There was a problem hiding this comment.
The security note claims “Django's automatic HTML escaping prevents XSS”, but this endpoint renders templates with fully client-provided context and some components use the safe filter (e.g., templates/users/components/table_rows.html renders cell.html|safe). That means a caller can inject raw HTML/JS into the rendered fragment. Consider restricting this endpoint to an explicit allowlist of templates that never use |safe with client-controlled values, and/or sanitizing/escaping specific context fields before rendering (or removing the XSS claim from the docstring).
| class UserDetailView(Auth0LoginRequiredMixin, DetailView): # pyright: ignore[reportMissingTypeArgument] | ||
| model = User | ||
| slug_field = "id" | ||
| slug_url_arg = "id" |
There was a problem hiding this comment.
slug_url_arg is not a Django DetailView attribute (the correct attribute name is slug_url_kwarg). As written, this configuration is ignored and can be misleading; either remove the slug configuration (since the URL uses <int:pk>) or rename it to slug_url_kwarg and update the URL pattern accordingly.
| slug_url_arg = "id" |
There was a problem hiding this comment.
C-3PO seems correct https://github.com/django/django/blob/main/django/views/generic/detail.py#L17
| return datasets | ||
|
|
||
|
|
||
| class SearchPublishedDatasetsView(Auth0LoginRequiredMixin, View): |
There was a problem hiding this comment.
SearchPublishedDatasetsView is described as “public, no auth required”, and it’s linked from the public home page, but it inherits Auth0LoginRequiredMixin which forces a login redirect. If this page is intended to be publicly browsable, drop the login-required mixin (and keep the serializer context logic for anonymous users).
| class SearchPublishedDatasetsView(Auth0LoginRequiredMixin, View): | |
| class SearchPublishedDatasetsView(View): |
There was a problem hiding this comment.
added last month, see if we need to update the mixin or the comment
| # Get all captures for these datasets and convert to list | ||
| captures_qs = Capture.objects.filter( | ||
| dataset__uuid__in=dataset_uuids, is_deleted=False | ||
| ) |
There was a problem hiding this comment.
Frequency filtering queries captures via the deprecated FK (Capture.dataset) only. Since dataset composition is handled via the M2M Capture.datasets / Dataset.captures relationship (expand/contract), this will miss captures added through the current UI and make frequency filters return empty/incorrect results. Use a query that includes both relationships (e.g., Q(datasets__uuid__in=...) | Q(dataset__uuid__in=...)), or reuse relationship_utils.get_dataset_captures()/get_dataset_files_including_captures() patterns.
Migrating views into new files and aligning with
urls.py. Keeping aviews_deprecated.pyfile for now to check accuracy.