|
|
@@ -1,7 +1,9 @@
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
+from django.db import transaction
|
|
|
from django.utils.translation import gettext as _
|
|
|
from netaddr import EUI, AddrFormatError
|
|
|
from rest_framework import serializers
|
|
|
+from rest_framework.exceptions import PermissionDenied
|
|
|
|
|
|
from dcim.choices import *
|
|
|
from dcim.constants import *
|
|
|
@@ -277,9 +279,12 @@ class InterfaceSerializer(
|
|
|
def validate(self, data):
|
|
|
# Pop mac_address before model validation — it's a cached_property, not a model field,
|
|
|
# and passing it to Interface(**attrs) in ValidatedModelSerializer.validate() would raise TypeError.
|
|
|
- mac_address = data.pop('mac_address', _UNSET)
|
|
|
+ # data may be an Interface instance (not a dict) in some custom field code paths (#18887).
|
|
|
+ mac_address = _UNSET
|
|
|
+ if isinstance(data, dict):
|
|
|
+ mac_address = data.pop('mac_address', _UNSET)
|
|
|
|
|
|
- if not self.nested:
|
|
|
+ if not self.nested and isinstance(data, dict):
|
|
|
if mac_address not in (_UNSET, None):
|
|
|
try:
|
|
|
EUI(mac_address, version=48)
|
|
|
@@ -353,33 +358,52 @@ class InterfaceSerializer(
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
mac_address = validated_data.pop('mac_address', None)
|
|
|
- instance = super().create(validated_data)
|
|
|
if mac_address is not None:
|
|
|
- mac = MACAddress.objects.create(mac_address=mac_address, assigned_object=instance)
|
|
|
- instance.primary_mac_address = mac
|
|
|
- instance.save()
|
|
|
- instance.__dict__.pop('mac_address', None)
|
|
|
+ request = self.context.get('request')
|
|
|
+ if request and not request.user.has_perm('dcim.add_macaddress'):
|
|
|
+ raise PermissionDenied(_('You do not have permission to create MAC addresses.'))
|
|
|
+ with transaction.atomic():
|
|
|
+ instance = super().create(validated_data)
|
|
|
+ if mac_address is not None:
|
|
|
+ mac = MACAddress.objects.create(mac_address=mac_address, assigned_object=instance)
|
|
|
+ instance.primary_mac_address = mac
|
|
|
+ instance.save()
|
|
|
+ instance.__dict__.pop('mac_address', None)
|
|
|
return instance
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
mac_address = validated_data.pop('mac_address', _UNSET)
|
|
|
- instance = super().update(instance, validated_data)
|
|
|
- if mac_address is _UNSET:
|
|
|
- pass
|
|
|
- elif mac_address is None:
|
|
|
- instance.primary_mac_address = None
|
|
|
- instance.save()
|
|
|
- instance.__dict__.pop('mac_address', None)
|
|
|
+
|
|
|
+ # Check permission and locate any existing MAC before any writes.
|
|
|
+ if mac_address not in (_UNSET, None):
|
|
|
+ existing_mac = instance.mac_addresses.filter(mac_address=mac_address).first()
|
|
|
+ if existing_mac is None:
|
|
|
+ request = self.context.get('request')
|
|
|
+ if request and not request.user.has_perm('dcim.add_macaddress'):
|
|
|
+ raise PermissionDenied(_('You do not have permission to create MAC addresses.'))
|
|
|
else:
|
|
|
- # Find an existing MACAddress on this interface with the target value, or create one.
|
|
|
- # Using find-or-create avoids duplicating a MAC that already exists on this interface.
|
|
|
- mac = instance.mac_addresses.filter(mac_address=mac_address).first()
|
|
|
- if mac is None:
|
|
|
- mac = MACAddress.objects.create(mac_address=mac_address, assigned_object=instance)
|
|
|
- if instance.primary_mac_address_id != mac.pk:
|
|
|
- instance.primary_mac_address = mac
|
|
|
- instance.save()
|
|
|
- instance.__dict__.pop('mac_address', None)
|
|
|
+ existing_mac = None
|
|
|
+
|
|
|
+ with transaction.atomic():
|
|
|
+ instance = super().update(instance, validated_data)
|
|
|
+ if mac_address is _UNSET:
|
|
|
+ pass
|
|
|
+ elif mac_address is None:
|
|
|
+ if instance.primary_mac_address_id is not None:
|
|
|
+ instance.snapshot()
|
|
|
+ instance.primary_mac_address = None
|
|
|
+ instance.save()
|
|
|
+ else:
|
|
|
+ # Find-or-create: prefer existing MAC on this interface; create only if absent.
|
|
|
+ mac = existing_mac
|
|
|
+ if mac is None:
|
|
|
+ mac = MACAddress.objects.create(mac_address=mac_address, assigned_object=instance)
|
|
|
+ if instance.primary_mac_address_id != mac.pk:
|
|
|
+ instance.snapshot()
|
|
|
+ instance.primary_mac_address = mac
|
|
|
+ instance.save()
|
|
|
+
|
|
|
+ instance.__dict__.pop('mac_address', None)
|
|
|
return instance
|
|
|
|
|
|
|