Been trying to make this work all day. I have a main model which is entry. An entry can include several shifts. This is part of an app to store working hours. Here's the view for creating an entry:
class EntryCreateView(FormView):
template_name = 'entry/create.html'
form_class = AddWorkDay
success_url = reverse_lazy('time_manager:index')
def get(self, request, ordinal=None, *args, **kwargs):
""" Initiates with a blank form or will populate the day field with the day represented by the passed
ordinal. """
if ordinal:
day = datetime.datetime.fromordinal(int(ordinal))
form = AddWorkDay(initial={'day': day})
else:
form = AddWorkDay()
formset = ShiftFormSet()
return render(request, self.template_name, {'form': form, 'formset': formset})
def post(self, request, ordinal=None, *args, **kwargs):
form = AddWorkDay(data=request.POST)
formset = ShiftFormSet(data=request.POST)
errors = []
shifts = []
if form.is_valid() and formset.is_valid():
# Build entry.
entry = form.save(commit=False)
entry.owner = request.user
errors.extend(entry.validate(request.user))
# Build shift.
for form in formset:
shift = form.save(commit=False)
shift.entry = entry
shifts.append(shift)
errors.extend(shift.validate(request.user))
if len(errors) == 0:
entry.save()
for shift in shifts:
shift.save()
return HttpResponseRedirect(reverse('time_manager:index'))
return render(request, self.template_name, {'form': form, 'formset': formset, 'errors': errors, 'shifts': shifts, 'entry': entry})
When I try to enter an entry with a shift and press save, it terminates saying: "IntegrityError at /time_manager/entry/create/ NOT NULL constraint failed: shift_shift.entry_id". I tried to figure out what was wrong with the shifts, so I commented the block out where the shift is saved (from "if len(errors)" to "return HttpResponseRedirect.") so that it would return to the view with the form. I then put {{ shifts }} into my template, to see what is in there. When I do that, it terminates with: "NoReverseMatch at /time_manager/entry/create/ Reverse for 'edit' with arguments '()' and keyword arguments '{'pk': None}' not found. 1 pattern(s) tried: ['time_manager/entry/shift/edit/(?P(\d+))/$']" as if I were trying to use the {% url %} tag referencing a view that doesn't exist. So I'm guessing that something goes wrong when I try to save the forms of the formset. However, I read the django documentation again and again and this is the way to store the forms of a formset, is it not?
Ok, with a fresh head I looked at my code, and then it occurred to me. I wanted to make sure that the entry and each shift are built correctly first, and then only save them if they don't violate any rules. So as you can see above: I'm saving both with commit=False. That means however that entry has not been assigned a primary key yet. The primary key is what the ForeignKeyField on my shift model needs. That's why Django failed saving it.
I changed the order of the method somewhat. This is the working code:
def post(self, request, ordinal=None, *args, **kwargs):
form = AddWorkDay(data=request.POST)
formset = ShiftFormSet(data=request.POST)
errors = []
shifts = []
if form.is_valid() and formset.is_valid():
# Build entry.
entry = form.save(commit=False)
entry.owner = request.user
errors.extend(entry.validate(request.user))
# Build shift.
for form in formset:
shift = form.save(commit=False)
shifts.append(shift)
errors.extend(shift.validate(request.user))
# If there are no errors, save the entry ans it's shifts.
if len(errors) == 0:
entry.save()
for shift in shifts:
shift.entry = entry
shift.save()
return HttpResponseRedirect(reverse('time_manager:index'))
return render(request, self.template_name, {'form': form, 'formset': formset, 'errors': errors, 'shifts': shifts, 'entry': entry})
Notice how entry is saved for the second time (without commit=False) and then assigned to shift.