Package: nti.contentratings
nti.contentratingsImportantThis is a friendly fork ofcollective.contentratings,
adding support for Python 3 and zope.component 5.The API and usage remain the same.This package provides an infrastructure for adding ratings to Zope
content.  It supports multiple categorized ratings per content object
and includes views for displaying those ratings.  For Plone rating
support, please seeplone.contentratings.Detailed DocumentationContent RatingsThis is a simple python package driven by Zope 3, which lets users
(including un-authenticated users) rate content.  It provides a set of
interfaces, adapters and views to allow the application of ratings to
any IAnnotatable object.Dependencies:BTreespersistentzope.annotationszope.app.contentzope.app.testingzope.componentzope.componentvocabularyzope.containerzope.interfacezope.lifecycleeventzope.locationzope.schemazope.talesAll of these packages are included in Zope 2.12+ and Plone 4.0+.  This
package is tested with Plone 4.0-4.3.Using contentratings in Your Packages or ProductsFirst install it somewhere in yourpython path(not in your Products
directory), $INSTANCE_HOME/lib/python may be a good place to start using it
with zope.  It can be installed as an egg from the Python Cheeseshop (PyPI).You’ll need to load the zcml for this package, so make sure that the
configure.zcml for your application contains:<include package="contentratings" />If you want to allow some content to be rated you must mark it asannotatable.  This is because the Rating Storage is contained in an
annotation on the content object.  The standard way to do this is to
to add the following to your product’s configure.zcml:<content class=".content.MyContentClass">
  <implements
      interface="zope.annotation.interfaces.IAttributeAnnotatable"
      />
</content>Rating CategoriesThis package provides an infrastructure for definingRating
Categories.  ARating Categoryis an object implementing theIRatingCategoryinterface, specifying atitle,description(for
the UI),view_name(how the rating should be rendered and managed in
the UI), TALES expressions which determine when ratings can be viewed
or set (read_exprandwrite_expr), anorder(for the UI), and
finally astorage(a factory which creates a persistent
implementation of a rating API to be stored in an annotation). All of
these attributes excepttitleare optional, with sensible default
values provided.  Any object may have multiple Rating Categories
applied to it each registered with a uniquename.The Default CategoriesThis package’s default configuration provides two rating categories.
One for user ratings, and one for editorial ratings.  They are
registered for the marker interfacesIUserRatableandIEditorRatablerespectively.  The TALES expressions used to
determine when they apply are designed to work with objects contained
in a Zope 2 CMF application (primarily for backwards compatibility
with older versions ofcontentratingswhich used direct permission
checks). Unless they want to allow all users to set and
read the ratings, other applications will need to define categories with
custom conditions.Let’s demonstrate how these categories might be used.  We need to
create some content and mark it with our marker interface:>>> from contentratings.tests import SampleContainer
>>> content = SampleContainer()
>>> from contentratings.interfaces import IUserRatable
>>> from zope.interface import alsoProvides
>>> alsoProvides(content, IUserRatable)Now we can adapt to the rating category using the IUserRating interface:>>> from contentratings.interfaces import IUserRating
>>> adapted = IUserRating(content)
>>> adapted.title
u'User Rating'
>>> float(adapted.averageRating)
0.0
>>> rating = adapted.rate(7.0)
>>> float(adapted.averageRating)
7.0
>>> adapted.numberOfRatings
1
>>> rating = adapted.rate(8.0, 'me')
>>> float(adapted.averageRating)
7.5
>>> adapted.numberOfRatings
2For more details on the IUserRating API seetests/userstorage.txt.Editorial ratings are applied similarly, but have a much simpler
implementation:>>> from contentratings.interfaces import IEditorRatable, IEditorialRating
>>> alsoProvides(content, IEditorRatable)
>>> adapted = IEditorialRating(content)
>>> adapted.title
u'Editor Rating'
>>> adapted.rating is None
True
>>> adapted.rating = 6.0
>>> float(adapted.rating)
6.0
>>> adapted.rating = 8.0
>>> float(adapted.rating)
8.0Seetests/editorialstorage.txtfor details on how the
IEditorialRating API works.Let’s remove these markers now so that we can examine custom
categories:>>> from zope.interface import noLongerProvides
>>> noLongerProvides(content, IUserRatable)
>>> noLongerProvides(content, IEditorRatable)
>>> IUserRatable(content) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ('Could not adapt', ...)
>>> IEditorialRating(content) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ('Could not adapt', ...)Custom Rating CategoriesThere are two ways to create a new rating category, declaratively
using ZCML, or programatically using the category factory directly
from python.  Let’s look at the ZCML way first.  To make it work we
need to enable the zcml directive:>>> from zope.configuration import xmlconfig
>>> import contentratings
>>> context = xmlconfig.file('meta.zcml', contentratings)Loading the package configuration will do the above automatically.Now we register our rating category using thecontentratings:categorydirective:>>> context = xmlconfig.string("""
... <configure
...    xmlns:contentratings="http://namespaces.plone.org/contentratings">
...  <contentratings:category
...      for="zope.container.sample.SampleContainer"
...      title="My Rating Category"
...      />
... </configure>""", context=context)Here we have made use of all of the category default values.  As a
result we have registered a category which uses the default ZODB
storage andIUserRatingAPI, with no restrictions on who can get
and set ratings.  We can verify this easily since the categories are
simply adapters providing the rating interface provided by the
(default) storage:>>> from contentratings.interfaces import IUserRating
>>> content = SampleContainer()
>>> adapted = IUserRating(content)
>>> IUserRating.providedBy(adapted)
True
>>> adapted.context is content
True
>>> adapted.title
u'My Rating Category'Note that because we provided no name in the configuration, the
adapter was registered as the default (unnamed) adapter.  The name of
the category is the name under which the adapter is registered and it
is stored in the category’s name attribute:>>> adapted.name
''To provide multiple categories, just register them with unique names:>>> context = xmlconfig.string("""
... <configure
...    xmlns:contentratings="http://namespaces.plone.org/contentratings">
...  <contentratings:category
...      for="zope.container.sample.SampleContainer"
...      title="My Other Rating Category"
...      name="other"
...      />
... </configure>""", context=context)
>>> from zope.component import getAdapter
>>> adapted = getAdapter(content, IUserRating, name=u'other')
>>> adapted.title
u'My Other Rating Category'
>>> adapted.name
u'other'If we wanted to accomplish the same thing programatically, we could
instantiate the factory directly and register it as an adpater:>>> from contentratings.category import RatingsCategoryFactory
>>> category = RatingsCategoryFactory(title=u'Another Title', name=u'another')
>>> from zope.component import provideAdapter
>>> provideAdapter(category, adapts=(SampleContainer,), provides=IUserRating,
...                name=u'another')
>>> adapted = getAdapter(content, IUserRating, name=u'another')
>>> adapted.title
u'Another Title'
>>> adapted.name
u'another'This involves some redundancy, since the interface provided by the
storage has to be explicitly declared, and the category name has to be
provided twice.  Otherwise they are equivalent.Note that categories are adapters, and adapters may only be registered under
the same name for different interfaces/classes.  As usual, for a given name,
the adapter registered for the most specific interface will be chosen.The Rating ManagerWhen the adapter corresponding to a given rating category is
queried, the object returned is not actually aRating Categoryitself, but aRating Manager:>>> adapted # doctest: +ELLIPSIS
<contentratings.category.RatingCategoryAdapter ...>
>>> from contentratings.interfaces import IRatingManager
>>> IRatingManager.providedBy(adapted)
TrueThe Rating Manager provides the API of the storage, and also many of
the attributes of the category.  It protects direct access to the
storage by checking the expressions specified for the category.  The
manager is implemented as a multi-adapter on the category and the
context, but generally it should not be retrieved directly.  The
category adapter is responsible for retrieving it.  The manager is
responsible for setting up the category specific storage on the
content object.>>> adapted.category # doctest: +ELLIPSIS
<contentratings.category.RatingsCategoryFactory ...>
>>> adapted.context # doctest: +ELLIPSIS
<zope...container.sample.SampleContainer ...>
>>> adapted.storage # doctest: +ELLIPSIS
<contentratings.storage.UserRatingStorage ...>
>>> isinstance(adapted.storage, adapted.category.storage)
TrueSince the rating manager is responsible for security checks and
populating the TALES expression context, it is very likely that
applications will want to replace this component (locally or for
specific content) with a subclass to provide application specific
security.The ViewsThe Rating ViewsEach category has an associatedview_namewhich is simply the name
of a view registered for the rating interface (e.g. IUserRating) to be
used when rendering the category in the UI.  These are looked up on
the Rating Manager and have access to theIRatingManagerAPI, as
well as the protected rating storage API, as provided by the
manager (e.g. IUserRating).Resuable base classes for rating views are provided inbrowser/basic.py(BasicUserRatingViewandBasicEditorialRatingView).  These views use a named vocabulary to
validate input and use the IRatingManager API to determine who can and
cannot rate content.  The package configuration provides a few rating
views by default:ratings_view (default):  A rating using 1-5 large (25px) stars
small_stars: A rating using 1-5 small (10px) stars
three_small_stars: A rating using 1-3 small (10px) starsThese are each highly customizable using CSS. They are all registered
for IUserRating.  Additionally, there is arating_viewis registered
for IEditorialRating.The views are responsible for looking up a rating vocabulary and
validating user input, as well as rendering the user interface.  The
security is enforced by theRating Managerused by the view, however
the view may go directly to the storage from rating manager if it
wants to override the expression checking (e.g. showing a user their
own ratings, though they cannot see others).  Creating new views
(e.g. non-starred ratings) is quite simple.A utility is provided for efficiently determining a reusable session
key in a generic manner.  This can be used to prevent repeat voting
from anonymous users.  Applications which implement their own
anonymous session tracking mechanisms may override this utility
locally if desired.The Aggregator ViewsThere are also views which find all the rating categories available on
the content object being viewed, rendering them in order.  These are
intended to be used within a viewlet, portlet, macro, or similar.  The
aggregator view for user ratings is calleduser-ratingsand the one
for editorial ratings is callededitorial-ratings.The StorageThough theUserRatingStorageshould be sufficient fr most usecases,
this package provides a simplemechanism for using custom objects for
storing ratings.  Two storage factory implementations are included,
both of which use the ZODB for storing ratings: one implementing theIUserRatinginterface and the other theIEditorialRatinginterface.  The former is intended to be used for any content which
will be rated (or voted on) by multiple users.  The latter stores a
single “editorial” rating on content, and exists primarily for
backwards compatibility.A custom sotrage factory (possibly sub-classed from one of the included
implementations), can be specified using thestorageattribute of
the zcml directive, orstorageparameter of the rating category factory.Not only are the storages replaceable, they can implement completely
custom APIs for managing ratings.  Though the need for this is is
probably limited, you may create a custom storage API by making an
interface for managing ratings, and having that interface provideIRatingType.  See the storage documentation intests/for more
information.Why a New Rating PackageThere are already theATRatings,lovely.rating,iqpp.rating, andiqpp.plone.ratingpackages, why do we need another package?First, contentratings preceeds all of those except ATRatings, which is
useful only under Plone with Archetypes content.contentratingswas
originally a very simple package intended to make it easy for
developers to add ratings to their products and applications.
However, there appears to be much demand for an end-user product to
facilitate adding ratings to existing content objects.Unfortunately, none of these packages offer direct support for
multiple ratings on a single content object, which appears to be a
common need.  It also (along withlovely.rating) decouples the
rating scoring system from the view in a manner which is probably
undesirable for a product which wants to allow user customization of
ratings.  Changing these packages to support these use-cases would
have required a complete rewrite.  As a result, I rewrote the simplest
(and most familiar) of these rating packages to support these
usecases, and also created a new package to integrate this new
functionality for Plone end-users (seeplone.contentratings).ToDoProvide view customization examplesMake the views work with Zope 3 authenticationPort the KSS view from plone.contentratings into contentratings (it
currently depends on some plone KSS commands)CreditsAuthor:Alec Mitchell<apm13@columbia.edu>Contributors:Maurizio Delmonte(feel free to add your name above if you have made significant contributions)Thanks To:Geoff Davisauthor ofATRatingsfrom which icons and ideas were
stolen.Philipp von Weitershausenauthor ofWeb Component Development with Zope 3which provides a nice example
of an annotation based rating product, which was the starting point for
this implementation.Kai Diefenbachauthor ofiqpp.plone.ratingfrom which other icons
and UI ideas were stolen.Some icons are fromMark James’Silk icon set 1.3The star rating is based onCSS Star Rating Reduxviaiqpp.plone.ratingChangelog1.2.0 (2021-04-13)Fixed XSS vulnerability on rating action
[keul, cekk]Add support for Python 3.Add support for PyPy.Remove support for Python 2.6.Add support for zope.component 5.1.1 (2013-09-20)Update to support Zope 2.13 and Plone 4.0 through 4.3 and clear up
dependencies.1.0-final (2011-08-21)Template markup fixes for chameleon compatibility.Added Basque and Catalan translations.1.0-rc3 (2011-05-12)Optimize for average/count calculations with large numbers of
ratings.  Improve performance of most recent rating lookup.Fixes for Plone 4.11.0-rc2 (2010-11-28)Don’t use a CSS id in view only rating view so that listings with
ratings can still have valid html.1.0-rc1 (2010-08-31)Add support for view only rating views.Remove _v attribute caching.Plone 4 / Zope 2.12 compatibility (still works on Plone 3.3.4 / Zope 2.10.11):permissions.py: Don’t depend on Products.__ac_permissions__Import ApplicationDefaultPermissions from AccessControl.PermissionFix tests:traverser.py, README.txt: SampleContainer class may come
from zope.* and zope.app.*utils.py: Date may or may not be followed by semicolonbrowser/basic.py: add publishTraverse method to BasicEditorialRatingView,
to prevent faulty traversal.events.py and interfaces.py: Subclass IObjectRatedEvent from IObjectEvent
(instead of from IObjectModifiedEvent) to prevent permissions errors.Added this file0.2 (2009-05-11)