From 061751873b1e36fe8aa4de08269b7ee12a85a072 Mon Sep 17 00:00:00 2001 From: Musale Martin Date: Tue, 19 May 2026 11:49:21 +0300 Subject: [PATCH] feat: add is_test field to ServiceProduct model and update admin and views for test mode handling --- src/apps/payments/admin.py | 4 ++-- .../migrations/0004_serviceproduct_is_test.py | 18 ++++++++++++++++++ src/apps/payments/models.py | 5 +++++ src/apps/payments/views.py | 8 ++++++++ src/static/css/main.css | 7 +++++++ src/templates/dashboard/services/create.html | 8 ++++++++ src/templates/dashboard/services/list.html | 18 +++++++++++++----- 7 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 src/apps/payments/migrations/0004_serviceproduct_is_test.py diff --git a/src/apps/payments/admin.py b/src/apps/payments/admin.py index c4b44c4..ca63095 100644 --- a/src/apps/payments/admin.py +++ b/src/apps/payments/admin.py @@ -11,8 +11,8 @@ class ServiceProductAdmin(admin.ModelAdmin): """Admin view for ServiceProduct model.""" - list_display = ("name", "slug", "is_active", "contact_email", "created_at") - list_filter = ("is_active", "created_at") + list_display = ("name", "slug", "is_active", "is_test", "contact_email", "created_at") + list_filter = ("is_active", "is_test", "created_at") search_fields = ("name", "slug", "contact_email") readonly_fields = ("api_key", "api_secret", "created_at", "updated_at") prepopulated_fields: ClassVar[dict] = {"slug": ("name",)} diff --git a/src/apps/payments/migrations/0004_serviceproduct_is_test.py b/src/apps/payments/migrations/0004_serviceproduct_is_test.py new file mode 100644 index 0000000..695437b --- /dev/null +++ b/src/apps/payments/migrations/0004_serviceproduct_is_test.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.14 on 2026-05-19 08:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('payments', '0003_payment_exchange_rate_payment_settlement_amount_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='serviceproduct', + name='is_test', + field=models.BooleanField(default=False, help_text='Mark as test product to use Paystack test credentials.', verbose_name='test product'), + ), + ] diff --git a/src/apps/payments/models.py b/src/apps/payments/models.py index 7e31364..0d26233 100644 --- a/src/apps/payments/models.py +++ b/src/apps/payments/models.py @@ -54,6 +54,11 @@ class ServiceProduct(models.Model): # Settings is_active = models.BooleanField("active", default=True) + is_test = models.BooleanField( + "test product", + default=False, + help_text="Mark as test product to use Paystack test credentials.", + ) allowed_currencies = models.JSONField( "allowed currencies", default=list, diff --git a/src/apps/payments/views.py b/src/apps/payments/views.py index 93522ae..adc92f2 100644 --- a/src/apps/payments/views.py +++ b/src/apps/payments/views.py @@ -242,6 +242,11 @@ def get_queryset(self): qs = qs.filter(is_active=True) elif status == "inactive": qs = qs.filter(is_active=False) + mode = self.request.GET.get("mode") + if mode == "test": + qs = qs.filter(is_test=True) + elif mode == "live": + qs = qs.filter(is_test=False) return qs.annotate( payment_count=models.Count("payments"), successful_payments=models.Count("payments", filter=models.Q(payments__status=Payment.Status.SUCCESS)), @@ -256,8 +261,10 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["total_services"] = ServiceProduct.objects.count() context["active_services"] = ServiceProduct.objects.filter(is_active=True).count() + context["test_services"] = ServiceProduct.objects.filter(is_test=True).count() context["search_query"] = self.request.GET.get("q", "") context["current_status"] = self.request.GET.get("status", "all") + context["current_mode"] = self.request.GET.get("mode", "all") return context @@ -270,6 +277,7 @@ class ServiceCreateView(AdminRequiredMixin, CreateView): "name", "slug", "description", + "is_test", "webhook_url", "default_callback_url", "contact_email", diff --git a/src/static/css/main.css b/src/static/css/main.css index bf83229..621edb7 100644 --- a/src/static/css/main.css +++ b/src/static/css/main.css @@ -4246,6 +4246,10 @@ details.collapse summary::-webkit-details-marker { border-radius: 9999px; padding: 0px; } +[type="checkbox"].checkbox-sm { + height: 1.25rem; + width: 1.25rem; +} .drawer-open > .drawer-toggle { display: none; } @@ -4804,6 +4808,9 @@ html:has(.drawer-toggle:checked) { .ml-1 { margin-left: 0.25rem; } +.ml-2 { + margin-left: 0.5rem; +} .ml-auto { margin-left: auto; } diff --git a/src/templates/dashboard/services/create.html b/src/templates/dashboard/services/create.html index b9e8464..3c26b9e 100644 --- a/src/templates/dashboard/services/create.html +++ b/src/templates/dashboard/services/create.html @@ -67,6 +67,14 @@

placeholder="Describe this product..." class="textarea textarea-bordered w-full rounded-lg bg-base-100/50 text-sm">{{ form.description.value|default:'' }} +
+ + +
diff --git a/src/templates/dashboard/services/list.html b/src/templates/dashboard/services/list.html index 2617141..5a9ea1c 100644 --- a/src/templates/dashboard/services/list.html +++ b/src/templates/dashboard/services/list.html @@ -76,11 +76,19 @@
- {% if service.is_active %} - - {% else %} - - {% endif %} +
+ {% if service.is_test %} + + + Test + + {% endif %} + {% if service.is_active %} + + {% else %} + + {% endif %} +

{{ service.name }}

{{ service.description|default:"No description" }}