Timm Simpkins Timm Simpkins - 1 year ago 92
Python Question

django - Using one form to create two model instances

This seems like something that should be rather simple, and something that should be a common thing to need, but the more I try and find an answer, the more confused I get.

I have an event model that is linked to multiple other models that need events, so I created a separate event model for it. Now, I want to create an event when I create a job model, but I am having trouble figuring out how to do it.

In my job field, I have a OneToOneField relationship to an event, and the only thing I need to decide at the time I make the event is the duration.

A simplified Job model might look like this:

class Job(models.Model):
customer = models.ForeignKey(Customer)
address = models.CharField(max_length=100, verbose_name="Job Address", null=False, blank=False)
city = models.CharField(max_length=100, verbose_name="City", null=True, blank=True)
state = models.CharField(max_length=2, verbose_name="State", null=True, blank=True)
zip = models.CharField(max_length=5, verbose_name="Zip Code", null=True, blank=True)
# Simply need to set duration
event = models.OneToOneField(Event, on_delete=models.CASCADE, null=False, blank=False)


And a simplified Event model might look like this:

class Event(models.Model):

TIME_LIST = (
(1, "0:30"),
(2, "1:00"),
(3, "1:30"),
(4, "2:00"),
(5, "2:30"),
(6, "3:00"),
(7, "3:30"),
(8, "4:00"),
(9, "4:30"),
(10, "5:00"),
(11, "5:30"),
(12, "6:00"),
(13, "6:30"),
(14, "7:00"),
(15, "7:30"),
(16, "8:00"),
)

title = models.CharField(max_length=255)
employee = models.ForeignKey(Employee, null=True, blank=True)
start_time = models.DateTimeField(null=True, blank=True)
end_time = models.DateTimeField(null=True, blank=True)
range = RecurrenceField(null=True, blank=True)
duration = models.IntegerField(choices=TIME_LIST, null=True, blank=True)
is_all_day = models.BooleanField(default=False, null=False, blank=False)


I'm using generic CBVs, so simply:

class JobCreate(CreateView):
template_name = 'jobs/create.html'
success_url = '/schedule/add/'
form_class = JobCreateForm

def get_success_url(self):
return self.success_url + str(self.object.id)


And my form can simply be:

class JobCreateForm(forms.ModelForm):
class Meta:
model = Job
fields = [
'customer',
'address',
'city',
'state',
'zip',
]


What I need is a way where I can make a select box for the duration within my job form, then create a new instance of Event, plug the event_id into the event field of my Job model, and submit it all in one go.

I looked at
inlineformset_factory
, which seems like it should be what I need, and I keep getting errors with it, so it is either the wrong idea, or I am using it wrong. I tried a couple mixins that others have suggested, and they seem like overkill, and didn't work for me anyway.

Answer Source

You could create a second form for Event and rendering it in your template as well. Then in your view you can save it first, creating an event, then save your Job form and set it's related event to the one just created.

Alternatively (the way I'd probably do it), if you'd prefer a single form, you could add the duration field to your jobs form and override the form's save function. An overridden save could look something like this

def save(self, commit=True):
    e = Event.objects.create(duration=self.cleaned_data.get('duration'))

    job = super().save(commit=False)
    job.event = e
    job.save()
    return job

Formsets are really for making multiple forms, so in your case as you are using a OneToOneField (thereby only creating requiring a single form to create a single related object) it wouldn't make sense to use them.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download