Do not save when either of the 2 fields are not unique

I have the following codes

models.py:

class Device(models.Model):
    hostname = models.CharField(max_length=50, unique=True)
    ipaddr = models.GenericIPAddressField(protocol='ipv4', default='0.0.0.0')
    date_added = models.DateTimeField(default=timezone.now)
    
    
    def __str__(self):
        return self.hostname

class DeviceDetail(models.Model):
    
    SUBNET_CHOICES = (
    ('16','16'),
    ('17', '17'),
    ('18','18'),
    ('19','19'),
    ('20','20'),
    ('21', '21'),
    ('22', '22'),
    ('23', '23'),
    ('24', '24'),
    ('25', '25'),
    ('26', '26'),
    ('27', '27'),
    ('28', '28'),
    ('29', '29'),
    ('30', '30'),
    )

    DEV_MODS =(
        ('Catalyst 9606R', 'Catalyst 9606R'),
        ('C9300L-48T-4X', 'C9300L-48T-4X')
    )

    hostname = models.CharField(max_length=50)
    mgt_interface = models.CharField(max_length=50)
    mgt_ip_addr = models.GenericIPAddressField(protocol='ipv4', unique=True)
    subnetmask = models.CharField(max_length=2, choices = SUBNET_CHOICES)
    ssh_id = models.CharField(max_length=50)
    ssh_pwd = models.CharField(max_length=50)
    enable_secret = models.CharField(max_length=50)
    dev_mod=models.CharField(max_length=50, choices = DEV_MODS) ##device_model replacement
    DD2DKEY = models.ForeignKey(Device, on_delete=models.CASCADE) ##The key to link up the tables
    
    def __str__(self):
        return self.hostname

class DeviceInterface(models.Model):
    MODULE_ID_CHOICES = (
        ('TenGigabitEthernet','TenGigabitEthernet'), 
        ('FortyGigabitEthernet','FortyGigabitEthernet'),
        ('GigabitEthernet','GigabitEthernet'),
        ('Ethernet','Ethernet'),        
    )
    
    moduletype = models.CharField(max_length = 50,choices = MODULE_ID_CHOICES)
    firstportid = models.CharField(max_length=50)
    lastportid = models.CharField(max_length=50)
    I2DKEY = models.ForeignKey(Device, on_delete=models.CASCADE) ##The key to link up the tables

    def __str__(self):
        return self.moduletype

forms.py:

class DeviceForm(ModelForm):
    class Meta:
        model= Device
        fields= ['hostname']
        labels = {
            "hostname": "Hostname",
            
        }

class DeviceDetailForm(ModelForm):
    class Meta:
        model= DeviceDetail
        fields= ['hostname', 'mgt_interface', 'mgt_ip_addr', 'subnetmask', 'ssh_id', 'ssh_pwd', 'enable_secret', 'dev_mod']
        labels = {
            "mgt_ip_addr": "Management IP Address",
        }
        widgets = {
            'enable_secret': forms.PasswordInput(),
            'ssh_pwd': forms.PasswordInput()
        }

    def clean_hostname(self):
        hostname = self.cleaned_data['hostname']
        if len(hostname) < 8:
            raise forms.ValidationError(f'Hostname needs to be more than 8 character long, {hostname}')
        return hostname

class DeviceInterfaceForm(ModelForm):
    class Meta:
        model= DeviceInterface
        fields= ['moduletype', 'firstportid', 'lastportid']
        labels = {
            "moduletype":"Module Type",
            "firstportid": "First Port ID",
            "lastportid": "Last Port ID"
        }
        widgets = {
            
            'firstportid':forms.TextInput(attrs={
                'placeholder': 'e.g. TenGigabitEthernet1/0/1',
                'class':'form-control',
                'onchange':'portidChange(this.value)'
                }),
            'lastportid':forms.TextInput(attrs={
                'placeholder': 'e.g. TenGigabitEthernet1/0/48',
                'class': 'form-control',
                'onchange':'portidChange(this.value)'
                })
        }

views.py:

