Based on the Generic Foreign Key idea suggested by karthikr, here is the full solution I've adopted. The other pieces of the puzzle are:
- using an entry in
settings.py
to restrict which models are allowed in this generic foreign key; - sniffing for the chosen model's many-to-many field;
- using
{% include "carousel_item.html" %}
in the template to generalize the item display. I'll provide a default implementation in the app, but this way the ultimate user does not have to conform to the fields I pre-define.
In models.py
:
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.conf import settings
allowed_models = getattr(settings, 'ALLOWED_MODELS_IN_CAROUSEL', [])
# must be a list of dictionaries with keys: app_label and model, e.g:
# ALLOWED_MODELS_IN_CAROUSEL=[{'app_label':'myapp', 'model':'screenshotgroup'},]
fk_models = None
if allowed_models:
# don't like this repetition - how can I improve this?
fk_models = models.Q(app_label = allowed_models[0]['app_label'].lower(),
model = allowed_models[0]['model'].lower())
for m in allowed_models[1:]:
fk_models = fk_models | models.Q(app_label = m['app_label'].lower(),
model = m['model'].lower())
class CarouselPluginModel(CMSPlugin):
content_type = models.ForeignKey(ContentType, limit_choices_to = fk_models)
object_id = models.PositiveIntegerField()
content_group = generic.GenericForeignKey('content_type', 'object_id')
...
The view needs to find the ManyToManyField in the chosen model, e.g:
if instance.content_group and instance.content_group._meta.many_to_many:
m2m_fieldname = instance.content_group._meta.many_to_many[0].name
context['item_list'] = getattr(instance.content_group, m2m_fieldname).all()
The template can look like this:
{% for item in item_list %}
{% include "carousel_item.html" %}
{% endfor %}
And finally I'll include a recommendation that the model you use include its id
in its description, since the admin panel will have to choose it by id, e.g:
class ScreenshotGroup(models.Model):
name = models.CharField(max_length=60)
screenshots = models.ManyToManyField(Screenshot, through="ScreenshotGroupMember")
def __unicode__(self):
return u"{0} (id {1})".format(self.name, self.id)