Refactor device management: add vendor field, enhance admin display, and improve MAC address handling
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 4m18s

This commit is contained in:
2025-06-01 19:27:04 +05:00
parent e1c862184e
commit 4db9d7fabd
8 changed files with 122 additions and 20 deletions

View File

@ -3,4 +3,20 @@ from django.contrib import admin
# Register your models here.
from .models import Device
admin.site.register(Device)
class DeviceAdmin(admin.ModelAdmin):
list_display = (
"id",
"user",
"mac",
"vendor",
"blocked_by",
"name",
"created_at",
"updated_at",
)
search_fields = ("mac", "name")
list_filter = ("user",)
admin.site.register(Device, DeviceAdmin)

View File

@ -0,0 +1,17 @@
# Generated by Django 5.2 on 2025-06-01 13:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("devices", "0006_alter_device_mac"),
]
operations = [
migrations.AddField(
model_name="device",
name="vendor",
field=models.CharField(blank=True, default="", max_length=255, null=True),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.2 on 2025-06-01 14:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("devices", "0007_device_vendor"),
]
operations = [
migrations.AlterField(
model_name="device",
name="blocked_by",
field=models.CharField(
blank=True,
choices=[("ADMIN", "Admin"), ("PARENT", "Parent")],
default=None,
max_length=255,
null=True,
),
),
]

View File

@ -21,6 +21,7 @@ class Device(models.Model):
validate_mac_address,
],
)
vendor = models.CharField(max_length=255, null=True, blank=True, default="")
has_a_pending_payment = models.BooleanField(default=False)
reason_for_blocking = models.CharField(max_length=255, null=True, blank=True)
is_active = models.BooleanField(default=False)
@ -29,7 +30,9 @@ class Device(models.Model):
blocked_by = models.CharField(
max_length=255,
choices=[("ADMIN", "Admin"), ("PARENT", "Parent")],
default="PARENT",
default=None,
blank=True,
null=True,
)
expiry_date = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(default=timezone.now)

View File

@ -1,3 +1,4 @@
from attr import dataclass
from rest_framework import generics, status
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
@ -11,6 +12,8 @@ from .serializers import (
from api.mixins import StaffEditorPermissionMixin
from .filters import DeviceFilter
import re
import requests
from decouple import config
class DeviceListCreateAPIView(
@ -26,7 +29,6 @@ class DeviceListCreateAPIView(
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# Filter devices by the logged-in user unless the user is a superuser
if not request.user.is_superuser:
queryset = queryset.filter(user=request.user)
@ -43,24 +45,31 @@ class DeviceListCreateAPIView(
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):
MAC_REGEX = re.compile(r"^([0-9A-Fa-f]{2}([.:-]?)){5}[0-9A-Fa-f]{2}$")
NORMALIZE_MAC_REGEX = re.compile(r"[^0-9A-Fa-f]")
if not isinstance(mac, str) or not MAC_REGEX.match(mac):
return Response({"message": "Invalid mac address."}, status=400)
if Device.objects.filter(mac=mac).exists():
return Response(
{"message": "Device with this mac address already exists."}, status=400
)
mac_details = get_mac_address_details(mac)
if mac_details.vendor == "Unknown":
return Response({"message": "MAC address vendor not found."}, status=400)
# Normalize MAC address to use "-" as separators
mac = re.sub(r"[^0-9A-Fa-f]", "-", mac).upper()
mac = re.sub(NORMALIZE_MAC_REGEX, "-", mac).upper()
request.data["mac"] = mac
return super().create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
mac_details = get_mac_address_details(serializer.validated_data.get("mac"))
serializer.save(
user=self.request.user,
vendor=mac_details.vendor,
)
class DeviceDetailAPIView(StaffEditorPermissionMixin, generics.RetrieveAPIView):
@ -138,3 +147,26 @@ class DeviceDestroyAPIView(StaffEditorPermissionMixin, generics.DestroyAPIView):
{"message": f"Device '{device_name}' deleted."},
status=status.HTTP_200_OK,
)
@dataclass
class MacResponse:
mac_address: str
vendor: str
detail: str | None = None
def get_mac_address_details(mac: str) -> MacResponse:
API_URL = config("MACVENDOR_API_URL")
if not API_URL:
raise ValueError("MACVENDOR API URL Not set. Please set it.")
response = requests.get(f"{API_URL}/lookup/{mac}")
json_data = response.json()
if response.status_code == 200:
return MacResponse(
mac_address=json_data.get("mac_address", mac),
vendor=json_data.get("vendor", ""),
detail=json_data.get("detail"),
)
else:
return MacResponse(mac_address=mac, vendor="Unknown", detail=None)