The built-in page module¶
FeinCMS is primarily a system to work with lists of content blocks which you can assign to arbitrary other objects. You do not necessarily have to use it with a hierarchical page structure, but that’s the most common use case of course. Being able to put content together in small manageable pieces is interesting for other uses too, e.g. for weblog entries where you have rich text content interspersed with images, videos or maybe even galleries.
Activating the page module and creating content types¶
To activate the page module, you need to follow the instructions in
Installation instructions and afterwards add feincms.module.page
to your
INSTALLED_APPS
.
Before proceeding with manage.py makemigrations
and ./manage.py migrate
,
it might be a good idea to take
a look at Page extension modules – the page module does have the minimum of
features in the default configuration and you will probably want to enable
several extensions.
You need to create some content models too. No models are created by default,
because there is no possibility to unregister models. A sane default might
be to create MediaFileContent
and
RichTextContent
models; you can do this
by adding the following lines somewhere into your project, for example in a
models.py
file that will be processed anyway:
from django.utils.translation import ugettext_lazy as _
from feincms.module.page.models import Page
from feincms.contents import RichTextContent
from feincms.module.medialibrary.contents import MediaFileContent
Page.register_extensions(
'feincms.extensions.datepublisher',
'feincms.extensions.translations'
) # Example set of extensions
Page.register_templates({
'title': _('Standard template'),
'path': 'base.html',
'regions': (
('main', _('Main content area')),
('sidebar', _('Sidebar'), 'inherited'),
),
})
Page.create_content_type(RichTextContent)
Page.create_content_type(MediaFileContent, TYPE_CHOICES=(
('default', _('default')),
('lightbox', _('lightbox')),
))
It will be a good idea most of the time to register the
RichTextContent
first, because it’s the most used content type for many applications. The
content type dropdown will contain content types in the same order as they
were registered.
Please note that you should put these statements into a models.py
file
of an app contained in INSTALLED_APPS
. That file is executed at Django startup time.
Setting up the admin interface¶
The customized admin interface code is contained inside the ModelAdmin
subclass, so you do not need to do anything special here.
If you use the RichTextContent
, you
need to download TinyMCE and configure FeinCMS’
richtext support:
FEINCMS_RICHTEXT_INIT_CONTEXT = {
'TINYMCE_JS_URL': STATIC_URL + 'your_custom_path/tiny_mce.js',
}
If you want to use a different admin site, or want to apply customizations to the admin class used, add the following setting to your site-wide settings:
FEINCMS_USE_PAGE_ADMIN = False
Wiring up the views¶
Just add the following lines to your urls.py
to get a catch-all URL pattern:
urlpatterns += [
url(r'', include('feincms.urls')),
]
If you want to define a page as home page for the whole site, you can give it
an override_url
value of '/'
.
More information can be found in Integrating 3rd party apps into your site
Adding another content type¶
Imagine you’ve got a third-party gallery application and you’d like to include
excerpts of galleries inside your content. You’d need to write a GalleryContent
base class and let FeinCMS create a model class for you with some important
attributes added.
from django.db import models
from django.template.loader import render_to_string
from feincms.module.page.models import Page
from gallery.models import Gallery
class GalleryContent(models.Model):
gallery = models.ForeignKey(Gallery)
class Meta:
abstract = True # Required by FeinCMS, content types must be abstract
def render(self, **kwargs):
return render_to_string('gallery/gallerycontent.html', {
'content': self, # Not required but a convention followed by
# all of FeinCMS' bundled content types
'images': self.gallery.image_set.order_by('?')[:5],
})
Page.create_content_type(GalleryContent)
The newly created GalleryContent
for Page
will live in the database table page_page_gallerycontent
.
Note
FeinCMS requires your content type model to be abstract.
More information about content types is available in Content types - what your page content is built of.
Page extension modules¶
Extensions are a way to put often-used functionality easily accessible without
cluttering up the core page model for those who do not need them. The extensions
are standard python modules with a register()
method which will be called
upon registering the extension. The register()
method receives the
Page
class itself and the model admin class
PageAdmin
as arguments. The extensions can
be activated as follows:
Page.register_extensions(
'feincms.module.page.extensions.navigation',
'feincms.module.page.extensions.titles',
'feincms.extensions.translations')
The following extensions are available currently:
feincms.extensions.changedate
— Creation and modification datesAdds automatically maintained creation and modification date fields to the page.
feincms.extensions.ct_tracker
— Content type cacheHelps reduce database queries if you have three or more content types by caching in the database which content types are available on each page. If this extension is used,
Page._ct_inventory
has to be nullified after adding and/or removing content blocks, otherwise changes might not be visible in the frontend. Saving the page instance accomplishes this.feincms.extensions.datepublisher
— Date-based publishingAdds publication date and end date fields to the page, thereby enabling the administrator to define a date range where a page will be available to website visitors.
feincms.page.extensions.excerpt
— Page summaryAdd a brief excerpt summarizing the content of this page.
feincms.extensions.featured
— Simple featured flag for a pageLets administrators set a featured flag that lets you treat that page special.
feincms.module.page.extensions.navigation
— Navigation extensionsAdds navigation extensions to the page model. You can define subclasses of
NavigationExtension
, which provide submenus to the navigation generation mechanism. See Letting 3rd party apps define navigation entries for more information on how to use this extension.feincms.module.page.extensions.navigationgroups
— Navigation groupsAdds a navigation group field to each page which can be used to distinguish between the header and footer (or meta) navigation. Filtering is achieved by passing the
group
argument tofeincms_nav
.feincms.module.page.extensions.relatedpages
— Links related contentAdd a many-to-many relationship field to relate this page to other pages.
feincms.extensions.seo
— Search engine optimizationAdds fields to the page relevant for search engine optimization (SEO), currently only meta keywords and description.
feincms.module.page.extensions.sites
— Limit pages to sitesAllows to limit a page to a certain site and not display it on other sites.
feincms.module.page.extensions.symlinks
— Symlinked content extensionSometimes you want to reuse all content from a page in another place. This extension lets you do that.
feincms.module.page.extensions.titles
— Additional titlesAdds additional title fields to the page model. You may not only define a single title for the page to be used in the navigation, the <title> tag and inside the content area, you are not only allowed to define different titles for the three uses but also enabled to define titles and subtitles for the content area.
feincms.extensions.translations
— Page translationsAdds a language field and a recursive translations many to many field to the page, so that you can define the language the page is in and assign translations. I am currently very unhappy with state of things concerning the definition of translations, so that extension might change somewhat too. This extension also adds new instructions to the setup_request method where the Django i18n tools are initialized with the language given on the page object.
While it is not required by FeinCMS itself it’s still recommended to add
django.middleware.locale.LocaleMiddleware
to theMIDDLEWARE_CLASSES
; otherwise you will see strange language switching behavior in non-FeinCMS managed views (such as third party apps not integrated usingfeincms.content.application.models.ApplicationContent
or Django’s own administration tool). You need to have definedsettings.LANGUAGES
as well.
Note
These extension modules add new fields to the Page
class. If you add or
remove page extensions you make and apply new migrations.
Using page request processors¶
A request processor is a function that gets the currently selected page and the
request as parameters and returns either None (or nothing) or a HttpResponse.
All registered request processors are run before the page is actually rendered.
If the request processor indeed returns a HttpResponse
, further rendering of
the page is cut short and this response is returned immediately to the client.
It is also possible to raise an exception which will be handled like all exceptions
are handled in Django views.
This allows for various actions dependent on page and request, for example a simple user access check can be implemented like this:
def authenticated_request_processor(page, request):
if not request.user.is_authenticated():
raise django.core.exceptions.PermissionDenied
Page.register_request_processor(authenticated_request_processor)
register_request_processor
has an optional second argument named key
.
If you register a request processor with the same key, the second processor
replaces the first. This is especially handy to replace the standard request
processors named path_active
(which checks whether all ancestors of
a given page are active too) and redirect
(which issues HTTP-level redirects
if the redirect_to
page field is filled in).
Using page response processors¶
Analogous to a request processor, a response processor runs after a page has been rendered. It needs to accept the page, the request and the response as parameters and may change the response (or throw an exception, but try not to).
A response processor is the right place to tweak the returned http response for whatever purposes you have in mind.
def set_random_header_response_processor(page, request, response):
response['X-Random-Number'] = 42
Page.register_response_processor(set_random_header_response_processor)
register_response_processor
has an optional second argument named key
,
exactly like register_request_processor
above. It behaves in the same way.
WYSIWYG Editors¶
TinyMCE 3 is configured by default to only allow for minimal formatting. This has proven to be the best compromise between letting the client format text without destroying the page design concept. You can customize the TinyMCE settings by creating your own init_richtext.html that inherits from admin/content/richtext/init_tinymce.html. You can even set your own CSS and linklist files like so:
FEINCMS_RICHTEXT_INIT_CONTEXT = {
'TINYMCE_JS_URL': STATIC_URL + 'your_custom_path/tiny_mce.js',
'TINYMCE_CONTENT_CSS_URL': None, # add your css path here
'TINYMCE_LINK_LIST_URL': None # add your linklist.js path here
}
FeinCMS is set up to use TinyMCE 3 but you can use CKEditor instead if you prefer that one. Change the following settings:
FEINCMS_RICHTEXT_INIT_TEMPLATE = 'admin/content/richtext/init_ckeditor.html'
FEINCMS_RICHTEXT_INIT_CONTEXT = {
'CKEDITOR_JS_URL': STATIC_URL + 'path_to_your/ckeditor.js',
}
Alternatively, you can also use TinyMCE 4 by changing the following setting:
FEINCMS_RICHTEXT_INIT_TEMPLATE = 'admin/content/richtext/init_tinymce4.html'
ETag handling¶
An ETag is a string that is associated with a page – it should change if (and only if) the page content itself has changed. Since a page’s content may depend on more than just the raw page data in the database (e.g. it might list its children or a navigation tree or an excerpt from some other place in the CMS alltogether), you are required to write an etag producing method for the page.
# Very stupid etag function, a page is supposed the unchanged as long
# as its id and slug do not change. You definitely want something more
# involved, like including last change dates or whatever.
def my_etag(page, request):
return 'PAGE-%d-%s' % ( page.id, page.slug )
Page.etag = my_etag
Page.register_request_processors(Page.etag_request_processor)
Page.register_response_processors(Page.etag_response_processor)
Sitemaps¶
To create a sitemap that is automatically populated with all pages in your Feincms site, add the following to your top-level urls.py:
from feincms.module.page.sitemap import PageSitemap
sitemaps = {'pages' : PageSitemap}
urlpatterns += [
url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap',
{'sitemaps': sitemaps}),
]
This will produce a default sitemap at the /sitemap.xml url. A sitemap can be further customised by passing it appropriate parameters, like so:
sitemaps = {'pages': PageSitemap(max_depth=2)}
The following parameters can be used to modify the behaviour of the sitemap:
navigation_only
– if set to True, only pages that are in_navigation will appear in the site map.max_depth
– if set to a non-negative integer, will limit the sitemap generated to this page hierarchy depth.changefreq
– should be a string or callable specifying the page update frequency, according to the sitemap protocol.queryset
– pass in a query set to restrict the Pages to include in the site map.filter
– pass in a callable that transforms a queryset to filter out the pages you want to include in the site map.extended_navigation
– if set to True, adds pages from any navigation extensions. If using PagePretender, make sure to include title, url, level, in_navigation and optionally modification_date.