Question

I have written a carousel plugin for Django-CMS which displays screenshots. The underlying model has some carousel-related parameters (height, animation style etc), and a ForeignKey to ScreenshotGroup:

class ScreenshotGroup(models.Model):
    name = models.CharField(max_length=60)
    screenshots = models.ManyToManyField(Screenshot, through="ScreenshotGroupMember")

class Screenshot(models.Model):
    name = models.CharField(max_length=60)
    desc = models.TextField(_("description"), blank=True)
    img = models.ImageField(upload_to='img/')

class CarouselPluginModel(CMSPlugin):
    group = models.ForeignKey(ScreenshotGroup)
    height = models.IntegerField()
    ...

The carousel's view method contains:

context['item_list'] = instance.group.screenshots.all()

(Actually since I'm using Django-CMS, it's in the cms_plugins.py render method, not a view method.)

The template refers to the screenshot fields via:

{% for item in item_list %}
    {{ item.name }}
    {{ item.desc }}
    ...{{ item.img }}...
{% endfor %}

My question is: I want to generalise my carousel plugin to reuse it in other projects, so that does not depend on the Screenshot model. I can replace the contents of the template's for loop with an include to allow each project to specify how to display the item in a carousel. But how do I generalise the CarouselPluginModel's ForeignKey?

In any particular application, I only want one type of model allowed (ScreenshotGroup in my example) - I don't want the admin console to allow any other models to be included.

Thanks!

Était-ce utile?

La solution

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)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top