Skip to content

Implement CSV export for fleet data#209

Open
shay-coding wants to merge 1 commit intomainfrom
export-fleet-as-csv
Open

Implement CSV export for fleet data#209
shay-coding wants to merge 1 commit intomainfrom
export-fleet-as-csv

Conversation

@shay-coding
Copy link
Collaborator

@shay-coding shay-coding commented Jan 7, 2026

Added a new view to export fleet data as a CSV file.

Summary by CodeRabbit

  • New Features
    • Added CSV export functionality for fleet data. Users can now download vehicle information with customizable filtering options (withdrawal status, depot location) and access comprehensive data fields including Fleet Number, Registration, Type, Livery, Branding, Depot, and service status details.

✏️ Tip: You can customize this high-level summary in your review settings.

Added a new view to export fleet data as a CSV file.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

Walkthrough

A CSV export function for fleet data has been added to fleet/views.py. The new endpoint export_fleet_csv accepts an operator slug and optional filters (withdrawn, depot) to generate and return a downloadable CSV file containing fleet vehicle records with standardized column headers. The function declaration appears twice in the diff.

Changes

Cohort / File(s) Summary
CSV Export Functionality
fleet/views.py
Added csv module import. Introduced export_fleet_csv(request, operator_slug) function to generate and return fleet data as CSV with filtering support (withdrawn, depot parameters) and 13 column headers (Fleet Number, Registration, Type, Type Details, Livery, Branding, Depot, Name, Previous Reg, Features, In Service, Open Top, Preserved). Function declaration appears twice in diff, requiring verification.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Poem

🐰 A CSV path does now unfold,
Fleet data bundled, neat and bold,
With filters wise and headers fair,
Downloads float through the air,
Though watch that double declaration there! 📋✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Implement CSV export for fleet data' directly and accurately summarizes the main change: adding CSV export capability for fleet data as described in the PR objectives and file changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @fleet/views.py:
- Around line 1123-1171: export_fleet_csv writes user-controlled text directly
to CSV and assumes v.features is always a list; add a small sanitizer function
(e.g., safe_csv_cell) and apply it to all string fields written (fleet_number,
reg, type_details, livery.name, branding, depot, name, prev_reg, and the
features cell) to prefix a quote when values start with "=", "+", "-", "@", or a
tab, and make the features cell robust by normalizing v.features before joining
(handle list, str, and other types safely and convert elements to strings).
Locate export_fleet_csv and wrap each text value passed into writer.writerow
with the sanitizer and replace the direct ', '.join(v.features) usage with a
defensive normalization that yields a single string for the features column.
🧹 Nitpick comments (1)
fleet/views.py (1)

1-50: Imports: csv is fine; consider making Q import explicit

  • Adding import csv here is appropriate for the new CSV view and consistent with the rest of the module.
  • export_fleet_csv (and other views) rely on Q, but this module does not explicitly import it; instead it likely leaks in via a * import. That’s brittle and is what Ruff is warning about (F405). Consider adding an explicit import, e.g. from django.db.models import Q, and removing reliance on transitive star‑imports for core ORM utilities.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a584c4 and a0a33b6.

📒 Files selected for processing (1)
  • fleet/views.py
🧰 Additional context used
🧬 Code graph analysis (1)
fleet/views.py (1)
fleet/models.py (3)
  • MBTOperator (129-169)
  • fleet (226-289)
  • vehicleType (72-87)
🪛 Ruff (0.14.10)
fleet/views.py

1124-1124: MBTOperator may be undefined, or defined from star imports

(F405)


1130-1130: fleet may be undefined, or defined from star imports

(F405)


1130-1130: Q may be undefined, or defined from star imports

(F405)


1130-1130: Q may be undefined, or defined from star imports

(F405)

Comment on lines +1123 to +1171
def export_fleet_csv(request, operator_slug):
operator = get_object_or_404(MBTOperator, operator_slug=operator_slug)

