Skip to content

Commit

Permalink
Merge pull request #7 from DanSheps/develop
Browse files Browse the repository at this point in the history
v1.0.2
  • Loading branch information
DanSheps authored Mar 20, 2024
2 parents a4d31b9 + 613683d commit be042da
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 159 deletions.
8 changes: 2 additions & 6 deletions netbox_lifecycle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,18 @@ def ready(self):
from netbox_lifecycle.models import SupportContractAssignment, HardwareLifecycle

# Add Generic Relations to appropriate models
GenericRelation(
to=SupportContractAssignment,
content_type_field='assigned_object_type',
object_id_field='assigned_object_id',
related_query_name='device'
).contribute_to_class(Device, 'contracts')
GenericRelation(
to=HardwareLifecycle,
content_type_field='assigned_object_type',
object_id_field='assigned_object_id',
related_name='device_type',
related_query_name='device_type'
).contribute_to_class(DeviceType, 'hardware_lifecycle')
GenericRelation(
to=HardwareLifecycle,
content_type_field='assigned_object_type',
object_id_field='assigned_object_id',
related_name='module_type',
related_query_name='module_type'
).contribute_to_class(ModuleType, 'hardware_lifecycle')

Expand Down
21 changes: 2 additions & 19 deletions netbox_lifecycle/api/nested_serializers/contract.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers

from dcim.api.nested_serializers import NestedManufacturerSerializer, NestedDeviceSerializer
from netbox.api.fields import ContentTypeField
from dcim.api.nested_serializers import NestedManufacturerSerializer
from netbox.api.serializers import WritableNestedSerializer
from netbox.constants import NESTED_SERIALIZER_PREFIX
from netbox_lifecycle.models import Vendor, SupportContract, SupportContractAssignment, SupportSKU

__all__ = (
Expand All @@ -15,8 +11,6 @@
'NestedSupportContractAssignmentSerializer',
)

from utilities.api import get_serializer_for_model


class NestedVendorSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_lifecycle-api:hardwarelifecycle-detail')
Expand Down Expand Up @@ -48,17 +42,6 @@ class NestedSupportContractAssignmentSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_lifecycle-api:licenseassignment-detail')
contract = NestedSupportContractSerializer()

assigned_object_type = ContentTypeField(
queryset=ContentType.objects.all()
)
assigned_object = serializers.SerializerMethodField(read_only=True)

class Meta:
model = SupportContractAssignment
fields = ('url', 'id', 'display', 'contract', 'assigned_object_type', 'assigned_object_id', 'assigned_object')

@extend_schema_field(serializers.JSONField(allow_null=True))
def get_assigned_object(self, instance):
serializer = get_serializer_for_model(instance.assigned_object, prefix=NESTED_SERIALIZER_PREFIX)
context = {'request': self.context['request']}
return serializer(instance.assigned_object, context=context).data
fields = ('url', 'id', 'display', 'contract', 'device', 'license')
27 changes: 5 additions & 22 deletions netbox_lifecycle/api/serializers/contract.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers

from dcim.api.nested_serializers import NestedManufacturerSerializer, NestedDeviceSerializer
from netbox.api.fields import ContentTypeField
from netbox.api.serializers import NetBoxModelSerializer
from netbox.constants import NESTED_SERIALIZER_PREFIX
from netbox_lifecycle.api.nested_serializers import NestedVendorSerializer, NestedSupportContractSerializer
from netbox_lifecycle.api.nested_serializers import NestedVendorSerializer, NestedSupportContractSerializer, \
NestedLicenseAssignmentSerializer
from netbox_lifecycle.models import Vendor, SupportContract, SupportContractAssignment, SupportSKU

