Passing Parent PK to ModelForm in Class Based Create and Update View

23 views Asked by At

I'm updating function based views to class based views and having issues re-establishing the link between campaign and books. My Book Model has a foreign key link to Campaigns.

campaign = models.ForeignKey(Campaign, on_delete=models.DO_NOTHING)

I have a ModelForm where I set the campaign_id and would like to get this from the CreateView.

class BookForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        author = kwargs.pop('author', None)
        campaign_id = kwargs.pop('campaign_id', None)
        super(BookForm, self).__init__(*args, **kwargs)
        if campaign_id:
            self.fields['campaign_id'].initial = campaign_id

     campaign_id = forms.CharField(widget=forms.HiddenInput())

I followed this using dispatch and get_form_kwargs and my CreateView looks like

class BookCreateView(generic.CreateView):
    model = Book
    template_name = 'PromoManager/book_form.html'
    form_class = BookForm
    success_url = '/profile'
    campaign_id = None

    # Retrieves the campaign_id from url
    def dispatch(self, request, *args, **kwargs):
        self.campaign_id = kwargs.get("pk")
        return super().dispatch(request, *args, **kwargs)

    ## Sends building id to the form
    def get_form_kwargs(self, *args, **kwargs):
        kwargs = super().get_form_kwargs(*args, **kwargs)
        kwargs["campaign_id"] = self.campaign_id
        return kwargs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['campaign'] = Campaign.objects.get(pk=self.campaign_id)
        context['title_label'] = "Create"
        return context

    def form_valid(self, form):
        instance = form.save(commit=False)
        instance.author = self.request.user
        instance.campaign = Campaign.objects.get(id=form.cleaned_data['campaign_id'])
        instance.save()
        form.save_m2m()
        return super().form_valid(form)

But it breaks the UpdateView which relies on the same form as the PK passed on the update view URL is the book pk. My UpdateView looks like:

class BookUpdateView(generic.UpdateView):
    model = Book
    template_name = 'PromoManager/book_form.html'
    form_class = BookForm
    success_url = '/profile'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title_label'] = "Update"
        return context

    def form_valid(self, form):
        print(form)
        form.save(commit=True)
        return super().form_valid(form)

    def form_invalid(self, form):
        print(form.errors)

How can I pass a Campaign Id or instance to the form to populate it upon create and then maintain the value during any updates. This value should not be changeable. TY

1

There are 1 answers

0
Byte Insight On

So, I have a solution but would still be interested to hear whether its a good solution or problematic. I have used get_initial to pass values across to the ModelForm.

The init for BookForm(ModelForm) looks like:

def __init__(self, *args, **kwargs):
    super(BookForm, self).__init__(*args, **kwargs)

    self.fields['campaign_id'].initial = kwargs['initial']['campaign'].id
    self.fields['author_name'].initial = kwargs['initial']['user'].first_name + " " + kwargs['initial']['user'].last_name

and the CreateView looks like:

def get_initial(self):
    campaign = Campaign.objects.get(pk=self.kwargs.get('pk'))
    return {'campaign': campaign, 'user': self.request.user}

and finally UpdateView looks like:

def get_initial(self):
    book = Book.objects.get(pk=self.kwargs.get('pk'))
    return {'campaign': book.campaign, 'user': self.request.user }

My question would be if this is the most efficient way to get the data for update given it will already be in the form instance (or will it?)