withdrawn = request.GET.get('withdrawn', '').lower() == 'true'
depot = request.GET.get('depot')

# Base queryset
qs = fleet.objects.filter(Q(operator=operator) | Q(loan_operator=operator))
if not withdrawn:
qs = qs.filter(in_service=True)
if depot:
qs = qs.filter(depot=depot)

# Order by fleet number
vehicles = qs.order_by('fleet_number_sort').select_related(
'operator', 'loan_operator', 'vehicleType', 'livery'
)

# Create the HttpResponse object with CSV header
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = f'attachment; filename="{operator_slug}_fleet.csv"'

writer = csv.writer(response)

# Write header row
headers = ['Fleet Number', 'Registration', 'Type', 'Type Details', 'Livery',
'Branding', 'Depot', 'Name', 'Previous Reg', 'Features', 'In Service',
'Open Top', 'Preserved']
writer.writerow(headers)

# Write data rows
for v in vehicles:
writer.writerow([
v.fleet_number or '',
v.reg or '',
v.vehicleType.type_name if v.vehicleType else '',
v.type_details or '',
v.livery.name if v.livery else '',
v.branding or '',
v.depot or '',
v.name or '',
v.prev_reg or '',
', '.join(v.features) if v.features else '',
'Yes' if v.in_service else 'No',
'Yes' if v.open_top else 'No',
'Yes' if v.preserved else 'No',
])

return response
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Overall CSV export logic looks good; consider hardening against CSV injection and legacy features types

The view mirrors vehicles_api’s filtering and ordering, and the selected columns/boolean formatting are all consistent with the data model, so functionally this is solid.

A couple of points worth tightening:

  • User‑controlled text (e.g. fleet_number, reg, name, branding, depot, prev_reg, features) is written directly to CSV. If an attacker can put a value starting with =, +, -, @ or a leading tab into any of those fields, opening the CSV in Excel/Sheets can trigger formula execution (“CSV injection”). Consider a small sanitizer applied to each string field before writing, for example:

    def safe_csv_cell(value):
        if not isinstance(value, str):
            return value
        if value and value[0] in ("=", "+", "-", "@", "\t"):
            return "'" + value
        return value

    and wrap the text fields (fleet_number, reg, type_details, livery.name, branding, depot, name, prev_reg, features) with safe_csv_cell(...) before passing them to writer.writerow. This keeps the export usable while mitigating formula injection in common spreadsheet tools.

  • features is a JSONField; most of the code paths now store a list of strings, but older rows might still contain a bare string or some other shape. ', '.join(v.features) will misbehave (or even raise) in those cases. A defensive pattern like:

    if isinstance(v.features, list):
        features_str = ", ".join(map(str, v.features))
    elif isinstance(v.features, str):
        features_str = v.features
    else:
        features_str = ""

    would make the export more robust against legacy data.

These are incremental hardening steps; the core view implementation is otherwise in good shape.

🧰 Tools
🪛 Ruff (0.14.10)

1124-1124: MBTOperator may be undefined, or defined from star imports

(F405)


1130-1130: fleet may be undefined, or defined from star imports

(F405)


1130-1130: Q may be undefined, or defined from star imports

(F405)


1130-1130: Q may be undefined, or defined from star imports

(F405)

🤖 Prompt for AI Agents
In @fleet/views.py around lines 1123 - 1171, export_fleet_csv writes
user-controlled text directly to CSV and assumes v.features is always a list;
add a small sanitizer function (e.g., safe_csv_cell) and apply it to all string
fields written (fleet_number, reg, type_details, livery.name, branding, depot,
name, prev_reg, and the features cell) to prefix a quote when values start with
"=", "+", "-", "@", or a tab, and make the features cell robust by normalizing
v.features before joining (handle list, str, and other types safely and convert
elements to strings). Locate export_fleet_csv and wrap each text value passed
into writer.writerow with the sanitizer and replace the direct ',
'.join(v.features) usage with a defensive normalization that yields a single
string for the features column.

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.

1 participant