django-simple-history icon indicating copy to clipboard operation
django-simple-history copied to clipboard

Allow custom table name for M2M fields

Open lazarust opened this issue 2 years ago • 2 comments

Problem Statement In version 3.2 the ability to track changes in m2m fields was added. You can currently set a table name for the main table but not for any of the through tables that are created.

Describe the solution you'd like I'd like to be able to set the database table name for each of the many to many fields. This could be done by passing a dict into m2m_fields when initializing the historical record.

lazarust avatar Dec 16 '22 20:12 lazarust

@PiotrKurnik @lazarust Any progress regarding this feature? 😸

We would also be really interested if we could have historical support for "customized" m2m_fields. For instance, currently, the following does not work 😢

class User(AbstractUser):
    ...

class Group(models.Model):
    members = models.ManyToManyField(
        User, through="Membership", through_fields=("group", "user")
    )
    history = HistoricalRecords(m2m_fields=[members])

class Membership(models.Model):
    group = HistoricForeignKey(
        Group,
        related_name="memberships",
        on_delete=models.CASCADE,
    )
    user = HistoricForeignKey(
        User,
        related_name="memberships",
        on_delete=models.CASCADE,
    )
Stacktrace
./manage.py makemigrations
Traceback (most recent call last):
  File "/.venv/lib/python3.10/site-packages/django/db/models/utils.py", line 15, in make_model_tuple
    app_label, model_name = model.split(".")
ValueError: not enough values to unpack (expected 2, got 1)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/./manage.py", line 47, in <module>
    main()
  File "/./manage.py", line 43, in main
    execute_from_command_line(sys.argv)
  File "/.venv/lib/python3.10/site-packages/django/core/management/__init__.py", line 446, in execute_from_command_line
    utility.execute()
  File "/.venv/lib/python3.10/site-packages/django/core/management/__init__.py", line 386, in execute
    settings.INSTALLED_APPS
  File "/.venv/lib/python3.10/site-packages/django/conf/__init__.py", line 92, in __getattr__
    self._setup(name)
  File "/.venv/lib/python3.10/site-packages/django/conf/__init__.py", line 79, in _setup
    self._wrapped = Settings(settings_module)
  File "/.venv/lib/python3.10/site-packages/django/conf/__init__.py", line 190, in __init__
    mod = importlib.import_module(self.SETTINGS_MODULE)
  File "/usr/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 992, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 992, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/project/__init__.py", line 3, in <module>
    from project.celery import app as celery_app
  File "/project/celery.py", line 21, in <module>
    django.setup()
  File "/.venv/lib/python3.10/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/.venv/lib/python3.10/site-packages/django/apps/registry.py", line 116, in populate
    app_config.import_models()
  File "/.venv/lib/python3.10/site-packages/django/apps/config.py", line 269, in import_models
    self.models_module = import_module(models_module_name)
  File "/usr/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "/project/models.py", line 40, in <module>
    class Group(models.Model):
  File "/.venv/lib/python3.10/site-packages/django/db/models/base.py", line 363, in __new__
    new_class._prepare()
  File "/.venv/lib/python3.10/site-packages/django/db/models/base.py", line 426, in _prepare
    class_prepared.send(sender=cls)
  File "/.venv/lib/python3.10/site-packages/django/dispatch/dispatcher.py", line 176, in send
    return [
  File "/.venv/lib/python3.10/site-packages/django/dispatch/dispatcher.py", line 177, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
  File "/.venv/lib/python3.10/site-packages/simple_history/models.py", line 193, in finalize
    m2m_changed.connect(
  File "/.venv/lib/python3.10/site-packages/django/db/models/signals.py", line 27, in connect
    self._lazy_method(
  File "/.venv/lib/python3.10/site-packages/django/db/models/signals.py", line 22, in _lazy_method
    apps.lazy_model_operation(partial_method, make_model_tuple(sender))
  File "/.venv/lib/python3.10/site-packages/django/db/models/utils.py", line 22, in make_model_tuple
    raise ValueError(
ValueError: Invalid model reference 'Membership'. String model references must be of the form 'app_label.ModelName'.

/cc @mlegner @bufferoverflow @max-wittig @fgreinacher

antoineauger avatar Feb 24 '23 17:02 antoineauger

@lazarust When you're mentioning "You can currently set a table name for the main table", I'm assuming you're talking about the table_name parameter..? In that case, what @antoineauger is talking about seems to be related to what's addressed with PR #1149, and not related to this issue.

ddabble avatar Sep 26 '23 17:09 ddabble