From 23419a241ffbcf27643b4b08fd4f6c1065370593 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Tue, 4 Feb 2025 13:11:40 +0100 Subject: [PATCH 1/3] Export plans and tiers to CSV --- codecov_auth/admin.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/codecov_auth/admin.py b/codecov_auth/admin.py index e9771f009d..643f296e3c 100644 --- a/codecov_auth/admin.py +++ b/codecov_auth/admin.py @@ -1,7 +1,7 @@ +import csv import logging from datetime import timedelta from typing import Optional, Sequence - import django.forms as forms from django.conf import settings from django.contrib import admin, messages @@ -9,7 +9,7 @@ from django.db.models import OuterRef, Subquery from django.db.models.fields import BLANK_CHOICE_DASH from django.forms import CheckboxInput, Select, Textarea -from django.http import HttpRequest +from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render from django.utils import timezone from django.utils.html import format_html @@ -734,9 +734,25 @@ class PlansInline(admin.TabularInline): Plan._meta.get_field("benefits"): {"widget": Textarea(attrs={"rows": 3})}, } +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" + @admin.register(Tier) class TierAdmin(admin.ModelAdmin): + actions = [export_to_csv] list_display = ( "tier_name", "bundle_analysis", @@ -791,6 +807,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", From 46b727b6fd4848368f53f215cef2bd6b85c48937 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Tue, 4 Feb 2025 15:25:04 +0100 Subject: [PATCH 2/3] lint, update with tests --- codecov_auth/admin.py | 21 +++------------- codecov_auth/helpers.py | 21 ++++++++++++++++ codecov_auth/tests/test_admin.py | 41 ++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/codecov_auth/admin.py b/codecov_auth/admin.py index 643f296e3c..8ffec83e2f 100644 --- a/codecov_auth/admin.py +++ b/codecov_auth/admin.py @@ -1,7 +1,7 @@ -import csv import logging from datetime import timedelta from typing import Optional, Sequence + import django.forms as forms from django.conf import settings from django.contrib import admin, messages @@ -9,7 +9,7 @@ from django.db.models import OuterRef, Subquery from django.db.models.fields import BLANK_CHOICE_DASH from django.forms import CheckboxInput, Select, Textarea -from django.http import HttpRequest, HttpResponse +from django.http import HttpRequest from django.shortcuts import redirect, render from django.utils import timezone from django.utils.html import format_html @@ -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 @@ -734,21 +734,6 @@ class PlansInline(admin.TabularInline): Plan._meta.get_field("benefits"): {"widget": Textarea(attrs={"rows": 3})}, } -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" - @admin.register(Tier) class TierAdmin(admin.ModelAdmin): 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 ec40c11f0d..92bd2bfb06 100644 --- a/codecov_auth/tests/test_admin.py +++ b/codecov_auth/tests/test_admin.py @@ -1007,6 +1007,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): @@ -1051,3 +1068,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) From a1317494129b27a877159145bdba48e4a370effe Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Tue, 4 Feb 2025 17:24:14 +0100 Subject: [PATCH 3/3] update with shared version --- requirements.in | 2 +- requirements.txt | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/requirements.in b/requirements.in index 488dc32235..667d55bbad 100644 --- a/requirements.in +++ b/requirements.in @@ -25,7 +25,7 @@ freezegun google-cloud-pubsub gunicorn>=22.0.0 https://github.com/codecov/opentelem-python/archive/refs/tags/v0.0.4a1.tar.gz#egg=codecovopentelem -https://github.com/codecov/shared/archive/c8dca747a7feb93d7c4db6d8845692420b99e676.tar.gz#egg=shared +https://github.com/codecov/shared/archive/7ba099fa0552244c77a8cc3a4b772216613c09c8.tar.gz#egg=shared https://github.com/photocrowd/django-cursor-pagination/archive/f560902696b0c8509e4d95c10ba0d62700181d84.tar.gz idna>=3.7 minio diff --git a/requirements.txt b/requirements.txt index e0f48a07e2..bcb25ecb15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -202,8 +202,6 @@ googleapis-common-protos[grpc]==1.59.1 # grpcio-status graphql-core==3.2.3 # via ariadne -greenlet==3.1.1 - # via sqlalchemy grpc-google-iam-v1==0.12.6 # via google-cloud-pubsub grpcio==1.62.1 @@ -418,7 +416,7 @@ sentry-sdk[celery]==2.13.0 # shared setproctitle==1.1.10 # via -r requirements.in -shared @ https://github.com/codecov/shared/archive/c8dca747a7feb93d7c4db6d8845692420b99e676.tar.gz +shared @ https://github.com/codecov/shared/archive/7ba099fa0552244c77a8cc3a4b772216613c09c8.tar.gz # via -r requirements.in simplejson==3.17.2 # via -r requirements.in