-
-
Notifications
You must be signed in to change notification settings - Fork 485
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
m2m: Admin doesn't show m2m of historic object #1063
Comments
...that exist on models that already have history tracking. Note that the many-to-many selection for these fields *are* correctly saved, they're just currently not correctly displayed in the objects' history page in Django admin (the *current* M2M selection is always shown); see jazzband/django-simple-history#1063.
This is still happening on 3.5.0. After changing a m2m relationship via the admin, all objects from the history displays the current m2m state |
Yeah, it seems fixing it requires a lot more changes than initially expected, and so it's been postponed. However, #1128 will alleviate it somewhat, by listing the (correct) M2M relations in the change history table 🙂 |
No worries! Thanks for the heads up and for the great work! |
I was able to get this working by creating a new M2M field that adds some additional logic to the related manager that gets created to filter down the results based on whether the instance is from historical data. This is being used to support showing the historical m2m relations when viewing history instances in the admin. from django.db.models import ManyToManyField
from django.db.models.fields import checks
from django.db.models.fields.related import ManyToManyDescriptor
from django.utils.functional import cached_property
from simple_history.models import to_historic
class HistoryManyToManyDescriptor(ManyToManyDescriptor):
"""Used by HistoryManyToManyField to support querying historical m2m relations.
We extend the django related manager to add support for returning m2m relations with their historical values.
"""
@cached_property
def related_manager_cls(self):
superclass = super().related_manager_cls
rel = self.rel
class HistoryManyRelatedManager(superclass):
@property
def core_filters(self):
if history := to_historic(self.instance):
# Filter the m2m field to only the values that existed on the historical version
# `rel` is the manytomany relation on the model
# `self.target_field` is the field on the through table that points to the underlying model
# being linked by the relation. E.g. This would be the image FK on a through model that linked tickets to many images
return {
"pk__in": getattr(history, rel.field.attname).values(
self.target_field.attname
)
}
# Not viewing history so use normal m2m filters
return self._core_filters
@core_filters.setter
def core_filters(self, value):
# Allow the normal filters that django sets up to be saved
self._core_filters = value
return HistoryManyRelatedManager
class HistoryManyToManyField(ManyToManyField):
"""Many to many field that supports querying relations for their historical values.
Using this field necessitates also including the field name in the models `_history_m2m_fields` list.
"""
def contribute_to_class(self, cls, name, **kwargs):
super().contribute_to_class(cls, name, **kwargs)
setattr(
cls,
self.name,
HistoryManyToManyDescriptor(self.remote_field, reverse=False),
)
def check(self, **kwargs):
return [*super().check(**kwargs), *self._check_field_in_history_m2m_fields()]
def _check_field_in_history_m2m_fields(self):
history_m2m_fields_list = getattr(self.model, "_history_m2m_fields", [])
if self.attname not in history_m2m_fields_list:
return [
checks.Error(
"HistoryManyToManyFields must be included in models"
" _history_m2m_fields list.",
hint=(
f'Add "{self.attname}" to the property "_history_m2m_fields" on'
f' "{self.model}".'
),
)
]
return [] To use this, replace your |
@trumpet2012 Thank you so much for this! It seems to work fine and is a great workaround for now. |
Describe the bug
Sidenote: thank you so much for the m2m field tracking! Stopped me migrating a large project to django-pghistory.
Unfortunately it doesn't work correctly in the admin integration. When I view a historic item, it will always show the current selection on an m2m field instead of the selection at the specific time.
The same is happening when using
as_of()
and I suspect the issues are related.To Reproduce
Steps to reproduce the behavior:
Code to reproduce:
Expected behavior
It should show the selection as it was at the time.
Environment
The text was updated successfully, but these errors were encountered: