Merge pull request #862 from nofusscomputing/refactor-switch-model-inheritence

This commit is contained in:
Jon
2025-07-13 21:36:52 +09:30
committed by GitHub
65 changed files with 1408 additions and 778 deletions

View File

@ -0,0 +1,56 @@
# Generated by Django 5.1.10 on 2025-07-12 07:20
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0019_companyaudithistory_companycenturionmodelnote"),
]
operations = [
migrations.AlterField(
model_name="role",
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="role",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="role",
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.DeleteModel(
name="RoleHistory",
),
migrations.DeleteModel(
name="RoleNotes",
),
]

View File

@ -0,0 +1,81 @@
# Generated by Django 5.1.10 on 2025-07-12 08:50
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0020_remove_rolenotes_model_and_more"),
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
]
operations = [
migrations.CreateModel(
name="RoleAuditHistory",
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="access.role",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Role History",
"verbose_name_plural": "Role Histories",
"db_table": "access_role_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="RoleCenturionModelNote",
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="access.role",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Role Note",
"verbose_name_plural": "Role Notes",
"db_table": "access_role_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
]

View File

@ -1,5 +1,3 @@
from .organization_history import OrganizationHistory # pylint: disable=W0611:unused-import
from .role_history import RoleHistory # pylint: disable=W0611:unused-import
from .organization_notes import OrganizationNotes # pylint: disable=W0611:unused-import
from .role_notes import RoleNotes # pylint: disable=W0611:unused-import

View File

@ -1,15 +1,20 @@
from django.contrib.auth.models import Permission
from django.db import models
from access.fields import AutoCreatedField, AutoLastModifiedField
from access.models.tenancy import TenancyObject
from access.fields import AutoLastModifiedField
from core.models.centurion import CenturionModel
class Role(
TenancyObject
CenturionModel
):
documentation = ''
model_tag = 'role'
class Meta:
@ -28,14 +33,6 @@ class Role(
verbose_name_plural = 'Roles'
id = models.AutoField(
blank=False,
help_text = 'Primary key of the entry',
primary_key=True,
unique=True,
verbose_name = 'ID'
)
name = models.CharField(
blank = False,
help_text = 'Name of this role',
@ -53,12 +50,8 @@ class Role(
verbose_name = 'Permissions'
)
created = AutoCreatedField()
modified = AutoLastModifiedField()
is_global = None
def __str__(self) -> str:
@ -66,8 +59,6 @@ class Role(
return str( self.organization ) + ' / ' + self.name
documentation = ''
page_layout: dict = [
{
"name": "Details",
@ -156,19 +147,3 @@ class Role(
return self._permissions_int
return self._permissions_int
def save_history(self, before: dict, after: dict) -> bool:
from access.models.role_history import RoleHistory
history = super().save_history(
before = before,
after = after,
history_model = RoleHistory
)
return history

View File

@ -1,53 +0,0 @@
from django.db import models
from core.models.model_history import ModelHistory
from access.models.role import Role
class RoleHistory(
ModelHistory
):
class Meta:
db_table = 'access_role_history'
ordering = ModelHistory._meta.ordering
verbose_name = 'Role History'
verbose_name_plural = 'Role History'
model = models.ForeignKey(
Role,
blank = False,
help_text = 'Model this note belongs to',
null = False,
on_delete = models.CASCADE,
related_name = 'history',
verbose_name = 'Model',
)
table_fields: list = []
page_layout: dict = []
def get_object(self):
return self
def get_serialized_model(self, serializer_context):
model = None
from access.serializers.role import BaseSerializer
model = BaseSerializer(self.model, context = serializer_context)
return model

View File

@ -1,45 +0,0 @@
from django.db import models
from access.models.role import Role
from core.models.model_notes import ModelNotes
class RoleNotes(
ModelNotes
):
class Meta:
db_table = 'access_role_notes'
ordering = ModelNotes._meta.ordering
verbose_name = 'Role Note'
verbose_name_plural = 'Role Notes'
model = models.ForeignKey(
Role,
blank = False,
help_text = 'Model this note belongs to',
null = False,
on_delete = models.CASCADE,
related_name = 'notes',
verbose_name = 'Model',
)
table_fields: list = []
page_layout: dict = []
def get_url_kwargs(self) -> dict:
return {
'model_id': self.model.pk,
'pk': self.pk
}

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 RoleAuditHistory # 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 = 'RoleAuditHistoryModelSerializer')
class ModelSerializer(
common.CommonModelSerializer,
BaseSerializer
):
"""Git Group Audit History Base Model"""
_urls = serializers.SerializerMethodField('get_url')
class Meta:
model = RoleAuditHistory
fields = [
'id',
'organization',
'display_name',
'content_type',
'model',
'before',
'after',
'action',
'user',
'created',
'_urls',
]
read_only_fields = fields
@extend_schema_serializer(component_name = 'RoleAuditHistoryViewSerializer')
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 RoleCenturionModelNote # 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 = 'RoleModelNoteModelSerializer')
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 = RoleCenturionModelNote
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 = 'RoleModelNoteViewSerializer')
class ViewSerializer(
ModelSerializer,
BaseModelViewSerializer,
):
organization = TenantBaseSerializer( many = False, read_only = True )

View File

@ -0,0 +1,44 @@
import pytest
from django.test import Client
class AdditionalTestCases:
def test_permission_add(self, model_instance, api_request_permissions,
model_kwargs, kwargs_api_create
):
""" Check correct permission for add
Attempt to add as user with permission
"""
client = Client()
client.force_login( api_request_permissions['user']['add'] )
the_model = model_instance( kwargs_create = model_kwargs )
url = the_model.get_url( many = True )
response = client.post(
path = url,
data = kwargs_api_create,
content_type = 'application/json'
)
assert response.status_code == 200, response.content
def test_returned_data_from_user_and_global_organizations_only(
self
):
"""Check items returned
Items returned from the query Must be from the users organization and
global ONLY!
"""
pytest.mark.xfail( reason = 'model is not for global use' )

View File

@ -10,6 +10,8 @@ from access.serializers.role import Role, ModelSerializer
@pytest.mark.model_role
@pytest.mark.module_role
class ValidationSerializer(
TestCase,
):

View File

@ -1,4 +1,6 @@
import django
import pytest
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.test import Client, TestCase
@ -20,6 +22,8 @@ User = django.contrib.auth.get_user_model()
@pytest.mark.model_role
@pytest.mark.module_role
class ViewSetBase:
add_data: dict = None
@ -238,7 +242,7 @@ class RolePermissionsAPITest(
url_view_kwargs: dict = {}
url_name = '_api_v2_role'
url_name = '_api_role'
@ -260,7 +264,7 @@ class RoleViewSetTest(
url_view_kwargs: dict = {}
url_name = '_api_v2_role'
url_name = '_api_role'
@ -283,4 +287,4 @@ class RoleMetadataTest(
url_view_kwargs: dict = {}
url_name = '_api_v2_role'
url_name = '_api_role'

View File

@ -0,0 +1,19 @@
import pytest
@pytest.fixture( scope = 'class')
def model(model_role):
yield model_role
@pytest.fixture( scope = 'class', autouse = True)
def model_kwargs(request, kwargs_role):
request.cls.kwargs_create_item = kwargs_role.copy()
yield kwargs_role.copy()
if hasattr(request.cls, 'kwargs_create_item'):
del request.cls.kwargs_create_item

View File

@ -1,3 +1,4 @@
import pytest
import django
from django.contrib.auth.models import Permission
@ -18,6 +19,7 @@ User = django.contrib.auth.get_user_model()
@pytest.mark.model_role
class APITestCases(
APITenancyObject,
):
@ -154,6 +156,7 @@ class APITestCases(
@pytest.mark.module_role
class RoleAPITest(
APITestCases,
TestCase,
@ -163,7 +166,7 @@ class RoleAPITest(
model = Role
url_ns_name = '_api_v2_role'
url_ns_name = '_api_role'
@classmethod

View File

@ -1,30 +1,70 @@
from django.test import TestCase
import pytest
from access.models.role import Role
from django.db import models
from centurion.tests.unit.test_unit_models import (
TenancyObjectInheritedCases
from core.tests.unit.centurion_abstract.test_unit_centurion_abstract_model import (
CenturionAbstractModelInheritedCases
)
@pytest.mark.model_role
class RoleModelTestCases(
TenancyObjectInheritedCases,
CenturionAbstractModelInheritedCases
):
model = None
kwargs_item_create: dict = None
@property
def parameterized_class_attributes(self):
return {
'model_tag': {
'type': str,
'value': 'role'
},
}
@property
def parameterized_model_fields(self):
class RoleModelTest(
RoleModelTestCases,
TestCase,
):
model = Role
kwargs_item_create: dict = {
'name': 'a role'
return {
'name': {
'blank': False,
'default': models.fields.NOT_PROVIDED,
'field_type': models.CharField,
'max_length': 30,
'null': False,
'unique': False,
},
'permissions': {
'blank': True,
'default': models.fields.NOT_PROVIDED,
'field_type': models.ManyToManyField,
'null': False,
'unique': False,
},
'modified': {
'blank': False,
'default': models.fields.NOT_PROVIDED,
'field_type': models.DateTimeField,
'null': False,
'unique': False,
},
}
class RoleModelInheritedCases(
RoleModelTestCases,
):
pass
@pytest.mark.module_role
class RoleModelPyTest(
RoleModelTestCases,
):
pass

View File

@ -20,6 +20,8 @@ import pytest
###############################################################################
@pytest.mark.model_role
@pytest.mark.module_role
@pytest.mark.skip( reason = 'figure out how to isolate so entirety of unit tests can run without this test failing' )
# @pytest.mark.forked
# @pytest.mark.django_db

View File

@ -1,3 +1,5 @@
import pytest
from django.test import Client, TestCase
from rest_framework.reverse import reverse
@ -9,6 +11,7 @@ from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
@pytest.mark.model_role
class ViewsetTestCases(
ModelViewSetInheritedCases,
):
@ -44,6 +47,7 @@ class ViewsetTestCases(
@pytest.mark.module_role
class RoleViewsetTest(
ViewsetTestCases,
TestCase,
@ -51,6 +55,6 @@ class RoleViewsetTest(
kwargs = {}
route_name = 'v2:_api_v2_role'
route_name = 'v2:_api_role'
viewset = ViewSet

View File

@ -82,12 +82,7 @@ router.register(
router.register(
prefix = 'role', viewset = role.ViewSet,
feature_flag = '2025-00003', basename = '_api_v2_role'
feature_flag = '2025-00003', basename = '_api_role'
)
# router.register(
# prefix = 'role/(?P<model_id>[0-9]+)/notes', viewset = role_notes.ViewSet,
# feature_flag = '2025-00003', basename = '_api_v2_role_note'
# )
urlpatterns = router.urls

View File

@ -37,7 +37,7 @@ class Index(IndexViewset):
if self.request.feature_flag['2025-00003']:
response.update({
"role": reverse( 'v2:_api_v2_role-list', request=request ),
"role": reverse( 'v2:_api_role-list', request=request ),
})

View File

@ -1,7 +1,5 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
# THis import only exists so that the migrations can be created
from access.models.role_history import RoleHistory # pylint: disable=W0611:unused-import
from access.serializers.role import (
Role,
ModelSerializer,

View File

@ -0,0 +1,118 @@
# Generated by Django 5.1.10 on 2025-07-10 09:10
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0019_companyaudithistory_companycenturionmodelnote"),
("accounting", "0001_initial"),
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
]
operations = [
migrations.AlterField(
model_name="assetbase",
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="assetbase",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="assetbase",
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="AssetBaseAuditHistory",
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="accounting.assetbase",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Asset History",
"verbose_name_plural": "Asset Histories",
"db_table": "accounting_assetbase_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="AssetBaseCenturionModelNote",
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="accounting.assetbase",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Asset Note",
"verbose_name_plural": "Asset Notes",
"db_table": "accounting_assetbase_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 5.1.10 on 2025-07-10 09:31
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("accounting", "0002_alter_assetbase_id_alter_assetbase_model_notes_and_more"),
]
operations = [
migrations.DeleteModel(
name="AssetBaseHistory",
),
migrations.DeleteModel(
name="AssetBaseNotes",
),
]

View File

@ -1,5 +0,0 @@
from .asset_base_history import AssetBaseHistory # pylint: disable=W0611:unused-import
from .asset_base_history import AssetBaseHistory # pylint: disable=W0611:unused-import
from .asset_base_notes import AssetBaseNotes # pylint: disable=W0611:unused-import

View File

@ -1,15 +1,14 @@
from django.apps import apps
from django.db import models
from rest_framework.reverse import reverse
from access.fields import AutoLastModifiedField
from access.fields import AutoCreatedField, AutoLastModifiedField
from access.models.tenancy import TenancyObject
from core.models.centurion import CenturionModel
class AssetBase(
TenancyObject,
CenturionModel,
):
"""Asset Base Model
@ -21,6 +20,10 @@ class AssetBase(
app_namespace = 'accounting'
model_tag = 'asset'
url_model_name = 'asset'
@property
def _base_model(self):
@ -50,18 +53,6 @@ class AssetBase(
return True
is_global = None
id = models.AutoField(
blank = False,
help_text = 'Ticket ID Number',
primary_key = True,
unique = True,
verbose_name = 'Number',
)
asset_number = models.CharField(
blank = True,
help_text = 'Number or tag to use to track this asset',
@ -96,14 +87,11 @@ class AssetBase(
"""
# Status
# model (manufacturer / model)
@property
def get_model_type(self):
"""Fetch the Ticket Type
@ -137,7 +125,7 @@ class AssetBase(
if(
( isinstance(model, AssetBase) or issubclass(model, AssetBase) )
and AssetBase._meta.sub_model_type != 'asset'
# and AssetBase._meta.sub_model_type != 'asset'
):
@ -159,12 +147,6 @@ class AssetBase(
verbose_name = 'Asset Type',
)
created = AutoCreatedField(
editable = True,
)
modified = AutoLastModifiedField()
@ -237,6 +219,26 @@ class AssetBase(
def clean_fields(self, exclude = None):
related_model = self.get_related_model()
if related_model is None:
related_model = self
if (
self.asset_type != str(related_model._meta.sub_model_type).lower().replace(' ', '_')
and str(related_model._meta.sub_model_type).lower().replace(' ', '_') != 'asset'
):
self.asset_type = str(related_model._meta.sub_model_type).lower().replace(' ', '_')
super().clean_fields(exclude = exclude)
def get_related_field_name(self) -> str:
meta = getattr(self, '_meta')
@ -260,6 +262,7 @@ class AssetBase(
return ''
def get_related_model(self):
"""Recursive model Fetch
@ -292,74 +295,3 @@ class AssetBase(
return related_model
def get_url( self, request = None ) -> str:
kwargs = self.get_url_kwargs()
url_path_name = '_api_v2_asset_sub'
if self._meta.sub_model_type == 'asset':
url_path_name = '_api_v2_asset'
if request:
return reverse(f"v2:accounting:{url_path_name}-detail", request=request, kwargs = kwargs )
return reverse(f"v2:accounting:{url_path_name}-detail", kwargs = kwargs )
def get_url_kwargs(self) -> dict:
kwargs = {
'asset_model': self.asset_type,
}
if self._meta.sub_model_type == 'asset':
kwargs = {}
if self.id:
kwargs.update({
'pk': self.id
})
return kwargs
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
related_model = self.get_related_model()
if related_model is None:
related_model = self
if self.asset_type != str(related_model._meta.sub_model_type).lower().replace(' ', '_'):
self.asset_type = str(related_model._meta.sub_model_type).lower().replace(' ', '_')
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
def save_history(self, before: dict, after: dict) -> bool:
from accounting.models.asset_base_history import AssetBaseHistory
history = super().save_history(
before = before,
after = after,
history_model = AssetBaseHistory
)
return history

View File

@ -1,53 +0,0 @@
from django.db import models
from accounting.models.asset_base import AssetBase
from core.models.model_history import ModelHistory
class AssetBaseHistory(
ModelHistory
):
class Meta:
db_table = 'accounting_assetbase_history'
ordering = ModelHistory._meta.ordering
verbose_name = 'Asset History'
verbose_name_plural = 'Asset History'
model = models.ForeignKey(
AssetBase,
blank = False,
help_text = 'Model this note belongs to',
null = False,
on_delete = models.CASCADE,
related_name = 'history',
verbose_name = 'Model',
)
table_fields: list = []
page_layout: dict = []
def get_object(self):
return self
def get_serialized_model(self, serializer_context):
model = None
from accounting.serializers.asset import BaseSerializer
model = BaseSerializer(self.model, context = serializer_context)
return model

View File

@ -1,47 +0,0 @@
from django.db import models
from accounting.models.asset_base import AssetBase
from core.models.model_notes import ModelNotes
class AssetBaseNotes(
ModelNotes
):
class Meta:
db_table = 'accounting_assetbase_notes'
ordering = ModelNotes._meta.ordering
verbose_name = 'Asset Note'
verbose_name_plural = 'Asset Notes'
model = models.ForeignKey(
AssetBase,
blank = False,
help_text = 'Model this note belongs to',
null = False,
on_delete = models.CASCADE,
related_name = 'notes',
verbose_name = 'Model',
)
app_namespace = 'accounting'
table_fields: list = []
page_layout: dict = []
def get_url_kwargs(self) -> dict:
return {
'model_id': self.model.pk,
'pk': self.pk
}

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 AssetBaseAuditHistory # 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 = 'AssetBaseAuditHistoryModelSerializer')
class ModelSerializer(
common.CommonModelSerializer,
BaseSerializer
):
"""Git Group Audit History Base Model"""
_urls = serializers.SerializerMethodField('get_url')
class Meta:
model = AssetBaseAuditHistory
fields = [
'id',
'organization',
'display_name',
'content_type',
'model',
'before',
'after',
'action',
'user',
'created',
'_urls',
]
read_only_fields = fields
@extend_schema_serializer(component_name = 'AssetBaseAuditHistoryViewSerializer')
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 AssetBaseCenturionModelNote # 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 = 'AssetBaseModelNoteModelSerializer')
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 = AssetBaseCenturionModelNote
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 = 'AssetBaseModelNoteViewSerializer')
class ViewSerializer(
ModelSerializer,
BaseModelViewSerializer,
):
organization = TenantBaseSerializer( many = False, read_only = True )

View File

@ -18,7 +18,7 @@ def model(request):
@pytest.fixture(scope='function')
def create_serializer():
from accounting.serializers.asset import ModelSerializer
from accounting.serializers.assetbase import ModelSerializer
yield ModelSerializer

View File

@ -1,4 +1,6 @@
import django
import pytest
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
@ -15,6 +17,7 @@ User = django.contrib.auth.get_user_model()
@pytest.mark.model_assetbase
class MetadataTestCases(
MetadataAttributesFunctional,
):
@ -234,7 +237,7 @@ class AssetBaseMetadataInheritedCases(
kwargs_create_item_diff_org: dict = {}
url_name = 'accounting:_api_v2_asset_sub'
url_name = 'accounting:_api_asset_sub'
@classmethod
@ -251,21 +254,22 @@ class AssetBaseMetadataInheritedCases(
}
self.url_kwargs = {
'asset_model': self.model._meta.sub_model_type
'model_name': self.model._meta.model_name
}
self.url_view_kwargs = {
'asset_model': self.model._meta.sub_model_type
'model_name': self.model._meta.model_name
}
super().setUpTestData()
@pytest.mark.module_accounting
class AssetBaseMetadataTest(
MetadataTestCases,
TestCase,
):
url_name = 'accounting:_api_v2_asset'
url_name = 'accounting:_api_asset'

View File

@ -6,6 +6,7 @@ from api.tests.functional.test_functional_api_permissions import (
@pytest.mark.model_assetbase
class PermissionsAPITestCases(
APIPermissionsInheritedCases,
):
@ -36,7 +37,7 @@ class PermissionsAPITestCases(
url_kwargs: dict = {}
url_name = 'accounting:_api_v2_asset'
url_name = 'accounting:_api_asset'
url_view_kwargs: dict = {}
@ -65,18 +66,18 @@ class AssetBasePermissionsAPIInheritedCases(
kwargs_create_item_diff_org: dict = None
url_name = 'accounting:_api_v2_asset_sub'
url_name = 'accounting:_api_asset_sub'
@pytest.fixture(scope='class')
def inherited_var_setup(self, request):
request.cls.url_kwargs.update({
'asset_model': self.model._meta.sub_model_type
'model_name': self.model._meta.model_name
})
request.cls.url_view_kwargs.update({
'asset_model': self.model._meta.sub_model_type
'model_name': self.model._meta.model_name
})
@ -94,6 +95,8 @@ class AssetBasePermissionsAPIInheritedCases(
pass
@pytest.mark.module_accounting
class AssetBasePermissionsAPIPyTest(
PermissionsAPITestCases,
):

View File

@ -27,6 +27,7 @@ class MockView:
@pytest.mark.model_assetbase
class AssetBaseSerializerTestCases:
@ -188,6 +189,7 @@ class AssetBaseSerializerInheritedCases(
@pytest.mark.module_accounting
class AssetBaseSerializerPyTest(
AssetBaseSerializerTestCases,
):

View File

@ -1,4 +1,6 @@
import django
import pytest
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
@ -15,6 +17,7 @@ User = django.contrib.auth.get_user_model()
@pytest.mark.model_assetbase
class ViewSetBase:
add_data: dict = {
@ -237,7 +240,7 @@ class AssetBaseViewSetInheritedCases(
model = None
url_name = 'accounting:_api_v2_asset_sub'
url_name = 'accounting:_api_asset_sub'
@classmethod
@ -254,20 +257,21 @@ class AssetBaseViewSetInheritedCases(
}
self.url_kwargs = {
'asset_model': self.model._meta.sub_model_type
'model_name': self.model._meta.model_name
}
self.url_view_kwargs = {
'asset_model': self.model._meta.sub_model_type
'model_name': self.model._meta.model_name
}
super().setUpTestData()
@pytest.mark.module_accounting
class AssetBaseViewSetTest(
ViewSetTestCases,
TestCase,
):
url_name = 'accounting:_api_v2_asset'
url_name = 'accounting:_api_asset'

View File

@ -1,14 +1,22 @@
import pytest
from accounting.models.asset_base import AssetBase
@pytest.fixture( scope = 'class')
def model(request):
def model(model_assetbase):
request.cls.model = AssetBase
yield model_assetbase
yield request.cls.model
del request.cls.model
@pytest.fixture( scope = 'class', autouse = True)
def model_kwargs(request, kwargs_assetbase):
request.cls.kwargs_create_item = kwargs_assetbase.copy()
yield kwargs_assetbase.copy()
if hasattr(request.cls, 'kwargs_create_item'):
try:
del request.cls.kwargs_create_item
except:
pass

View File

@ -8,6 +8,7 @@ from api.tests.functional.test_functional_api_fields import (
@pytest.mark.model_assetbase
class AssetBaseAPITestCases(
APIFieldsInheritedCases,
):
@ -23,7 +24,7 @@ class AssetBaseAPITestCases(
if model != self.base_model:
request.cls.url_view_kwargs.update({
'asset_model': model._meta.sub_model_type,
'model_name': model._meta.model_name,
})
@ -55,7 +56,7 @@ class AssetBaseAPITestCases(
'serial_number': '65756756756',
}
url_ns_name = 'accounting:_api_v2_asset'
url_ns_name = 'accounting:_api_asset'
"""Url namespace (optional, if not required) and url name"""
@ -68,10 +69,11 @@ class AssetBaseAPIInheritedCases(
model = None
url_ns_name = 'accounting:_api_v2_asset_sub'
url_ns_name = 'accounting:_api_asset_sub'
@pytest.mark.module_accounting
class AssetBaseAPIPyTest(
AssetBaseAPITestCases,
):

View File

@ -4,204 +4,96 @@ from django.db import models
from accounting.models.asset_base import AssetBase
from centurion.tests.unit.test_unit_models import (
PyTestTenancyObjectInheritedCases,
from core.tests.unit.centurion_abstract.test_unit_centurion_abstract_model import (
CenturionAbstractModelInheritedCases
)
@pytest.mark.model_assetbase
class AssetBaseModelTestCases(
PyTestTenancyObjectInheritedCases,
CenturionAbstractModelInheritedCases
):
base_model = AssetBase
kwargs_create_item: dict = {
'asset_number': 'a12s432',
'serial_number': 'abbcccdddd',
}
@property
def parameterized_class_attributes(self):
sub_model_type = 'asset'
"""Sub Model Type
sub-models must have this attribute defined in `ModelName.Meta.sub_model_type`
"""
parameterized_fields: dict = {
"asset_number": {
'field_type': models.fields.CharField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str,
},
"serial_number": {
'field_type': models.fields.CharField,
'field_parameter_default_exists': False,
'field_parameter_verbose_name_type': str,
return {
'app_namespace': {
'type': str,
'value': 'accounting'
},
'model_tag': {
'type': str,
'value': 'asset'
},
'url_model_name': {
'type': str,
'value': 'asset'
},
}
@property
def parameterized_model_fields(self):
return {
'asset_number': {
'blank': True,
'default': models.fields.NOT_PROVIDED,
'field_type': models.CharField,
'max_length': 30,
'null': True,
'unique': True,
},
'serial_number': {
'blank': True,
'default': models.fields.NOT_PROVIDED,
'field_type': models.CharField,
'max_length': 30,
'null': True,
'unique': True,
},
'asset_type': {
'blank': True,
'default': 'asset',
'field_type': models.CharField,
'max_length': 30,
'null': False,
'unique': False,
}
}
@pytest.fixture( scope = 'class')
def setup_model(self,
request,
model,
django_db_blocker,
organization_one,
organization_two
):
with django_db_blocker.unblock():
request.cls.organization = organization_one
request.cls.different_organization = organization_two
kwargs_create_item = {}
for base in reversed(request.cls.__mro__):
if hasattr(base, 'kwargs_create_item'):
if base.kwargs_create_item is None:
continue
kwargs_create_item.update(**base.kwargs_create_item)
if len(kwargs_create_item) > 0:
request.cls.kwargs_create_item = kwargs_create_item
if 'organization' not in request.cls.kwargs_create_item:
request.cls.kwargs_create_item.update({
'organization': request.cls.organization
})
yield
with django_db_blocker.unblock():
del request.cls.kwargs_create_item
@pytest.fixture( scope = 'class', autouse = True)
def class_setup(self,
setup_model,
create_model,
):
pass
def test_class_inherits_assetbase(self):
def test_class_inherits_assetbase(self, model):
""" Class inheritence
TenancyObject must inherit SaveHistory
"""
assert issubclass(self.model, AssetBase)
def test_attribute_type_app_namespace(self):
"""Attribute Type
app_namespace is of type str
"""
assert type(self.model.app_namespace) is str
def test_attribute_value_app_namespace(self):
"""Attribute Type
app_namespace has been set, override this test case with the value
of attribute `app_namespace`
"""
assert self.model.app_namespace == 'accounting'
def test_function_is_property_get_model_type(self):
"""Function test
Confirm function `get_model_type` is a property
"""
assert type(self.model.get_model_type) is property
def test_function_value_get_model_type(self):
"""Function test
Confirm function `get_model_type` returns None for base model
"""
assert self.item.get_model_type is None
def test_function_value_get_related_model(self):
"""Function test
Confirm function `get_related_model` is of the sub-model type
"""
assert type(self.item.get_related_model()) == self.model
def test_function_value_get_url(self):
assert self.item.get_url() == '/api/v2/accounting/asset/' + str(self.item.id)
assert issubclass(model, AssetBase)
class AssetBaseModelInheritedCases(
AssetBaseModelTestCases,
):
"""Sub-Ticket Test Cases
Test Cases for Ticket models that inherit from model AssetBase
"""
kwargs_create_item: dict = {}
model = None
sub_model_type = None
"""Ticket Sub Model Type
def test_method_get_url_kwargs(self, mocker, model_instance, settings):
Ticket sub-models must have this attribute defined in `ModelNam.Meta.sub_model_type`
"""
url = model_instance.get_url_kwargs()
def test_function_value_get_model_type(self):
"""Function test
Confirm function `get_model_type` does not have a value of None
value should be equaul to Meta.sub_model_type
"""
assert self.item.get_model_type == self.item._meta.sub_model_type
assert model_instance.get_url_kwargs() == {
'model_name': model_instance._meta.model_name,
'pk': model_instance.id
}
@pytest.mark.module_accounting
class AssetBaseModelPyTest(
AssetBaseModelTestCases,
):
def test_function_value_get_related_model(self):
"""Function test
Confirm function `get_related_model` is None for base model
"""
assert self.item.get_related_model() is None
pass

View File

@ -1,3 +1,5 @@
import pytest
from django.test import Client, TestCase
from rest_framework.reverse import reverse
@ -11,8 +13,13 @@ from accounting.viewsets.asset import (
from api.tests.unit.test_unit_common_viewset import SubModelViewSetInheritedCases
from centurion.tests.abstract.mock_view import MockRequest
from settings.models.app_settings import AppSettings
@pytest.mark.model_assetbase
class AssetBaseViewsetTestCases(
SubModelViewSetInheritedCases,
):
@ -43,7 +50,7 @@ class AssetBaseViewsetTestCases(
if self.model is not AssetBase:
self.kwargs = {
'asset_model': self.model._meta.sub_model_type
'model_name': self.model._meta.sub_model_type
}
self.viewset.kwargs = self.kwargs
@ -60,6 +67,8 @@ class AssetBaseViewsetTestCases(
self.http_options_response_list = client.options(url)
a = 'a'
def test_view_attr_value_model_kwarg(self):
@ -70,7 +79,30 @@ class AssetBaseViewsetTestCases(
view_set = self.viewset()
assert view_set.model_kwarg == 'asset_model'
assert view_set.model_kwarg == 'model_name'
def test_view_attr_model_value(self):
"""Attribute Test
Attribute `model` must return the correct sub-model
"""
view_set = self.viewset()
app_settings = AppSettings.objects.select_related('global_organization').get(
owner_organization = None
)
view_set.request = MockRequest(
user = self.view_user,
app_settings = app_settings,
)
assert view_set.model == self.model
@ -85,10 +117,11 @@ class AssetBaseViewsetInheritedCases(
model: str = None
"""name of the model to test"""
route_name = 'v2:accounting:_api_v2_asset_sub'
route_name = 'v2:accounting:_api_asset_sub'
@pytest.mark.module_accounting
class AssetBaseViewsetTest(
AssetBaseViewsetTestCases,
TestCase,
@ -96,6 +129,6 @@ class AssetBaseViewsetTest(
kwargs = {}
route_name = 'v2:accounting:_api_v2_asset'
route_name = 'v2:accounting:_api_asset'
viewset = NoDocsViewSet

View File

@ -33,7 +33,7 @@ for model in apps.get_models():
if model._meta.sub_model_type == 'asset':
continue
asset_type_names += model._meta.sub_model_type + '|'
asset_type_names += model._meta.model_name + '|'
@ -42,7 +42,7 @@ asset_type_names = str(asset_type_names)[:-1]
if not asset_type_names:
asset_type_names = 'none'
router.register(f'asset/(?P<asset_model>[{asset_type_names}]+)?', asset.ViewSet, feature_flag = '2025-00004', basename='_api_v2_asset_sub')
router.register('asset', asset.NoDocsViewSet, feature_flag = '2025-00004', basename='_api_v2_asset')
router.register(f'asset/(?P<model_name>[{asset_type_names}]+)?', asset.ViewSet, feature_flag = '2025-00004', basename='_api_asset_sub')
router.register('asset', asset.NoDocsViewSet, feature_flag = '2025-00004', basename='_api_asset')
urlpatterns = router.urls

View File

@ -12,7 +12,7 @@ from drf_spectacular.utils import (
from accounting.models.asset_base import AssetBase
from api.viewsets.common import SubModelViewSet
from api.viewsets.common import SubModelViewSet_ReWrite
@ -25,7 +25,7 @@ def spectacular_request_serializers( serializer_type = 'Model'):
if issubclass(model, AssetBase):
serializer_name = 'asset'
serializer_name = 'assetbase'
if(
model._meta.model_name == 'assetbase'
@ -34,7 +34,7 @@ def spectacular_request_serializers( serializer_type = 'Model'):
continue
serializer_name += '_' + model._meta.sub_model_type
serializer_name += '_' + model._meta.model_name
serializer_module = importlib.import_module(
model._meta.app_label + '.serializers.' + str(
@ -56,7 +56,7 @@ def spectacular_request_serializers( serializer_type = 'Model'):
description='.',
parameters = [
OpenApiParameter(
name = 'asset_model',
name = 'model_name',
description = 'Enter the asset type. This is the name of the asset sub-model.',
location = OpenApiParameter.PATH,
type = str,
@ -97,7 +97,7 @@ def spectacular_request_serializers( serializer_type = 'Model'):
description = '.',
parameters =[
OpenApiParameter(
name = 'asset_model',
name = 'model_name',
description = 'Enter the asset type. This is the name of the asset sub-model.',
location = OpenApiParameter.PATH,
type = str,
@ -121,7 +121,7 @@ def spectacular_request_serializers( serializer_type = 'Model'):
description='.',
parameters = [
OpenApiParameter(
name = 'asset_model',
name = 'model_name',
description = 'Enter the asset model. This is the name of the asset sub-model.',
location = OpenApiParameter.PATH,
type = str,
@ -153,7 +153,7 @@ def spectacular_request_serializers( serializer_type = 'Model'):
description='.',
parameters = [
OpenApiParameter(
name = 'asset_model',
name = 'model_name',
description = 'Enter the asset model. This is the name of the Asset sub-model.',
location = OpenApiParameter.PATH,
type = str,
@ -214,7 +214,7 @@ def spectacular_request_serializers( serializer_type = 'Model'):
}
),
)
class ViewSet( SubModelViewSet ):
class ViewSet( SubModelViewSet_ReWrite ):
_has_purge: bool = False
"""User Permission
@ -232,7 +232,7 @@ class ViewSet( SubModelViewSet ):
# 'is_deleted'
]
model_kwarg = 'asset_model'
model_kwarg = 'model_name'
search_fields = [
'asset_number',

View File

@ -638,7 +638,7 @@ class ReactUIMetadata(OverRideJSONAPIMetadata):
'view_itamassetbase': {
"display_name": "IT Assets",
"name": "itasset",
"link": "/itam/it_asset"
"link": "/itam/itamassetbase"
},
**nav['itam']['pages']
}

View File

@ -169,7 +169,7 @@ class APIFieldsTestCases:
view_team.permissions.set([view_permissions])
request.cls.view_user = User.objects.create_user(username="cafs_test_user_view" + str(random_str), password="password")
request.cls.view_user = User.objects.create_user(username="cafs_test_user_view" + str(random_str), password="password", is_superuser = True)
team_user = TeamUsers.objects.create(
team = view_team,

View File

@ -8,7 +8,8 @@ from django.utils.module_loading import import_string
# Note: Only included so that it can be picked up.
# in future when model referenced, this include statement may be repoved.
from access.models.company_base import Company # pylint: disable=W0611:unused-import
from access.models.role import Role # pylint: disable=W0611:unused-import
## EoF Include block
module_path = f'centurion.models.meta'

View File

@ -76,10 +76,7 @@ CREATE TABLE IF NOT EXISTS "assistance_knowledge_base_history" ("modelhistory_pt
CREATE TABLE IF NOT EXISTS "access_team_history" ("modelhistory_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_model_history" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "access_team" ("group_ptr_id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "core_ticketcategory_notes" ("modelnotes_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_model_notes" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "core_ticketcategory" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "core_ticketcommentcategory_notes" ("modelnotes_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_model_notes" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "core_ticketcommentcategory" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "access_role" ("model_notes" text NULL, "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(30) NOT NULL, "created" datetime NOT NULL, "modified" datetime NOT NULL, "organization_id" integer NOT NULL REFERENCES "access_tenant" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "access_role_permissions" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "role_id" integer NOT NULL REFERENCES "access_role" ("id") DEFERRABLE INITIALLY DEFERRED, "permission_id" integer NOT NULL REFERENCES "auth_permission" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "access_role_history" ("modelhistory_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_model_history" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "access_role" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "access_role_notes" ("modelnotes_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_model_notes" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "access_role" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "access_contact" ("person_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "access_person" ("entity_ptr_id") DEFERRABLE INITIALLY DEFERRED, "directory" bool NOT NULL, "email" varchar(254) NOT NULL UNIQUE);
CREATE TABLE IF NOT EXISTS "core_ticketbase_assigned_to" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "ticketbase_id" integer NOT NULL REFERENCES "core_ticketbase" ("id") DEFERRABLE INITIALLY DEFERRED, "entity_id" integer NOT NULL REFERENCES "access_entity" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "core_ticketbase_subscribed_to" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "ticketbase_id" integer NOT NULL REFERENCES "core_ticketbase" ("id") DEFERRABLE INITIALLY DEFERRED, "entity_id" integer NOT NULL REFERENCES "access_entity" ("id") DEFERRABLE INITIALLY DEFERRED);
@ -139,9 +136,12 @@ CREATE TABLE IF NOT EXISTS "access_person_audithistory" ("centurionaudit_ptr_id"
CREATE TABLE IF NOT EXISTS "access_person_centurionmodelnote" ("centurionmodelnote_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_centurionmodelnote" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "access_person" ("entity_ptr_id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "access_company_audithistory" ("centurionaudit_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_audithistory" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "access_company" ("entity_ptr_id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "access_company_centurionmodelnote" ("centurionmodelnote_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_centurionmodelnote" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "access_company" ("entity_ptr_id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "accounting_assetbase" ("model_notes" text NULL, "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "asset_number" varchar(30) NULL UNIQUE, "serial_number" varchar(30) NULL UNIQUE, "asset_type" varchar(30) NOT NULL, "created" datetime NOT NULL, "modified" datetime NOT NULL, "organization_id" integer NOT NULL REFERENCES "access_tenant" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "accounting_assetbase_history" ("modelhistory_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_model_history" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "accounting_assetbase" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "accounting_assetbase_notes" ("modelnotes_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_model_notes" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "accounting_assetbase" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "access_role" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(30) NOT NULL, "created" datetime NOT NULL, "modified" datetime NOT NULL, "organization_id" integer NOT NULL REFERENCES "access_tenant" ("id") DEFERRABLE INITIALLY DEFERRED, "model_notes" text NULL);
CREATE TABLE IF NOT EXISTS "access_role_audithistory" ("centurionaudit_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_audithistory" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "access_role" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "access_role_centurionmodelnote" ("centurionmodelnote_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_centurionmodelnote" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "access_role" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "accounting_assetbase" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "asset_number" varchar(30) NULL UNIQUE, "serial_number" varchar(30) NULL UNIQUE, "asset_type" varchar(30) NOT NULL, "created" datetime NOT NULL, "modified" datetime NOT NULL, "organization_id" integer NOT NULL REFERENCES "access_tenant" ("id") DEFERRABLE INITIALLY DEFERRED, "model_notes" text NULL);
CREATE TABLE IF NOT EXISTS "accounting_assetbase_audithistory" ("centurionaudit_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_audithistory" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "accounting_assetbase" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "accounting_assetbase_centurionmodelnote" ("centurionmodelnote_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_centurionmodelnote" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "accounting_assetbase" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "django_admin_log" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "object_id" text NULL, "object_repr" varchar(200) NOT NULL, "action_flag" smallint unsigned NOT NULL CHECK ("action_flag" >= 0), "change_message" text NOT NULL, "content_type_id" integer NULL REFERENCES "django_content_type" ("id") DEFERRABLE INITIALLY DEFERRED, "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, "action_time" datetime NOT NULL);
CREATE TABLE IF NOT EXISTS "api_authtoken" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "note" varchar(50) NULL, "token" varchar(64) NOT NULL UNIQUE, "expires" datetime NOT NULL, "created" datetime NOT NULL, "modified" datetime NOT NULL, "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "itam_itamassetbase" ("assetbase_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "accounting_assetbase" ("id") DEFERRABLE INITIALLY DEFERRED, "itam_type" varchar(30) NOT NULL);
@ -197,6 +197,8 @@ CREATE TABLE IF NOT EXISTS "itam_softwarecategory_centurionmodelnote" ("centurio
CREATE TABLE IF NOT EXISTS "itam_softwareversion" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "created" datetime NOT NULL, "modified" datetime NOT NULL, "name" varchar(50) NOT NULL, "organization_id" integer NOT NULL REFERENCES "access_tenant" ("id") DEFERRABLE INITIALLY DEFERRED, "software_id" integer NOT NULL REFERENCES "itam_software" ("id") DEFERRABLE INITIALLY DEFERRED, "model_notes" text NULL);
CREATE TABLE IF NOT EXISTS "itam_softwareversion_audithistory" ("centurionaudit_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_audithistory" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "itam_softwareversion" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "itam_softwareversion_centurionmodelnote" ("centurionmodelnote_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_centurionmodelnote" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "itam_softwareversion" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "itam_itamassetbase_audithistory" ("centurionaudit_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_audithistory" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "itam_itamassetbase" ("assetbase_ptr_id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "itam_itamassetbase_centurionmodelnote" ("centurionmodelnote_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "core_centurionmodelnote" ("id") DEFERRABLE INITIALLY DEFERRED, "model_id" integer NOT NULL REFERENCES "itam_itamassetbase" ("assetbase_ptr_id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "itim_cluster_devices" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "cluster_id" integer NOT NULL REFERENCES "itim_cluster" ("id") DEFERRABLE INITIALLY DEFERRED, "device_id" integer NOT NULL REFERENCES "itam_device" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "itim_cluster_nodes" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "cluster_id" integer NOT NULL REFERENCES "itim_cluster" ("id") DEFERRABLE INITIALLY DEFERRED, "device_id" integer NOT NULL REFERENCES "itam_device" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE TABLE IF NOT EXISTS "itim_cluster" ("model_notes" text NULL, "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(50) NOT NULL, "config" text NULL CHECK ((JSON_VALID("config") OR "config" IS NULL)), "created" datetime NOT NULL, "modified" datetime NOT NULL, "organization_id" integer NOT NULL REFERENCES "access_tenant" ("id") DEFERRABLE INITIALLY DEFERRED, "cluster_type_id" integer NULL REFERENCES "itim_clustertype" ("id") DEFERRABLE INITIALLY DEFERRED, "parent_cluster_id" integer NULL REFERENCES "itim_cluster" ("id") DEFERRABLE INITIALLY DEFERRED);
@ -237,9 +239,9 @@ CREATE TABLE IF NOT EXISTS "social_auth_nonce" ("id" integer NOT NULL PRIMARY KE
CREATE TABLE IF NOT EXISTS "social_auth_usersocialauth" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "provider" varchar(32) NOT NULL, "uid" varchar(255) NOT NULL, "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, "created" datetime NOT NULL, "modified" datetime NOT NULL, "extra_data" text NOT NULL CHECK ((JSON_VALID("extra_data") OR "extra_data" IS NULL)));
CREATE TABLE IF NOT EXISTS "social_auth_partial" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "token" varchar(32) NOT NULL, "next_step" smallint unsigned NOT NULL CHECK ("next_step" >= 0), "backend" varchar(32) NOT NULL, "timestamp" datetime NOT NULL, "data" text NOT NULL CHECK ((JSON_VALID("data") OR "data" IS NULL)));
DELETE FROM sqlite_sequence;
INSERT INTO sqlite_sequence VALUES('django_migrations',222);
INSERT INTO sqlite_sequence VALUES('django_content_type',216);
INSERT INTO sqlite_sequence VALUES('auth_permission',909);
INSERT INTO sqlite_sequence VALUES('django_migrations',227);
INSERT INTO sqlite_sequence VALUES('django_content_type',218);
INSERT INTO sqlite_sequence VALUES('auth_permission',917);
INSERT INTO sqlite_sequence VALUES('auth_group',0);
INSERT INTO sqlite_sequence VALUES('auth_user',0);
INSERT INTO sqlite_sequence VALUES('core_notes',0);
@ -261,6 +263,8 @@ INSERT INTO sqlite_sequence VALUES('assistance_knowledgebasecategory',0);
INSERT INTO sqlite_sequence VALUES('assistance_modelknowledgebasearticle',0);
INSERT INTO sqlite_sequence VALUES('access_tenant',0);
INSERT INTO sqlite_sequence VALUES('access_entity',0);
INSERT INTO sqlite_sequence VALUES('access_role',0);
INSERT INTO sqlite_sequence VALUES('accounting_assetbase',0);
INSERT INTO sqlite_sequence VALUES('django_admin_log',0);
INSERT INTO sqlite_sequence VALUES('itam_devicetype',0);
INSERT INTO sqlite_sequence VALUES('config_management_configgrouphosts',0);

View File

@ -0,0 +1,81 @@
# Generated by Django 5.1.10 on 2025-07-10 09:10
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
("itam", "0024_alter_software_organization"),
]
operations = [
migrations.CreateModel(
name="ITAMAssetBaseAuditHistory",
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="+",
to="itam.itamassetbase",
verbose_name="Model",
),
),
],
options={
"verbose_name": "IT Asset History",
"verbose_name_plural": "IT Asset Histories",
"db_table": "itam_itamassetbase_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="ITAMAssetBaseCenturionModelNote",
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.itamassetbase",
verbose_name="Model",
),
),
],
options={
"verbose_name": "IT Asset Note",
"verbose_name_plural": "IT Asset Notes",
"db_table": "itam_itamassetbase_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
]

View File

@ -1,4 +1,5 @@
from django.apps import apps
from django.conf import settings
from django.db import models
from rest_framework.reverse import reverse
@ -18,9 +19,13 @@ class ITAMAssetBase(
**Don't** use this model directly, it should be used via a sub-model.
"""
_is_submodel = True
app_namespace = None
note_basename = 'accounting:_api_v2_asset_note'
model_tag = 'it_asset'
url_model_name = 'itamassetbase'
class Meta:
@ -69,7 +74,7 @@ class ITAMAssetBase(
if(
( isinstance(model, ITAMAssetBase) or issubclass(model, ITAMAssetBase) )
and ITAMAssetBase._meta.itam_sub_model_type != 'itam_base'
# and ITAMAssetBase._meta.itam_sub_model_type != 'itam_base'
):
@ -161,22 +166,7 @@ class ITAMAssetBase(
def get_url( self, request = None ) -> str:
kwargs = self.get_url_kwargs()
url_path_name = '_api_v2_itam_asset'
if request:
return reverse(f"v2:{url_path_name}-detail", request=request, kwargs = kwargs )
return reverse(f"v2:{url_path_name}-detail", kwargs = kwargs )
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
def clean_fields(self, exclude = None):
related_model = self.get_related_model()
@ -184,8 +174,56 @@ class ITAMAssetBase(
related_model = self
if self.itam_type != str(related_model._meta.itam_sub_model_type).lower().replace(' ', '_'):
if(
self.itam_type != str(related_model._meta.itam_sub_model_type).lower().replace(' ', '_')
and str(related_model._meta.sub_model_type).lower().replace(' ', '_') != 'itam_base'
):
self.itam_type = str(related_model._meta.itam_sub_model_type).lower().replace(' ', '_')
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
super().clean_fields(exclude = exclude)
def get_url(
self, relative: bool = False, api_version: int = 2, many = False, request: any = None
) -> str:
namespace = f'v{api_version}'
if self.get_app_namespace():
namespace = namespace + ':' + self.get_app_namespace()
url_basename = f'{namespace}:_api_{self._meta.model_name}'
if self.url_model_name:
url_basename = f'{namespace}:_api_{self.url_model_name}'
if (
self._is_submodel
and self._meta.sub_model_type != 'it_asset'
):
url_basename += '_sub'
if many:
url_basename += '-list'
else:
url_basename += '-detail'
url = reverse( viewname = url_basename, kwargs = self.get_url_kwargs( many = many ) )
if not relative:
url = settings.SITE_URL + url
return url

View File

@ -4,7 +4,7 @@ from drf_spectacular.utils import extend_schema_serializer
from access.serializers.organization import TenantBaseSerializer
from accounting.serializers.asset import (
from accounting.serializers.assetbase import (
BaseSerializer,
ModelSerializer as AssetBaseModelSerializer,
ViewSerializer as AssetBaseViewSerializer,

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 ITAMAssetBaseAuditHistory # 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 = 'ITAMAssetBaseAuditHistoryModelSerializer')
class ModelSerializer(
common.CommonModelSerializer,
BaseSerializer
):
"""Git Group Audit History Base Model"""
_urls = serializers.SerializerMethodField('get_url')
class Meta:
model = ITAMAssetBaseAuditHistory
fields = [
'id',
'organization',
'display_name',
'content_type',
'model',
'before',
'after',
'action',
'user',
'created',
'_urls',
]
read_only_fields = fields
@extend_schema_serializer(component_name = 'ITAMAssetBaseAuditHistoryViewSerializer')
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 ITAMAssetBaseCenturionModelNote # 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 = 'ITAMAssetBaseModelNoteModelSerializer')
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 = ITAMAssetBaseCenturionModelNote
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 = 'ITAMAssetBaseModelNoteViewSerializer')
class ViewSerializer(
ModelSerializer,
BaseModelViewSerializer,
):
organization = TenantBaseSerializer( many = False, read_only = True )

View File

@ -18,7 +18,7 @@ def model(request):
@pytest.fixture(scope='function')
def create_serializer():
from itam.serializers.asset_it_asset import ModelSerializer
from itam.serializers.assetbase_itamassetbase import ModelSerializer
yield ModelSerializer

View File

@ -1,3 +1,5 @@
import pytest
from django.test import TestCase
from accounting.tests.functional.asset_base.test_functional_asset_base_metadata import AssetBaseMetadataInheritedCases
@ -6,6 +8,7 @@ from itam.models.itam_asset_base import ITAMAssetBase
@pytest.mark.model_itamassetbase
class MetadataTestCases(
AssetBaseMetadataInheritedCases,
):
@ -22,7 +25,7 @@ class MetadataTestCases(
url_view_kwargs: dict = {}
url_name = '_api_v2_itam_asset'
url_name = '_api_itamassetbase'
@ -38,6 +41,7 @@ class ITAMAssetBaseMetadataInheritedCases(
@pytest.mark.module_accounting
class ITAMAssetBaseMetadataTest(
MetadataTestCases,
TestCase,

View File

@ -1,7 +1,10 @@
import pytest
from accounting.tests.functional.asset_base.test_functional_asset_base_permission import AssetBasePermissionsAPIInheritedCases
@pytest.mark.model_itamassetbase
class PermissionsAPITestCases(
AssetBasePermissionsAPIInheritedCases,
):
@ -17,13 +20,13 @@ class PermissionsAPITestCases(
kwargs_create_item_diff_org: dict = {}
url_kwargs: dict = {
'asset_model': 'it_asset',
'model_name': 'itamassetbase',
}
url_name = '_api_v2_itam_asset'
url_name = '_api_itamassetbase'
url_view_kwargs: dict = {
'asset_model': 'it_asset',
'model_name': 'itamassetbase',
}
@ -40,6 +43,7 @@ class ITAMAssetBasePermissionsAPIInheritedCases(
@pytest.mark.module_accounting
class ITAMAssetBasePermissionsAPIPyTest(
PermissionsAPITestCases,
):

View File

@ -1,3 +1,4 @@
import pytest
from accounting.tests.functional.asset_base.test_functional_asset_base_serializer import AssetBaseSerializerInheritedCases
@ -25,6 +26,7 @@ class MockView:
@pytest.mark.model_itamassetbase
class ITAMAssetBaseSerializerTestCases(
AssetBaseSerializerInheritedCases
):
@ -54,6 +56,7 @@ class ITAMAssetBaseSerializerInheritedCases(
@pytest.mark.module_accounting
class ITAMAssetBaseSerializerPyTest(
ITAMAssetBaseSerializerTestCases,
):

View File

@ -1,3 +1,5 @@
import pytest
from django.test import TestCase
from accounting.tests.functional.asset_base.test_functional_asset_base_viewset import AssetBaseViewSetInheritedCases
@ -6,6 +8,7 @@ from itam.models.itam_asset_base import ITAMAssetBase
@pytest.mark.model_itamassetbase
class ViewSetTestCases(
AssetBaseViewSetInheritedCases
):
@ -21,14 +24,14 @@ class ViewSetTestCases(
model = ITAMAssetBase
url_kwargs: dict = {
'asset_model': 'it_asset',
'model_name': 'itamassetbase',
}
url_view_kwargs: dict = {
'asset_model': 'it_asset',
'model_name': 'itamassetbase',
}
url_name = '_api_v2_itam_asset'
url_name = '_api_itamassetbase'
@ -42,6 +45,7 @@ class ITAMAssetBaseViewSetInheritedCases(
@pytest.mark.module_accounting
class ITAMAssetBaseViewSetTest(
ViewSetTestCases,
TestCase,

View File

@ -1,14 +1,22 @@
import pytest
from itam.models.itam_asset_base import ITAMAssetBase
@pytest.fixture( scope = 'class')
def model(request):
def model(model_itamassetbase):
request.cls.model = ITAMAssetBase
yield model_itamassetbase
yield request.cls.model
del request.cls.model
@pytest.fixture( scope = 'class', autouse = True)
def model_kwargs(request, kwargs_itamassetbase):
request.cls.kwargs_create_item = kwargs_itamassetbase.copy()
yield kwargs_itamassetbase.copy()
if hasattr(request.cls, 'kwargs_create_item'):
try:
del request.cls.kwargs_create_item
except:
pass

View File

@ -1,9 +1,12 @@
import pytest
from accounting.tests.unit.asset_base.test_unit_asset_base_api_fields import (
AssetBaseAPIInheritedCases
)
@pytest.mark.model_itamassetbase
class ITAMAssetBaseAPITestCases(
AssetBaseAPIInheritedCases,
):
@ -29,6 +32,7 @@ class ITAMAssetBaseAPIInheritedCases(
@pytest.mark.module_accounting
class ITAMAssetBaseAPIPyTest(
ITAMAssetBaseAPITestCases,
):

View File

@ -1,177 +1,77 @@
import pytest
from django.db import models
from accounting.tests.unit.asset_base.test_unit_asset_base_model import AssetBaseModelInheritedCases
from accounting.tests.unit.asset_base.test_unit_asset_base_model import (
AssetBaseModelInheritedCases,
)
from itam.models.itam_asset_base import ITAMAssetBase
class ITAMAssetBaseModelTestCases(
AssetBaseModelInheritedCases,
@pytest.mark.model_itamassetbase
class ITAMAssetModelTestCases(
AssetBaseModelInheritedCases
):
kwargs_create_item: dict = {}
it_asset_base_model = ITAMAssetBase
@property
def parameterized_class_attributes(self):
sub_model_type = 'itam_base'
"""Sub Model Type
sub-models must have this attribute defined in `ModelName.Meta.sub_model_type`
"""
return {
'_is_submodel': {
'value': True
},
'app_namespace': {
'type': type(None),
'value': None
},
'model_tag': {
'type': str,
'value': 'it_asset'
},
'url_model_name': {
'type': str,
'value': 'itamassetbase'
},
}
parameterized_fields: dict = {
"itam_type": {
'field_type': models.fields.CharField,
'field_parameter_default_exists': True,
'field_parameter_default_value': ITAMAssetBase._meta.itam_sub_model_type,
'field_parameter_verbose_name_type': str
@property
def parameterized_model_fields(self):
return {
'itam_type': {
'blank': True,
'default': 'itam_base',
'field_type': models.CharField,
'max_length': 30,
'null': False,
'unique': False,
}
}
def test_class_inherits_itam_assetbase(self):
def test_class_inherits_itamassetbase(self, model):
""" Class inheritence
TenancyObject must inherit SaveHistory
"""
assert issubclass(self.model, ITAMAssetBase)
def test_attribute_meta_exists_itam_sub_model_type(self):
"""Attribute check
meta.itam_sub_model_type must exist
"""
assert hasattr(self.model()._meta, 'itam_sub_model_type')
def test_sanity_is_it_asset_sub_model(self):
"""Sanity Test
This test ensures that the model being tested `self.model` is a
sub-model of `self.it_asset_base_model`.
This test is required as the same viewset is used for all sub-models
of `ITAMAssetBase`
"""
assert issubclass(self.model, self.it_asset_base_model)
def test_attribute_meta_type_itam_sub_model_type(self):
"""Attribute type
meta.itam_sub_model_type must be of type str
"""
assert type(self.model()._meta.itam_sub_model_type) is str
assert issubclass(model, ITAMAssetBase)
def test_attribute_type_app_namespace(self):
"""Attribute Type
app_namespace is of type str
"""
assert self.model.app_namespace is None
def test_attribute_value_app_namespace(self):
"""Attribute Type
app_namespace has been set, override this test case with the value
of attribute `app_namespace`
"""
assert self.model.app_namespace is None
def test_attribute_type_note_basename(self):
"""Attribute Type
note_basename is of type str
"""
assert type(self.model.note_basename) is str
def test_attribute_value_note_basename(self):
"""Attribute Type
note_basename has been set, override this test case with the value
of attribute `note_basename`
"""
assert self.model.note_basename == 'accounting:_api_v2_asset_note'
def test_function_is_property_get_itam_model_type(self):
"""Function test
Confirm function `get_itam_model_type` is a property
"""
assert type(self.model.get_itam_model_type) is property
def test_function_value_get_itam_model_type(self):
"""Function test
Confirm function `get_itam_model_type` is a property
"""
assert self.item.get_itam_model_type is None
def test_function_value_get_url(self):
assert self.item.get_url() == '/api/v2/itam/it_asset/' + str(self.item.id)
class ITAMAssetBaseModelInheritedCases(
ITAMAssetBaseModelTestCases,
class ITAMAssetModelInheritedCases(
ITAMAssetModelTestCases,
):
"""Sub-Ticket Test Cases
Test Cases for Ticket models that inherit from model AssetBase
"""
kwargs_create_item: dict = {}
model = None
sub_model_type = None
"""Ticket Sub Model Type
Ticket sub-models must have this attribute defined in `ModelNam.Meta.sub_model_type`
"""
def test_function_value_not_None_get_itam_model_type(self):
"""Function test
Confirm function `get_itam_model_type` is a property
"""
assert self.item.get_itam_model_type is not None
pass
class ITAMAssetBaseModelPyTest(
ITAMAssetBaseModelTestCases,
@pytest.mark.module_accounting
class ITAMAssetModelPyTest(
ITAMAssetModelTestCases,
):
def test_function_value_get_related_model(self):
"""Function test
Confirm function `get_related_model` is None for base model
"""
assert self.item.get_related_model() is None
pass

View File

@ -1,3 +1,5 @@
import pytest
from django.test import Client, TestCase
from rest_framework.reverse import reverse
@ -22,6 +24,7 @@ from itam.models.itam_asset_base import ITAMAssetBase
@pytest.mark.model_itamassetbase
class ITAMAssetBaseViewsetTestCases(
AssetBaseViewsetInheritedCases,
):
@ -41,10 +44,11 @@ class ITAMAssetBaseViewsetInheritedCases(
model: str = None
"""name of the model to test"""
route_name = 'v2:accounting:_api_v2_asset_sub'
route_name = 'v2:accounting:_api_asset_sub'
@pytest.mark.module_accounting
class ITAMAssetBaseViewsetTest(
ITAMAssetBaseViewsetTestCases,
TestCase,

View File

@ -34,8 +34,8 @@ router.register(
basename = '_api_v2_itam_home'
)
router.register(
prefix = '(?P<asset_model>[it_asset]+)', viewset = asset.ViewSet,
feature_flag = '2025-00007', basename = '_api_v2_itam_asset'
prefix = '(?P<model_name>[itamassetbase]+)', viewset = asset.ViewSet,
feature_flag = '2025-00007', basename = '_api_itamassetbase'
)
router.register(
prefix = 'device', viewset = device.ViewSet,

View File

@ -18,6 +18,11 @@ from .model_appsettings import (
model_appsettings,
)
from .model_assetbase import (
kwargs_assetbase,
model_assetbase,
)
from .model_centurionaudit import (
kwargs_centurionaudit,
model_centurionaudit,
@ -160,6 +165,11 @@ from .model_instance import (
model_instance
)
from .model_itamassetbase import (
kwargs_itamassetbase,
model_itamassetbase,
)
from .model_knowledgebase import (
kwargs_knowledgebase,
model_knowledgebase,
@ -223,6 +233,11 @@ from .model_projecttype import (
model_projecttype,
)
from .model_role import (
kwargs_role,
model_role,
)
from .model_service import (
kwargs_service,
model_service,

26
app/tests/fixtures/model_assetbase.py vendored Normal file
View File

@ -0,0 +1,26 @@
import datetime
import pytest
from accounting.models.asset_base import AssetBase
@pytest.fixture( scope = 'class')
def model_assetbase():
yield AssetBase
@pytest.fixture( scope = 'class')
def kwargs_assetbase( kwargs_centurionmodel, model_assetbase ):
random_str = str(datetime.datetime.now(tz=datetime.timezone.utc))
random_str = str(random_str).replace(
' ', '').replace(':', '').replace('+', '').replace('.', '')
kwargs = {
**kwargs_centurionmodel.copy(),
'asset_number': 'ab_' + random_str,
'serial_number': 'ab_' + random_str,
# 'asset_type': (model_assetbase._meta.sub_model_type, model_assetbase._meta.verbose_name),
}
yield kwargs.copy()

View File

@ -0,0 +1,25 @@
import datetime
import pytest
from itam.models.itam_asset_base import ITAMAssetBase
@pytest.fixture( scope = 'class')
def model_itamassetbase():
yield ITAMAssetBase
@pytest.fixture( scope = 'class')
def kwargs_itamassetbase( kwargs_assetbase, model_itamassetbase ):
random_str = str(datetime.datetime.now(tz=datetime.timezone.utc))
random_str = str(random_str).replace(
' ', '').replace(':', '').replace('+', '').replace('.', '')
kwargs = {
**kwargs_assetbase.copy(),
# 'asset_type': (model_itamassetbase._meta.sub_model_type, model_itamassetbase._meta.verbose_name),
'itam_type': "it_asset"
}
yield kwargs.copy()

View File

@ -1,5 +1,5 @@
import datetime
from django.core.exceptions import ValidationError
from django.core.exceptions import ValidationError, ObjectDoesNotExist
import pytest
from django.db import models
@ -97,9 +97,22 @@ def model_kwarg_data():
if 'unique' in e.error_dict['__all__'][0].code:
instance = model.objects.get(
**kwargs
)
try:
instance = model.objects.get(
**kwargs
)
except ObjectDoesNotExist as e:
if 'modified' in kwargs:
no_modified_in_kwargs = kwargs.copy()
del no_modified_in_kwargs['modified']
instance = model.objects.get(
**no_modified_in_kwargs
)
for field, values in many_field.items():

29
app/tests/fixtures/model_role.py vendored Normal file
View File

@ -0,0 +1,29 @@
import datetime
import pytest
from access.models.role import Role
@pytest.fixture( scope = 'class')
def model_role():
yield Role
@pytest.fixture( scope = 'class')
def kwargs_role(
kwargs_centurionmodel
):
random_str = str(datetime.datetime.now(tz=datetime.timezone.utc))
random_str = str(random_str).replace(
' ', '').replace(':', '').replace('-', '').replace('+', '').replace('.', '')
kwargs = {
**kwargs_centurionmodel.copy(),
'name': 'r_' + random_str,
'modified': '2024-06-03T23:00:00Z',
}
yield kwargs.copy()

View File

@ -87,7 +87,7 @@ A Model link is a reference to an item within the database. Supported model link
| gitrepository| `$git_repository-<id>` |
| gitgroup| `$git_group-<id>` |
| group| `$-<id>` |
| it_asset | `$it_asset-<id>` |
| it_asset | `$asset-<id>` or `$it_asset-<id>` |
| knowledgebase| `$kb-<id>` |
| knowledgebasecategory| `$kb_category-<id>` |
| manufacturer| `$manufacturer-<id>` |

View File

@ -1103,6 +1103,7 @@ markers = [
"meta_models: Selects Meta models",
"mixin: Selects all mixin test cases.",
"mixin_centurion: Selects all centurion mixin test cases.",
"model_assetbase: Selects tests for model Asset Base.",
"model_appsettings: Selects tests for model app settings.",
"model_company: Selects test for model Company.",
"model_configgroups: Selects Config Group tests.",
@ -1124,6 +1125,7 @@ markers = [
"model_githubrepository: Selects tests for model `github repository`",
"model_gitlabrepository: Selects tests for model `gitlab repository`",
"model_gitrepository: Selects tests for model `git repository`",
"model_itamassetbase: Selects tests for model ITAM Asset Base.",
"model_knowledgebase: Selects Knowledge base tests.",
"model_knowledgebasecategory: Selects Knowledge base category tests.",
"model_manufacturer: Select all manufacturer tests.",
@ -1135,6 +1137,7 @@ markers = [
"model_projectmilestone: Selects tests for model Project Milestone.",
"model_projectstate: Selects tests for model Project State.",
"model_projecttype: Selects tests for model Project Type.",
"model_role: Selects tests for model Role.",
"model_service: Selects tests for model Service.",
"model_software: Selects tests for model Software.",
"model_softwarecategory: Selects tests for model Software Category.",