How to save M2M Field in a formset when commit=False - django

deals_formset_factory = modelformset_factory(Deal, form=DealCForm, extra=1)
attached_deals_formset = deals_formset_factory(request.POST, prefix='deals')
Since some fields of my Deal model are not shown in the form and hence can't be set by the user (but the M2M field is shown and can be set by the user), I can't just do a
for fm in attached_deals_formset:
if fm.has_changed():
fm.save()
since it would break.
So theoretically the idea in such situations is to do
deal = fm.save(commit=False)
...
deal.save()
but this doesn't save my M2M field inside deal. The Through table remains untouched. What is the best approach to solve this?
class Deal(models.Model):
deal_id = UUIDField()
....
sales_item = models.ManyToManyField(SalesItem)

I found the solution, there is no need to override the save method.
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.
To work around this problem, every time you save a form using
commit=False, Django adds a save_m2m() method to your ModelForm
subclass. After you've manually saved the instance produced by the
form, you can invoke save_m2m() to save the many-to-many form data
Source
After deal.save() simply:
fm.save_m2m()

Related

Django Iterating over Many to Many Objects

My main problem is that my code never goes into the for loop though within the debugger I can see that hardware exists. The for loop gets just skipped and I can´t figure out why this is the case.
Models:
class Hardware(models.Model):
name = models.CharField(max_length=30)
description = models.TextField()
class Bundle(models.Model):
name = models.CharField(max_length=30)
description = models.TextField()
devices = models.ManyToManyField(Hardware)
class BundleForm(ModelForm):
class Meta:
model = Bundle
fields = ('name', 'description', 'devices')
labels = {
'name': _('Bundlename'),
'description': _('Beschreibung des Bundle'),
'devices': _('Hardware im Bundle')
}
Views:
elif request.method == 'POST' and 'newbundle' in request.POST:
form = BundleForm(request.POST)
if form.is_valid():
bundle = form.save(commit=False)
bundle.save()
for hardware in bundle.devices.all():
print(hardware)
messages.success(request, 'Erfolg! Die Daten wurden erfolgreich gespeichert.')
return redirect('/knowledgeeditor/bundle/', {'messages': messages})
Your question is not about iterating many-to-many fields, but saving them.
The modelforms documentation has this to say about using commit=False with a many-to-many field:
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.
To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you’ve manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data.
As noted there, you could use save_m2m() to save the field, but instead of doing that you should ask yourself why you are using commit=False here at all. There is no reason to do so, so you should omit that parameter and the subsequent separate save, and just let the form save itself in one go.

Set value for Model field that is not part of ModelForm?

I have a ModelForm which has a subset of fields of the model it is for. When I'm processing that form, checking is_valid etc, I want to check the value of a given field that may have changed, so I check if form.has_changed() and then I check if myfield in form.changed_data. If that is true, I want to set the value for a field on my model, but that field is NOT on this modelform. How might I go about doing that?
I know I can access myform.instance directly, but since I'm already calling myform.save() later on, can I set the value on the form itself somehow, even if that field is not part of the modelform?
Use commit=False in ModelForm.save, like this:
instance = form.save(commit=False)
if requires_update:
instance.field_to_change = value_to_set
instance.save()

How to Stop Django ModelForm From Creating Choices for a Foreign Key

I have a Django model with a Foreign key to a table that contains about 50,000 records. I am using the Django forms.ModelForm to create a form. The problem is I only need a small subset of the records from the table the Foreign key points to.
I am able to create that subset of choices in the init method. How can I prevent ModelForm from creating that initial set of choices?
I tried using the widgets parameter in the Meta method. But Django debug toolbar indicates the database is still being hit.
Thanks
The autogenerated ModelChoiceField will have its queryset initialized to the default. The widget is not where you are supposed to customize the queryset property.
Define the ModelChoiceField manually, initialize its queryset to be empty. Remember to name the ModelChoiceField the same as the one that would have been automatically generated, and remember to mention that field in the fields tuple. Now you can set the queryset from the constructor and avoid the database being hit twice.
If you are lucky (and you probably are, please test though), the queryset has not been evaluated during construction, and in that case, defining the ModelChoiceField manually is not required.
class YourModelForm(ModelForm):
your_fk_field_name = forms.ModelChoiceField(queryset=YourModel.objects.none())
class Meta:
model = YourModel
fields = ('your_fk_field_name', .......)
def __init__(self, *args, **kwargs):
super(YourModelForm, self).__init__(*args, **kwargs)
self.fields['your_fk_field_name'].queryset = ....

Django: Creating bound form from model instance

I am trying to write my first unit test in Django. It's for a Staff registration form.
The Staff model for the form has a OneToOne relation with a UserProfile (AUTH_PROFILE_MODULE).
The UserProfile has a OneToOne relation with django.contrib.auth.models.User.
I am using https://github.com/dnerdy/factory_boy to create a test model instance for the staff model. The idea is to use a StaffFactory so I can easily create test model instances. To create a bound form I need to pass it a data dict. I thought it would be convenient to just use django.forms.models.model_to_dict to convert my model instance into a data dict when testing the form.
Now, my problem is: model_to_dict does not traverse the foreign keys of my Staff model (Staff->UserProfile->User). This means the form stays invalid since required fields like the User's email are still missing inside the form data.
Currently my StaffRegistrationFormTest looks like:
class StaffRegistrationFormTest(unittest.TestCase):
def test_success(self):
staff1 = StaffFactory()
form = StaffRegistrationForm(model_to_dict(staff1))
# print jsonpickle.encode(model_to_dict(staff1))
self.assertTrue(form.is_valid(), form.errors)
Is there a way to pass in a dict, where the foreign keys are serialized by re-using a model instance?
So it seems as if one way of solving this is by creating additional dictionaries for the OneToOne fields of the Staff model and merging them.
This makes the test pass:
data = dict(model_to_dict(staff1).items() +
model_to_dict(staff1.profile).items() +
model_to_dict(staff1.profile.user).items())
form = StaffRegistrationForm(data=data)
self.assertTrue(form.is_valid(), form.errors)
I am not sure if this is the way to go in terms of best practice. Feel free to comment if this it completely against the grain.

Django Forms save_m2m

Hi I have a model which has 2 many to many fields in it. one is a standard m2m field which does not use any through tables whereas the other is a bit more complecated and has a through table. I am using the Django forms.modelform to display and save the forms. The code I have to save the form is
if form.is_valid():
f = form.save(commit=False)
f.modified_by = request.user
f.save()
form.save_m2m()
When i try to save the form I get the following error:
Cannot set values on a ManyToManyField which specifies an intermediary model.
I know this is happening when I do the form.save_m2m() because of the through table. What I'd liek to do is tell Django to ignore the m2m field with the through table but still save the m2m field without the through table. I can then go on to manually save the data for the through table field.
Thanks
If you have a model with multiple fields one is done with a through table and the other one is a regular many-to-many relation without a through table. You can still use save_m2m() to save the regular one. Simply add the through fields to your exclude list on your form.
Add inside your form class:
class Meta:
model = YourModel
exclude = ('m2mthroughfield',)
you can't save the m2m "without the through table"
the data you want to save is stored in the through table (and only in the through table)