Extending Entry model

New in version 0.8.

The Entry model bundled in Zinnia can now be extended and customized.

This feature is useful for who wants to add some fields in the model, or change its behavior. It also allows Zinnia to be a really generic and reusable application.

Why extending ?

Imagine that I find Zinnia really great for my project but some fields or features are missing to be the Weblog app that suits to my project. For example I need to add a custom field linking to an image gallery, two solutions:

  • I search for another Django blogging app fitting my needs.
  • I do a monkey patch, into the Zinnia code base.

These two solutions are really bad.

For the first solution maybe you will not find the desired application and also mean that Zinnia is not a reusable application following the Django’s convention. For the second solution, I don’t think that I need to provide more explanations about the evil side of monkey patching (evolution, reproduction…). That’s why Zinnia provides a third generic solution.

  • Customizing the Entry model noninvasively with the power of class inheritance !

The extension process is done in three main steps:

  1. Write a model class containing your customizations.
  2. Register your model class into Zinnia to be used.
  3. Create and register a new ModelAdmin class for your new model class.

Writing model extension

In the suite of this document we will show how to add an image gallery into the Entry model to illustrate the concepts involved when extending. We assume that the pieces of codes written for this document belong in the zinnia_gallery module/application.

Changed in version 0.13.

The zinnia.models.entry.EntryAbstractClass has been moved and renamed to zinnia.models_bases.entry.AbstractEntry.

The first step to extend the Entry model is to define a new class inherited from the AbstractEntry and add your fields or/and override the inherited methods if needed. So in zinnia_gallery let’s write our gallery models and the extension in the Entry model in models.py.

from django.db import models
from zinnia.models_bases.entry import AbstractEntry

class Picture(models.Model):
    title = models.CharField(max_length=50)
    image = models.ImageField(upload_to='gallery')

class Gallery(models.Model):
    title = models.CharField(max_length=50)
    pictures = models.ManyToManyField(Picture)

class EntryGallery(AbstractEntry):
    gallery = models.ForeignKey(Gallery)

    def __str__(self):
        return 'EntryGallery %s' % self.title

    class Meta(AbstractEntry.Meta):
        abstract = True

In this code sample, we simply add in our Entry model a new ForeignKey field named gallery pointing to a Gallery model and we override the Entry.__unicode__() method.

Note

You have to respect 2 important rules to make extending working :

  1. Do not import the Entry model in your file defining the extended model because it will cause a circular importation.
  2. Don’t forget to tell that your model is abstract. Otherwise a table will be created and the extending process will not work as expected.

See also

Model inheritance for more information about the concepts behind the model inheritence in Django and the limitations.

Writing model customisation

Adding fields is pretty easy, but now that the Entry model has been extended, we want to change the image field wich is an ImageField by default to use our new Picture instead.

To customise this field, the same process as extending apply, but we can take advantage of all the abstracts classes provided to build the AbstractEntry to rebuild our own custom Entry model like this:

from django.db import models
from zinnia.models_bases import entry

class Picture(models.Model):
    title = models.CharField(max_length=50)
    image = models.ImageField(upload_to='gallery')

class Gallery(models.Model):
    title = models.CharField(max_length=50)
    pictures = models.ManyToManyField(Picture)

class EntryGallery(
          entry.CoreEntry,
          entry.ContentEntry,
          entry.DiscussionsEntry,
          entry.RelatedEntry,
          entry.ExcerptEntry,
          entry.FeaturedEntry,
          entry.AuthorsEntry,
          entry.CategoriesEntry,
          entry.TagsEntry,
          entry.LoginRequiredEntry,
          entry.PasswordRequiredEntry,
          entry.ContentTemplateEntry,
          entry.DetailTemplateEntry):

    image = models.ForeignKey(Picture)
    gallery = models.ForeignKey(Gallery)

    def __str__(self):
        return 'EntryGallery %s' % self.title

    class Meta(entry.CoreEntry.Meta):
        abstract = True

Now we have an Entry model extended with a gallery of pictures and customised with a Picture model relation as the image field.

Note that the same process apply if you want to delete some built-in fields.

Considerations about the database

If you do the extension of the Entry model, you have to alter the Zinnia’s database tables for reflecting your changes made on the model class.

Fortunately since Django 1.7 you just have to write a new migration for reflecting your changes, but the migration script will be written in the zinnia.migrations module, which is not recommended because the result is not replicable for multiple installations and breaks the migration system with future releases of Zinnia.

Fortunatly Django provides a solution with the MIGRATION_MODULES setting. Once this setting is done for the 'zinnia' key, can now start to write new migrations. Note that it is recommended to use a different package then the default one for your app to avoid conflict (e.g. MIGRATION_MODULES = {'zinnia': 'zinnia_gallery.migrations_zinnia'}).

It’s recommended that the new initial migration represents the default Entry schema provided by Zinnia, because after that, you just have to write a new migration for reflecting your changes, and you just alter your database schema with the migrate command.

Registering the extension

Once your extension class is defined you simply have to register it, with the ZINNIA_ENTRY_BASE_MODEL setting in your Django settings. The expected value is a string representing the full Python path to the extented model’s class name. This is the easiest part of the process.

Following our example we must add this line in the project’s settings.

ZINNIA_ENTRY_BASE_MODEL = 'zinnia_gallery.models.EntryGallery'

If an error occurs when your new class is imported a warning will be raised and the EntryAbstractClass will be used.

Updating the admin interface

Now we should create a new EntryAdmin admin class to reflect our changes and use the new fields.

To do that we will write a new admin class inherited from EntryAdmin and register it within the admin site.

In the file zinnia_gallery/admin.py we can write these code lines for adding the gallery field:

from django.contrib import admin
from django.utils.translation import ugettext_lazy as _

from zinnia.models.entry import Entry
from zinnia.admin.entry import EntryAdmin

class EntryGalleryAdmin(EntryAdmin):
  # In our case we put the gallery field
  # into the 'Content' fieldset
  fieldsets = (
    (_('Content'), {
      'fields': (('title', 'status'), 'lead', 'content',)}),
    (_('Illustration'), {
      'fields': ('image', 'gallery'),
      'classes': ('collapse', 'collapse-closed')}),) + \
    EntryAdmin.fieldsets[2:]

admin.site.register(Entry, EntryGalleryAdmin)

Templating

Now we can easily customize the templates provided by Zinnia to display the gallery field into the Weblog’s pages.

For more information you can see another implementation example in the cmsplugin-zinnia package.