3

我正在使用 Django 1.4 和 django-CMS 2.3。

我有几个带有 apphooks 的应用程序,我希望在 django-CMS 中能够引用应用程序的对象。

为了建立可持续的链接,我正在尝试使用 django-CMS 菜单或通用外键找到一个插件。

如果不存在这样的东西,那么最好的通用外键应用程序是什么?我刚刚使用 django.contrib.contenttypes 制作了一个小插件,它限制了对具有get_absolute_url方法的模型的选择,但这太糟糕了。

谢谢。

4

1 回答 1

3

我刚刚碰到你的问题。我最近为几个项目实现了类似的东西,这是我根据 Adam Alton 在这里描述的工作所做的:http: //adamalton.co.uk/blog/displaying-django-genericforeignkey-as-single-form -场地/

然而,这不是一个 CMSPlugin,所以,我知道这并不能直接回答这个问题,但它是我所拥有的,我希望它可以帮助其他人寻找类似的解决方案。

作为概述,我在我的模型中定义了一个“横幅”类型,它代表了我客户网站首页上的横幅。每个横幅都可以链接到其他内容。在这些情况下,链接目标可能是 Django-CMS 页面,或许多其他类型中的一种。所有人都定义了 get_absolute_url 方法,尽管我不使用自省来确定这一点,我只是在此处出现的所有类型上实现了 get_absolute_url。无论如何,这里是:

首先,这是 Banner 的简单模型:

class Banner(models.Model):
  short_name       = models.CharField(max_length=64, unique=True)
  html             = models.TextField()
  link_text        = models.CharField(max_length=128, default='Learn more')
  destination_type = models.ForeignKey(ContentType, null=True, blank=True,
    limit_choices_to={"model__in": ("Page", "Project", "Person", "Client")}
  )
  destination_id   = models.PositiveIntegerField(null=True, blank=True)
  destination      = generic.GenericForeignKey('destination_type', 'destination_id')
  published        = models.BooleanField(blank=True, default=False)

  def __unicode__(self):
    return self.short_name

这是我的forms.py:

import re
from django.forms import ModelForm, ChoiceField
from cms.models import Page
from django.contrib.contenttypes.models import ContentType

from apps.your_application.models import Project, Person, Client

class BannerAdminForm(ModelForm):
  class Meta:
    model = Banner
    fields = ("short_name", "html", "link_text", "destination", "link_hash", "published",)

  # GenericForeignKey form field, will hold combined object_type and object_id
  destination = ChoiceField(required=False)  # Note the 'required=False' here.

  def __init__(self, *args, **kwargs):
    super(BannerAdminForm, self).__init__(*args, **kwargs)

    # Combine object_type and object_id into a single 'destination' field
    # Get all the objects that we want the user to be able to choose from

    # Note: The user is going to locate these by name, so we should
    # alphabetize all of these

    available_objects  = list(Page.objects.all().order_by('title_set__title'))
    available_objects += list(Project.objects.all().order_by('title'))
    available_objects += list(Person.objects.all().order_by('name'))
    available_objects += list(Client.objects.all().order_by('name'))

    # Now create our list of choices for the <select> field
    object_choices = []
    object_choices.append(["", "--"])
    for obj in available_objects:
      type_class = ContentType.objects.get_for_model(obj.__class__)
      type_id = type_class.id
      obj_id = obj.id
      form_value = "type:%s-id:%s" % (type_id, obj_id)  # e.g."type:12-id:3"
      display_text = "%s : %s" % (str(type_class), str(obj))  # E.g. "Client : Apple, Inc."
      object_choices.append([form_value, display_text])

    self.fields['destination'].choices = object_choices

    # If there is an existing value, pre-select it
    if self.instance.destination:
      type_class = ContentType.objects.get_for_model(self.instance.destination.__class__)
      type_id = type_class.id
      obj_id = self.instance.destination.id
      current_value = "type:%s-id:%s" % (type_id, obj_id)
      self.fields['destination'].initial = current_value

  def save(self, *args, **kwargs):
    try:
      #get object_type and object_id values from combined destination field
      object_string = self.cleaned_data['destination']
      matches = re.match("type:(\d+)-id:(\d+)", object_string).groups()
      object_type_id = matches[0]  # get 45 from "type:45-id:38"
      object_id = matches[1]       # get 38 from "type:45-id:38"
      object_type = ContentType.objects.get(id=object_type_id)
      self.cleaned_data['destination_type'] = object_type_id
      self.cleaned_data['destination_id'] = object_id
      self.instance.destination_id = object_id
      self.instance.destination_type = object_type
    except:
      # If anything goes wrong, leave it blank,
      # This is also the case for when '--' is chosen
      # In the drop-down (tsk, tsk, bad code style =/)
      self.cleaned_data['destination_type'] = None
      self.cleaned_data['destination_id'] = None
      self.instance.destination_id = None
      self.instance.destination_type = None

    return super(BannerAdminForm, self).save(*args, **kwargs)

然后,您可以通过调用 {% if banner.destination %}{{ banner.destination.get_absolute_url }}{% endif %} 模板来获取目标对象的 URL。

效果很好,在 CMSPlugin 中使用应该不会太难。

这就是 Django Admin 中的样子

编辑:实际上,我刚刚实现了与 CMSPlugin 表单完全相同的东西。存在本质上的差异。只需记住将表单添加到您的 cms_plugins.py 文件中的插件类中,如下所示:

class CMSBannerPlugin(CMSPluginBase):
  form = BannerAdminForm  #   <==== Don't forget this part
  model = Banner
  name = _("Banner Plugin")
  render_template = "apps/your_application/_banner.html"

  def render(self, context, instance, placeholder):
    ...
    return context

plugin_pool.register_plugin(CMSBannerPlugin)
于 2012-11-09T23:39:56.413 回答