diff --git a/codecov_auth/admin.py b/codecov_auth/admin.py index e9771f009d..8ffec83e2f 100644 --- a/codecov_auth/admin.py +++ b/codecov_auth/admin.py @@ -25,7 +25,7 @@ from codecov.admin import AdminMixin from codecov.commands.exceptions import ValidationError -from codecov_auth.helpers import History +from codecov_auth.helpers import History, export_to_csv from codecov_auth.models import OrganizationLevelToken, Owner, SentryUser, Session, User from codecov_auth.services.org_level_token_service import OrgLevelTokenService from services.task import TaskService @@ -737,6 +737,7 @@ class PlansInline(admin.TabularInline): @admin.register(Tier) class TierAdmin(admin.ModelAdmin): + actions = [export_to_csv] list_display = ( "tier_name", "bundle_analysis", @@ -791,6 +792,8 @@ def clean_monthly_uploads_limit(self) -> int | None: @admin.register(Plan) class PlanAdmin(admin.ModelAdmin): form = PlanAdminForm + actions = [export_to_csv] + list_display = ( "name", "marketing_name", diff --git a/codecov_auth/helpers.py b/codecov_auth/helpers.py index 3998d7529f..d06eba1215 100644 --- a/codecov_auth/helpers.py +++ b/codecov_auth/helpers.py @@ -1,8 +1,10 @@ +import csv from traceback import format_stack import requests from django.contrib.admin.models import CHANGE, LogEntry from django.contrib.contenttypes.models import ContentType +from django.http import HttpResponse from codecov_auth.constants import GITLAB_BASE_URL @@ -69,3 +71,22 @@ def log(objects, message, user, action_flag=None, add_traceback=False): change_message=message, action_flag=action_flag, ) + + +def export_to_csv(modeladmin, request, queryset): + model = queryset.model + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = ( + f'attachment; filename="{model._meta.model_name}s.csv"' + ) + writer = csv.writer(response) + + writer.writerow([field.name for field in model._meta.fields]) + + for obj in queryset: + writer.writerow([getattr(obj, field.name) for field in model._meta.fields]) + + return response + + +export_to_csv.short_description = "Export selected items to CSV" diff --git a/codecov_auth/tests/test_admin.py b/codecov_auth/tests/test_admin.py index 6b36c50027..94b7103086 100644 --- a/codecov_auth/tests/test_admin.py +++ b/codecov_auth/tests/test_admin.py @@ -1005,6 +1005,23 @@ def test_plan_change_form_validation(self): self.assertEqual(response.status_code, 200) self.assertContains(response, "Monthly uploads limit cannot be negative.") + def test_export_to_csv(self): + response = self.client.post( + reverse("admin:codecov_auth_plan_changelist"), + data={"action": "export_to_csv", ACTION_CHECKBOX_NAME: [self.plan.pk]}, + ) + self.assertEqual(response.status_code, 200) + + def test_export_to_csv_with_multiple_selected_items(self): + response = self.client.post( + reverse("admin:codecov_auth_plan_changelist"), + data={ + "action": "export_to_csv", + ACTION_CHECKBOX_NAME: [self.plan.pk, self.tier.pk], + }, + ) + self.assertEqual(response.status_code, 200) + class TierAdminTest(TestCase): def setUp(self): @@ -1049,3 +1066,27 @@ def test_tier_change_form(self): "private_repo_support", ]: self.assertContains(response, f"id_{field}") + + def test_export_to_csv(self): + response = self.client.post( + reverse("admin:codecov_auth_tier_changelist"), + data={"action": "export_to_csv", ACTION_CHECKBOX_NAME: [self.tier.pk]}, + ) + self.assertEqual(response.status_code, 200) + + def test_export_to_csv_with_multiple_selected_items(self): + response = self.client.post( + reverse("admin:codecov_auth_tier_changelist"), + data={ + "action": "export_to_csv", + ACTION_CHECKBOX_NAME: [self.tier.pk, self.plan.pk], + }, + ) + self.assertEqual(response.status_code, 200) + + def test_export_to_csv_with_no_selected_items(self): + response = self.client.post( + reverse("admin:codecov_auth_tier_changelist"), + data={"action": "export_to_csv"}, + ) + self.assertEqual(response.status_code, 200)