Open bcarberry opened 7 years ago
The root problem here is that bidirectional ManyToMany relationships, with publishable models on both ends, need special treatment in the Django admin to show only the IDs of draft copies, never IDs of published copies.
In most cases, hiding the published copies in the admin is relatively simple: you override the get_queryset
methods to filter out the published copies.
However, with ManyToMany relationships and the form fields that support them, there is no direct way to control which of the less directly related items should be shown in the admin and which should not.
We solved this for SFMOMA previously (see code snippet below) by:
We need a more generic and flexible solution for ICEkit Publishing in general, for cases like this. Ideally we would find a better way to filter the items shown in Django admin forms for bidirectional M2M publishable relationships. Or if no better mechanism is available, at least handle more field types than only choice/select fields (e.g. include raw ID fields)
Here is example code showing the monkey-patching workaround for choice fields:
class ExampleAdminForm(form.ModelForm):
def __init__(self, *args, **kwargs):
super(ExampleAdminForm, self).__init__(*args, **kwargs)
# Monkey-patch bidirectional publishable M2M relationship fields
self._filter_m2m_field_qs_to_draft_only()
def _filter_m2m_field_qs_to_draft_only(self):
"""
Alter the queryset and `prepare_value` method for `ModelChoiceField`
and `ModelMultipleChoiceField` to exclude published objects, if the
related model is publishable, to avoid listing published copies of
related items in the admin as choices or as existing relationships.
"""
def prepare_value__pre_filter_with_queryset(self, value):
"""
Method to override field's `prepare_value` method to filter out
PKs not in field's queryset, i.e. PKs to published items.
This does not cause relationships to published relationships to be
cleared in the admin because the `handle_publishable_m2m_changed`
signal handler does extra work to keep them.
"""
# Only process a list, presumably of PKs
if value and isinstance(value, (tuple, list)):
# Valid PKs according to our queryset
valid_pks = set(self.queryset.values_list('pk', flat=True))
# Update `value` to be intersection of given and valid values
value = list(set(value) & valid_pks)
# Continue processing value as normal, using original method
return self._original_prepare_value(value)
# Exclude published objects related via M2M fields.
for field in self.fields.values():
# Field is FK or M2M.
if isinstance(field, (ModelChoiceField, ModelMultipleChoiceField)):
# Related model is publishable.
if issubclass(field.queryset.model, PublishingModel):
# Exclude published objects from queryset
field.queryset = \
field.queryset.exclude(publishing_is_draft=False)
# Preempt the original `prepare_value` method with our own
# pre-processor method to apply the QS filter to values.
field._original_prepare_value = field.prepare_value
field.prepare_value = \
prepare_value__pre_filter_with_queryset.__get__(
field, field.__class__)
I've created an app called 'publications'. This has a 'publication' model that is publishable (imagine a list of books the museum has published, each item contains full metadata and excerpts from a book).
A publication instance can be related to other publishable content, ie exhibitions, events, or other publications.
When relating a publication instance to an instance of an exhibition or a publication via a raw ID field, I am able to select only a draft foreign key which is good, however after saving the publication instance, I then see that both the draft and the published foreign keys are being related in the raw ID field for related exhibition, or related publication.
From James Murty: unfortunately ICEkit does not yet include a generic, or more easily reused, way of fixing choice fields in the admin for bidirectional ManyToMany relationships between publishable models, to avoid incorrectly showing FKs to published copies. This work is done by
SFMOMAEventsAdminForm._filter_m2m_field_qs_to_draft_only
in SFMOMA's implementation, and I believe this is the only place the issue has been dealt with so far.Suggest adding the functionality that currently is in Eventkit as noted above, into Icekit, to easily reuse in publishable models and content items.