From cb15aed94be37bb8ba84ae19644cb9a9f90e30ee Mon Sep 17 00:00:00 2001 From: i701 Date: Sat, 28 Jun 2025 23:23:04 +0500 Subject: [PATCH 1/3] =?UTF-8?q?WIP=20(Payments)=20Add=20PaymentFilter=20fo?= =?UTF-8?q?r=20filtering=20payment=20records=20=F0=9F=9A=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- billing/filters.py | 18 ++++++++++++++++++ billing/views.py | 5 +++++ 2 files changed, 23 insertions(+) create mode 100644 billing/filters.py diff --git a/billing/filters.py b/billing/filters.py new file mode 100644 index 0000000..858e6c4 --- /dev/null +++ b/billing/filters.py @@ -0,0 +1,18 @@ +import django_filters +from .models import Payment + + +class PaymentFilter(django_filters.FilterSet): + amount = django_filters.NumericRangeFilter(field_name="amount") + number_of_months = django_filters.NumericRangeFilter(field_name="number_of_months") + paid = django_filters.BooleanFilter(field_name="paid") + method = django_filters.ChoiceFilter( + choices=Payment.PAYMENT_TYPES, lookup_expr="iexact" + ) + mib_reference = django_filters.CharFilter(lookup_expr="icontains") + paid_at = django_filters.DateFromToRangeFilter() + created_at = django_filters.DateFromToRangeFilter() + + class Meta: + model = Payment + fields = "__all__" diff --git a/billing/views.py b/billing/views.py index ce187d7..584925c 100644 --- a/billing/views.py +++ b/billing/views.py @@ -6,6 +6,7 @@ from datetime import timedelta import requests from django.utils import timezone from django.utils.timezone import localtime +from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics, status from rest_framework.response import Response @@ -16,6 +17,7 @@ import logging from .models import Device, Payment from .serializers import PaymentSerializer, UpdatePaymentSerializer +from .filters import PaymentFilter env.read_env(os.path.join(BASE_DIR, ".env")) @@ -31,6 +33,9 @@ class InsufficientFundsError(Exception): class ListCreatePaymentView(StaffEditorPermissionMixin, generics.ListCreateAPIView): serializer_class = PaymentSerializer queryset = Payment.objects.all().select_related("user") + filter_backends = [DjangoFilterBackend] + filterset_fields = "__all__" + filterset_class = PaymentFilter def get_queryset(self): queryset = super().get_queryset() From b149c86899125c473cde595c26fed0fd3d8cf4eb Mon Sep 17 00:00:00 2001 From: i701 Date: Sun, 29 Jun 2025 19:17:40 +0500 Subject: [PATCH 2/3] Add seed_payments management command to populate payment models with dummy data --- billing/management/commands/__init__.py | 0 billing/management/commands/seed_payments.py | 95 ++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 billing/management/commands/__init__.py create mode 100644 billing/management/commands/seed_payments.py diff --git a/billing/management/commands/__init__.py b/billing/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/billing/management/commands/seed_payments.py b/billing/management/commands/seed_payments.py new file mode 100644 index 0000000..5102b81 --- /dev/null +++ b/billing/management/commands/seed_payments.py @@ -0,0 +1,95 @@ +# billing/management/commands/seed_billing.py + +import random +from django.core.management.base import BaseCommand +from django.utils import timezone +from faker import Faker +from billing.models import ( + Payment, +) +from devices.models import Device +from api.models import User + + +class Command(BaseCommand): + help = "Seeds payment models with dummy data." + + def add_arguments(self, parser): + parser.add_argument( + "--number", + type=int, + default=10, + help="The number of payments to create.", + ) + + def handle(self, *args, **options): + number = options["number"] + fake = Faker() + + users = User.objects.all() + if not users.exists(): + self.stdout.write( + self.style.ERROR( + "No users found. Please seed users first (e.g., python manage.py seed_users)." + ) + ) + return + + all_devices = Device.objects.all() + if not all_devices.exists(): + self.stdout.write( + self.style.ERROR( + "No devices found. Please seed devices first (e.g., python manage.py seed_devices)." + ) + ) + return + + self.stdout.write(self.style.NOTICE(f"Seeding {number} payments...")) + + for _ in range(number): + random_user = random.choice(users) + + num_devices_to_attach = random.randint(1, min(3, len(all_devices))) + + devices_for_payment = random.sample( + list(all_devices), num_devices_to_attach + ) + + paid_status = fake.boolean(chance_of_getting_true=80) + paid_at_date = fake.date_time_this_year() if paid_status else None + + if paid_at_date and timezone.is_naive(paid_at_date): + paid_at_date = timezone.make_aware(paid_at_date) + + expires_at_date = None + if paid_at_date: + from datetime import timedelta + + expires_at_date = paid_at_date + timedelta( + days=30 * fake.random_int(min=1, max=12) + ) + + payment_method = fake.random_element( + elements=[choice[0] for choice in Payment.PAYMENT_TYPES] + ) + + payment = Payment.objects.create( + number_of_months=fake.random_int(min=1, max=12), + amount=fake.pydecimal( + left_digits=4, + right_digits=2, + positive=True, + min_value=100.00, + max_value=5000.00, + ), + paid=paid_status, + user=random_user, + paid_at=paid_at_date, + method=payment_method, + expires_at=expires_at_date, + updated_at=timezone.now(), + ) + + payment.devices.set(devices_for_payment) + + self.stdout.write(self.style.SUCCESS(f"Successfully seeded {number} payments.")) From 428d9ae0e142ce6e16d210860c309c56a191a9ba Mon Sep 17 00:00:00 2001 From: i701 Date: Sun, 29 Jun 2025 19:28:41 +0500 Subject: [PATCH 3/3] Refactor PaymentFilter to use RangeFilter for amount and number_of_months; add vendor filter to DeviceFilter --- billing/filters.py | 4 ++-- devices/filters.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/billing/filters.py b/billing/filters.py index 858e6c4..07cc71a 100644 --- a/billing/filters.py +++ b/billing/filters.py @@ -3,8 +3,8 @@ from .models import Payment class PaymentFilter(django_filters.FilterSet): - amount = django_filters.NumericRangeFilter(field_name="amount") - number_of_months = django_filters.NumericRangeFilter(field_name="number_of_months") + amount = django_filters.RangeFilter(field_name="amount") + number_of_months = django_filters.RangeFilter(field_name="number_of_months") paid = django_filters.BooleanFilter(field_name="paid") method = django_filters.ChoiceFilter( choices=Payment.PAYMENT_TYPES, lookup_expr="iexact" diff --git a/devices/filters.py b/devices/filters.py index f412a2b..ad7ea80 100644 --- a/devices/filters.py +++ b/devices/filters.py @@ -5,6 +5,7 @@ from .models import Device class DeviceFilter(django_filters.FilterSet): name = django_filters.CharFilter(lookup_expr="icontains") mac = django_filters.CharFilter(lookup_expr="icontains") + vendor = django_filters.CharFilter(lookup_expr="icontains") user = django_filters.CharFilter( field_name="user__last_name", lookup_expr="icontains" )