Skip to content

feat(backend): Implement CSV exporting#59

Open
CaellumYHL wants to merge 2 commits intomainfrom
feat/export-csv
Open

feat(backend): Implement CSV exporting#59
CaellumYHL wants to merge 2 commits intomainfrom
feat/export-csv

Conversation

@CaellumYHL
Copy link
Contributor

Summary

This PR implements the front-end and back-end logic for CSV exportation.

Related Issues

Changes

  • Added back-end export endpoint export_items
  • Created export modal component with dropdown menus and range picker in Admin Dashboard
  • Added backend tests for export endpoint

How to Test

  1. Start backend and frontend servers
  2. Log in as admin, go to /admin, click "Export to CSV", the modal should open with filter options
  3. Click "Export CSV" with no filters
  4. Verify that the downloaded CSV correctly filters objects

Screenshots / UI (if applicable)

image

Checklist

  • Tests added or updated
  • CI passes (lint, tests, build)
  • Documentation updated (if behavior changed)
  • No secrets or credentials committed

Copy link
Contributor

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

Implements CSV export functionality across the stack by adding a backend CSV export endpoint and integrating a new Export modal into the admin UI to trigger downloads with optional filters.

Changes:

  • Added GET /api/inventory/export/ endpoint that returns collection items as a CSV with optional query-parameter filters.
  • Added a reusable ExportModal component and wired it into Admin Dashboard and Admin Catalogue pages.
  • Added backend tests for the export endpoint and updated existing frontend tests to mock the new modal.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
frontend/src/pages/admin/AdminDashboard.tsx Opens the new export modal from the dashboard “Export to CSV” button
frontend/src/pages/admin/AdminCataloguePage.tsx Replaces client-side CSV generation with the export modal flow
frontend/src/pages/admin/AdminCataloguePage.test.tsx Extends component mocks to include ExportModal
frontend/src/components/items/index.ts Re-exports ExportModal from the items component barrel
frontend/src/components/items/ExportModal.tsx Implements the export modal UI + download logic calling the backend export endpoint
frontend/src/components/items/ExportModal.css Styling for the export modal
frontend/package-lock.json Lockfile updates from dependency install
backend/inventory/views.py Adds export_items CSV export endpoint with filters
backend/inventory/urls.py Adds route for /api/inventory/export/
backend/inventory/tests.py Adds backend tests for CSV export endpoint behavior and filters
Files not reviewed (1)
  • frontend/package-lock.json: Language not supported

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

Comment on lines +218 to +220
today = datetime.now().strftime("%Y%m%d")
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="made_export_{today}.csv"'
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

today = datetime.now() produces a naive timestamp and may not match the app's configured timezone. Use Django's timezone utilities (e.g. django.utils.timezone.localdate()/timezone.now()) so the exported filename date is consistent with the rest of the system.

Copilot uses AI. Check for mistakes.
Comment on lines +217 to +239
# Build CSV response
today = datetime.now().strftime("%Y%m%d")
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="made_export_{today}.csv"'

writer = csv.writer(response)
writer.writerow(
[
"MADE ID",
"Title",
"Platform",
"Item Type",
"Box Code",
"Location",
"Location Type",
"Working Condition",
"Status",
"Created At",
]
)

for item in queryset:
writer.writerow(
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

This endpoint can return a very large CSV (especially with no filters) and currently builds it into a normal HttpResponse. For better reliability and memory usage, consider switching to StreamingHttpResponse and iterating the queryset (e.g. queryset.iterator()) so exports don't require buffering the entire file in memory.

Copilot uses AI. Check for mistakes.
Comment on lines +240 to +251
[
item.item_code,
item.title,
item.platform,
item.get_item_type_display(),
item.box.box_code if item.box else "",
item.current_location.name if item.current_location else "",
item.current_location.get_location_type_display() if item.current_location else "",
"Yes" if item.working_condition else "No",
item.get_status_display(),
item.created_at.strftime("%Y-%m-%d %H:%M:%S") if item.created_at else "",
]
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

CSV export is vulnerable to spreadsheet/CSV injection if any user-controlled string fields start with =, +, -, or @ (e.g. title, platform, location name). Consider sanitizing these values before writing (commonly by prefixing an apostrophe) so opening the CSV in Excel/Sheets can’t execute formulas.

Copilot uses AI. Check for mistakes.
)

if box_id:
queryset = queryset.filter(box__id=box_id)
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

box_id is used directly in the ORM filter; if a non-numeric value is provided (e.g. box_id=abc) Django will raise ValueError: Field 'id' expected a number and return a 500. Parse/validate box_id (and return a 400 on invalid input) before applying the filter, similar to the date parsing above.

Suggested change
queryset = queryset.filter(box__id=box_id)
try:
box_id_int = int(box_id)
except (TypeError, ValueError):
return Response(
{"error": "Invalid box_id. Must be an integer."},
status=status.HTTP_400_BAD_REQUEST,
)
queryset = queryset.filter(box__id=box_id_int)

Copilot uses AI. Check for mistakes.
Comment on lines +191 to +205
if start_date:
try:
parsed = datetime.strptime(start_date, "%Y-%m-%d")
queryset = queryset.filter(created_at__date__gte=parsed.date())
except ValueError:
return Response(
{"error": "Invalid start_date format. Use YYYY-MM-DD."},
status=status.HTTP_400_BAD_REQUEST,
)

if end_date:
try:
parsed = datetime.strptime(end_date, "%Y-%m-%d")
queryset = queryset.filter(created_at__date__lte=parsed.date())
except ValueError:
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The created_at__date__gte/lte filters apply a date extraction function to the DB column, which can prevent index usage and become slow on larger tables. Consider filtering with datetime boundaries instead (e.g., created_at__gte=start_of_day and created_at__lt=end_of_day_plus_1) to keep the query sargable.

Copilot uses AI. Check for mistakes.
Comment on lines +196 to +199
return Response(
{"error": "Invalid start_date format. Use YYYY-MM-DD."},
status=status.HTTP_400_BAD_REQUEST,
)
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

Error responses here use an { "error": ... } payload, but other API endpoints in this codebase typically return { "detail": ... } for client-facing errors. For consistency (and to better align with DRF conventions), consider switching these to { "detail": ... } or raising a DRF ValidationError.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@vivjd vivjd left a comment

Choose a reason for hiding this comment

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

Hey this is looking good! Can you resolve the copilot comments and re-request review? I believe some of its suggestions are actually worthwhile and could help improve this PR.

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.

Advanced CSV Data Management (Export & Import)

3 participants