inlineformset_factory with multiple ManyToManyField s

All models and views and forms are simplified for reading… I will add more code if I removed to mutch :slight_smile:

Models:

class PhoneNumber(Context):
    telephoneNumber  = models.CharField(_('telephonenumber'), max_length=22)
class Entity(models.Model):

    addresses = models.ManyToManyField(  to=Address
                                       , related_name="address_entity_set"
                                       , blank=True )

    websites = models.ManyToManyField(to=Website
                                      , related_name="website_entity_set"
                                      , blank=True)

    telephoneNumbers = models.ManyToManyField( to=PhoneNumber
                                    ,related_name="phonenumber_entity_set"
                                   )

    emails = models.ManyToManyField(to=Email
                                    , related_name="emails_entity_set"
                                    , blank=True)
class Person(Entity):
   #I will add code if needed

class Organisation(Entity):
   #I will add code if needed

Views:

class PersonCreateView(CreateView):
    model = Person
    template_name = "person_form_extended.html"
    form_class = PersonModelForm
    success_url = reverse_lazy('person-list')

Forms:

class PhoneNumberModelForm(ContextModelForm):
    prefix="phone"

    phone_validator = RegexValidator("^((\+)?[1-9]{1,2})?([-\s\.])?((\(\d{1,4}\))|\d{1,4})(([-\s\.])?[0-9]{1,12}){1,2}$", _('This phone number is invallid'))

    telephoneNumber = forms.CharField(
        validators = [phone_validator],
        widget = forms.TextInput(attrs=
            {
                "placeholder": "phonenumber",
                "type": "tel",
                "size": 40, # width in characters
                "maxLength": PhoneNumber._meta.get_field('telephoneNumber').max_length,
                
            }
        )) 

(Regex source)

class EntityModelForm(ModelForm):
    telephoneNumbers = forms.ModelMultipleChoiceField(
            queryset = PhoneNumber.objects.all(),
            widget = forms.CheckboxSelectMultiple(attrs=
                {
                }
            ),
        )

    #same for other fields
class PersonModelForm(EntityModelForm):
    pass

Problem
All separate parts function perfectly. But I want to be able to create a person with the telephone numbers, addresses, email addresses and websites in one form. But when I added:

PhoneNumberFormSet = inlineformset_factory(
    Entity,  PhoneNumber, 
    #fk_name="phonenumber_entity_set",
    #form=PhoneNumberModelForm,
    fields=['telephonenumber'],
    extra=1,
    can_delete=True
)

I get:

ValueError: ‘MyNetwork.PhoneNumber’ has more than one ForeignKey to ‘MyNetwork.Entity’. You must specify a ‘fk_name’ attribute

and using

fk_name="phonenumber_entity_set",

fails too with: ValueError: 'MyNetwork.PhoneNumber' has no field named 'phonenumber_entity_set'.

I have looked at

*Django inline formsets with Class-based views and crispy forms
*Multiple Models to one Form and all the links mentioned there
*Creating forms in Django with multiple Many-to-Many fields in Models
and many more but I seem to have a readers block and the more I read the less I seem to understand.

I hope someone can help me solve this.
Wouter

There is some stuff that has been trimmed that I think we need to see.

First, is Entity an Abstract model or are you using multi-table inheritance?

Also, you haven’t posted the PhoneNumber model.

I have added the phonenumber class.

no… I am not sure about the benefits and drawbacks of abstract models so I will look at that later.

If you mean my models inherit from other models? yes I do that 4 or 5 layers deep so I can program everything once. I started with a baseModel with a last_edited date etc..

If the multi means I inherit from two or more models… no

You should read the docs at Model Inheritance to understand the difference is between abstract base classes and multi-table inheritance. These are Django-specific terms with a very precise meaning and set of implications regarding your model design.

I was confused about the ‘multi’ part. I understand the concepts. I use multi inheritance and I see I need to use Abstract base classes for some.

I think I was sleeping when I wrote this… My goal is to add address, phonenumber and others to the person form. I omitted that :frowning: that is why I added the phonenumber inlineformset_factory. How can I continue? I presume I need to solve the described problem and hope someone can help.

When you say “the person form”, there’s a question as to whether you’re referring to the Django form or the HTML form - there’s not necessarily a 1-1 relationship between the two.

You can’t effectively add those fields to the Django form because of the many-to-many relationship between the two models. However, you can create a formset for those, and render them as part of the HTML form being generated for the page. This is something typically done in the view.

The HTML form. I want to add form fields for phonenumber and address etc to the form to add a person and set the ManyToManyField reference as showed in the Entity model between person (entity) and the other models. I also want to be able to add multiple phonenumbers etc at the same time.

So beside the following fields:

I assumed I needed to add the inline formset factory to handle the multiple instances of the phone form fields etc. So I have form.html templates for person_form.html and phonenumber_form.html etc. And I even have a way to add excisting phonenumbers to a person but not new phonenumbers.

So I have:

class PersonCreateView(CreateView):
    model = Person
    template_name = "MyNetwork/person_form_extended.html"
    form_class = PersonModelForm
    success_url = reverse_lazy('person-list')

I always assumed the view.py (CreateView) creates the html form using the forms.py to set some constraints and the _form.html template for the rendering?!

But I dont understand how to create:

and if it is otherwise related to the view.

Thanks for the responses. Sorry for the unclear question.

I did add

PhoneNumberFormSet = formset_factory(PhoneNumberModelForm)

and that did not result in errors like adding the inlineformset_factory

So, if I understand the situation: (and addressing only phone numbers with the understanding that doing this with addresses and other many-to-many fields is only a difference in quantity and not of kind)

  • You want a page with the person form.
  • On that same page you want to be able to select existing phone numbers to be assigned to that person.
  • On that same page you want to be able to add new phone numbers, and have them associated with that person.

If my understanding is correct, then the first thing I’m going to suggest is that the Django generic CBVs are not the best “fit” for this. You’re going to be overriding enough functionality such that I believe your results are going to be cleaner if you just create an FBV from the start. (Or, if you really want to stay with the CBV structure, use View as the base class.)

With that as a premise then, the “GET” handler for a “create view” would have a form for Person, including the many-to-many select widget for the existing phone numbers. You would then create a model formset for the PhoneNumber model. This formset would be empty to start, rendering 1 blank form.
The template needs to render both forms.
On that same page, then you are likely going to want to add some JavaScript, with some type of icon on that page, allowing you to add additional phone number forms to the formset.

Upon submission, the view needs to validate the Person form and the PhoneNumber formset. Once both are validated, you can save the new person, save the phone number(s), and add the associate between the two.

Note that since this is a “create” situation, you don’t have existing entries to match / query / assign. There’s no real value to using an inline formset here. I’d simplify this by treating them as two separate forms (well, a form and a formset) on the same page and handling the assignment in the view after the data has been saved.

In essence, it’s as simple as:
view:

my_form = ModelForm1()
my_formset = ModelFormset1()
context = {'form1': my_form, 'form2': my_formset}
render(request, 'my_template.html', context=context)

And in the template:

<form>
{{ form1 }}
{{ form2 }}
<button type='submit'>
</form>

Now, the JavaScript involved with this adds some complexity, but it’s just to be able to add multiple phone numbers. If you want to predefine a limit, say 3, then you could create the formset with 3 blank forms and forget about the ability to add more.

No apologies necessary here - this is intended to be a conversation where we end up understanding each other.

1 Like

I will react asap but cant atm. Thanks for the big response