简体   繁体   中英

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.

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.

I just realized that the answer I accepted was not exactly correct. For some reason (django magic), save is called more than once, one for getting data, and once to save, so it was making two event instances every time a job was created. Another problem was that the event must also be saved prior to saving the job, or trying to run job.save() throws an error. I solved that by using the following code.

def save(self, commit=True):
    job = super(JobCreateForm, self).save(commit=False)
    if commit:
        event = Event(duration=self.cleaned_data.get('duration'), title=self.cleaned_data.get('customer'))
        event.save()
        job.event = event
        job.save()
    return job

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM