02 Mar 2015

feedPlanet Plone

Plone.org: February Newsletter

Welcome to the Plone Newsletter where on or about the beginning of each month we send you the previous month's news and upcoming events from around the world.

02 Mar 2015 1:44pm GMT

28 Feb 2015

feedPlanet Plone

Alex Clark: Don't sudo pip install

How many times have you seen this?

28 Feb 2015 11:09pm GMT

Timo Stollenwerk: Continuous Integration for Plone

How we set up a new distributed CI infrastructure for Plone with Jenkins, Ansible, Jenkins Job Builder, and mr.roboto.

Continue reading on Medium »

28 Feb 2015 10:36am GMT

27 Feb 2015

feedPlanet Plone

Davide Moro: How to install Kotti CMS on Windows

Yes, as expected, you can install Kotti CMS also on Windows if you have this constraint!

What is Kotti

From the official doc:

"""A high-level, Pythonic web application framework based on Pyramid and SQLAlchemy. It includes an extensible Content Management System called the Kotti CMS.

Kotti is most useful when you are developing applications that:
  • have complex security requirements
  • use workflows, and/or
  • work with hierarchical data
"""

It is developer friendly and with a good user interface. You can easily extend it, develop new features or install one of the available third party modules (search for Kotti on https://pypi.python.org/pypi if you want to browse existing modules ready to be used). Heavily inspired by Plone (http://plone.org).
If you want to evaluate Kotti you can install it locally (no database installation is required, you can use SQLlite during evaluation or development).
Otherwise if you are particular lazy there is a working demo online with admin / qwerty administrator credentials:

Prerequisites

  • python (tested with python 2.7.9 but it should work also on newer versions)
  • Microsoft Visual C++ 9.0 available on the following url http://aka.ms/vcpython27 (needed for an issue with bcrypt)
  • virtualenv (suggested)

Installation steps

Once you have installed python from http://www.python.org you can start installing Kotti. I assume in this article that your Python installation path is C:\Python27.
Now create a new folder (it doesn't matter the name, in this article my folder name is just kotti):

> mkdir kotti
> cd kotti

Install virtualenv and create a new isolated python environment in your kotti dir:

> C:\Python27\Scripts\pip.exe install virtualenv> C:\Python27\Scripts\virtualenv.exe --no-site-packages .

Install Kotti and its requirements:

> Scripts\pip.exe install -r https://raw.github.com/Kotti/Kotti/stable/requirements.txt
> Scripts\pip.exe install Kotti

Put inside your kotti dir the app.ini file downloaded from:
Runs Kotti:

Scripts\pserve.exe app.ini
Starting server in PID 2452
serving on http://127.0.0.1:5000

Done!

Update 20150219: if you want to install Kotti as a standard Windows service see this tutorial: http://pyramid-cookbook.readthedocs.org/en/latest/deployment/windows.html.

Troubleshooting (tested on Windows Vista)

If Microsoft Visual C++ Compiler for Python 2.7 is not installed on your environment you'll get an error during the requirements installation phase (only on Windows):

> Scripts\pip.exe install -r https://raw.githubusercontent.co
m/Kotti/Kotti/stable/requirements.txt
....
Running setup.py install for py-bcrypt
building 'bcrypt._bcrypt' extension
error: Microsoft Visual C++ 9.0 is required (Unable to find vcvarsall.bat).
Get it from http://aka.ms/vcpython27
Complete output from command C:\Users\dmoro\kotti\Scripts\python.exe -c "imp
ort setuptools, tokenize;__file__='c:\\users\\dmoro\\appdata\\local\\temp\\pip-b
uild-mact2r\\py-bcrypt\\setup.py';exec(compile(getattr(tokenize, 'open', open)(_
_file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record c:\u
sers\dmoro\appdata\local\temp\pip-wcmy6c-record\install-record.txt --single-vers
ion-externally-managed --compile --install-headers C:\Users\dmoro\kotti\include\
site\python2.7:
running install

running build

running build_py

creating build

creating build\lib.win-amd64-2.7

creating build\lib.win-amd64-2.7\bcrypt

copying bcrypt\__init__.py -> build\lib.win-amd64-2.7\bcrypt

running build_ext

building 'bcrypt._bcrypt' extension

error: Microsoft Visual C++ 9.0 is required (Unable to find vcvarsall.bat).
Get it from http://aka.ms/vcpython27

You just need to install this requirement and all will work fine.

Troubleshooting 2 (tested on Windows 2008 R2 Server - updated 20150219)

You might experience other compilation errors on Windows due to different compiler versions, role management tool configuration, missing DLLs, environment variables (vcvars32.bat), missing header files, etc. The same C code that compiles fine on a Windows machine, on a different version of Windows could produce a compilation error (compiling under Windows is a pain).

Anyway the following links helped me a lot a install py-bcrypt under Windows 2008 R2 Server with Visual Studio 2008 Express (free version downloadable from http://go.microsoft.com/?linkid=7729279):

Links

Screenshots

Kotti's front page (from the public demo online):


Kotti's folder contents (from the public demo online), requires authentication:

All posts about Kotti


27 Feb 2015 11:17pm GMT

Davide Moro: Kotti - avoid types addable in content root

With Kotti CMS (http://kotti.pylonsproject.org/) you don't have to fight against the framework: after one or two days you'll love it and you will be productive.

You can add new content types mapped on database tables, extend existing ones, add one or more object actions, easy building of add and edit views without having to touch any html file.

Kotti is shipped with the pytest framework and I love it! The tests setup is very easy and you can mock or initialize your reusable fixtures with a dependency injection technique.

If your customer wants to use Windows, no problem:

How to prevent your content types to be added in content root

This blog post will explain how to prevent your content type to be added in the content root but only in Document types (they behave like folderish items too). What's the matter? The root itself is a Document.

My solution was similar to the following one, but a bit different:


# resources.py

from kotti.resources import TypeInfo
from kotti.resources import get_root
from kotti.resources import Content
...

class YourContentTypeInfo(TypeInfo):

def addable(self, context, request):
root = get_root()
if context == root:
return False
return super(YourContentTypeInfo, self).addable(context, request)

yourcontent_type_info_data = Content.type_info.copy(
name=u'YourContent',
title=_(u'YourContent'),
add_view=u'add_yourcontent',
addable_to=[u'Document'],
).__dict__.copy()
yourcontent_type_info = YourContentTypeInfo(**course_type_info_data)

class YourContent(Content):
""" A yourcontent type. """

implements(IDefaultWorkflow)

id = Column(Integer, ForeignKey('contents.id'), primary_key=True)
...

type_info = yourcontent_type_info

I tried to inherit all the default options and actions from the default Content's type info. This way you'll inherit all the backend menu actions.

Done!

Feedback

After using Kotti for a while I can tell that the feedback is absolutely positive. It is the right choice when you don't need a much more complex system like Plone. So join the Python, Pyramid and Kotti community and say love to Kotti!

https://twitter.com/davidemoro/status/558188730222383104

All posts about Kotti

27 Feb 2015 11:17pm GMT

Davide Moro: Kotti CMS - how to create a new content type with an image

If you want to create a new content type based on an existing one with Kotti you need to write few lines of code and zero html for the add and edit views: it is very simple (browse Kotti's resources.py and views code).


Basically you have to extend the existing content type shipped with Kotti and add your custom fields.

But let's suppose you need a new content type named ImageWithLink with the following fields:

In this case the implementation is more verbose compared to extend another content type (like the Document, but it is still an easy job).

resources.py

from zope.interface import implements
from kotti.resources import Image
from kotti.interfaces import IImage
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import Unicode


class ImageWithLink(Image):
implements(IImage)

id = Column(Integer, ForeignKey('images.id'), primary_key=True)
link = Column(Unicode(1000))

type_info = Image.type_info.copy(
name=u'ImageWithLink',
title=u'ImageWithLink',
add_view=u'add_image_link',
addable_to=['Document'],
)

def __init__(self, link=u"", **kwargs):
super(ImageWithLink, self).__init__(**kwargs)
self.link = link

The code is quite self-explaining: you create a new ImageWithLink class that inherits from Image. You only need to add your custom field named link and you initialize the link in the __init__ code after calling the super method.

views/content.py

import colander
from deform import FileData
from deform.widget import FileUploadWidget
from kotti.views.edit import ContentSchema
from kotti.views.edit.content import ImageEditForm
from kotti.views.edit.content import ImageAddForm
from kotti.views.form import validate_file_size_limit
from kotti.views.form import FileUploadTempStore
from kotti.views.form import AddFormView
from pyramid.view import view_config
from kotti_yourplugin import _
from kotti_yourplugin.resources import ImageWithLink
from kotti_yourplugin.validators import link_validator


def ImageWithLinkSchema(tmpstore):
""" File schema with no set title missing binding """
class ImageWithLinkSchema(ContentSchema):
file = colander.SchemaNode(
FileData(),
title=_(u'File'),
widget=FileUploadWidget(tmpstore),
validator=validate_file_size_limit,
)
link = colander.SchemaNode(
colander.String(),
title=_('Link'),
validator=link_validator,
missing=u'',
)

def after_bind(node, kw):
del node['tags']

return ImageWithLinkSchema(after_bind=after_bind)


@view_config(name='edit', permission='edit',
renderer='kotti:templates/edit/node.pt')
class ImageWithLinkEditForm(ImageEditForm):
def schema_factory(self):
tmpstore = FileUploadTempStore(self.request)
return ImageWithLinkSchema(tmpstore)


@view_config(name=ImageWithLink.type_info.add_view, permission='add',
renderer='kotti:templates/edit/node.pt')
class ImageWithLinkAddForm(ImageAddForm):
item_type = _(u"Banner Box")
item_class = ImageWithLink

def schema_factory(self):
tmpstore = FileUploadTempStore(self.request)
return ImageWithLinkSchema(tmpstore)

def save_success(self, appstruct):
# override this method (no filename as title
# like images)
return AddFormView.save_success(self, appstruct)

def add(self, **appstruct):
# override (no tags in our form)
buf = appstruct['file']['fp'].read()
filename = appstruct['file']['filename']
return self.item_class(
title=appstruct['title'] or filename,
description=appstruct['description'],
data=buf,
filename=filename,
mimetype=appstruct['file']['mimetype'],
size=len(buf),
)

Here the code is more complex. There is a dynamic schema definition with the Kotti's temp store implementation. Both the add and the edit form refer to this schema, with some overrides because our object does not behave like files or images.

validators.py
UPDATE 20150211: no need to write this validator. Use the url validator provided by colander instead (colander.url). Anyway you can use all the builtin colander validators or write your own validators.

import re
import colander
from kotti_yourplugin import _


VALID_PROTOCOLS = ('http',)
URL_REGEXP = r'(%s)s?://[^\s\r\n]+' % '|'.join(VALID_PROTOCOLS)


def link_validator(node, value):
""" Raise a colander.Invalid exception if the provided url
is not valid
"""
def raise_invalid_url(node, value):
raise colander.Invalid(
node, _(u"You must provide a valid url."))
if value:
if not re.match(URL_REGEXP, value):
raise_invalid_url(node, value)

Here you can see an example of link validator based on a regular expression. This validator decorates our link field of the ImageWithLink schema.

Obviously you need to add in your kotti_configure method your ImageWithLink in the kotti.available_types settings.

__init__.py

def kotti_configure(settings):
settings['pyramid.includes'] += ' kotti_yourplugin'
settings['kotti.available_types'] += ' kotti_yourplugin.resources.ImageWithLink'


and enable your configurator in your .ini file:

kotti.configurators = mip_course.kotti_configure


And what about the default view of your content types? If you visit an ImageWithLink box it will behave like an image: it inherits the default view of the image (you should customize it adding the link on the image, very simple: not showed in this blog post), no need to deal with the image resize machinery, etc.

As you can see, Kotti is a flexible solution if you need a simple but powerful CMS solution based on Python, Pyramid and SQLAlchemy. You may consider it as a simple framework (but easy to understand, don't be scared by the word framework. It is really developer friendly). If you are curious about how to manage contents with Kotti you may play with the demo online: http://kottidemo.danielnouri.org/ (admin - qwerty).

All posts about Kotti

27 Feb 2015 11:16pm GMT

Davide Moro: Kotti CMS - workflow reference

Yet another blog post about Kotti CMS (http://kotti.pylonsproject.org/): this time I'm going to talk about workflows and security.

Workflows in Kotti are based on repoze.workflow. See http://docs.repoze.org/workflow/ for further information. Basically you can use an xml file (zcml) in order to describe your workflow definition. You can see an example here: https://github.com/Kotti/Kotti/blob/master/kotti/workflow.zcml. A you can see it is quite straightforward adding new states, new transitions, new permissions, etc. You can easily turn your 2-states website workflow into a 3-states website workflow with reviewers or turn Kotti app into an intranet application.

The default workflow definition is loaded from your project .ini file settings (using the kotti.use_workflow settings). The kotti.use_workflow setting's default value is:

kotti.use_workflow = kotti:workflow.zcml

but can change change default workflow for the whole site, register new workflows related to specific content types or disable it as well.

Anyway, if you need to write a Python based CMS-ish application with hierarchical contents, custom content types, workflows, security, global and local ACL (sharing permissions), pluggable and extensible, based on relational databases, developer friendly, with a simple UI, etc... Kotti is your friend!

How to disable the default workflow

Kotti is shipped with a simple workflow implementation based on private and public states. If your particular use case does not require workflows at all, you can disable this feature with a non true value. For example:

kotti.use_workflow = 0

How to override the Kotti's default workflow for all content types

The default workflow is quite useful for websites, but sometimes you need something of different. Just change your workflow setting and point to your zcml file:

kotti.use_workflow = kotti_yourplugin:workflow.zcml

The simplest way to deal with workflow definitions is:

If your site already has content and you configure it use a workflow for the first time, or you use a different workflow than the one you used before, run the kotti-reset-workflow command to reset all your content's workflow.

How to enable the custom workflow for images and files

Images and files are not associated with the default workflow. If you need a workflow for these items you need to attach the IDefaultWorkflow marker interface.

You can add the following lines in your includeme function:

from zope.interface import implementer
from kotti.interfaces import IDefaultWorkflow
from kotti.resources import File
from kotti.resources import Image
...

def includeme(config):
...
# enable workflow for images and files
implementer(IDefaultWorkflow)(Image)
implementer(IDefaultWorkflow)(File)
...

How to assign a different workflow to a content type

In this kind of situation you want to use the default workflow for all your types and a different workflow implementation for a particular content type.

You'll need to:

.ini file (optional)

kotti_boxes.use_workflow = kotti_boxes:workflow.zcml

__init__.py

from pyramid.i18n import TranslationStringFactory
from kotti import FALSE_VALUES


def includeme(config):
...
workflow = config.registry.settings.get('kotti_boxes.use_workflow', None)
if workflow and workflow.lower() not in FALSE_VALUES:
config.begin()

config.hook_zca()
config.include('pyramid_zcml')
config.load_zcml(workflow)
config.commit()

...



workflow.py
From the repoze.workflow documentation: """A workflow is unique in a system using multiple workflows if the combination of its type, its content type, its elector, and its state_attr are different than the combination of those attributes configured in any other workflow."""
Depending on how specific is your combination you may need to implement an elector (a function that returns True or False for a given context).

from kotti_boxes.interfaces import IBoxWorkflow


def elector(context):
return IBoxWorkflow.providedBy(context)

workflow.zcml

<configure xmlns="http://namespaces.repoze.org/bfg"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="Kotti">

<include package="repoze.workflow" file="meta.zcml"/>

<workflow
type="security"
name="simple"
state_attr="state"
initial_state="private"
content_types="kotti_boxes.interfaces.IBoxWorkflow"
elector='kotti_boxes.workflow.elector'
permission_checker="pyramid.security.has_permission"
>

<state name="private" callback="kotti.workflow.workflow_callback">

<key name="title" value="_(u'Private')" />
<key name="order" value="1" />

<key name="inherit" value="0" />
<key name="system.Everyone" value="" />
<key name="role:viewer" value="viewbox view" />
<key name="role:editor" value="viewbox view add edit delete state_change" />
<key name="role:owner" value="viewbox view add edit delete manage state_change" />

</state>

...


<transition
name="private_to_public"
from_state="private"
to_state="public"
permission="state_change" />
...


</workflow>

</configure>

All posts about Kotti

27 Feb 2015 11:16pm GMT

Davide Moro: Kotti CMS - how to store arbitrary data with annotations

With Kotti CMS you can extend existing types inheriting from a base class (eg: Document) and obtain another type of object (eg: MyDocument) with new fields, new workflows, custom views, custom addability conditions, etc.

But sometimes you may want to add a custom field to one or more resources, without having to create a new type. For example you might want to add a colour attribute to all existing Document objects, let's imagine a simple select widget with few colours that will be used for adding a class depending on the choosen colour.

By default Kotti is shipped with an annotations column that can be used to store arbitrary data in a nested dictionary.

You can store arbitrary data in the nested dictionary with a syntax similar to the following one:

context.annotations['SOMEKEY'] = VALUE

and read annotations with:

context.annotations

All you need to do is overriding the add and edit form of your target class. With Pyramid is quite easy to extending an existing application and override views, assets, routes, etc. See http://docs.pylonsproject.org/docs/pyramid/en/latest/narr/extending.html for further info.

Here you can see one possible implementation:

from pyramid.view import view_config
import colander
from deform.widget import SelectWidget
from kotti_actions.views.edit.actions.link import (
LinkActionAddForm as OriginalLinkActionAddForm,
LinkActionEditForm as OriginalLinkActionEditForm,
)
from kotti_actions.resources import (
LinkAction
)
...

colours = [
('', 'Select'),
('red', 'Red'),
('brown', 'Brown'),
('beige', 'Beige'),
('blue', 'Blue'),
]


def add_colour(schema):
schema['colour'] = colander.SchemaNode(
colander.String(),
title=_('Colour'),
widget=SelectWidget(values=colours),
missing=u"",
)

@view_config(name=LinkAction.type_info.add_view, permission='add',
renderer='kotti:templates/edit/node.pt')
class LinkActionAddForm(OriginalLinkActionAddForm):
""" Form to add a new instance of CustomContent. """

def schema_factory(self):
schema = super(LinkActionAddForm, self).schema_factory()
add_colour(schema)
return schema

def add(self, **appstruct):
colour = u''
try:
colour = appstruct.pop('colour')
except KeyError:
pass
obj = super(LinkActionAddForm, self).add(**appstruct)

obj.annotations['colour'] = colour
return obj


@view_config(name='edit', context=LinkAction, permission='edit',
renderer='kotti:templates/edit/node.pt')
class LinkActionEditForm(OriginalLinkActionEditForm):
""" Form to edit existing calendars. """

def schema_factory(self):
schema = super(LinkActionEditForm, self).schema_factory()
add_colour(schema)
return schema

def before(self, form):
super(LinkActionEditForm, self).before(form)
colour = self.context.annotations.get('colour')
if colour:
form.appstruct.update({'colour': colour})

def edit(self, **appstruct):
super(LinkActionEditForm, self).edit(**appstruct)
self.context.annotations['colour'] = appstruct['colour']


Now our LinkAction add and edit form will have an additional select with our colours.

All posts about Kotti

27 Feb 2015 11:16pm GMT

Davide Moro: Kotti CMS - how to turn your Kotti CMS into an intranet

In the previous posts we have seen that Kotti is a minimal but robust high-level Pythonic web application framework based on Pyramid that includes an extensible CMS solution, both user and developer friendly. For developer friendly I mean that you can be productive in one or two days without any knowledge of Kotti or Pyramid if you already know the Python language programming.

If you have to work relational databases, hierarchical data, workflows or complex security requirements Kotti is your friend. It uses well know Python libraries.

In this post we'll try to turn our Kotti CMS public site into a private intranet/extranet service.

I know, there are other solutions keen on building intranet or collaboration portals like Plone (I've been working 8 years on large and complex intranets, big public administration customers with thousands of active users and several editor teams, multiple migrations, etc) or the KARL project. But let's pretend that in our use case we have simpler requirements and we don't need too complex solutions, features like communities, email subscriptions or similar things.

Thanks to the Pyramid and Kotti's architectural design, you can turn your public website into an intranet without having to fork the Kotti code: no forks!

How to turn your site into an intranet

This could be an hard task if you use other CMS solutions, but with Kotti (or the heavier Plone) it will requires you just 4 steps:

  1. define a custom intranet workflow
  2. apply your custom worklows to images and files (by default they are not associated to any workflow, so once added they are immediatly public)
  3. set a default fallback permission for all views
  4. override the default root ACL (populators)

1 - define a custom intranet workflow

Intranet workflows maybe different depending on your organization requirements. It might be very simple or with multiple review steps.

The important thing is: no more granting the view permission for anonymous users, unless you are willing to define an externally published state

With Kotti you can design your workflow just editing an xml file. For further information you can follow the Kotti CMS - workflow reference article.

2 - apply your custom workflow to images and files

By default they are not associated to any workflow, so once added they are immediately public.

This step will requires you just two additional lines of code in your includeme or kotti_configure function.

Already described here: Kotti CMS - workflow reference, see the "How to enable the custom workflow for images and files" section.

3 - set a default fallback permission

In your includeme function you just need to tell the configurator to set a default permission even for public views already registered.

I mean that if somewhere into the Kotti code there is any callable view not associated to a permission, it won't be accessible by anonymous after this step.

In your includeme function you'll need to :

def includeme(config):
...
# set a default permission even for public views already registered
# without permission
config.set_default_permission('view')

If you want to bypass the default permission for certain views, you can decorate them with a special permission (NO_PERMISSION_REQUIRED from pyramid.security) which indicates that the view should always be executable by entirely anonymous users, regardless of the default permission. See:

4 - override the default root ACL (populators)

The default Kotti's ACL associated with the root of the site

from kotti.security import SITE_ACL

gives view privileges to every user, including anonymous.
You can override this configuration to require users to log in before they can view any of your site's pages. To achieve this, you'll have to set your site's ACL as shown on the following url:

You'll need you add or override the default populator. See the kotti.populators options here:

Results

After reading this article you should be able to close your Kotti site for anonymous users and obtaining a simple, private intranet-like area.

Off-topic: you can also use Kotti as a content backend-only administration area for public websites, with a complete decoupled frontend solution.

Useful links

All posts about Kotti

27 Feb 2015 11:15pm GMT

23 Feb 2015

feedPlanet Plone

BubbleNet: Purging some CMFEditions versions

A customer had instantiated a portlet on its homepage. He then made several changes that got saved in version history.

Later, the add-on that provided the portlet was uninstalled from the site.

When saving that page, the user would obviously get a broken object error.

To fix this issue, it was necessary to both remove the broken portlet on the given page and to delete (purge) all CMFEditions versions of that page that were holding an instance of the broken portlet.

While the former was easy (just use manage portlets page to remove the portlet), the latter took me much more time.

I document how I did it for anyone that might need to do something similar.

Through instance debug, I was able to find which versions were broken with a loop like:

>>> for i in range(my_page.version_id):
...     print i
...     portal_repository.isUpToDate(my_page, i)

I purged the given versions with:

>>> from Products.CMFEditions.utilities import dereference
>>> dereference(my_page)
(<ATDocument at /Plone/my_page>, 1)
>>> for i in range(77,15,-1):
...     portal_historiesstorage.purge(1, i, metadata={'sys_metadata': {'comment':'purge version with broken portlet'}}, countPurged=False)

I also had to update the version_id before comitting:

>>> from transaction import commit
>>> my_page.version_id = 15
>>> commit()

23 Feb 2015 1:14pm GMT

T. Kim Nguyen: The open horizon

11 years of Plone at UW Oshkosh, and now a new start

23 Feb 2015 4:36am GMT

20 Feb 2015

feedPlanet Plone

Andreas Jung: Docker - the pain of finding the right distribution+kernel+hardware combination

20 Feb 2015 12:38pm GMT

Andreas Jung: XML Director 0.4.0 release/Newsletter #4

XML Director is a Plone-based XML content-management-system (framework) backed by eXist-db or BaseX.

20 Feb 2015 9:46am GMT

19 Feb 2015

feedPlanet Plone

Six Feet Up: How We Made our Site Responsive

responsive design header

The following post was pulled from our Sixie's author archive and has been updated to reflect current practice and standards. Written by our Senior Template Developer, check out how we added Responsive Design the Six Feet Up website and learn a few tricks along the way!

You can read and see plenty of articles about mobile and responsive development, but this is quite different than actually making an existing production site fully responsive. Here's how I went at it for our own Six Feet Up site, which is powered by the open source CMS Plone.

The idea was to take our existing design and modify it to look good on mobile browsers. Previously, the site was using a fixed width at 980px wide. The decision of how the site would look on narrow browsers first went to the marketing team. They decided how the elements would rearrange, and which would be dropped, if any. They came up with a wireframe of the narrow design.

Six Feet Up's mobile drop-down menu

I then chose how everything would fall in to place for all the in-between widths. The site continues to be fixed for browsers greater than 1280px, but becomes fluid as you get narrower than that. I then set break points at widths when either something broke the layout, or a column became too narrow.

The biggest code changes in the site happened to the main_template. Having been upgraded from old versions of Plone, we were still using the tabled version of the site. The table cells were converted to divs, and the divs were arranged to match how they would appear for the narrowest view. The order became content column, right column, left column. Nearly all other site changes were done in CSS, including those for the global navigation.

A common challenge for developers working on a responsive design can be to get it to actually work in a mobile browser. You can do all your testing using Firefox, and get a decent responsive design together, but it may not work in any other browser. You may need to add a meta tag for viewport:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />

If you are working with a newer version of Plone, this tag is already part of the main_template.

For the CSS I prefer to keep all my responsive styles in their own stylesheet so they are easier to find, and don't bloat the primary stylesheet. This can then be added to the registry as its own entry. The media field should be set to all or set the max-width, if all the responsive styles fit in that restriction: screen and (max-width: 1280px). You can check out this site's responsive styles at http://www.sixfeetup.com/responsive.css.

Once you know these tricks, future experiences with responsive design should be much faster. For me, writing the CSS was the easiest part. One of the most time-consuming parts will likely be getting the client and designer to decide how the site should display at various break points.

Do you have questions about how to integrate responsive design into your site? Leave us a message in the comments section below or feel free to contact us with any questions.

19 Feb 2015 12:00pm GMT

15 Feb 2015

feedPlanet Plone

Jazkarta Blog: Adding SCORM packages to Open edX via SCORMCloud and LTI

Do you want to be able to use existing training content in an Open edX course? SCORM - the "Sharable Content Object Reference Model" - is a set of interoperability standards for e-learning software that governs how online learning content and Learning Management Systems (LMSs) communicate with each other. It allows units of online training material to be shared across e-learning systems.

Open edX doesn't support SCORM natively yet, but here at Jazkarta we've successfully integrated SCORM Cloud and Open edX. SCORM Cloud is a third party service that wraps e-learning content in a SCORM dispatch package that can be embedded into an LMS. The gist of this integration is as follows:

  1. Export a learning module from your e-Learning authoring tool as a SCORM package.
  2. Upload SCORM package to SCORM Cloud.
  3. Configure SCORM Cloud dispatch to serve them up as LTI (Learning Tools Interoperability) components.
  4. Use the LTI component in Open edX to add these packages, which are actually being hosted on SCORM Cloud.

The user doesn't need to login again as this is transmitted via LTI, and the activity is passed back into edX for grading purposes.

Here is a detailed how-to for people who would like to duplicate our work. I found some sample courses in this blog article and used the "Picture Perfect_Simulation Sample" course. Open that course in Adobe Captivate and follow these instructions for prepping, uploading, and using this sample in Open edX. (You can download Adobe Captivate 8 here - a free 30 day trial is available by clicking the 'Try' link on that URL.)

Mark interactive elements as graded and assign points

wpid898-media_1423439432060.png

For each interactive element that you want graded, you need to click on the element, click on the Properties icon (in the upper right corner of the screen), mark it as graded in the properties panel, assign a point value that will count towards the grade, and check the Report Answers checkbox.

Set project end action to close project

wpid895-media_1423432715374.png

In Publish Settings -> Project -> Start and End, set the Project End Action to "Close project" (I think it defaults to Stop project). That way the window showing the assessment will automatically close when it's done playing.

wpid896-media_1423438279635.png

In Publish Settings -> Project -> Quiz -> Reporting, set the Standard form to SCORM 2004, and then click the Configure button.

Give the course an identifier and title

wpid897-media_1423438365903.png

You can optionally give your course an identifier and title. If you skip this step, then all of your courses will have the default identifier "Captivate E-Learning Course" and it will be hard to differentiate between the different courses.

Export from Captivate

wpid899-media_1423440309394.png

From the File menu, choose Publish… and make sure the eLearning output value is set to SCORM 2004, which you should have already set in the previous step. Click the Publish button and it will create a .zip file in the location you specified.

Upload to SCORMCloud

wpid900-media_1423441153174.png

Create a free account at SCORMCloud.com and from the Add Content section of the dashboard, upload the .zip file that you just created.

Tell SCORM Cloud to make it available via LTI

wpid901-media_1423441286007.png

Once the content has been uploaded, you need to create a dispatch that will be used to make it available via LTI.

Create a dispatch

wpid902-media_1423441822304.png

If the first time, click Create New Destination.

Create a destination

wpid903-media_1423441862466.png

Enter 'Open EdX' as the name. On subsequent times you can click Add Existing Destination and choose Open EdX. This name can really be anything - it doesn't need to be 'Open edX'.

Export dispatch as BLTI

wpid904-media_1423442143729.png

From the Dispatch screen, click on the Dispatch that you just created, and under Export your dispatch, click the BLTI button

Capture the BLTI information

wpid905-media_1423442253917.png

You should see a pop-up with information about the BLTI endpoint URL, key and secret key. Make a note of the URL, Key, and Secret that it displays

Enable LTI in your Open edX course

wpid906-media_1423446914909.png

Login to edX Studio and go to your course, and select Settings -> Advanced Settings. For the Advanced Module List, add "lti" to enable adding LTI components to your course.

Add an LTI passport entry

wpid907-media_1423447130599.png

On the same Advanced Settings page, scroll down to the section entitled LTI Passports, and add a new entry in the form:
lti_id:client_key:client_secret where the client_key and client_secret are the key and secret that you got when you made the BLTI dispatch in SCORM Cloud. The LTI ID can be anything (no spaces) - it's just an identifier that will be used when we add an individual LTI component to our edX course.

So for our last example, it would be:

[
"pictureperfect:5f82ddc4-ff27-45ab-b2ab-02fa929e9e34:MHDEcqX4HTz6ZOk6FJNpdxxxxxxxxxxxxxx"
]

Note: you need to add a new entry for each course in SCORM Cloud that you want to embed in an edX course. It's annoying that this is an extra step required for each item. Open edX assumes that an LTI provider will have one key/secret combo for all courses, but SCORM Cloud uses a different one for each course.

Add a new LTI component in your course

wpid908-media_1423447746722.png

Go to your course outline: Content -> Outline and navigate to where you want to add the LTI component containing your SCORM Cloud exercise/quiz. Click the New Unit button.

Add an advanced component

wpid909-media_1423447853259.png

If you've enabled Advanced Components for your course, you should see the Advanced Component button appear when you add a new unit.

Choose the LTI component

wpid910-media_1423447963126.png

Edit the component to set the values

wpid911-media_1423448041431.png

Configure the LTI component settings

wpid912-media_1423448220826.png

For LTI Application Information, you can type "SCORM Cloud" or whatever you want in this field. For LTI ID, this needs to match exactly, the LTI ID that you used in the LTI passports entry. The LTI URL for SCORM Cloud should be set to http://cloud.scorm.com/sc/blti

The Open in New Page setting should be set to True.

More LTI component settings

wpid916-media_1423449382811.png

Make sure that you set Scored to be True and assign a weight that you want it to have on the total grade. You can also set a Display Name if you want.

Preview or Publish your changes

media_1424039696280.png

Once you've added the component, you need to click the Preview Changes button or Publish it to your course.

Login as a student and try the exercise

wpid914-media_1423449098174.png

Now we can login as a student and test it out. When you navigate to the page, there is a link "View resource in a new window". Click on this button to launch the SCORM Cloud exercise in a new window.

Take the exercise

wpid913-media_1423448750594.png

You then take the exercise and SCORM Cloud is tracking your activity.

Complete the exercise

wpid915-media_1423449188409.png

At the end of the exercise, it will compute a score for you, and if the score is above the threshhold to get a passing grade, then get a "Pass" grade.

See score in edX progress page

media_1424034133393.png

This grade is sent back into edX and can be seen on the student's Progress page. Please note that these scores don't match because I took a screenshot of a different student.

Caveats

1. Make sure that your Open edX site is at an internet address that can be reached by SCORM Cloud. This means basic auth must be disabled and if you're using SSL, that you have a real SSL certificate, not a self-signed certificate.

2. A fix to the edX code which parses the SCORM Cloud request handle a non-standard (well, old standard) XML namespace that it uses. https://github.com/jazkarta/edx-platform/commit/ebcf93002557176102df2bf93ab79a979aaf0fba

3. A fix for edX's validation of the oauth signature (https://github.com/edx/edx-platform/pull/5016)

Get involved

We invite you to join the conversation on the edx-code mailing list about SCORM support in edX, and feel free to reach out and contact us if you are looking for professional help with edX.

Or if you want to take Open edX for a test-drive, you can get a free trial from our partner Appsembler.


Tagged: e-learning, edX, LTI, openedx, scorm, SCORM Cloud

15 Feb 2015 11:19pm GMT