Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AttributeError when ParentalManyToManyField is specified with a through model #83

Open
m1kola opened this issue Aug 21, 2017 · 4 comments

Comments

@m1kola
Copy link

m1kola commented Aug 21, 2017

Django throws AttributeError when ParentalManyToManyField is specified with a through model.

Demo with Wagtail:

from django.db import models
from wagtail.wagtailcore.models import Page
from modelcluster.fields import ParentalManyToManyField


class FooModel(models.Model):
    name = models.CharField(max_length=256)

    def __str__(self):
        return self.name


class BarToFoo(models.Model):
    foo = models.ForeignKey('appname.FooModel', blank=True, null=True)
    page = models.ForeignKey('BarPage', related_name='+')


class BarPage(Page):
    asset_programs = ParentalManyToManyField(
        to='appname.FooModel', through='appname.BarToFoo'
    )

Traceback:

File "/Users/mikalai/.virtualenvs/venv_name/lib/python3.4/site-packages/django/core/handlers/exception.py" in inner
  42.             response = get_response(request)

File "/Users/mikalai/.virtualenvs/venv_name/lib/python3.4/site-packages/django/core/handlers/base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)

File "/Users/mikalai/.virtualenvs/venv_name/lib/python3.4/site-packages/django/core/handlers/base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/Users/mikalai/.virtualenvs/venv_name/lib/python3.4/site-packages/django/views/decorators/cache.py" in _cache_controlled
  43.             response = viewfunc(request, *args, **kw)

File "/Users/mikalai/.virtualenvs/venv_name/lib/python3.4/site-packages/wagtail/wagtailadmin/decorators.py" in decorated_view
  31.             return view_func(request, *args, **kwargs)

File "/Users/mikalai/.virtualenvs/venv_name/lib/python3.4/site-packages/wagtail/wagtailadmin/views/pages.py" in edit
  341.                 revision.publish()

File "/Users/mikalai/.virtualenvs/venv_name/lib/python3.4/site-packages/wagtail/wagtailcore/models.py" in publish
  1539.         page.save()

File "/Users/mikalai/.pyenv/versions/3.4.6/lib/python3.4/contextlib.py" in inner
  30.                 return func(*args, **kwds)

File "/Users/mikalai/.virtualenvs/venv_name/lib/python3.4/site-packages/wagtail/wagtailcore/models.py" in save
  477.         result = super(Page, self).save(*args, **kwargs)

File "/Users/mikalai/.virtualenvs/venv_name/lib/python3.4/site-packages/modelcluster/models.py" in save
  209.             getattr(self, field).commit()

File "/Users/mikalai/.virtualenvs/venv_name/lib/python3.4/site-packages/modelcluster/fields.py" in commit
  442.                 original_manager.add(*items_to_add)

File "/Users/mikalai/.virtualenvs/venv_name/lib/python3.4/site-packages/django/db/models/fields/related_descriptors.py" in add
  876.                     (opts.app_label, opts.object_name)

Exception Type: AttributeError at /admin/pages/133/edit/
Exception Value: Cannot use add() on a ManyToManyField which specifies an intermediary model. Use appname.BarToFoo's Manager instead.

Django==1.10.7
wagtail==1.11.1

@gasman
Copy link
Contributor

gasman commented Aug 21, 2017

Is this feature really needed, or can the same thing be done with a ParentalKey on the through model (i.e. the way we 'faked' M2M relationships before ParentalManyToManyField was implemented)...?

@m1kola
Copy link
Author

m1kola commented Aug 22, 2017

This feature is really useful for projects with models that were implemented before modelcluster/Wagtail introduced M2M support. So models like

class BarToFoo(models.Model):
    foo = models.ForeignKey('appname.FooModel', blank=True, null=True)
    page = models.ParentalKey('BarPage', related_name='something')

can be modified to become:

class BarToFoo(models.Model):
    foo = models.ForeignKey('appname.FooModel', blank=True, null=True)
    page = models.ForeignKey('BarPage', related_name='+')

Then you just need to add something = ParentalManyToManyField(..., through='appname.BarToFoo') and replace InlinePanel in the "target" model with FieldPanel. No need to write data migrations.

It also allows developers to add additional data into through models.

@mjpieters
Copy link

mjpieters commented May 20, 2019

A through model is a requirement when you want to record additional information about the relationship.

I need to record a rich text description per many-to-many relationship, the (source, target) tuples must be unique, and I need to put minimum and maximum limits on the relationship (each source needs to have at least 1, at most 8 such relationships recorded).

I guess I can achieve this with an inline panel and a full-blown model with a unique_together constraint, and some way to limit the available choices based on what's already present when editing? The latter would become rather hackish, I fear.

@mihow
Copy link

mihow commented Jun 10, 2019

The syntax for ParentalManyToManyField(to=FooModel, through=BarToFoo) seems more Django-like rather than only defining the intermediary model and setting a ParentalKey as seen in the Wagtail examples.

Also it looks like Django just merged better support for updating m2m relationships that have an intermediary model as well: django/django#8981. So for the example in the ticket description you will be able to use bar_page.asset_programs.add(foo) even with a through model!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants