Initial commit

This commit is contained in:
2025-01-20 14:33:03 +05:00
commit 4d0eb86478
84 changed files with 4436 additions and 0 deletions

0
devices/__init__.py Normal file
View File

6
devices/admin.py Normal file
View File

@ -0,0 +1,6 @@
from django.contrib import admin
# Register your models here.
from .models import Device
admin.site.register(Device)

6
devices/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class DevicesConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "devices"

12
devices/filters.py Normal file
View File

@ -0,0 +1,12 @@
import django_filters
from .models import Device
class DeviceFilter(django_filters.FilterSet):
name = django_filters.CharFilter(lookup_expr="icontains")
mac = django_filters.CharFilter(lookup_expr="icontains")
user = django_filters.CharFilter(field_name='user__last_name', lookup_expr="icontains")
class Meta:
model = Device
fields = "__all__"

View File

@ -0,0 +1,84 @@
# Generated by Django 5.1.2 on 2025-01-20 04:51
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="BillFormula",
fields=[
(
"id",
models.CharField(max_length=255, primary_key=True, serialize=False),
),
("formula", models.CharField(max_length=255)),
("base_amount", models.FloatField()),
("discount_percentage", models.FloatField()),
("created_at", models.DateTimeField(default=django.utils.timezone.now)),
("updated_at", models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name="Payment",
fields=[
(
"id",
models.CharField(max_length=255, primary_key=True, serialize=False),
),
("number_of_months", models.IntegerField()),
("amount", models.FloatField()),
("paid", models.BooleanField(default=False)),
("paid_at", models.DateTimeField(blank=True, null=True)),
(
"method",
models.CharField(
choices=[("WALLET", "Wallet"), ("TRANSFER", "Transfer")],
default="TRANSFER",
max_length=255,
),
),
("expires_at", models.DateTimeField(blank=True, null=True)),
("created_at", models.DateTimeField(default=django.utils.timezone.now)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="payments",
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.CreateModel(
name="Topup",
fields=[
(
"id",
models.CharField(max_length=255, primary_key=True, serialize=False),
),
("amount", models.FloatField()),
("paid", models.BooleanField(default=False)),
("created_at", models.DateTimeField(default=django.utils.timezone.now)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="topups",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2025-01-20 04:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("billing", "0001_initial"),
("devices", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="payment",
name="devices",
field=models.ManyToManyField(related_name="payments", to="billing.device"),
),
]

View File

@ -0,0 +1,68 @@
# Generated by Django 5.1.2 on 2025-01-20 06:58
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("devices", "0002_payment_devices"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="Device",
fields=[
(
"id",
models.CharField(max_length=255, primary_key=True, serialize=False),
),
("name", models.CharField(max_length=255)),
("mac", models.CharField(max_length=255)),
(
"reason_for_blocking",
models.CharField(blank=True, max_length=255, null=True),
),
("is_active", models.BooleanField(default=False)),
("registered", models.BooleanField(default=False)),
("blocked", models.BooleanField(default=False)),
(
"blocked_by",
models.CharField(
choices=[("ADMIN", "Admin"), ("PARENT", "Parent")],
default="PARENT",
max_length=255,
),
),
("expiry_date", models.DateTimeField(blank=True, null=True)),
("created_at", models.DateTimeField(default=django.utils.timezone.now)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="devices",
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.DeleteModel(
name="BillFormula",
),
migrations.RemoveField(
model_name="topup",
name="user",
),
migrations.DeleteModel(
name="Payment",
),
migrations.DeleteModel(
name="Topup",
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 5.1.2 on 2025-01-20 07:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("devices", "0003_device_delete_billformula_remove_topup_user_and_more"),
]
operations = [
migrations.AlterField(
model_name="device",
name="id",
field=models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
]

View File

21
devices/models.py Normal file
View File

@ -0,0 +1,21 @@
from django.db import models
from django.utils import timezone
from api.models import User
class Device(models.Model):
name = models.CharField(max_length=255)
mac = models.CharField(max_length=255)
reason_for_blocking = models.CharField(max_length=255, null=True, blank=True)
is_active = models.BooleanField(default=False)
registered = models.BooleanField(default=False)
blocked = models.BooleanField(default=False)
blocked_by = models.CharField(max_length=255, choices=[('ADMIN', 'Admin'), ('PARENT', 'Parent')], default='PARENT')
expiry_date = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='devices')
def __str__(self):
return self.name

20
devices/serializers.py Normal file
View File

@ -0,0 +1,20 @@
from rest_framework import serializers
from .models import Device
from api.serializers import CustomReadOnlyUserSerializer
class CreateDeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ["name", "mac"]
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = "__all__"
class ReadOnlyDeviceSerializer(serializers.ModelSerializer):
user = CustomReadOnlyUserSerializer(read_only=True)
class Meta: #type: ignore
depth = 2
model = Device
fields = "__all__"

3
devices/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

26
devices/urls.py Normal file
View File

@ -0,0 +1,26 @@
from django.urls import path
from . import views
urlpatterns = [
path(
"",
views.DeviceListCreateAPIView.as_view(),
name="device-list",
),
path(
"<int:pk>/",
views.DeviceDetailAPIView.as_view(),
name="device-detail",
),
path(
"<int:pk>/update/",
views.DeviceUpdateAPIView.as_view(),
name="device-edit",
),
path(
"<int:pk>/delete/",
views.DeviceDestroyAPIView.as_view(),
name="device-delete",
),
]

76
devices/views.py Normal file
View File

@ -0,0 +1,76 @@
from rest_framework import generics, status
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import Device
from .serializers import CreateDeviceSerializer, DeviceSerializer, ReadOnlyDeviceSerializer
from api.mixins import StaffEditorPermissionMixin
from .filters import DeviceFilter
import re
class DeviceListCreateAPIView(
StaffEditorPermissionMixin,
generics.ListCreateAPIView,
):
queryset = Device.objects.select_related("user").all()
serializer_class = CreateDeviceSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = "__all__"
filterset_class = DeviceFilter
def get_serializer_class(self) -> type:
if self.request.method == "POST":
return CreateDeviceSerializer
return DeviceSerializer
# @method_decorator(cache_page(10))
def create(self, request, *args, **kwargs):
mac = request.data.get("mac", None)
if not re.match(r"^([0-9A-Fa-f]{2}([.:-]?)){5}[0-9A-Fa-f]{2}$", mac):
return Response({"error": "Invalid mac address"}, status=400)
if Device.objects.filter(mac=mac).exists():
return Response({"error": "Device with this mac already exists"}, status=400)
return super().create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class DeviceDetailAPIView(StaffEditorPermissionMixin, generics.RetrieveAPIView):
queryset = Device.objects.select_related("user").all()
serializer_class = ReadOnlyDeviceSerializer
lookup_field = "pk"
class DeviceUpdateAPIView(StaffEditorPermissionMixin, generics.UpdateAPIView):
queryset = Device.objects.all()
serializer_class = CreateDeviceSerializer
lookup_field = "pk"
def update(self, request, *args, **kwargs):
# Pass 'partial=True' to allow partial updates
partial = kwargs.pop("partial", True)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
class DeviceDestroyAPIView(StaffEditorPermissionMixin, generics.DestroyAPIView):
queryset = Device.objects.all()
serializer_class = DeviceSerializer
lookup_field = "pk"
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
device_name = instance.name
self.perform_destroy(instance)
return Response(
{"message": f"Device '{device_name}' deleted."},
status=status.HTTP_200_OK,
)