Reputation: 1054
I am using commit=False to save my form because I want to include some non-form data (the form's author) in the table of data before saving it. When you set commit=False in Django there is a side effect mentioned in the docs:
Another side effect of using commit=False is seen when your model has a many-to-many relation with another model. If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save many-to-many data for an instance until the instance exists in the database.
So I try to use the save_m2m() method as described in the docs here but to no avail, I am getting the error below.
class MinutesCreate(LoginRequiredMixin, View):
@method_decorator(permission_required('pd.add_agenda'))
def dispatch(self, *args, **kwargs):
return super(MinutesCreate, self).dispatch(*args, **kwargs)
def get(self, request, **kwargs):
minutes_form = MinutesForm()
return render(request, "pd/minutes_form.html", {"form": minutes_form, "kwargs": kwargs})
def post(self, request, **kwargs):
minutes_form = MinutesForm(request.POST)
if minutes_form.is_valid():
minutes = minutes_form.save(commit=False)
minutes.author = request.user
minutes.save()
minutes_form.save_m2m()
return redirect('pd:agenda_list')
return render(request, 'pd/minutes_form.html', {'form': minutes_form})
class MinutesForm(BetterModelForm):
def __init__(self, *args, **kwargs):
super(MinutesForm, self).__init__(*args, **kwargs)
self.fields['participants'].required = False
participants = UserModelMultipleChoiceField(queryset=UserProfile.objects.all(), widget=forms.CheckboxSelectMultiple())
class Meta:
model = Minutes
fieldsets = (
("Minutes", {"fields": ["participants","minutes"]}),
)
class Minutes(models.Model):
agenda = models.OneToOneField(
Agenda,
on_delete=models.CASCADE,
primary_key=True,
)
published = models.DateTimeField(verbose_name='Minutes Published', auto_now_add=True)
edited = models.DateTimeField(verbose_name='Last Modified', auto_now=True)
author = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="minutes_author", db_index=True, blank=True)
participants = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="participants")
minutes = models.TextField(blank=True)
Environment:
Request Method: POST
Request URL: http://localhost:8000/committee/agenda/2016/6/22/minutes/add
Django Version: 1.8
Python Version: 2.7.11
Installed Applications:
('flat',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'pdpauth',
'pd',
'bootstrap3',
'recurrence',
'mail_templated',
'django_navtag',
'debug_toolbar')
Installed Middleware:
(u'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'rollbar.contrib.django.middleware.RollbarNotifierMiddleware')
Traceback:
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
132. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
22. return view_func(request, *args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/views/generic/base.py" in view
71. return self.dispatch(request, *args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/utils/decorators.py" in _wrapper
34. return bound_func(*args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
22. return view_func(request, *args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/utils/decorators.py" in bound_func
30. return func.__get__(self, type(self))(*args2, **kwargs2)
File "/Users/ryancastner/Code/pdpsite/pd/views.py" in dispatch
410. return super(MinutesCreate, self).dispatch(*args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/views/generic/base.py" in dispatch
89. return handler(request, *args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/pd/views.py" in post
422. minutes_form.save_m2m()
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/forms/models.py" in save_m2m
102. f.save_form_data(instance, cleaned_data[f.name])
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/db/models/fields/related.py" in save_form_data
2576. setattr(instance, self.attname, data)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/db/models/fields/related.py" in __set__
1259. manager = self.__get__(instance)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/db/models/fields/related.py" in __get__
1242. through=self.field.rel.through,
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/db/models/fields/related.py" in __init__
874. (instance, source_field_name))
Exception Type: ValueError at /committee/agenda/2016/6/22/minutes/add
Exception Value: "<Minutes: Minutes object>" needs to have a value for field "minutes" before this many-to-many relationship can be used.
Request information
GET
No GET data
POST
Variable Value
csrfmiddlewaretoken u'W7tWohIGT1YsMR45pbGhuK5I6VbdIv9m'
minutes u'<p>testing this out</p>'
participants u'17'
[23/Jun/2016 13:08:04]"GET /committee/agenda/2016/6/22/minutes/add HTTP/1.1" 200 25470
minutes object's minutes:
<p>testinga askdlj;slkdje</p>
[23/Jun/2016 13:08:14]"POST /committee/agenda/2016/6/22/minutes/add HTTP/1.1" 500 148995
class MinutesCreate(LoginRequiredMixin, View):
@method_decorator(permission_required('pd.add_agenda'))
def dispatch(self, *args, **kwargs):
return super(MinutesCreate, self).dispatch(*args, **kwargs)
def get(self, request, **kwargs):
minutes_form = MinutesForm()
return render(request, "pd/minutes_form.html", {"form": minutes_form, "kwargs": kwargs})
def post(self, request, **kwargs):
minutes_form = MinutesForm(request.POST)
if minutes_form.is_valid():
minutes = minutes_form.save(commit=False)
minutes.author = request.user
print "minutes object's minutes:\n", minutes.minutes # new print that is shown above
minutes.save()
minutes_form.save_m2m()
return redirect('pd:agenda_list')
return render(request, 'pd/minutes_form.html', {'form': minutes_form})
Upvotes: 0
Views: 2619
Reputation: 1054
The issue was due to me using BetterModelForm
Django package. BetterModelForm
divides forms into fieldsets
to allow for repeatable or multiple forms on the same page. As a result the method of save_m2m()
was not propagating through on the form
but on a formset
which required a different way to call save_m2m()
.
I fixed this by converting my BetterModelForm
classes to standard forms.ModelForm
and when the fieldsets
were removed everything worked as expected.
NOTE** I also was providing a UserProfile
queryset to my form and my model accepted a User
object. I updated the code to provide the correct queryset to my form as well.
Updated code is as follows:
class AgendaForm(forms.ModelForm):
time_fmt = ["%I:%M %p"]
date_fmt = ["%Y/%m/%d"]
start = forms.SplitDateTimeField(label="Meeting Start", input_time_formats=time_fmt,
input_date_formats=date_fmt,
widget=forms.widgets.SplitDateTimeWidget(date_format=date_fmt[0], time_format=time_fmt[0]))
end = forms.SplitDateTimeField(label="Meeting End", input_time_formats=time_fmt,
input_date_formats=date_fmt,
widget=forms.widgets.SplitDateTimeWidget(date_format=date_fmt[0], time_format=time_fmt[0]))
location = forms.CharField(initial='TBD')
class Meta:
model = Agenda
fields = ["start", "end", "location", "announcements", "agenda"]
class MinutesForm(forms.ModelForm):
participants = UserModelMultipleChoiceField(queryset=User.objects.all(), widget=forms.CheckboxSelectMultiple())
class Meta:
model = Minutes
fields = ["participants","minutes"]
class MinutesCreate(LoginRequiredMixin, View):
@method_decorator(permission_required('pd.add_agenda'))
def dispatch(self, *args, **kwargs):
return super(MinutesCreate, self).dispatch(*args, **kwargs)
def get(self, request, **kwargs):
minutes_form = MinutesForm()
return render(request, "pd/minutes_form.html", {"form": minutes_form, "kwargs": kwargs})
def post(self, request, **kwargs):
minutes_form = MinutesForm(request.POST)
if minutes_form.is_valid():
minutes = minutes_form.save(commit=False)
minutes.author = request.user
ag = get_object_or_404(Agenda, start__year=kwargs.get('year'),
start__month=kwargs.get('month'),
start__day=kwargs.get('day'))
minutes.agenda = ag
minutes.save()
minutes_form.save_m2m()
return redirect('pd:agenda_detail', **kwargs)
return render(request, 'pd/minutes_form.html', {'form': minutes_form})
Upvotes: 1