views:

127

answers:

4

I have a situation where I need to check if a form has m2m relation before saving it in views.py as I am using the same views.py for different models.

Example:

#models.py
class BaseClass(models.Model):
   # Some generic stuff.

class SomeClass(BaseClass):
   # This class doesnt have any many2many relations

class SomeOtherClass(BaseClass):
   # This class has many2many relations

#views.py
def do_some_stuff(request):
   # Instantiate a form
   # Save it in a normal way
   form.save()
   # Now, in here while saving I need to check if the form has any 
   # m2m relations so I can use the save_m2m() function after form.save()

I just need an extra check in there to be on the safer side. Is there any way around this? Thanks in advance

A: 
if hasattr(form, 'save_m2m'):
    form.save_m2m()

You should bear in mind that save_m2m is only necessary (and only exists) when you call form.save with commit=False argument. If you save it with commit=True which is the default, there's no need in save_m2m.

Antony Hatchkins
Thanks for the answer but I think you didnt really get what I want to say here.I want to know if theres any way to know if a form has many2many relations in it and not save_m2m() function.I want to use this function only if my form contains any m2m relations.Cheers
bigmac
The function `save_m2m` is designed in such a way that it will do nothing wrong even in case there're no ManyToMany fields in the model. See my second answer: http://stackoverflow.com/questions/2100297/django-check-on-type-of-relation-a-form-has/2103895#2103895.
Antony Hatchkins
A: 

I just figured out the probable solution for this.

You could use something like this:

if len(form_instance._meta.many_to_many) > 0:
   for i in form_instance._meta.many_to_many:
      if type(i) = ManyToManyField:
         form_instance.save_m2m()
         break
bigmac
`save_m2m` automatically saves _all_ `ManyToMany` fields. There's no need in re-run it for each field. Also this very check is done automatically inside save_m2m function so there's no need in doing it in user code. See my second answer: http://stackoverflow.com/questions/2100297/django-check-on-type-of-relation-a-form-has/2103895#2103895
Antony Hatchkins
@Antony: I thought too that the method `save_m2m` for every field but he notice that he `break` s out of the loop if he encounters such a field
Felix Kling
@Felix: oh, yes, you're right. But this way the loop makes even less sense because all many_to_many fields are `ManyToManyField`'s.
Antony Hatchkins
A: 

Antony is right and it is even easier, I quote from the documentation:

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.

According to that, if you don't save a form with commit=False the method save_m2m() does not even exist, no matter if many-to-many relationships exist.

True to the Python motto "easier to ask forgiveness than permission" you can also do:

try:
    form.save_m2m()
except:
    pass

if you really want to.

Felix Kling
It turned out to be a bit more complex. `save_m2m` in fact doesn't exist if save(commit=True) is called. But it does exist if the model without `ManyToMany` fields has been saved with `commit=False` argument.
Antony Hatchkins
So to distinguish between a model with ManyToMany fields and a model without them one has to check `instance._meta.many_to_many` attribute. But in `save_m2m` it is done internally, so there's no need in doing that manually.
Antony Hatchkins
@Antony: Isn't this what I said?
Felix Kling
What you said is absolute truth. It just doesn't answer the question completely. Namely your `try/except` statement (as well as my `hasattr` statement) doesn't test the presence of ManyToMany fields (what bigmac seems to be striving for for some mysterious reason). It only tests the value of `commit` argument of `form.save()` function.
Antony Hatchkins
Hi guys, Thanks for the answers and Anthony I think you are right. I had a look at the source for save_m2m and I figured out we dont really need the explicit checking as it does it in save_m2m anyways. Cheers Mac...
bigmac
A: 

In fact, save_m2m() is always injected into ModelForm instance after save(commit=False). Even if there're no ManyToMany fields in the model.

Here's the source of save_m2m function:

def save_m2m():
    opts = instance._meta
    cleaned_data = form.cleaned_data
    for f in opts.many_to_many:
        if fields and f.name not in fields:
            continue
        if f.name in cleaned_data:
            f.save_form_data(instance, cleaned_data[f.name])

If there are no ManyToMany fields (instance._meta.many_to_many=[]) nothing will be done.

So you can safely call save_m2m in all cases.

Antony Hatchkins
Not in all cases, only if `save(commit=False)` and we still don't know if this is what the OP is doing or not...
Felix Kling
Well, OP seems to be aware of had he `commit=False` or not when saving - in this case try/catching it is not necessary.
Antony Hatchkins