__all__ = (
Expand Down Expand Up @@ -52,25 +49,11 @@ class SupportContractAssignmentSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_lifecycle-api:licenseassignment-detail')
contract = NestedSupportContractSerializer()

assigned_object_type = ContentTypeField(
queryset=ContentType.objects.all()
)
assigned_object = serializers.SerializerMethodField(read_only=True)
device = NestedDeviceSerializer()
license = NestedLicenseAssignmentSerializer()

class Meta:
model = SupportContractAssignment
fields = (
'url', 'id', 'display', 'contract', 'assigned_object_type', 'assigned_object_id',
'assigned_object', 'end'
'url', 'id', 'display', 'contract', 'device', 'license', 'end'
)

assigned_object_type = ContentTypeField(
queryset=ContentType.objects.all()
)
assigned_object = serializers.SerializerMethodField(read_only=True)

@extend_schema_field(serializers.JSONField(allow_null=True))
def get_assigned_object(self, instance):
serializer = get_serializer_for_model(instance.assigned_object, prefix=NESTED_SERIALIZER_PREFIX)
context = {'request': self.context['request']}
return serializer(instance.assigned_object, context=context).data
45 changes: 14 additions & 31 deletions netbox_lifecycle/filtersets/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from dcim.models import Manufacturer, Device
from netbox.filtersets import NetBoxModelFilterSet
from netbox_lifecycle.models import Vendor, SupportContract, SupportContractAssignment, SupportSKU, LicenseAssignment
from netbox_lifecycle.models import Vendor, SupportContract, SupportContractAssignment, SupportSKU, LicenseAssignment, \
License

__all__ = (
'SupportContractFilterSet',
Expand Down Expand Up @@ -87,27 +88,24 @@ class SupportContractAssignmentFilterSet(NetBoxModelFilterSet):
queryset=SupportContract.objects.all(),
label=_('Contract'),
)
assigned_object_type_id = django_filters.ModelMultipleChoiceFilter(
queryset=ContentType.objects.all()
)
device = MultiValueCharFilter(
method='filter_device',
field_name='name',
device = django_filters.ModelMultipleChoiceFilter(
field_name='device__name',
queryset=Device.objects.all(),
label=_('Device (name)'),
)
device_id = MultiValueNumberFilter(
method='filter_device',
field_name='pk',
device_id = django_filters.ModelMultipleChoiceFilter(
field_name='device',
queryset=Device.objects.all(),
label=_('Device (ID)'),
)
license = MultiValueCharFilter(
method='filter_license',
field_name='name',
license = django_filters.ModelMultipleChoiceFilter(
field_name='license__license__name',
queryset=License.objects.all(),
label=_('License (SKU)'),
)
license_id = MultiValueNumberFilter(
method='filter_license',
field_name='pk',
license_id = django_filters.ModelMultipleChoiceFilter(
field_name='license',
queryset=LicenseAssignment.objects.all(),
label=_('License (ID)'),
)

Expand All @@ -127,18 +125,3 @@ def search(self, queryset, name, value):
Q(license__license__name__icontains=value)
)
return queryset.filter(qs_filter).distinct()

def filter_device(self, queryset, name, value):
licenses = LicenseAssignment.objects.filter(**{'device__{}__in'.format(name): value})
devices = Device.objects.filter(**{'{}__in'.format(name): value})
device_ids = devices.values_list('id', flat=True)
license_ids = licenses.values_list('id', flat=True)

return queryset.filter(
Q(device__in=device_ids) | Q(license__in=license_ids)
)

def filter_license(self, queryset, name, value):
return queryset.filter(
license__in=value
)
24 changes: 6 additions & 18 deletions netbox_lifecycle/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,6 @@ class Meta:
}

def __init__(self, *args, **kwargs):

# Initialize helper selectors
instance = kwargs.get('instance')
initial = kwargs.get('initial', {}).copy()
if instance:
if type(instance.assigned_object) is Device:
initial['device'] = instance.assigned_object
elif type(instance.assigned_object) is LicenseAssignment:
initial['license'] = instance.assigned_object
kwargs['initial'] = initial

super().__init__(*args, **kwargs)

def clean(self):
Expand All @@ -107,14 +96,13 @@ def clean(self):
field for field in ('device', 'license') if self.cleaned_data[field]
]

if len(selected_objects) > 1:
raise forms.ValidationError({
selected_objects[1]: "You can only assign a device or license"
if len(selected_objects) == 0:
raise forms.ValidationErrr({
selected_objects[1]: "You must select at least a device or license"
})
elif selected_objects:
self.instance.assigned_object = self.cleaned_data[selected_objects[0]]
else:
self.instance.assigned_object = None

if self.cleaned_data.get('license') and not self.cleaned_data.get('device'):
self.cleaned_data['device'] = self.cleaned_data.get('license').device


class LicenseForm(NetBoxModelForm):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Generated by Django 4.2.4 on 2024-03-13 04:24

from django.db import migrations, models
import django.db.models.deletion


def migrate_assigned_object_forward(apps, schema_editor):
SupportContractAssignment = apps.get_model('netbox_lifecycle', 'SupportContractAssignment')
LicenseAssignment = apps.get_model('netbox_lifecycle', 'LicenseAssignment')
Device = apps.get_model('dcim', 'Device')
ContentType = apps.get_model('contenttypes', 'ContentType')

for assignment in SupportContractAssignment.objects.all():
if assignment.assigned_object_type == ContentType.objects.get(app_label='dcim', model='device'):
device = Device.objects.get(pk=assignment.assigned_object_id)
assignment.device = device
assignment.save()
else:
license_assignment = LicenseAssignment.objects.get(pk=assignment.assigned_object_id)
assignment.device = license_assignment.device
assignment.license = license_assignment
assignment.save()

def migrate_assigned_object_reverse(apps, schema_editor):
SupportContractAssignment = apps.get_model('netbox_lifecycle', 'SupportContractAssignment')
ContentType = apps.get_model('contenttypes', 'ContentType')

device_type = ContentType.objects.get(app_label='dcim', model='device')
license_type = ContentType.objects.get(app_label='netbox_lifecycle', model='licenseassignment')

for assignment in SupportContractAssignment.objects.all():
if assignment.license is None:
assignment.assigned_object_type = device_type
assignment.assigned_object_id = assignment.device.pk
assignment.save()
else:
assignment.assigned_object_id = license_type
assignment.assigned_object_id = assignment.license.pk
assignment.save()


class Migration(migrations.Migration):

dependencies = [
('dcim', '0185_gfk_indexes'),
('netbox_lifecycle', '0010_licenseassignment_quantity'),
]

operations = [
migrations.AddField(
model_name='supportcontractassignment',
name='device',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contracts', to='dcim.device'),
),
migrations.AddField(
model_name='supportcontractassignment',
name='license',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contracts', to='netbox_lifecycle.licenseassignment'),
),
migrations.RunPython(migrate_assigned_object_forward, migrate_assigned_object_reverse),
migrations.AlterModelOptions(
name='supportcontractassignment',
options={'ordering': ['contract', 'device', 'license']},
),
migrations.RemoveConstraint(
model_name='supportcontractassignment',
name='netbox_lifecycle_supportcontractassignment_unique_assignments',
),
migrations.RemoveConstraint(
model_name='supportcontractassignment',
name='netbox_lifecycle_supportcontractassignment_unique_assignment_null_sku',
),
migrations.RemoveField(
model_name='supportcontractassignment',
name='assigned_object_id',
),
migrations.RemoveField(
model_name='supportcontractassignment',
name='assigned_object_type',
),
]
49 changes: 17 additions & 32 deletions netbox_lifecycle/models/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,22 +124,19 @@ class SupportContractAssignment(NetBoxModel):
blank=True,
related_name='assignments',
)

assigned_object_type = models.ForeignKey(
to=ContentType,
limit_choices_to=('dcim.Device', 'netbox_lifecycle.LicenseAssignment'),
on_delete=models.PROTECT,
related_name='+',
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.SET_NULL,
null=True,
blank=True,
null=True
related_name='contracts',
)
assigned_object_id = models.PositiveBigIntegerField(
license = models.ForeignKey(
to='netbox_lifecycle.LicenseAssignment',
on_delete=models.SET_NULL,
null=True,
blank=True,
null=True
)
assigned_object = GenericForeignKey(
ct_field='assigned_object_type',
fk_field='assigned_object_id'
related_name='contracts',
)
end = models.DateField(
null=True,
Expand All @@ -159,23 +156,13 @@ class SupportContractAssignment(NetBoxModel):
)

class Meta:
ordering = ['contract', 'assigned_object_type', 'assigned_object_id']
constraints = (
models.UniqueConstraint(
'contract', 'sku', 'assigned_object_type', 'assigned_object_id',
name='%(app_label)s_%(class)s_unique_assignments',
violation_error_message="Contract assignments must be unique."
),
models.UniqueConstraint(
'contract', 'assigned_object_type', 'assigned_object_id',
name='%(app_label)s_%(class)s_unique_assignment_null_sku',
condition=Q(sku__isnull=True),
violation_error_message="Contract assignments to assigned_objects must be unique."
),
)
ordering = ['contract', 'device', 'license']
constraints = ()

def __str__(self):
return f'{self.assigned_object}: {self.contract.contract_id}'
if self.license and self.device:
return f'{self.device} ({self.license}): {self.contract.contract_id}'
return f'{self.device}: {self.contract.contract_id}'

def get_absolute_url(self):
return reverse('plugins:netbox_lifecycle:supportcontract_assignments', args=[self.contract.pk])
Expand All @@ -187,8 +174,6 @@ def end_date(self):
return self.contract.end

def get_device_status_color(self):
if self.assigned_object is None:
if self.device is None:
return
if hasattr(self.assigned_object, 'device'):
return DeviceStatusChoices.colors.get(self.assigned_object.device.status)
return DeviceStatusChoices.colors.get(self.assigned_object.status)
return DeviceStatusChoices.colors.get(self.device.status)
8 changes: 0 additions & 8 deletions netbox_lifecycle/models/license.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,6 @@ class LicenseAssignment(NetBoxModel):
blank=True,
)

contracts = GenericRelation(
to='netbox_lifecycle.SupportContractAssignment',
content_type_field='assigned_object_type',
object_id_field='assigned_object_id',
related_query_name='license'

)

clone_fields = (
'vendor', 'license',
)
Expand Down
Loading

0 comments on commit be042da

Please sign in to comment.