Search code examples
pythondjangoforeign-keysdjango-generic-views

With Django, how can I add my model's ForeignKey to CreateView?


I'm making an app that allows people to set up instructional events that may span multiple days. Here's some of my code:

Models:

class Event(models.Model):
    event_name = models.CharField('Event Name', max_length=200)
    short_description = models.TextField('Short Description', max_length=140)

class EventDay(models.Model):
    event = models.ForeignKey(Event)
    day_name = models.CharField(max_length=30)
    start_time = models.DateTimeField('Starting Date and Time')
    end_time = models.DateTimeField('Estimate Ending Time')

Views

from .models import Event, EventDay

class EventCreate(LoginRequiredMixin, CreateView):
    model = Event
    fields = ['event_name', 'short_description']

In the admin when I add an event it works perfectly, allowing me to add as many days to the event as I want. But the event add page outside of the admin only displays the fields from Event, not EventDay.

I know my Views code looks a little empty as far as anything relating to the EventDay goes. But, before posting here I tried as many different things as I could think of to try and get fields that work like the admin page. I just left out the mess I tried here. I also couldn't find anything in the docs about where I'm going wrong.

I didn't include my template/url code since I don't think that's the problem. Again I can add events, just not the EventDay part of it. But I'm new to this all so if you need me to post more code I will.


Solution

  • You're looking for inline formset.

    CreateView can be used with this but I think you should use a TemplateView (a base class for CreateView) instead, which is simpler to extend/modify.

    # forms.py
    from django import forms
    from .models import Event
    
    class EventForm(forms.ModelForm):
        class Meta:
            model = Event
    
    
    # views.py
    from django.http import HttpResponseRedirect
    from django.views.generic import TemplateView
    from django.forms.models import inlineformset_factory
    from .models import EventDay, Event
    from .forms import EventForm
    
    class EventCreate(TemplateView):
        template_name = 'event_create.html'
    
        def get(self, request, *args, **kwargs):
            "GET forms ready!"
            # get form for Event
            event_form = EventForm()
            # here's the 'magic' inlineformset, better read the
            # django documentation about this
            EventDayFormSet = inlineformset_factory(Event, EventDay)
            formset = EventDayFormSet()
            # add to context and return response
            context = {'form': event_form, 'formset': formset}
            return self.render_to_response(context)
    
        def post(self, request, *args, **kwargs):
            "Handle form submission on POST request"
            # get form for Event with POST data
            event_form = EventForm(data=request.POST)
            # get formset for EventDay with POST data
            EventDayFormSet = inlineformset_factory(Event, EventDay)
            formset = EventDayFormSet(data=request.POST)
            if event_form.is_valid() and formset.is_valid():
                # valid forms, OK to save
                event = event_form.save()
                # EventDay needs a ForeignKey for Event since the field is
                # not nullable. Save the forms without committing to database...
                eventdays = formset.save(commit=False)
                for eventday in eventdays:
                    # ... and add the ForeignKey field
                    eventday.event = event
                    eventday.save()
                # TODO use reverse('name_of_the_view_to_redirect_to') instead of '/'
                return HttpResponseRedirect('/')
            # Some error occurred with the forms, display errors and forms
            # so the user can fix it
            context = {'form': event_form, 'formset': formset}
            return self.render_to_response(context)
    

    To know a bit more about class base views structure and which method to override and the like, checkout Classy Class Based Views.