idris
idris

Reputation: 187

Django UpdateView with related model

I have the following situation and I don't know how to do an update:

#models.py
class Task(models.Model):
    creation_date = models.DateField(
        default=None,
    )
    name = models.CharField(
        max_length=255,
    )
    description = models.TextField(
        max_length=500,
        blank=True,
        null=True, 
    )

class TaskDetails(models.Model):
    PEND = 1
    COMP = 2
    TASK_STATUS = (
        (PEND, 'pending'),
        (COMP, 'completed'),
    )
    task = models.OneToOneField(
        Task,
        primary_key=True, 
        on_delete=models.CASCADE
    )
    solution = models.CharField(
        max_length=255,
    )
    due_date = models.DateField(
        null=True,
        default=None,
        blank=True,
    )    
    status = models.PositiveSmallIntegerField(
        default=1,
        choices=TASK_STATUS,
    )

And now my view

#views.py
class TaskUpdate(UpdateView):
    model = Task
    second_model = TaskDetails
    form_class = TaskForm
    second_form_class = TaskDetailsForm
    pk_url_kwarg = 'task_id'

    def get_context_data(self, **kwargs):
        context = super(TaskUpdate, self).get_context_data(**kwargs)
        if self.request.method == 'POST':
            details_form = self.second_form_class(self.request.POST, prefix='details')
        else:
            details_object = self.second_model.objects.get(pk=self.kwargs.get(self.pk_url_kwarg))
            details_form = self.second_form_class(instance=details_object, prefix='details')

        context['details_form'] = details_form
        return context

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        form = self.form_class(request.POST)
        details_form = self.second_form_class(request.POST, prefix='details')
        if form.is_valid() and details_form.is_valid():
            return self.form_valid(form, details_form)
        else:
            return self.form_invalid(form, details_form)

    def form_valid(self, form, details_form):
        form.instance.creation_date = datetime.now().date()
        self.object = form.save()
        details_form.instance.task = self.object
        details_form.save()
        return HttpResponseRedirect(self.success_url)

    def form_invalid(self, form, details_form):
        return self.render_to_response(self.get_context_data(form=form, details_form=details_form))

I also have the ModelForms: TaskForm and TaskDetailsForm. Not relevant here. The 2 forms are displayed and submitted at once. But instead of updating the existing record in Task and TaskDetails tables, it creates a new one in both tables

I think my problem is in form_valid. What should I put there? Thanks a lot

Upvotes: 0

Views: 2539

Answers (2)

Mihai Zamfir
Mihai Zamfir

Reputation: 2166

I think this should work. You don't need the form_valid and form_invalid methods in this case,

def post(self, request, *args, **kwargs):
    response = super(TaskUpdate, self).post(request, *args, **kwargs)
    details_form = self.second_form_class(self.request.POST, prefix='details')
    if details_form.is_valid():
        task = self.get_object()
        self.second_model.objects.filter(task=task)
                                 .update(**details_form.cleaned_data)
        return response
    return render(request, self.template_name, {
        'form': self.get_form(self.get_form_class()),
        'details_form': details_form,
    })

PS: put related_name=task_details for the OneToOneField and auto_now_add=True for the creation date of your task

Nevertheless, why don't you include the Task Details into task and stop using this OneToOneKey?

Upvotes: 0

crunching
crunching

Reputation: 43

In the post method forms are created without instance. You have to pass you instances there.

def post(self, request, *args, **kwargs):
    # get current task
    obj = self.get_object()
    #initiate the task form with this object as instance
    form = self.form_class(request.POST, instance=obj)

    #get realted details object or None. 
    #I can't check if this string works, but it should.
    details_obj = getattr(object, 'taskdetails', None)

    #initiate the details_form with this details_object as instance
    details_form = self.second_form_class(request.POST, prefix='details',
                                                        instance=details_obj)

    if form.is_valid() and details_form.is_valid():
        return self.form_valid(form, details_form)
    else:
        return self.form_invalid(form, details_form)

def form_valid(self, form, details_form):

    #save object
    obj = form.save(commit=False)
    obj.creation_date = datetime.now().date()
    obj.save()

    #save details_object
    details_obj = details_form.save(commit=False)
    details_obj.task = obj
    details_obj.save()

    return HttpResponseRedirect(self.success_url)

Upvotes: 3

Related Questions