def device_add(request):
    if request.method == "POST":
        device_frm = DeviceForm(request.POST) ##Part A1
        dd_form = DeviceDetailForm(request.POST)
        
        di_formset = modelformset_factory(DeviceInterface, fields=('moduletype', 'firstportid', 'lastportid'), extra=1,max_num=3)
        di_form=di_formset(request.POST)
        if device_frm.is_valid():
        # Create and save the device
        # new_device here is the newly created Device object
            new_device = device_frm.save()
            if dd_form.is_valid():
                # Create an unsaved instance of device detail
                deviceD = dd_form.save(commit=False)
                # Set the device we just created above as this device detail's device
                deviceD.DD2DKEY = new_device
                # If you did not render the hostname for the device detail, set it from the value of new device
                deviceD.hostname = new_device.hostname
                deviceD.save()
                if di_form.is_valid():
                    deviceI=di_form.save(commit=False) 
                    for instances in deviceI:          
                        instances.I2DKEY=new_device
                        instances.save()
                return render(request, 'interface/device_added.html',{'devices':Device.objects.all()})
            return render(request,'interface/device_add.html',{'form':device_frm, 'dd_form': dd_form, 'di_form':di_form})
        return render(request,'interface/device_add.html',{'form':device_frm, 'dd_form': dd_form, 'di_form':di_form})
                
        
    else:
        device_frm = DeviceForm()
        dd_form = DeviceDetailForm()
        di_formset = modelformset_factory(DeviceInterface, fields=('moduletype', 'firstportid', 'lastportid'),extra=1, max_num=3)
        di_form=di_formset(queryset = DeviceInterface.objects.none())
        return render(request,'interface/device_add.html',{'form':device_frm, 'dd_form': dd_form, 'di_form':di_form})

As u can see, in my models.py under class device(hostname- unique=true) and class devicedetail(mgt_ip_addr - unique = true). So whenever user type the same existing hostname or mgt_ip_addr on the page, it should be rejected and not saved in database.

Here is the scenario:

  1. User A keys in Testing for hostname and 1.1.1.1 for mgt_ip_addr.
  2. User B keys in Testing for hostname and 1.1.1.1 for mgt_ip_addr.
  3. User C: keys in Testing1 for hostname and 1.1.1.1 for mgt_ip_addr
  4. User D: keys in Testing1 for hostname and 1.1.1.2 for mgt_ip_addr

Results:

  1. Form get saved as User A is the first user to key in the following hostname and mgt_ip_addr (Worked as intended)

  2. Form is rejected for User B as it has identical hostname and identical mgt_ip_addr as User A and does not get saved. (Worked as intended)

  3. Warning is display saying mgt_ip_addr exist already as User A has this mgt_ip_addr. But the form still save the part for 1st table (Device) before rejecting the other fields (Not working as intended)

  4. Warning displayed for hostname existing as User C has this hostname. (Not intended to save User C due to scenario 3). So the forms get rejected (Working as intended)

Desired outcome for scenario 3 :
Warning to display that mgt_ip_addr already exist so the form get rejected even though the hostname is different from any existing one

The codes seems to work as intended but at the very backend it got haywire which i dont know why. Can someone suggest what i can do to fix this issue?

Probably the easiest way to address this is to check all your forms for being valid before doing work on any of them.
e.g:
if device_frm.is_valid() and dd_form.is_valid() and di_form.is_valid():

Can do 3 at once? I thought max was 2. But its okay. I fixed it by moving all the unique fields under 1 model and it work

I’m curious, what have you seen or read that would have given you that impression?

Im recalling what i previously do in C which i only did a max of 2. I know its not a good reference but thats what is helping me to understand so far

Ok, but that’s not a requirement in C, either. A statement such as:
if (function_a() && function_b() && function_c()) { ...
is also perfectly valid.

(Not only that, but like Python, C also specifies that logical operators will be performed in the proper sequence and are “short-circuited” as well - if function_a() is false, then function_b and function_c are not evaluated.)

Thank you for all your explanation! I appreciate it alot !