mirror of
https://github.com/i701/sarlink-portal-api.git
synced 2025-06-28 15:53:57 +00:00
Initial commit
This commit is contained in:
0
devices/__init__.py
Normal file
0
devices/__init__.py
Normal file
6
devices/admin.py
Normal file
6
devices/admin.py
Normal 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
6
devices/apps.py
Normal 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
12
devices/filters.py
Normal 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__"
|
84
devices/migrations/0001_initial.py
Normal file
84
devices/migrations/0001_initial.py
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
18
devices/migrations/0002_payment_devices.py
Normal file
18
devices/migrations/0002_payment_devices.py
Normal 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"),
|
||||
),
|
||||
]
|
@ -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",
|
||||
),
|
||||
]
|
19
devices/migrations/0004_alter_device_id.py
Normal file
19
devices/migrations/0004_alter_device_id.py
Normal 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"
|
||||
),
|
||||
),
|
||||
]
|
0
devices/migrations/__init__.py
Normal file
0
devices/migrations/__init__.py
Normal file
21
devices/models.py
Normal file
21
devices/models.py
Normal 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
20
devices/serializers.py
Normal 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
3
devices/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
26
devices/urls.py
Normal file
26
devices/urls.py
Normal 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
76
devices/views.py
Normal 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,
|
||||
)
|
||||
|
Reference in New Issue
Block a user