Home Blog Widgy's Links Framework


Widgy's Links Framework

Posted by rocky on March 31, 2015, 6 p.m.

For those of you who don't know Widgy is a widget-based content editor that we wrote as our CMS solution for Django. If you do know about it, awesome! High Five! If you don't know about it, you should check it out, it even has a demo that you can try online right now!

Anyway, for Widgy, one of the features that we knew we would need is the ability to for a widget to link to a page. At the surface, that sounds like a pretty easy problem. We could just store the URL for that page in a CharField on the model for that widget and be done with it. Of course, what happens when the page that it was linked to changes its URL? In this case nothing, the widget would continue to point to the old URL. This is not good enough for two reasons:

  1. The user would have to manually go back to that widget and edit it to point to the new page URL.
  2. Old versions of the widget are forever wrong now. It would be impossible to revert to old versions of the widget without having to once again manually change the URL.

Another solution would be to use a ForeignKey and just point to a Page model. That would work if the Page model was the only model that had URLs on your site. However, we wanted to make Widgy CMS-agnostic. We wanted it to work with Mezzanine's Page object, but we also wanted it to work with other CMS solutions and normal Django models. Just using a ForeignKey is not good enough becuase we want to be able to link to any Django model, not just Mezzanine's Page object.

Not good enough at all.

So we came up with the Links Framework. With the Links Framework you can add a field to any widget that allows you to point to any Django model that you want.

How do I use it?

You can add a LinkField to any model that you want. For example, if you wanted to create a Button model that had some text and a link, it would look something like this:

from django.db import models
  from widgy.models import links

  class Button(models.Model):
      text = models.CharField(max_length=255
      link = links.LinkField()

Now you need to let the Links Framework know which models are link-able. This is done through the Registry.

from django.db import models
  from django.core import urlresolvers
  from widgy.models import links

  class BlogPost(models.Model):
      title = models.CharField(max_length=255)
      body = models.TextField()

      def __str__(self):
          return self.title

      def get_absolute_url(self):
          return urlresolvers.reverse('blog_detail', kwargs={'pk': self.pk})

  class Pdf(models.Model):
      name = models.CharField(max_length=255)
      file = models.FileField(upload_to='pdfs/')

      def __str__(self):
          return self.name

      def get_absolute_url(self):
          return self.file.url

There are three requirements for a model in order for it to be link-able:

  • It needs to be registered using links.register.
  • It needs to define a useful __str__ (__unicode__ in Python 2) method.
  • It needs to define a get_absolute_url method.

The __str__/__unicode__ and the get_absolute_url method are used by the Links Framework to create the options in the LinkFormField. Without these two methods, it would be pretty hard for users to know what each option was.

Next you can make a Form class that has the LinkFormField. Unfortunately, because GenericForeignKeys are not implemented as normal ModelFields[1], they aren't supported by the built-in Django Forms, so we had to add a LinkFormMixin that you can add to any form to make it support LinkFormFields.

from django import forms
  from widgy.models import links

  from .models import Button

  class ButtonForm(links.LinkFormMixin, forms.ModelForm):
      link = LinkField(label='Link')

      class Meta:
          model = Button
          fields = ('text', 'link',)

You are done. Your Button class is now able to link to both BlogPost and Pdf objects!



[1]: GenericForeignKeys are implemented as virtual fields. They don't appear in ModelForms and are not allowed in QuerySet filter expressions.