feat(itam): switch model Device to inheirt from CenturionModel

ref: #789 #799
This commit is contained in:
2025-06-06 13:05:50 +09:30
parent 8b8ee525f7
commit bcd66fd657
5 changed files with 299 additions and 41 deletions

View File

@ -0,0 +1,126 @@
# Generated by Django 5.1.9 on 2025-06-06 03:35
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0012_teamusers_model_notes_alter_teamusers_id_and_more"),
("core", "0031_remove_ticketcategory_is_global_and_more"),
("itam", "0013_remove_devicetype_is_global_remove_devicetype_slug_and_more"),
]
operations = [
migrations.RemoveField(
model_name="device",
name="is_global",
),
migrations.RemoveField(
model_name="device",
name="slug",
),
migrations.AlterField(
model_name="device",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="device",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="device",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="DeviceAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="itam.device",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Device History",
"verbose_name_plural": "Device Histories",
"db_table": "itam_device_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="DeviceCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="itam.device",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Device Note",
"verbose_name_plural": "Device Notes",
"db_table": "itam_device_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -3,10 +3,12 @@ import re
from datetime import timedelta
from django.core.exceptions import (
ValidationError
)
from django.db import models
from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.forms import ValidationError
from rest_framework import serializers
@ -31,7 +33,7 @@ from settings.models.app_settings import AppSettings
class DeviceType(
CenturionModel
CenturionModel,
):
@ -117,7 +119,9 @@ class DeviceType(
class Device(DeviceCommonFieldsName, SaveHistory):
class Device(
CenturionModel,
):
class Meta:
@ -155,7 +159,9 @@ class Device(DeviceCommonFieldsName, SaveHistory):
for invalid_key in Device.reserved_config_keys:
if invalid_key in value.keys():
raise ValidationError(f'json key "{invalid_key}" is a reserved configuration key')
raise ValidationError(
message = f'json key "{invalid_key}" is a reserved configuration key'
)
def validate_uuid_format(self):
@ -164,8 +170,8 @@ class Device(DeviceCommonFieldsName, SaveHistory):
if not re.match(pattern, str(self)):
raise serializers.ValidationError(
f'UUID must be formated to match regex {str(pattern)}',
raise ValidationError(
message = f'UUID must be formated to match regex {str(pattern)}',
code = 'invalid_uuid'
)
@ -176,9 +182,10 @@ class Device(DeviceCommonFieldsName, SaveHistory):
if not re.match(pattern, str(self).lower()):
raise serializers.ValidationError(
'''[RFC1035 2.3.1] A hostname must start with a letter, end with a letter or digit,
and have as interior characters only letters, digits, and hyphen.''',
raise ValidationError(
message = '[RFC1035 2.3.1] A hostname must start with a letter,' \
'end with a letter or digit, and have as interior characters only letters,' \
' digits, and hyphen.',
code = 'invalid_hostname'
)
@ -200,7 +207,7 @@ class Device(DeviceCommonFieldsName, SaveHistory):
null = True,
unique = True,
verbose_name = 'Serial Number',
)
uuid = models.CharField(
@ -233,7 +240,6 @@ class Device(DeviceCommonFieldsName, SaveHistory):
verbose_name = 'Type'
)
config = models.JSONField(
blank = True,
default = None,
@ -362,6 +368,16 @@ class Device(DeviceCommonFieldsName, SaveHistory):
]
def clean_fields(self, exclude = None):
if self.uuid is not None:
self.uuid = str(self.uuid).lower()
super().clean_fields(exclude = exclude)
def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
@ -371,10 +387,6 @@ class Device(DeviceCommonFieldsName, SaveHistory):
of the same organization as the device.
"""
if self.uuid is not None:
self.uuid = str(self.uuid).lower()
super().save(
force_insert=False, force_update=False, using=None, update_fields=None
@ -405,22 +417,6 @@ class Device(DeviceCommonFieldsName, SaveHistory):
).delete()
def save_history(self, before: dict, after: dict) -> bool:
from itam.models.device_history import DeviceHistory
history = super().save_history(
before = before,
after = after,
history_model = DeviceHistory
)
return history
def __str__(self):
return self.name
@ -559,13 +555,6 @@ class Device(DeviceCommonFieldsName, SaveHistory):
@receiver(post_delete, sender=Device, dispatch_uid='device_delete_signal')
def signal_deleted_model(sender, instance, using, **kwargs):
deleted_model.send(sender='device_deleted', item_id=instance.id, item_type = TicketLinkedItem.Modules.DEVICE)
class DeviceSoftware(DeviceCommonFields, SaveHistory):
""" A way for the device owner to configure software to install/remove """

View File

@ -0,0 +1,56 @@
from rest_framework import serializers
from drf_spectacular.utils import extend_schema_serializer
from api.serializers import common
from centurion.models.meta import DeviceAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
from core.serializers.centurionaudit import (
BaseSerializer,
ViewSerializer as AuditHistoryViewSerializer
)
@extend_schema_serializer(component_name = 'DeviceAuditHistoryModelSerializer')
class ModelSerializer(
common.CommonModelSerializer,
BaseSerializer
):
"""Git Group Audit History Base Model"""
_urls = serializers.SerializerMethodField('get_url')
class Meta:
model = DeviceAuditHistory
fields = [
'id',
'organization',
'display_name',
'content_type',
'model',
'before',
'after',
'action',
'user',
'created',
'_urls',
]
read_only_fields = fields
@extend_schema_serializer(component_name = 'DeviceAuditHistoryViewSerializer')
class ViewSerializer(
ModelSerializer,
AuditHistoryViewSerializer,
):
"""Git Group Audit History Base View Model"""
pass

View File

@ -0,0 +1,87 @@
from rest_framework import serializers
from drf_spectacular.utils import extend_schema_serializer
from access.serializers.organization import (TenantBaseSerializer)
from centurion.models.meta import DeviceCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
BaseSerializer,
ModelSerializer as BaseModelModelSerializer,
ViewSerializer as BaseModelViewSerializer
)
@extend_schema_serializer(component_name = 'DeviceModelNoteModelSerializer')
class ModelSerializer(
BaseModelModelSerializer,
):
_urls = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> dict:
return {
'_self': item.get_url( request = self._context['view'].request ),
}
class Meta:
model = DeviceCenturionModelNote
fields = [
'id',
'organization',
'display_name',
'body',
'created_by',
'modified_by',
'content_type',
'model',
'created',
'modified',
'_urls',
]
read_only_fields = [
'id',
'display_name',
'organization',
'created_by',
'modified_by',
'content_type',
'model',
'created',
'modified',
'_urls',
]
def validate(self, attrs):
is_valid = False
note_model = self.Meta.model.model.field.related_model
attrs['model'] = note_model.objects.get(
id = int( self.context['view'].kwargs['model_id'] )
)
is_valid = super().validate(attrs)
return is_valid
@extend_schema_serializer(component_name = 'DeviceModelNoteViewSerializer')
class ViewSerializer(
ModelSerializer,
BaseModelViewSerializer,
):
organization = TenantBaseSerializer( many = False, read_only = True )

View File

@ -8,7 +8,7 @@ from devops.viewsets import (
from itam.viewsets import (
index as itam_index_v2,
device as device_v2,
device,
device_software as device_software_v2,
device_operating_system,
inventory,
@ -38,8 +38,8 @@ router.register(
feature_flag = '2025-00007', basename = '_api_v2_itam_asset'
)
router.register(
prefix = 'device', viewset = device_v2.ViewSet,
basename = '_api_v2_device'
prefix = 'device', viewset = device.ViewSet,
basename = '_api_device'
)
router.register(
prefix = 'device/(?P<device_id>[0-9]+)/operating_system',