How to create an extra instance of another model for a one to one relationship in the same form

Hi!
I’m currently building a server monitoring application with django and I’m a bit stuck on how to structure my form. The form creates an instance of a model with a one to one field and I’d like to automatically create and assign an instance from another model to this one. I’m currently getting an error due to missing this behaviour IntegrityError at / NOT NULL constraint failed: checker_icmpmonitor.monitor_id_id

Here are my current models:

class MonitorChoices(models.IntegerChoices):
    __empty__ = "Please select a monitor option"
    ICMP = 0, "ICMP monitor"
    OPEN_PORT = 1, "Open port monitor"

class MonitorList(models.Model):
    """
    List with all the monitors
    """

    user = models.ForeignKey(User, on_delete=models.CASCADE)
    name = models.CharField(max_length=200)
    choice = models.IntegerField(choices=MonitorChoices.choices)


class BaseMonitor(models.Model):
    """
    Base class for a monitor
    """

    monitor_id = models.OneToOneField(MonitorList, on_delete=models.CASCADE)
    description = models.TextField(blank=True)
    created = models.DateField(auto_now=True)
    error_email = models.EmailField()
    endpoint = models.CharField(max_length=100)
    sleep_time = models.PositiveSmallIntegerField(default=60)
    message_sleep_time = models.PositiveSmallIntegerField(default=300)

    def __str__(self):
        return self.monitor_id.name

    class Meta:
        abstract = True


class IcmpMonitor(BaseMonitor):
    ping_timeout = models.PositiveSmallIntegerField()


class OpenPortMonitor(BaseMonitor):
    port_to_check = models.PositiveSmallIntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(65535)]
    )

forms:

class AddIcmpMonitorForm(forms.ModelForm):
    name = forms.CharField(max_length=200)

    class Meta:
        model = IcmpMonitor
        fields = [
            "description",
            "error_email",
            "endpoint",
            "sleep_time",
            "message_sleep_time",
            "ping_timeout",
        ]

views:

class MonitorListListView(ListView):
    model = MonitorList
    template_name = "checker/index.html"


class IcmpMonitorCreateView(CreateView):
    model = IcmpMonitor
    form_class = AddIcmpMonitorForm
    template_name = "checker/add_form.html"

    def get_success_url(self) -> str:
        return reverse_lazy("index-view")

index.html template:

<h1>Monitor List</h1>
<table>
  <tr>
    <th scope="col">Name</th>
    <th scope="col">Type</th>
  </tr>
  <tr>
    {% for monitor in object_list %}
      <th scope="row">{{ monitor.name }}</th>
      <td>{{ monitor.choice }}</td>
    {% empty %}
      <li>No Monitors yet.</li>
    {% endfor %}
  </tr>
</table>

add_form.html template:

<form action={% url "index-view" %} method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <input type="submit" value="Submit">
</form>

Thanks in advance :slight_smile:

Your AddIcmpMonitorForm doesn’t have monitor as one of the fields. How are you specifying which Monitor it should be related to?

Side notes: I would strongly encourage you to adopt the standard database naming conventions for models and fields.

  • Models should be singular entities, not plural or collective. (It should be Monitor, not MonitorList) Note: This would make it match your monitor model names.
  • Your foreign keys (along with one-to-one relationships) should be named for the model. It should be monitor, not monitor_id, since an orm reference to monitor is a reference to the model and not the id field. If you need to specifically access the id, Django makes it available by the name monitor_id. (As you can see from your error message, Django is referencing the field named monitor_id_id.)

Thanks for your quick reply!

I’ve changed the names of in accordance to your suggestions.

The AddIcmpMonitorForm doesn’t have a monitor field because I want to create these dynamically. Whenever a new “specific” monitor is created (the ones that inherit from the base one) I want to also create a new entry in MonitorList (now Monitor) and have the newly created IcmpMonitor be related to this entry.

Oh, ok.

Then what you would want to do is override the form_valid method of your create view to create the new Monitor instance, save it, and then assign that instance to the monitor field in your IcmpMonitor instance before saving it.

1 Like