Content Management with Podius
A presentation for the Open Source Developers Conference
OSDC::Israel::2006 and
Perl Workshop in Israel, 2007,
by Mikhael Goikhman.
See index page.
Applied Content Management
A good content management system may be useful anywhere, from a
personal blog to enterprise stores and online magazines.
Podius is an example of a flexible
CMS that simplifies building of dynamic web-sites, by providing clean ways
to create, manage and publish all kinds of complex content.
This presentation discusses common content management issues, and
demonstrates how to setup a simple dynamic shop-like site from scratch.
Application: Online shop
Say, you inherited your family shop and want to catalog all
products in the store.
Model: Shop, Product (Book, Computer, Phone), Category.
|
|
Application: Online magazine
Or you are a newspaper editor that works non-stop and publishes a new
edition daily (and updates it with flash news in real time).
Model: Archive, Edition, Section, Article.
|
|
Application: Modern site
These days many sites of geeks or companies are not static, but
update their contents and contain some kind of "news" for visitors.
The look of such sites is not changed, just the content.
Model: Site, Section, RegularPage, NewsItem.
|
|
Creating data model
Designing suitable application object model (bussiness objects) is the
most critical step. The model is directly affected by all application
requirements, and in the end it dictates the application behaviour and
shortcomings.
Possible questions about the model (application specific):
- whether a Person has one or more addresses (shipping, billing)
- whether articles are contained in article sets (multi-level hierarchy)
- whether "category" is a property of Product or its container object,
or maybe an object by itself
- whether front_articles are just pointers to actual articles
contained somewhere in the object tree
Possible issues about accessing objects (CMS specific):
- given the head of the object tree, how to reach certain object
- given one object, how to reach its relatives (children, parents)
- accessing objects using free search on its properties
Solution to the model problems in Podius
Component == business object. Supplied component types:
- abstract Publishable, Multilingual, AccessControlable
- AccessCenter, AccessUser, AccessGroup
- Page, Edition, Section, Article...
Components consist of properties. Example of properties:
property name | property type | arguments |
author | Scalar | optional default value |
is_valid | Boolean | optional default value |
articles | ComponentCollection | Article |
schedule | ScalarTable | [ "start_time", "end_time", "worker_name" ] |
Rich set of supplied property types:
- Scalar, Text, Integer, Number, Time, Date, Password, Email, URL, FileSelection
- ScalarList, LimitedScalarList, ScalarTable, DateRanges, ScalarHash, Rating, PerlData
- MultilingualSelection, MultilingualBoolean, MultilingualLanguage, Multimedia, Image
- Selection, Boolean, Country, Currency, CurrencyAmount, MultipleSelections, AccessPermissions
- VComponent, ComponentCollection, VComponentCollection, RComponentCollection
- property mix-ins Automanaged, Constant, Hidden, Multilingual
Hierarchy of component and property classes. Creating custom classes
is easy by inheriting.
Solution for containment in Podius and terminology
All components compose a tree. Every component (except for
main_components on level 1 has a parent, so called container.
Every component may have any number of children collections.
Each property is owned by its owner component. Each component is part
of exactly one ComponentCollection property, owned by the parent
component (container).
To implement this, components have two special properties, "container"
(a link to its parent component) and "container_collection_name" (the
name of the parent property containing this component). These properties
are hidden and managed automatically.
Aggregation versus Linkage
- ComponentCollection - aggregation; parent-sons
- VComponentCollection - virtual collection; linkage
- RComponentCollection - reverse collection; automatic linkage
- VComponent - single link (one parent, one address)
Podius: Creating new project
The podius software comes with the core project. Each new project should
inherit from the core or any other podius-based project.
% bin/admin/prepare-project OnlineShop
The project's home directory is /data/projects/OnlineShop
Creating project directories ... done.
Copying initial project files ... done.
Modifying some project files ... done.
Additional initializations ... done.
Project OnlineShop is prepared.
% cd ../OnlineShop/
Practical part.
Defining component model
The first thing we want in a new project is to define its model.
The default model looks like:
% bin/admin/list-model
Publishable
Edition
Section
Article
|
This is done by creating perl class for each component.
Our goal is this component model:
% bin/admin/list-model
Publishable
Page
Center
Section
ItemPage
Article
Product
ArticleSet
User
UserSet
Address
BillingInfo
ProductSet
|
|
package Podius::Component::Page;
use base 'Podius::Component::Publishable';
sub get_own_property_types {
return [
[ title => 'Scalar' ],
[ phrase => 'Scalar' ],
[ keywords => 'BigScalar' ],
[ description => 'BigScalar' ],
];
}
sub get_name {
my $self = shift;
return $self->phrase || $self->name || $self->title;
}
1;
package Podius::Component::ItemPage;
use base 'Podius::Component::Page';
sub get_own_property_types {
return [
[ abstract => 'BigScalar' ],
[ body => 'Text' ],
[ active => 'Boolean' ],
];
}
1;
package Podius::Component::Section;
use base 'Podius::Component::Page';
sub get_own_property_types {
return [
[ sections => 'ComponentCollection' , 'Section' ],
[ products => 'VComponentCollection', 'Product' ],
[ articles => 'VComponentCollection', 'Article' ],
[ large_image => 'Image', 'images/auto/Section/default1.gif', 'Default Label' ],
[ small_image => 'Image', 'images/auto/Section/default2.gif', 'Default Label' ],
];
}
sub get_name {
my $self = shift;
return $self->phrase || $self->get_id;
}
1;
package Podius::Component::UserSet;
use base 'Podius::Component::Publishable';
sub get_own_property_types {
return [
[ users => 'ComponentCollection', 'User' ],
[ user_sets => 'ComponentCollection', 'UserSet' ],
];
}
1;
package Podius::Component::User;
use base 'Podius::Component::Publishable';
sub get_own_property_types {
return [
[ user_name => 'Scalar' ],
[ password => 'Password' ],
[ first_name => 'Scalar' ],
[ middle_name => 'Scalar' ],
[ last_name => 'Scalar' ],
[ email => 'Scalar' ],
[ phone => 'Scalar' ],
[ cellular => 'Scalar' ],
[ fax => 'Scalar' ],
[ addresses => 'ComponentCollection', 'Address' ],
[ shipping_address => 'VComponent', 'Address' ],
[ billing_address => 'VComponent', 'Address' ],
[ billing_info => 'ComponentCollection', 'BillingInfo' ],
[ order_details => 'ScalarTable', [ 'Product name', 'Color', 'Flavors', 'Quant', 'Price' ] ],
[ affiliate_id => 'Integer' ],
[ is_validated => 'Boolean' ],
];
}
1;
more component classes...
Populating data
|
Once the model is defined, it is the time for our content editors to
create useful content. This may be done using a web interface (podius.cgi).
|
Changing model of live data
At some point we realize that attaching one order per User was a mistake.
Possible steps to refactor our model:
- create new component class Order with property "details" of type
ScalarTable
- add new User property "orders" of type ComponentCollection
of Orders,
using script bin/admin/apply-new-component-properties
- programmatically create one Order component per "order_details"
and add it to "orders"
- delete obsolete User property "order_details"
use Podius;
use Podius::Shortcuts;
my $cache = create_component_cache();
my $center = $cache->get_component("Center", 1) or die "No Center";
$cache->begin_tx;
$center->foreach_user(sub {
my $user = shift;
my $order = $cache->create_component("Order");
$order->details($user->order_details);
$user->orders->add($order);
});
$cache->commit_tx;
Publishing content
One of the common task of content management systems is publishing the
content into different medias. I.e. converting certain samples of the
content into appropriate formats, like:
- HTML, for the site's web pages
- plain TEXT, to be sent in the email
- RSS or a different XML format, to feed to subscribers
Creating web design (template pages)
- separating presentation from data using template pages
- rich template language
- easily accessible data
- data filters (escape url, htmlize text)
- loops over elements
- separate template pages per different data types
- separate template sub-pages to increase reusability (footer, header)
Semi-static vus constantly-changed content
Semi-static content
- rarely changed sections with articles
- rarely added new articles
Changed content
- new shop products or constant price/specification/stock amount updates
- special needs: managing product variants (different flavours or seasonal sell-outs)
- special needs: managing translations of content parts
Rotated content
- new daily/weekly magazine edition
- special needs: keeping archive of historical content
Templating systems
There is a large number of templating systems available for any programming
language, with a different set of features and syntax.
Recursive syntax:
[[ FOREACH $article @articles
unmodified_content [[ $article.name ]] unmodified_content
]]
Flat syntax:
[[ FOREACH $article @articles ]]
unmodified_content [[ $article.name ]] unmodified_content
[[ END FOREACH ]]
Embeding template directives into the text:
Name is <% =$name %>
Embeding text into the template directives:
<% foreach $article (@articles) { print $article->title, "<br>" } %>
Podius: Publishing
Every Publishable component has property "page_names" - a
list of pairs (template page name, published page name). This list is
initialized from the project configuration, and may be changed or reset.
A component that is mapped directly into at least one web page may
better inherit from Page rather than pure Publishable, it
adds standard page attributes, like "keywords", "description", "title",
"body" and more.
See example for the main
Center component.
Neat features:
- more than one published entry per component:
- main web page
- secondary web page (short summary, or search element)
- RSS feeds
- multi-page templates:
- several continuation pages for long article
- section of items divided to many pages with Prev/Next navigation
Template syntax
<{ get_name }> # name of the current component
<a href="photos/<{ nick &escape_page_name }>.jpg"><{nick}></a>
<{ container get_name }> # name of the parent component
<{ container container get_id }> # id of the grand-parent
<{ has_property('english_name') ? english_name : name &translate }>
<h1><{ title &htmlize }></h1>
<div class="body"><{ body_as_html }></div>
Articles: <ul>
<{articles get_all ({
<li><a href="<{&link}>"><{title &htmlize}></a>
})}>
</ul>
Creating templates
Let's define 3 published pages for our main
Center
component:
generic.html is a standard
template; other two short templates may look like this:
best_products.txt - feed data of 10 chosen products:
<{
products get_first(10) ({<{name}>|<{title}>|<{price}>|<{sale_price}>})
}>
best_articles.txt - feed data of chosen articles:
<{
articles get_all ({<{date_created &format_date}>|<{name}>|<{title &remove_breaks}>})
}>
Static vus dynamic publishing
Static publishing
The recommended practice is to preview before real publishing. To
catch all kinds of errors in the data or templates before the content
riches the visitors.
Publishing may be done by content editors either using the web GUI or
from command line.
bin/admin/show-component-tree # show component names and ids
bin/admin/publish-component --recursive Center 1 # publish all components
bin/admin/publish-component --preview Article 2001 # preview one component
bin/admin/publish-component 2001,3001 # publish individual components
Dynamic publishing
A more risky method is to make all data available in real time. This
may be done using provided dynapub.cgi. The first "page_names"
entry of each component is considered its main page and main static link.
Modern world with Web 2.0
More and more community web sites appear that customize their web
pages to match the visitor preferences and even allow visitors to create
their own content.
- need of login and registration-by-email process (Podius::EmailOperations)
- building parts of content at the visit time accourding to visitor preferences
- conversation and awareness of site users with/about other site users
- allowing limited functionality for non-users (site guests)
- creating content in several languages (Multilingual language_clones)
- making all site interface texts translatable (Podius::Locale)
- auto-detecting and using the visitor preferred language
Compromise: static plus dynamic publishing
Apache::ASP is a nice framework to create dynamical sites in
perl. Still, if there are thousands or millions of visits daily, building
the same content again and again is not optimal. Solution:
- creating asp pages from templates (static publishing)
- building visitor-related stuff only in the asp (perl) code
- loading statically pre-published contents in the visit time
Possible CMS applications
- online magazine
- online shop
- numismatic catalog
- data center
- dating service
- narrow-topic or wide-open communities
- wiki
- blob
- what not...
|
|
References