New style: convert Blog cube to new layout
authorNsukami Patrick <ndkpatt at gmail dot com>
Sun, 11 Nov 2018 20:37:51 +0000
changeset 371 30f59dac9970
parent 370 564d5ec64f53
child 372 fa1c146aead2
New style: convert Blog cube to new layout - move files to cubicweb_blog folder - update setup.py, MANIFEST.in, cubicweb_blog/__pkginfo__.py
MANIFEST.in
__init__.py
__pkginfo__.py
cubicweb_blog/__init__.py
cubicweb_blog/__pkginfo__.py
cubicweb_blog/data/cubes.blog.css
cubicweb_blog/data/icon_blog.gif
cubicweb_blog/data/postdatabg.jpg
cubicweb_blog/entities.py
cubicweb_blog/hooks.py
cubicweb_blog/i18n/en.po
cubicweb_blog/i18n/es.po
cubicweb_blog/i18n/fr.po
cubicweb_blog/migration/1.2.0_Any.py
cubicweb_blog/migration/1.4.2_Any.py
cubicweb_blog/migration/1.5.0_Any.py
cubicweb_blog/migration/1.7.0_Any.py
cubicweb_blog/migration/1.7.2_Any.py
cubicweb_blog/migration/1.7.3_Any.py
cubicweb_blog/migration/1.9.0_Any.py
cubicweb_blog/migration/postcreate.py
cubicweb_blog/schema.py
cubicweb_blog/site_cubicweb.py
cubicweb_blog/sobjects.py
cubicweb_blog/uiprops.py
cubicweb_blog/views/__init__.py
cubicweb_blog/views/blog.py
cubicweb_blog/views/boxes.py
cubicweb_blog/views/entry.py
cubicweb_blog/views/urlpublishing.py
data/cubes.blog.css
data/icon_blog.gif
data/postdatabg.jpg
entities.py
hooks.py
i18n/en.po
i18n/es.po
i18n/fr.po
migration/1.2.0_Any.py
migration/1.4.2_Any.py
migration/1.5.0_Any.py
migration/1.7.0_Any.py
migration/1.7.2_Any.py
migration/1.7.3_Any.py
migration/1.9.0_Any.py
migration/postcreate.py
schema.py
setup.py
site_cubicweb.py
sobjects.py
tox.ini
uiprops.py
views/__init__.py
views/blog.py
views/boxes.py
views/entry.py
views/urlpublishing.py
--- a/MANIFEST.in	Tue Jan 17 12:14:00 2017 +0100
+++ b/MANIFEST.in	Sun Nov 11 20:37:51 2018 +0000
@@ -1,5 +1,7 @@
-include *.py
-include */*.py
-recursive-include data *.gif *.jpg *.png *.css external_resources
-recursive-include i18n *.po
+include setup.py
+include README
 
+recursive-include cubicweb_blog *
+recursive-include cubicweb_blog/data *.gif *.jpg *.png *.css external_resources
+recursive-include cubicweb_blog/i18n *.po
+recursive-include cubicweb_blog/migration *.py
--- a/__init__.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-"""cubicweb-blog"""
--- a/__pkginfo__.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-# pylint: disable-msg=W0622
-"""cubicweb-blog packaging information"""
-
-modname = 'blog'
-distname = "cubicweb-%s" % modname
-
-numversion = (1, 12, 0)
-version = '.'.join(str(num) for num in numversion)
-
-license = 'LGPL'
-description = "blogging component for the CubicWeb framework"
-web = 'http://www.cubicweb.org/project/%s' % distname
-mailinglist = "mailto://cubicweb@lists.logilab.org"
-author = "Logilab"
-author_email = "contact@logilab.fr"
-classifiers = [
-    'Environment :: Web Environment',
-    'Framework :: CubicWeb',
-    'Programming Language :: Python',
-    'Programming Language :: JavaScript',
-]
-
-__depends__ = {'cubicweb': '>= 3.19.0',
-               'six': '>= 1.4.0', }
-__recommends__ = {'cubicweb-tag': None,
-                  'cubicweb-preview': None,
-                  'cubicweb-comment': '>= 1.6.3',
-                  'cubicweb-seo': None,
-                  'cubicweb-sioc': None,
-                  'feedparser': None,
-                  'rdflib': None,
-                  }
-
-
-# packaging ###
-
-from os import listdir as _listdir
-from os.path import join, isdir
-from glob import glob
-
-THIS_CUBE_DIR = join('share', 'cubicweb', 'cubes', modname)
-
-
-def listdir(dirpath):
-    return [join(dirpath, fname) for fname in _listdir(dirpath)
-            if fname[0] != '.' and not fname.endswith('.pyc')
-            and not fname.endswith('~')
-            and not isdir(join(dirpath, fname))]
-
-data_files = [
-    # common files
-    [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']],
-]
-# check for possible extended cube layout
-for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema',
-                'data', 'i18n', 'migration', 'wdoc'):
-    if isdir(dirname):
-        data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)])
-# Note: here, you'll need to add subdirectories if you want
-# them to be included in the debian package
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/__init__.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,1 @@
+"""cubicweb-blog"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/__pkginfo__.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,32 @@
+# pylint: disable-msg=W0622
+"""cubicweb-blog packaging information"""
+
+modname = 'blog'
+distname = "cubicweb-%s" % modname
+
+numversion = (1, 12, 0)
+version = '.'.join(str(num) for num in numversion)
+
+license = 'LGPL'
+description = "blogging component for the CubicWeb framework"
+web = 'http://www.cubicweb.org/project/%s' % distname
+mailinglist = "mailto://cubicweb@lists.logilab.org"
+author = "Logilab"
+author_email = "contact@logilab.fr"
+classifiers = [
+    'Environment :: Web Environment',
+    'Framework :: CubicWeb',
+    'Programming Language :: Python',
+    'Programming Language :: JavaScript',
+]
+
+__depends__ = {'cubicweb': '>= 3.19.0',
+               'six': '>= 1.4.0', }
+__recommends__ = {'cubicweb-tag': None,
+                  'cubicweb-preview': None,
+                  'cubicweb-comment': '>= 1.6.3',
+                  'cubicweb-seo': None,
+                  'cubicweb-sioc': None,
+                  'feedparser': None,
+                  'rdflib': None,
+                  }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/data/cubes.blog.css	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,46 @@
+div.blogwrapper {
+    font-size: 14px;
+}
+div.blogwrapper img {
+    padding: 1em;
+    /* the following 3 lines taken verbatim from bootstrap's .img-responsive */
+    max-width: 100%;
+    display: block;
+    height: auto;
+}
+ul.invisible li {
+    background: none repeat scroll 0 0 rgba(0, 0, 0, 0);
+}
+span.previousmonth {
+    float: left;
+}
+span.nextmonth {
+    float: right;
+}
+div.author_date div {
+    color: #999999;
+    float: right;
+    padding-top: 3px;
+}
+div.author_date {
+    border-top: thin ridge #ccc;
+    font-size: 1.2em;
+    font-style: italic;
+}
+div.microblog {
+    clear: both;
+}
+div.microblog span.author {
+    float: left;
+    margin: 0 10px 5px 0;
+}
+div.microblog span.msgtxt {
+    margin: 0 0 10px;
+}
+div.microblog span.meta {
+    color: #999;
+    display: block;
+}
+div.microblog span.meta a {
+    color: #999;
+}
Binary file cubicweb_blog/data/icon_blog.gif has changed
Binary file cubicweb_blog/data/postdatabg.jpg has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/entities.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,112 @@
+"""entity classes for Blog entities
+
+:organization: Logilab
+:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: Lesser General Public License version 2 or above - http://www.gnu.org/
+"""
+# __docformat__ = "restructuredtext en"
+
+from logilab.common.date import todate
+
+from cubicweb.entities import AnyEntity, fetch_config
+from cubicweb.view import EntityAdapter
+from cubicweb.predicates import is_instance
+
+__docformat__ = "restructuredtext en"
+
+
+class BlogIFeedAdapter(EntityAdapter):
+    __regid__ = 'IFeed'
+    __select__ = is_instance('Blog', 'MicroBlog')
+
+    def rss_feed_url(self):
+        if getattr(self.entity, 'rss_url', None):
+            return self.entity.rss_url
+        rql = ('Any E ORDERBY D DESC '
+               'WHERE E entry_of X, X eid %s, E creation_date D'
+               )
+        return self._cw.build_url(rql=rql % self.entity.eid, vid='rss',
+                                  vtitle=self.entity.dc_title())
+
+
+class BlogISiocContainerAdapter(EntityAdapter):
+    __regid__ = 'ISIOCContainer'
+    __select__ = is_instance('Blog')
+
+    def isioc_type(self):
+        return 'Weblog'
+
+    def isioc_items(self):
+        return self.entity.reverse_entry_of
+
+
+class BlogEntry(AnyEntity):
+    """customized class for BlogEntry entities"""
+    __regid__ = 'BlogEntry'
+    fetch_attrs, cw_fetch_order = fetch_config(
+        ['creation_date', 'title'], order='DESC')
+
+    def dc_title(self):
+        return self.title
+
+    def dc_description(self, format='text/plain'):
+        return self.printable_value('content', format=format)
+
+    def dc_date(self, date_format=None):
+        dc_date = self.creation_date
+        for tr_info in self.reverse_wf_info_for:
+            if tr_info.new_state.name == 'published':
+                dc_date = tr_info.creation_date
+                break
+        return self._cw.format_date(dc_date, date_format=date_format)
+
+
+class BlogEntryICalendarableAdapter(EntityAdapter):
+    __regid__ = 'ICalendarable'
+    __select__ = is_instance('BlogEntry')
+
+    @property
+    def start(self):
+        return self.entity.creation_date
+
+    @property
+    def stop(self):
+        return self.entity.creation_date
+
+
+class BlogEntryICalendarViewsAdapter(EntityAdapter):
+    __regid__ = 'ICalendarViews'
+    __select__ = is_instance('BlogEntry')
+
+    def matching_dates(self, begin, end):
+        """calendar views interface"""
+        mydate = self.entity.creation_date
+        if not mydate:
+            return []
+        mydate = todate(mydate)
+        if begin < mydate < end:
+            return [mydate]
+        return []
+
+
+class BlogEntryISiocItemAdapter(EntityAdapter):
+    __regid__ = 'ISIOCItem'
+    __select__ = is_instance('BlogEntry')
+
+    def isioc_content(self):
+        return self.entity.content
+
+    def isioc_container(self):
+        return self.entity.entry_of and self.entity.entry_of[0] or None
+
+    def isioc_type(self):
+        return 'BlogPost'
+
+    def isioc_replies(self):
+        # XXX link to comments
+        return []
+
+    def isioc_topics(self):
+        # XXX link to tags, folders?
+        return []
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/hooks.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,12 @@
+from cubicweb.predicates import is_instance, is_in_state
+from cubicweb.sobjects.notification import NotificationView, StatusChangeMixIn
+
+
+class BlogEntryPublishedView(StatusChangeMixIn, NotificationView):
+    """get notified from published blogs"""
+    __select__ = is_instance('BlogEntry',) & is_in_state('published')
+    content_attr = 'content'
+
+    def subject(self):
+        entity = self.cw_rset.get_entity(0, 0)
+        return '[%s] %s' % (self._cw.vreg.config.appid, entity.dc_title())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/i18n/en.po	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,315 @@
+# cubicweb-blog i18n catalog
+# Copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# Logilab <contact@logilab.fr>
+msgid ""
+msgstr ""
+"Project-Id-Version: cubicweb-blog 0.1.0\n"
+"PO-Revision-Date: 2010-10-12 11:59+0200\n"
+"Last-Translator: Logilab Team <contact@logilab.fr>\n"
+"Language-Team: en <contact@logilab.fr>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+# schema pot file, generated on 2007-09-28 17:24:46
+#
+# singular and plural forms for each entity type
+msgid "Blog"
+msgstr "Blog"
+
+msgid "BlogEntry"
+msgstr "Blog entry"
+
+msgid "BlogEntry_plural"
+msgstr "Blog entries"
+
+msgid "Blog_plural"
+msgstr "Blogs"
+
+msgid "Comment"
+msgstr "Comment"
+
+msgid "MicroBlog"
+msgstr "Micro Blog"
+
+msgid "MicroBlogEntry"
+msgstr "Micro Blog Entry"
+
+msgid "MicroBlogEntry_plural"
+msgstr "Micro Blog Entries"
+
+msgid "MicroBlog_plural"
+msgstr "Micro Blogs"
+
+msgid "New Blog"
+msgstr "New blog"
+
+msgid "New BlogEntry"
+msgstr "New blog entry"
+
+msgid "New MicroBlog"
+msgstr "New Micro Blog"
+
+msgid "New MicroBlogEntry"
+msgstr "New Micro Blog Entry"
+
+msgid "New UserAccount"
+msgstr "New User Account"
+
+msgid "This Blog"
+msgstr "This blog"
+
+msgid "This BlogEntry"
+msgstr "This blog entry"
+
+msgid "This MicroBlog"
+msgstr "This micro blog"
+
+msgid "This MicroBlogEntry"
+msgstr "This micro blog entry"
+
+msgid "This UserAccount"
+msgstr "This user account"
+
+msgid "UserAccount"
+msgstr "User Account"
+
+msgid "UserAccount_plural"
+msgstr "User Accounts"
+
+# add related box generated message
+msgid "add BlogEntry entry_of Blog object"
+msgstr "blog entry"
+
+msgid "add MicroBlogEntry entry_of MicroBlog object"
+msgstr ""
+
+#, python-format
+msgid "blog entries created by %s"
+msgstr ""
+
+msgid "blog's rss url (useful for when using external site such as feedburner)"
+msgstr ""
+
+msgid "blog.archives_by_author"
+msgstr "posts by author"
+
+msgid "blog.archives_by_date"
+msgstr "posts by date"
+
+msgid "blog.latest_blogs"
+msgstr "latest posts"
+
+msgid "blogged in "
+msgstr ""
+
+msgid "by"
+msgstr "by"
+
+msgctxt "BlogEntry"
+msgid "content"
+msgstr ""
+
+msgctxt "MicroBlogEntry"
+msgid "content"
+msgstr ""
+
+msgctxt "BlogEntry"
+msgid "content_format"
+msgstr "format"
+
+msgctxt "MicroBlogEntry"
+msgid "content_format"
+msgstr "format"
+
+msgid "creating BlogEntry (BlogEntry entry_of Blog %(linkto)s)"
+msgstr "creating blogentry in blog %(linkto)s"
+
+msgid "creating MicroBlogEntry (MicroBlogEntry entry_of MicroBlog %(linkto)s)"
+msgstr "creating blogentry in micro-blog %(linkto)s"
+
+msgid "ctxcomponents_blog.archives_by_author"
+msgstr "blog archives by author"
+
+msgid "ctxcomponents_blog.archives_by_author_description"
+msgstr "box displaying posts by author"
+
+msgid "ctxcomponents_blog.archives_by_date"
+msgstr "blog archives by date"
+
+msgid "ctxcomponents_blog.archives_by_date_description"
+msgstr "box displaying posts by date"
+
+msgid "ctxcomponents_blog.latest_blogs"
+msgstr "latest blogs box"
+
+msgid "ctxcomponents_blog.latest_blogs_description"
+msgstr "box displaying latest blog entries posted"
+
+msgid "ctxcomponents_blogsubscribe"
+msgstr "blog subscribtion icon"
+
+msgid "ctxcomponents_blogsubscribe_description"
+msgstr ""
+
+msgid "default BlogEntry workflow"
+msgstr ""
+
+msgctxt "Blog"
+msgid "description"
+msgstr ""
+
+msgctxt "MicroBlog"
+msgid "description"
+msgstr ""
+
+msgctxt "Blog"
+msgid "description_format"
+msgstr "format"
+
+msgctxt "MicroBlog"
+msgid "description_format"
+msgstr "format"
+
+msgid "draft"
+msgstr ""
+
+# subject and object forms for each relation type
+# (no object form for final relation types)
+msgid "entry_of"
+msgstr "blog entry of"
+
+msgctxt "BlogEntry"
+msgid "entry_of"
+msgstr "blog entry of"
+
+msgctxt "MicroBlogEntry"
+msgid "entry_of"
+msgstr "micro blog entry of"
+
+msgid "entry_of_object"
+msgstr "posts"
+
+msgctxt "Blog"
+msgid "entry_of_object"
+msgstr "posts"
+
+msgctxt "MicroBlog"
+msgid "entry_of_object"
+msgstr "posts"
+
+msgid "facets_cwuri-facet"
+msgstr ""
+
+msgid "facets_cwuri-facet_description"
+msgstr ""
+
+msgid "has_avatar"
+msgstr "has avatar"
+
+msgctxt "UserAccount"
+msgid "has_avatar"
+msgstr ""
+
+msgid "has_avatar_object"
+msgstr "avatar of"
+
+msgctxt "ExternalUri"
+msgid "has_avatar_object"
+msgstr ""
+
+msgid "has_creator"
+msgstr "has creator"
+
+msgctxt "BlogEntry"
+msgid "has_creator"
+msgstr ""
+
+msgctxt "MicroBlogEntry"
+msgid "has_creator"
+msgstr ""
+
+msgid "has_creator_object"
+msgstr "creator of"
+
+msgctxt "UserAccount"
+msgid "has_creator_object"
+msgstr ""
+
+msgctxt "UserAccount"
+msgid "name"
+msgstr ""
+
+msgid "next month"
+msgstr ""
+
+msgid "previous month"
+msgstr ""
+
+msgid "publish"
+msgstr ""
+
+msgid "published"
+msgstr ""
+
+msgid "rss icon"
+msgstr ""
+
+msgid "rss_url"
+msgstr "rss feed url"
+
+msgctxt "Blog"
+msgid "rss_url"
+msgstr "rss feed url"
+
+msgctxt "BlogEntry"
+msgid "same_as"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "same_as"
+msgstr ""
+
+msgctxt "MicroBlogEntry"
+msgid "same_as"
+msgstr ""
+
+msgid "see more"
+msgstr "see more"
+
+msgid "subscribe"
+msgstr "subscribe"
+
+msgid "subscribe to this blog"
+msgstr ""
+
+msgid "tags"
+msgstr "tags"
+
+msgctxt "Blog"
+msgid "title"
+msgstr ""
+
+msgctxt "BlogEntry"
+msgid "title"
+msgstr ""
+
+msgctxt "MicroBlog"
+msgid "title"
+msgstr ""
+
+#~ msgid "boxes_blog_summary_box"
+#~ msgstr "posts by author"
+
+#~ msgid "boxes_blog_summary_box_description"
+#~ msgstr ""
+#~ "this box contains a list of authors with the number of blog entries they "
+#~ "posted"
+
+#~ msgid "boxes_latest_blogs_box"
+#~ msgstr "latest blogs"
+
+# add related box generated message
+#~ msgid "boxes_latest_blogs_box_description"
+#~ msgstr "this box contains the latest blog entries posted"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/i18n/es.po	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,299 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: cubicweb 2.99.1\n"
+"PO-Revision-Date: 2009-01-27 10:06+0100\n"
+"Last-Translator: Logilab Team <contact@logilab.fr>\n"
+"Language-Team: fr <contact@logilab.fr>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: cubicweb-devtools\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+# schema pot file, generated on 2008-11-27 07:53:37
+#
+# singular and plural forms for each entity type
+msgid "Blog"
+msgstr "Blog"
+
+msgid "BlogEntry"
+msgstr "Entrada de blog"
+
+msgid "BlogEntry_plural"
+msgstr "Entradas de Blog"
+
+msgid "Blog_plural"
+msgstr "Blogs"
+
+msgid "Comment"
+msgstr "Participa"
+
+msgid "MicroBlog"
+msgstr ""
+
+msgid "MicroBlogEntry"
+msgstr ""
+
+msgid "MicroBlogEntry_plural"
+msgstr ""
+
+msgid "MicroBlog_plural"
+msgstr ""
+
+msgid "New Blog"
+msgstr "Nuevo Blog"
+
+msgid "New BlogEntry"
+msgstr "Nueva Entrada de Blog"
+
+msgid "New MicroBlog"
+msgstr ""
+
+msgid "New MicroBlogEntry"
+msgstr ""
+
+msgid "New UserAccount"
+msgstr ""
+
+msgid "This Blog"
+msgstr "Este Blog"
+
+msgid "This BlogEntry"
+msgstr "Esta Entrada de Blog"
+
+msgid "This MicroBlog"
+msgstr ""
+
+msgid "This MicroBlogEntry"
+msgstr ""
+
+msgid "This UserAccount"
+msgstr ""
+
+msgid "UserAccount"
+msgstr ""
+
+msgid "UserAccount_plural"
+msgstr ""
+
+# add related box generated message
+msgid "add BlogEntry entry_of Blog object"
+msgstr "entrada de blog"
+
+msgid "add MicroBlogEntry entry_of MicroBlog object"
+msgstr ""
+
+#, python-format
+msgid "blog entries created by %s"
+msgstr "Entradas de blog creadas por %s"
+
+msgid "blog's rss url (useful for when using external site such as feedburner)"
+msgstr ""
+
+msgid "blog.archives_by_author"
+msgstr ""
+
+msgid "blog.archives_by_date"
+msgstr "espacio archivo de blogs"
+
+msgid "blog.latest_blogs"
+msgstr "último"
+
+msgid "blogged in "
+msgstr ""
+
+msgid "by"
+msgstr "por"
+
+msgctxt "BlogEntry"
+msgid "content"
+msgstr ""
+
+msgctxt "MicroBlogEntry"
+msgid "content"
+msgstr ""
+
+msgctxt "BlogEntry"
+msgid "content_format"
+msgstr ""
+
+msgctxt "MicroBlogEntry"
+msgid "content_format"
+msgstr ""
+
+msgid "creating BlogEntry (BlogEntry entry_of Blog %(linkto)s)"
+msgstr "creación de una entrada de blog %(linkto)s"
+
+msgid "creating MicroBlogEntry (MicroBlogEntry entry_of MicroBlog %(linkto)s)"
+msgstr ""
+
+msgid "ctxcomponents_blog.archives_by_author"
+msgstr ""
+
+msgid "ctxcomponents_blog.archives_by_author_description"
+msgstr ""
+
+msgid "ctxcomponents_blog.archives_by_date"
+msgstr "espacio archivo de blogs"
+
+msgid "ctxcomponents_blog.archives_by_date_description"
+msgstr "espacio hacia los archivos del blog en meses anteriores"
+
+msgid "ctxcomponents_blog.latest_blogs"
+msgstr "último blogs"
+
+msgid "ctxcomponents_blog.latest_blogs_description"
+msgstr "espacio que contiene la lista de los últimos blogs"
+
+msgid "ctxcomponents_blogsubscribe"
+msgstr ""
+
+msgid "ctxcomponents_blogsubscribe_description"
+msgstr ""
+
+msgid "default BlogEntry workflow"
+msgstr ""
+
+msgctxt "Blog"
+msgid "description"
+msgstr ""
+
+msgctxt "MicroBlog"
+msgid "description"
+msgstr ""
+
+msgctxt "Blog"
+msgid "description_format"
+msgstr ""
+
+msgctxt "MicroBlog"
+msgid "description_format"
+msgstr ""
+
+msgid "draft"
+msgstr ""
+
+# subject and object forms for each relation type
+# (no object form for final relation types)
+msgid "entry_of"
+msgstr "entrada de blog"
+
+msgctxt "BlogEntry"
+msgid "entry_of"
+msgstr ""
+
+msgctxt "MicroBlogEntry"
+msgid "entry_of"
+msgstr ""
+
+msgid "entry_of_object"
+msgstr "contiene las entradas"
+
+msgctxt "Blog"
+msgid "entry_of_object"
+msgstr ""
+
+msgctxt "MicroBlog"
+msgid "entry_of_object"
+msgstr ""
+
+msgid "facets_cwuri-facet"
+msgstr ""
+
+msgid "facets_cwuri-facet_description"
+msgstr ""
+
+msgid "has_avatar"
+msgstr ""
+
+msgctxt "UserAccount"
+msgid "has_avatar"
+msgstr ""
+
+msgid "has_avatar_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "has_avatar_object"
+msgstr ""
+
+msgid "has_creator"
+msgstr ""
+
+msgctxt "BlogEntry"
+msgid "has_creator"
+msgstr ""
+
+msgctxt "MicroBlogEntry"
+msgid "has_creator"
+msgstr ""
+
+msgid "has_creator_object"
+msgstr ""
+
+msgctxt "UserAccount"
+msgid "has_creator_object"
+msgstr ""
+
+msgctxt "UserAccount"
+msgid "name"
+msgstr ""
+
+msgid "next month"
+msgstr ""
+
+msgid "previous month"
+msgstr ""
+
+msgid "publish"
+msgstr ""
+
+msgid "published"
+msgstr ""
+
+msgid "rss icon"
+msgstr "ícono rss"
+
+msgid "rss_url"
+msgstr ""
+
+msgctxt "Blog"
+msgid "rss_url"
+msgstr ""
+
+msgctxt "BlogEntry"
+msgid "same_as"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "same_as"
+msgstr ""
+
+msgctxt "MicroBlogEntry"
+msgid "same_as"
+msgstr ""
+
+msgid "see more"
+msgstr "ver más"
+
+msgid "subscribe"
+msgstr "suscribirse"
+
+msgid "subscribe to this blog"
+msgstr ""
+
+msgid "tags"
+msgstr "palabras clave"
+
+msgctxt "Blog"
+msgid "title"
+msgstr ""
+
+msgctxt "BlogEntry"
+msgid "title"
+msgstr ""
+
+msgctxt "MicroBlog"
+msgid "title"
+msgstr ""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/i18n/fr.po	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,302 @@
+# cubicweb-blog i18n catalog
+# Copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# Logilab <contact@logilab.fr>
+msgid ""
+msgstr ""
+"Project-Id-Version: cubicweb-blog 0.1.0\n"
+"PO-Revision-Date: 2010-10-12 12:03+0200\n"
+"Last-Translator: Logilab Team <contact@logilab.fr>\n"
+"Language-Team: fr <contact@logilab.fr>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+# schema pot file, generated on 2007-09-28 17:24:46
+#
+# singular and plural forms for each entity type
+msgid "Blog"
+msgstr "Blog"
+
+msgid "BlogEntry"
+msgstr "Billet"
+
+msgid "BlogEntry_plural"
+msgstr "Billets"
+
+msgid "Blog_plural"
+msgstr "Blogs"
+
+msgid "Comment"
+msgstr "Commentaire"
+
+msgid "MicroBlog"
+msgstr "Microblog"
+
+msgid "MicroBlogEntry"
+msgstr "Micro-billet"
+
+msgid "MicroBlogEntry_plural"
+msgstr "Micro-billets"
+
+msgid "MicroBlog_plural"
+msgstr "Microblogs"
+
+msgid "New Blog"
+msgstr "Nouveau blog"
+
+msgid "New BlogEntry"
+msgstr "Nouveau billet"
+
+msgid "New MicroBlog"
+msgstr "Nouveau microblog"
+
+msgid "New MicroBlogEntry"
+msgstr "Nouveau microbillet"
+
+msgid "New UserAccount"
+msgstr "Nouveau compte utilisateur"
+
+msgid "This Blog"
+msgstr "Ce blog"
+
+msgid "This BlogEntry"
+msgstr "Ce billet"
+
+msgid "This MicroBlog"
+msgstr "Ce microblog"
+
+msgid "This MicroBlogEntry"
+msgstr "Ce microbillet"
+
+msgid "This UserAccount"
+msgstr "Ce compte utilisateur"
+
+msgid "UserAccount"
+msgstr "Compte utilisateur"
+
+msgid "UserAccount_plural"
+msgstr "Comptes utilisateurs"
+
+# add related box generated message
+msgid "add BlogEntry entry_of Blog object"
+msgstr "billet"
+
+msgid "add MicroBlogEntry entry_of MicroBlog object"
+msgstr "billet"
+
+#, python-format
+msgid "blog entries created by %s"
+msgstr "billets créés par %s"
+
+msgid "blog's rss url (useful for when using external site such as feedburner)"
+msgstr ""
+"url du flux rss du blog (peut être utile lorsque l'on utilise un service tel "
+"que feedburner)"
+
+msgid "blog.archives_by_author"
+msgstr "billets par auteur"
+
+msgid "blog.archives_by_date"
+msgstr "billets par date"
+
+msgid "blog.latest_blogs"
+msgstr "derniers billets"
+
+msgid "blogged in "
+msgstr "bloggué dans "
+
+msgid "by"
+msgstr "par"
+
+msgctxt "BlogEntry"
+msgid "content"
+msgstr "contenu"
+
+msgctxt "MicroBlogEntry"
+msgid "content"
+msgstr "contenu"
+
+msgctxt "BlogEntry"
+msgid "content_format"
+msgstr "format"
+
+msgctxt "MicroBlogEntry"
+msgid "content_format"
+msgstr "format"
+
+msgid "creating BlogEntry (BlogEntry entry_of Blog %(linkto)s)"
+msgstr "création d'un billet dans le blog %(linkto)s"
+
+msgid "creating MicroBlogEntry (MicroBlogEntry entry_of MicroBlog %(linkto)s)"
+msgstr "création d'un billet dans le microblog %(linkto)s"
+
+msgid "ctxcomponents_blog.archives_by_author"
+msgstr "boîte des billets par auteur"
+
+msgid "ctxcomponents_blog.archives_by_author_description"
+msgstr "boîte contenant la liste des auteurs et leur nombre de billets"
+
+msgid "ctxcomponents_blog.archives_by_date"
+msgstr "boîte des billets par date"
+
+msgid "ctxcomponents_blog.archives_by_date_description"
+msgstr "boîte permettant d'accéder aux archives des blogs pour les mois passés"
+
+msgid "ctxcomponents_blog.latest_blogs"
+msgstr "boîte des derniers billets"
+
+msgid "ctxcomponents_blog.latest_blogs_description"
+msgstr "boîte contenant la liste des derniers billets postés dans un blog ou micro-blog"
+
+msgid "ctxcomponents_blogsubscribe"
+msgstr "souscrire au blog"
+
+msgid "ctxcomponents_blogsubscribe_description"
+msgstr "icône permettant de souscrire à un blog"
+
+msgid "default BlogEntry workflow"
+msgstr "workflow des billets par défaut"
+
+msgctxt "Blog"
+msgid "description"
+msgstr "description"
+
+msgctxt "MicroBlog"
+msgid "description"
+msgstr "description"
+
+msgctxt "Blog"
+msgid "description_format"
+msgstr "format"
+
+msgctxt "MicroBlog"
+msgid "description_format"
+msgstr "format"
+
+msgid "draft"
+msgstr "brouillon"
+
+# subject and object forms for each relation type
+# (no object form for final relation types)
+msgid "entry_of"
+msgstr "dans le blog"
+
+msgctxt "BlogEntry"
+msgid "entry_of"
+msgstr "posté dans"
+
+msgctxt "MicroBlogEntry"
+msgid "entry_of"
+msgstr "posté dans"
+
+msgid "entry_of_object"
+msgstr "billets"
+
+msgctxt "Blog"
+msgid "entry_of_object"
+msgstr "billets"
+
+msgctxt "MicroBlog"
+msgid "entry_of_object"
+msgstr "micro-billets"
+
+msgid "facets_cwuri-facet"
+msgstr ""
+
+msgid "facets_cwuri-facet_description"
+msgstr ""
+
+msgid "has_avatar"
+msgstr "a pour avatar"
+
+msgctxt "UserAccount"
+msgid "has_avatar"
+msgstr "a pour avatar"
+
+msgid "has_avatar_object"
+msgstr "est l'avatar de"
+
+msgctxt "ExternalUri"
+msgid "has_avatar_object"
+msgstr "est l'avatar de"
+
+msgid "has_creator"
+msgstr "a pour créateur"
+
+msgctxt "BlogEntry"
+msgid "has_creator"
+msgstr "a pour créateur"
+
+msgctxt "MicroBlogEntry"
+msgid "has_creator"
+msgstr "a pour créateur"
+
+msgid "has_creator_object"
+msgstr "est le créateur de"
+
+msgctxt "UserAccount"
+msgid "has_creator_object"
+msgstr "est le créateur de"
+
+msgctxt "UserAccount"
+msgid "name"
+msgstr "nom"
+
+msgid "next month"
+msgstr "mois suivant"
+
+msgid "previous month"
+msgstr "mois précédent"
+
+msgid "publish"
+msgstr "publier"
+
+msgid "published"
+msgstr "publié"
+
+msgid "rss icon"
+msgstr "icône rss"
+
+msgid "rss_url"
+msgstr "url du fil rss"
+
+msgctxt "Blog"
+msgid "rss_url"
+msgstr "url du flux RSS"
+
+msgctxt "BlogEntry"
+msgid "same_as"
+msgstr "identique à"
+
+msgctxt "ExternalUri"
+msgid "same_as"
+msgstr "identique à"
+
+msgctxt "MicroBlogEntry"
+msgid "same_as"
+msgstr "identique à"
+
+msgid "see more"
+msgstr "voir plus"
+
+msgid "subscribe"
+msgstr "souscrire"
+
+msgid "subscribe to this blog"
+msgstr "souscrire à ce blog"
+
+msgid "tags"
+msgstr "étiquettes"
+
+msgctxt "Blog"
+msgid "title"
+msgstr "titre"
+
+msgctxt "BlogEntry"
+msgid "title"
+msgstr "titre"
+
+msgctxt "MicroBlog"
+msgid "title"
+msgstr "titre"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/migration/1.2.0_Any.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,5 @@
+rename_entity_type('Blog', u'BlogEntry')
+add_entity_type('Blog')
+rql('INSERT Blog B: B title "main blog"')
+rql('SET BE entry_of B WHERE BE is BlogEntry, B is Blog')
+checkpoint()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/migration/1.4.2_Any.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,3 @@
+add_attribute('Blog', 'description_format')
+add_attribute('Blog', 'rss_url')
+checkpoint()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/migration/1.5.0_Any.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,2 @@
+change_relation_props('BlogEntry', 'entry_of', 'Blog',
+                      commit=True, cardinality='**')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/migration/1.7.0_Any.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,26 @@
+add_relation_definition('BlogEntry',  'in_state', 'State')
+add_relation_definition('TrInfo',  'wf_info_for', 'BlogEntry')
+add_relation_definition('BlogEntry',  'custom_workflow', 'Workflow')
+
+
+# add BlogEntry workflow
+if confirm('add blog entry workflow'):
+    bwf = add_workflow(_('default BlogEntry workflow'), 'BlogEntry')
+
+    draft = bwf.add_state(_('draft'), initial=True)
+    published = bwf.add_state(_('published'))
+
+    publish = bwf.add_transition(_('publish'), draft, published,
+                                 ('managers',))
+    commit()
+
+# set state to published for already existing blog entries
+blogentries = rql('Any B WHERE B is BlogEntry')
+
+for eid, in blogentries:
+    session.unsafe_execute('SET B in_state S WHERE S name "published", '
+                           'S state_of WF, WF name "default BlogEntry workflow", '
+                           'B eid %(b)s',
+                           {'b': eid}, 'b')
+
+commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/migration/1.7.2_Any.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,1 @@
+sync_schema_props_perms('BlogEntry')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/migration/1.7.3_Any.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,4 @@
+# add group "owners" in transition "publish" of BlogEntry workflow permissions
+rql('SET T require_group G WHERE G name "owners", T transition_of W, '
+    'T name "publish", W workflow_of ET, ET name "BlogEntry"')
+commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/migration/1.9.0_Any.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,3 @@
+add_entity_type('MicroBlog')
+add_entity_type('MicroBlogEntry')
+add_entity_type('UserAccount')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/migration/postcreate.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,15 @@
+# postcreate script. You could setup a workflow here for example
+
+try:
+    from cubicweb import _
+except ImportError:
+    _ = unicode
+
+# BlogEntry workflow
+bwf = add_workflow(_('default BlogEntry workflow'), 'BlogEntry')
+
+draft = bwf.add_state(_('draft'), initial=True)
+published = bwf.add_state(_('published'))
+
+publish = bwf.add_transition(_('publish'), draft, published,
+                             ('managers', 'owners'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/schema.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,60 @@
+from six import text_type as unicode
+
+try:
+    from cubicweb import _
+except ImportError:
+    _ = unicode
+
+from yams.buildobjs import EntityType, String, RichString, SubjectRelation, RelationDefinition
+from cubicweb.schema import WorkflowableEntityType, ERQLExpression
+
+
+class Blog(EntityType):
+    title = String(maxsize=50, required=True)
+    description = RichString()
+    rss_url = String(maxsize=128, description=_(
+        'blog\'s rss url (useful for when using external site such as feedburner)'))
+
+
+class BlogEntry(WorkflowableEntityType):
+    __permissions__ = {
+        'read': ('managers', 'users', ERQLExpression('X in_state S, S name "published"'),),
+        'add': ('managers', 'users'),
+        'update': ('managers', 'owners'),
+        'delete': ('managers', 'owners')
+    }
+    title = String(required=True, fulltextindexed=True, maxsize=256)
+    content = RichString(required=True, fulltextindexed=True)
+    entry_of = SubjectRelation('Blog')
+    same_as = SubjectRelation('ExternalUri')
+
+
+class MicroBlog(EntityType):
+    title = String(maxsize=50, required=True)
+    description = RichString()
+
+
+class MicroBlogEntry(EntityType):
+    __permissions__ = {
+        'read': ('managers', 'users'),
+        'add': ('managers', 'users'),
+        'update': ('managers', 'owners'),
+        'delete': ('managers', 'owners')
+    }
+    content = RichString(required=True, fulltextindexed=True)
+    entry_of = SubjectRelation('MicroBlog')
+    same_as = SubjectRelation('ExternalUri')
+
+
+class UserAccount(EntityType):
+    name = String(required=True)  # see foaf:accountName
+
+
+class has_creator(RelationDefinition):
+    subject = ('BlogEntry', 'MicroBlogEntry')
+    object = 'UserAccount'
+
+
+class has_avatar(RelationDefinition):
+    subject = 'UserAccount'
+    object = 'ExternalUri'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/site_cubicweb.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,6 @@
+# XML <-> yams equivalence
+from cubicweb.xy import xy
+xy.add_equivalence('Blog', 'sioc:Weblog')
+xy.add_equivalence('BlogEntry', 'sioc:BlogPost')
+xy.add_equivalence('BlogEntry title', 'dcterms:title')
+xy.add_equivalence('BlogEntry content', 'sioc:content')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/sobjects.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,252 @@
+# -*- coding: utf-8 -*-
+from __future__ import print_function
+
+from datetime import datetime
+from six import text_type as unicode
+
+from lxml.html import fromstring, tostring
+
+try:
+    import feedparser
+except ImportError:
+    feedparser = None
+
+try:
+    import rdflib
+except ImportError:
+    rdflib = None
+else:
+    RDF = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
+    SIOC = rdflib.Namespace('http://rdfs.org/sioc/ns#')
+    DCTERMS = rdflib.Namespace('http://purl.org/dc/terms/')
+
+try:
+    from cubes.datafeed.sobjects import DataFeedParser
+except ImportError:
+    DataFeedParser = None
+
+
+def get_subject(g, pred, obj):
+    subjects = list(g.subjects(pred, obj))
+    assert len(subjects) == 1
+    return subjects[0]
+
+
+def get_object(g, subj, pred):
+    objects = list(g.objects(subj, pred))
+    assert len(objects) == 1
+    return objects[0]
+
+
+def parse_blogpost_sioc(url):
+    g = rdflib.ConjunctiveGraph()
+    g.parse(url)
+    for post, type_, blogpost_ in g.triples((None, RDF.type, SIOC.BlogPost)):
+        item = {'uri': unicode(post)}
+        item['title'] = unicode(get_object(g, post, DCTERMS.title))
+        item['content'] = unicode(get_object(g, post, SIOC.content))
+        yield item
+
+
+format_map = {'application/xhtml+xml': u'text/html',
+              'text/html': u'text/html',
+              'text/plain': u'text/plain',
+              }
+
+IMG_SPIES = ['http://feeds.feedburner.com',
+             'http://creatives.commindo-media',
+             'http://imp.constantcontact.com',
+             'https://blogger.googleusercontent.com/tracker',
+             'http://stats.wordpress.com/',
+             ]
+
+
+def is_img_spy(node):
+    if node.tag != 'img':
+        return False
+    for url in IMG_SPIES:
+        if node.get('src').startswith(url):
+            return True
+    return False
+
+
+def is_tweetmeme_spy(node):
+    href = node.get('href')
+    if href and href.startswith('http://api.tweetmeme.com/share'):
+        return True
+    return False
+
+
+def remove_content_spies(content):
+    root = fromstring(content)
+    if is_img_spy(root):
+        return u''
+    for img in root.findall('.//img'):
+        if is_img_spy(img):
+            img.drop_tree()
+        elif img.get('height') == '1' and img.get('width') == '1':
+            print(tostring(img), 'is probably a spy')
+    for anchor in root.findall('.//a'):
+        if is_tweetmeme_spy(anchor):
+            anchor.drop_tree()
+    return unicode(tostring(root))
+
+
+def parse_blogpost_rss(url):
+    data = feedparser.parse(url)
+    feed = data.feed
+    for entry in data.entries:
+        item = {}
+        if 'feedburner_origlink' in entry:
+            item['uri'] = entry.feedburner_origlink
+        else:
+            item['uri'] = entry.link
+        item['title'] = entry.title
+        if hasattr(entry, 'content'):
+            content = entry.content[0].value
+            mimetype = entry.content[0].type
+        elif hasattr(entry, 'summary_detail'):
+            content = entry.summary_detail.value
+            mimetype = entry.summary_detail.type
+        else:
+            content = u''  # XXX entry.description?
+            mimetype = u'text/plain'
+        if mimetype == u'text/html':
+            content = remove_content_spies(content)
+        item['content'] = content
+        item['content_format'] = format_map.get(mimetype, u'text/plain')
+        if hasattr(entry, 'date_parsed'):
+            item['creation_date'] = datetime(*entry.date_parsed[:6])
+        if hasattr(entry, 'author_detail') and hasattr(entry.author_detail, 'href'):
+            item['author'] = entry.author_detail.href
+        elif hasattr(feed, 'author_detail') and hasattr(feed.author_detail, 'href'):
+            item['author'] = feed.author_detail.href
+        elif hasattr(feed, 'author'):
+            item['author'] = feed.author
+        elif hasattr(feed, 'image') and hasattr(feed.image, 'link'):
+            item['author'] = feed.image.link
+        else:
+            item['author'] = url
+        item['cwuri'] = feed.link
+        yield item
+
+
+def parse_microblogpost_rss(url):
+    feed = feedparser.parse(url)
+    for entry in feed.entries:
+        item = {}
+        item['uri'] = entry.id
+        # fix weird parsing
+        if hasattr(entry, 'content'):
+            content = entry.content[0].value
+            mimetype = entry.content[0].type
+        else:
+            content = entry.description
+            mimetype = u'text/plain'
+        if ': ' in content:
+            author, text = content.split(': ', 1)
+            if ' ' not in author:
+                content = text
+        item['content'] = content
+        item['content_format'] = format_map.get(mimetype, u'text/plain')
+        item['creation_date'] = datetime(*entry.date_parsed[:6])
+        item['modification_date'] = datetime(*entry.date_parsed[:6])
+        item['author'] = feed.channel.link  # true for twitter
+        item['cwuri'] = feed.channel.link
+        for link in entry.links:
+            if link.type.startswith('image/') and link.rel == 'image':
+                item['avatar'] = link.href
+                break
+        else:
+            screen_name = feed.channel.link.split('/')[-1]
+            item['avatar'] = get_twitter_avatar(screen_name)
+        yield item
+
+
+def search_twitter(word):
+    import urllib2
+    from simplejson import loads
+    data = urllib2.urlopen(
+        'http://search.twitter.com/search.json?q=%s&rpp=100' % word).read()
+    loads(data)
+    # process results
+    # print results
+    return []
+
+
+AVATAR_CACHE = {}
+
+
+def get_twitter_avatar(screen_name):
+    if screen_name not in AVATAR_CACHE:
+        from urllib2 import urlopen
+        import simplejson
+        data = urlopen(
+            'http://api.twitter.com/1/users/show.json?screen_name=%s' % screen_name).read()
+        user = simplejson.loads(data)
+        AVATAR_CACHE[screen_name] = user['profile_image_url']
+    return AVATAR_CACHE[screen_name]
+
+
+if DataFeedParser is not None:
+    class BlogPostParser(DataFeedParser):
+        __abstract__ = True
+        entity_type = 'BlogEntry'
+
+        def process(self, url):
+            stats = {'update': 0, 'creation': 0}
+            for item in self.parse(url):
+                author = item.pop('author', None)
+                avatar = item.pop('avatar', None)
+                euri = self.sget_entity('ExternalUri', uri=item.pop('uri'))
+                if euri.same_as:
+                    # sys.stdout.write('.')
+                    stats['update'] += 1
+                    post = self.update_blogpost(euri.same_as[0], item)
+                else:
+                    # sys.stdout.write('+')
+                    stats['creation'] += 1
+                    post = self.create_blogpost(item, euri)
+                if author:
+                    account = self.sget_entity('UserAccount', name=author)
+                    self.sget_relation(post.eid, 'has_creator', account.eid)
+                    if avatar:
+                        auri = self.sget_entity('ExternalUri', uri=avatar)
+                        self.sget_relation(account.eid, 'has_avatar', auri.eid)
+                # sys.stdout.flush()
+            return stats
+
+        def create_blogpost(self, item, uri):
+            entity = self._cw.create_entity(self.entity_type, **item)
+            entity.set_relations(same_as=uri)
+            return entity
+
+        def update_blogpost(self, entity, item):
+            entity.set_attributes(**item)
+            return entity
+
+    if rdflib is not None:
+        class BlogPostSiocParser(BlogPostParser):
+            __regid__ = 'blogpost-sioc'
+            parse = staticmethod(parse_blogpost_sioc)
+
+    if feedparser is not None:
+        class BlogPostRSSParser(BlogPostParser):
+            __regid__ = 'blogpost-rss'
+            parse = staticmethod(parse_blogpost_rss)
+
+        class MicroBlogPostRSSParser(BlogPostParser):
+            __regid__ = 'microblogpost-rss'
+            entity_type = 'MicroBlogEntry'
+            parse = staticmethod(parse_microblogpost_rss)
+
+
+if __name__ == '__main__':
+    import sys
+    from pprint import pprint
+
+    name = sys.argv[1]
+    url = sys.argv[2]
+
+    parser = globals()[name]
+    pprint(list(parser(url)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/uiprops.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,1 @@
+BLOG_ICON = data('icon_blog.gif') # noqa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/views/__init__.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,73 @@
+from cubicweb.predicates import is_instance
+from cubicweb.view import EntityAdapter
+from cubicweb.web.views import ibreadcrumbs
+from cubicweb.web.views.autoform import AutomaticEntityForm
+
+
+class BlogEntryIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
+    __select__ = is_instance('BlogEntry', 'MicroBlogEntry')
+
+    def parent_entity(self):
+        return self.entity.entry_of and self.entity.entry_of[0] or None
+
+
+class BlogEntryIPrevNextAdapter(EntityAdapter):
+    __regid__ = 'IPrevNext'
+    __select__ = is_instance('BlogEntry', 'MicroBlogEntry')
+
+    def next_entity(self):
+        return self._sibling_entry('ASC', '>')
+
+    def previous_entity(self):
+        return self._sibling_entry('DESC', '<')
+
+    def _sibling_entry(self, order, operator):
+        if self.entity.entry_of:
+            rql = ('Any B ORDERBY B %s LIMIT 1 '
+                   'WHERE B entry_of BL, BL eid %%(blog)s, '
+                   'B eid %s %%(eid)s')
+            rset = self._cw.execute(rql % (order, operator),
+                                    {'blog': self.entity.entry_of[0].eid,
+                                     'eid': self.entity.eid})
+        else:
+            rql = ('Any B ORDERBY B %s LIMIT 1 '
+                   'WHERE B eid %s %%(eid)s, NOT B entry_of BL, '
+                   'B is ET, ET name IN ("BlogEntry", "MicroBlogEntry")')
+            rset = self._cw.execute(rql % (order, operator),
+                                    {'eid': self.entity.eid})
+        if rset:
+            return rset.get_entity(0, 0)
+
+
+def registration_callback(vreg):
+    vreg.register(BlogEntryIBreadCrumbsAdapter)
+    vreg.register(BlogEntryIPrevNextAdapter)
+
+    loaded_cubes = vreg.config.cubes()
+
+    if 'seo' in loaded_cubes:
+        from cubes.seo.views import SitemapRule
+
+        class BlogEntrySitemapRule(SitemapRule):
+            __regid__ = 'blogentry'
+            query = 'Any X WHERE X is BlogEntry'
+            priority = 1.0
+            chfreq = 'yearly'
+
+        class MicroBlogEntrySitemapRule(SitemapRule):
+            __regid__ = 'microblogentry'
+            query = 'Any X WHERE X is MicroBlogEntry'
+            priority = 1.0
+            chfreq = 'yearly'
+
+        vreg.register(BlogEntrySitemapRule)
+        vreg.register(MicroBlogEntrySitemapRule)
+
+    if 'preview' in loaded_cubes:
+        from cubes.preview.views.forms import PreviewFormMixin
+
+        class PreviewAutomaticEntityForm(PreviewFormMixin, AutomaticEntityForm):
+            preview_mode = 'inline'
+            __select__ = AutomaticEntityForm.__select__ & is_instance('Blog', 'BlogEntry',
+                                                                      'MicroBlog', 'MicroBlogEntry')
+        vreg.register(PreviewAutomaticEntityForm)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/views/blog.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,61 @@
+"""Primary views for blogs
+
+:organization: Logilab
+:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+from logilab.mtconverter import xml_escape
+
+from cubicweb.utils import UStringIO
+from cubicweb.predicates import is_instance
+from cubicweb.web import component
+from cubicweb.web.views import uicfg, primary
+
+__docformat__ = "restructuredtext en"
+
+_pvs = uicfg.primaryview_section
+_pvdc = uicfg.primaryview_display_ctrl
+_abaa = uicfg.actionbox_appearsin_addmenu
+for etype in ('Blog', 'MicroBlog'):
+    _pvs.tag_attribute((etype, 'title'), 'hidden')
+    _pvs.tag_object_of(('*', 'entry_of', etype), 'hidden')
+    _pvdc.tag_attribute((etype, 'description'), {'showlabel': False})
+    _abaa.tag_object_of(('*', 'entry_of', etype), True)
+_pvs.tag_attribute(('Blog', 'rss_url'), 'hidden')
+
+_pvs.tag_object_of(('*', 'has_creator', 'UserAccount'), 'relations')
+_pvs.tag_attribute(('UserAccount', 'name'), 'hidden')
+
+
+class BlogPrimaryView(primary.PrimaryView):
+    __select__ = is_instance('Blog', 'MicroBlog')
+
+    def entity_call(self, entity):
+        self.w(u'<div class="blogwrapper">')
+        super(BlogPrimaryView, self).entity_call(entity)
+        self.w(u'</div>')
+
+    def render_entity_relations(self, entity):
+        super(BlogPrimaryView, self).render_entity_relations(entity)
+        rset = entity.related('entry_of', 'object')
+        if rset:
+            strio = UStringIO()
+            self.paginate(w=strio.write, page_size=10, rset=rset)
+            self.w(strio.getvalue())
+            self.wview('sameetypelist', rset, showtitle=False)
+            self.w(strio.getvalue())
+
+
+class SubscribeToBlogComponent(component.EntityCtxComponent):
+    __regid__ = 'blogsubscribe'
+    __select__ = component.EntityVComponent.__select__ & is_instance(
+        'Blog', 'MicroBlog')
+    context = 'ctxtoolbar'
+
+    def render_body(self, w):
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        w('<a href="%s"><img src="%s" alt="%s"/></a>' % (
+            xml_escape(entity.cw_adapt_to('IFeed').rss_feed_url()),
+            self._cw.uiprops['RSS_LOGO_16'],
+            self._cw._(u'subscribe to this blog')))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/views/boxes.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,163 @@
+"""Various blog boxes: archive, per author, etc...
+
+:organization: Logilab
+:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+from six import text_type as unicode
+
+from cubicweb import tags
+from cubicweb.predicates import (none_rset, one_line_rset, is_instance,
+                                 has_related_entities, match_view)
+from cubicweb.web import component
+
+try:
+    from cubicweb import _
+except ImportError:
+    _ = unicode
+
+__docformat__ = "restructuredtext en"
+
+
+class BlogArchivesBox(component.CtxComponent):
+    """blog side box displaying a Blog Archive"""
+    __regid__ = 'blog.archives_by_date'
+    __select__ = (component.CtxComponent.__select__
+                  & is_instance('Blog', 'MicroBlog')
+                  & has_related_entities('entry_of', 'object'))
+    title = _('blog.archives_by_date')
+    order = 35
+    context = 'left'
+
+    def render_body(self, w):
+        # FIXME doesn't handle (yet) multiple blogs
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        rset = entity.related('entry_of', 'object')
+        self._cw.view('cw.archive.by_date', rset, maxentries=6,
+                      basepath=entity.rest_path() + '/blogentries',
+                      w=w)
+
+
+class BlogEntryArchivesBox(BlogArchivesBox):
+    __regid__ = 'blog.entry_archives_by_date'
+    __select__ = (component.CtxComponent.__select__
+                  & is_instance('BlogEntry', 'MicroBlogEntry')
+                  & has_related_entities('entry_of', 'subject'))
+
+    def render_body(self, w):
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        box = self._cw.vreg['ctxcomponents'].select('blog.archives_by_date', self._cw, w=w,
+                                                    rset=entity.related('entry_of', 'subject'))
+        box.render_body(w)
+
+
+class BlogByAuthorBox(component.CtxComponent):
+    __regid__ = 'blog.archives_by_author'
+    __select__ = (component.CtxComponent.__select__
+                  & is_instance('Blog', 'MicroBlog')
+                  & has_related_entities('entry_of', 'object'))
+    title = _('blog.archives_by_author')
+    order = 36
+    context = 'left'
+
+    def render_body(self, w):
+        # FIXME doesn't handle (yet) multiple blogs
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        rset = entity.related('entry_of', 'object')
+        self._cw.view('cw.archive.by_author', rset,
+                      basepath=entity.rest_path() + '/blogentries',
+                      w=w)
+
+
+class BlogEntryByAuthorBox(BlogByAuthorBox):
+    __regid__ = 'blog.entry_archives_by_author'
+    __select__ = (component.CtxComponent.__select__
+                  & is_instance('BlogEntry', 'MicroBlogEntry')
+                  & has_related_entities('entry_of', 'subject'))
+
+    def render_body(self, w):
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        box = self._cw.vreg['ctxcomponents'].select('blog.archives_by_author', self._cw, w=w,
+                                                    rset=entity.related('entry_of', 'subject'))
+        box.render_body(w)
+
+
+class LatestBlogsBox(component.CtxComponent):
+    """display a box with latest blogs and rss"""
+    __regid__ = 'blog.latest_blogs'
+    __select__ = (component.CtxComponent.__select__
+                  & none_rset() & match_view('index'))
+    title = _('blog.latest_blogs')
+    order = 34
+    display_see_more_link = True
+    contextual = False
+
+    def latest_blogs_rset(self):
+        return self._cw.execute(
+            'Any X,T,CD ORDERBY CD DESC LIMIT 5 WHERE X is IN (MicroBlogEntry, BlogEntry), '
+            'X title T, X creation_date CD')
+
+    def render_body(self, w):
+        # XXX turn into a predicate
+        rset = self.latest_blogs_rset()
+        if not rset:
+            return
+        w(u'<ul class="boxListing">')
+        for entity in rset.entities():
+            w(u'<li>%s</li>\n' %
+              tags.a(entity.dc_title(), href=entity.absolute_url()))
+        rqlst = rset.syntax_tree()
+        rqlst.set_limit(None)
+        rql = rqlst.as_string(kwargs=rset.args)
+        if self.display_see_more_link:
+            url = self._cw.build_url('view', rql=rql, page_size=10)
+            w(u'<li>%s</li>\n' %
+              tags.a(u'[%s]' % self._cw._(u'see more'), href=url))
+        rss_icon = self._cw.uiprops['RSS_LOGO_16']
+        # FIXME - could use rss_url defined as a property if available
+        rss_label = u'%s <img src="%s" alt="%s"/>' % (
+            self._cw._(u'subscribe'), rss_icon, self._cw._('rss icon'))
+        blogs = self._cw.execute('Any B,RSS WHERE B is Blog, B rss_url RSS')
+        if len(blogs) == 1:
+            rss_url = blogs[0][1]
+        else:
+            rss_url = self._cw.build_url('view', vid='rss', rql=rql)
+        w(u'<li>%s</li>\n' %
+          tags.a(rss_label, href=rss_url, escapecontent=False))
+        w(u'</ul>\n')
+
+
+class LatestBlogsBlogBox(LatestBlogsBox):
+    """display a box with latest blogs and rss, filtered for a particular blog
+    """
+    __select__ = (component.CtxComponent.__select__
+                  & one_line_rset() & is_instance('Blog'))
+    display_see_more_link = False
+    contextual = True
+
+    def latest_blogs_rset(self):
+        blog = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        return self._cw.execute(
+            'Any X,T,CD ORDERBY CD DESC LIMIT 5 WHERE '
+            'X title T, X creation_date CD, X entry_of B, B eid %(b)s',
+            {'b': blog.eid})
+
+
+class LatestBlogsBlogEntryBox(LatestBlogsBox):
+    """display a box with latest blogs and rss, filtered for a particular blog
+    """
+    __select__ = (component.CtxComponent.__select__
+                  & is_instance('BlogEntry')
+                  & has_related_entities('entry_of', 'subject'))
+    display_see_more_link = False
+    contextual = True
+
+    def latest_blogs_rset(self):
+        blogentry = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        # FIXME doesn't handle (yet) multiple blogs
+        blog = blogentry.related('entry_of', 'subject').get_entity(0, 0)
+        return self._cw.execute(
+            'Any X,T,CD ORDERBY CD DESC LIMIT 5 WHERE '
+            'X title T, X creation_date CD, X entry_of B, B eid %(b)s',
+            {'b': blog.eid})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/views/entry.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,278 @@
+"""Secondary views for blogs
+
+:organization: Logilab
+:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+import re
+
+from calendar import monthrange
+from datetime import datetime
+
+from six import PY2
+from six import text_type as unicode
+
+from logilab.mtconverter import xml_escape
+
+from cubicweb.schema import display_name
+from cubicweb.view import EntityView
+from cubicweb.predicates import paginated_rset, sorted_rset, is_instance
+from cubicweb.web.views import uicfg
+from cubicweb.web.views import primary, baseviews, calendar, navigation, workflow
+from cubicweb.web.views.xmlrss import RSSItemView
+try:
+    from cubicweb import _
+except ImportError:
+    _ = unicode
+
+__docformat__ = "restructuredtext en"
+
+_pvs = uicfg.primaryview_section
+_pvs.tag_attribute(('BlogEntry', 'title'), 'hidden')
+_pvs.tag_attribute(('BlogEntry', 'content'), 'hidden')
+_pvs.tag_subject_of(('BlogEntry', 'entry_of', '*'), 'relations')
+_afs = uicfg.autoform_section
+_afs.tag_subject_of(('BlogEntry', 'entry_of', 'Blog'), 'main', 'attributes')
+
+# blog entries ###########################################################
+
+
+def render_blogentry_title(req, w, entity):
+    w(u'<h1>%s</h1>' % entity.view('incontext'))
+    w(u'<div class="author_date"><div>%s' %
+      req.format_date(entity.creation_date))
+    rql = None
+    if entity.has_creator:
+        creator = entity.has_creator[0]
+        name = creator.name
+        rql = 'Any X ORDERBY D DESC WHERE X is BlogEntry, X has_creator Y, '\
+              'Y eid %s, X creation_date D' % creator.eid
+    elif entity.creator:
+        creator = entity.creator
+        name = creator.name()
+        rql = 'Any X ORDERBY D DESC WHERE X is BlogEntry, X created_by Y, '\
+              'Y eid %s, X creation_date D' % creator.eid
+    if rql:
+        vtitle = _('blog entries created by %s') % name
+        url = req.build_url('view', rql=rql, vtitle=vtitle, page_size=10)
+        w(u' %s <a title="%s" href="%s">%s</a>' % (
+            _('by'), xml_escape(vtitle), xml_escape(url), xml_escape(name)))
+    w(u'</div></div>')
+
+
+class BlogEntryPrimaryView(primary.PrimaryView):
+    __select__ = is_instance('BlogEntry')
+    show_attr_label = False
+
+    def render_entity_attributes(self, entity):
+        super(BlogEntryPrimaryView, self).render_entity_attributes(entity)
+        # render the actual blog entry content outside the attributes table
+        # which causes major CSS headaches, see bug #5450612.
+        self.w(u'<div class="blogwrapper">')
+        self.w(entity.printable_value('content'))
+        self.w(u'</div>')
+
+    def render_entity_title(self, entity):
+        self._cw.add_css('cubes.blog.css')
+        w = self.w
+        w(u'<div class="blogentry_title">')
+        render_blogentry_title(self._cw, w, entity)
+        w(u'</div>')
+        w(u'<br class="clear"/>')
+
+
+# don't show workflow history for blog entry
+class BlogEntryWFHistoryVComponent(workflow.WFHistoryVComponent):
+    __select__ = workflow.WFHistoryVComponent.__select__ & is_instance(
+        'BlogEntry')
+
+    def render(self, w, **kwargs):
+        pass
+
+
+class BlogEntrySameETypeListView(baseviews.SameETypeListView):
+    __select__ = baseviews.SameETypeListView.__select__ & is_instance(
+        'BlogEntry')
+    countrql = ('Any COUNT(B) WHERE B is BlogEntry, '
+                'B creation_date >= %(firstday)s, B creation_date <= %(lastday)s')
+    item_vid = 'blog'
+
+    def call(self, **kwargs):
+        self._cw.add_css('cubes.blog.css')
+        super(BlogEntrySameETypeListView, self).call(**kwargs)
+        # XXX Iirk, IPrevNext
+        if 'year' in self._cw.form and 'month' in self._cw.form:
+            self.render_next_previous(
+                int(self._cw.form['year']), int(self._cw.form['month']))
+
+    def render_next_previous(self, year, month):
+        if month == 12:
+            nextmonth = 1
+            year = year + 1
+        else:
+            nextmonth = month + 1
+        if month == 1:
+            previousmonth = 12
+            year = year - 1
+        else:
+            previousmonth = month - 1
+        self.w(u'<div class="prevnext">')
+        self.w(u'<span class="previousmonth">%s</span>'
+               % self.render_link(year, previousmonth,
+                                  xml_escape(u'<< ' + self._cw._(u'previous month'))))
+        self.w(u'<span class="nextmonth">%s</span>'
+               % self.render_link(year, nextmonth,
+                                  xml_escape(self._cw._(u'next month') + u' >>')))
+        self.w(u'</div>')
+
+    def render_link(self, year, month, atitle):
+        firstday = datetime(year, month, 1)
+        lastday = datetime(year, month, monthrange(year, month)[1])
+        args = {'firstday': firstday, 'lastday': lastday}
+        nmb_entries = self._cw.execute(self.countrql, args)[0][0]
+        if not nmb_entries:
+            return
+        rql = ('Any B, BD ORDERBY BD DESC '
+               'WHERE B is BlogEntry, B creation_date BD, '
+               'B creation_date >= "%s", B creation_date <= "%s"' %
+               (firstday.strftime('%Y-%m-%d'), lastday.strftime('%Y-%m-%d')))
+        label = u'%s %s [%s]' % (self._cw._(calendar.MONTHNAMES[month - 1]), year,
+                                 nmb_entries)
+        vtitle = '%s %s' % (display_name(
+            self._cw, 'BlogEntry', 'plural'), label)
+        url = self._cw.build_url('view', rql=rql, vtitle=vtitle,
+                                 month=month, year=year)
+        return u'<a href="%s">%s</a>' % (xml_escape(url), atitle)
+
+
+class BlogEntryBlogView(EntityView):
+    __regid__ = 'blog'
+    __select__ = is_instance('BlogEntry')
+
+    toolbar_components = (primary.PrimaryView.content_navigation_components.im_func if PY2 else
+                          primary.PrimaryView.content_navigation_components)
+
+    def cell_call(self, row, col, **kwargs):
+        entity = self.cw_rset.get_entity(row, col)
+        w = self.w
+        w(u'<div class="post">')
+        self.toolbar_components('ctxtoolbar')
+        render_blogentry_title(self._cw, w, entity)
+        w(u'<div class="entry">')
+        body = entity.printable_value('content')
+        w(body)
+        w(u'</div>')
+        w(u'<br class="clear"/>')
+        w(u'<div class="postmetadata">%s</div>' % entity.view('post-reldata'))
+        w(u'</div>')
+
+
+class BlogEntryPostMetaData(EntityView):
+    __regid__ = 'post-reldata'
+    __select__ = is_instance('BlogEntry')
+
+    def cell_call(self, row, col):
+        entity = self.cw_rset.get_entity(row, col)
+        _ = lambda ertype, form='': display_name(self._cw, ertype, form)
+        reldata = []
+        w = reldata.append
+        schema = self._cw.vreg.schema
+        if 'comments' in schema and \
+                'BlogEntry' in schema.rschema('comments').objects():
+            from cubes.comment.entities import subcomments_count
+            count = subcomments_count(entity)
+            if count:
+                url = xml_escape(entity.absolute_url())
+                if count > 1:
+                    label = _('Comment', 'plural')
+                else:
+                    label = _('Comment')
+                w(u'<a href="%s">%s %s</a>' % (url, count, label))
+            else:
+                w(u'%s %s' % (count, _('Comment')))
+        if 'tags' in schema and 'BlogEntry' in schema.rschema('tags').objects():
+            tag_rset = entity.related('tags', 'object')
+            if tag_rset:
+                w(u'%s %s' % (_('tags', 'object'), self._cw.view('csv', tag_rset)))
+        rset = entity.related('entry_of', 'subject')
+        if rset:
+            w(u'%s %s' % (self._cw._('blogged in '),
+                          self._cw.view('csv', rset, 'null')))
+        self.w(u' | '.join(reldata))
+
+
+class BlogNavigation(navigation.PageNavigation):
+    __select__ = paginated_rset() & sorted_rset() & is_instance('BlogEntry')
+
+    def index_display(self, start, stop):
+        return unicode(int(start / self.page_size) + 1)
+
+
+# micro blog entries #####################################################
+
+def format_microblog(entity):
+    if entity.has_creator:
+        author = entity.has_creator[0]
+        if author.has_avatar:
+            imgurl = author.has_avatar[0].uri
+            ablock = u'<a href="%s"><img src="%s" alt="avatar"/></a>' % (
+                author.absolute_url(), xml_escape(imgurl))
+        else:
+            ablock = entity.has_creator[0].view('outofcontext')
+    else:
+        ablock = entity.dc_creator()
+    if entity.content_format == 'text/html':
+        content = entity.content
+    else:
+        words = []
+        for word in entity.content.split():
+            if word.startswith('http://'):
+                word = u'<a href="%s">%s</a>' % (word, word)
+            else:
+                word = xml_escape(word)
+            words.append(word)
+        content = u' '.join(words)
+    return (u'<div class="microblog">'
+            u'<span class="author">%s</span>'
+            u'<span class="msgtxt">%s</span>'
+            u'<span class="meta"><a href="%s">%s</a></span>'
+            u'</div>' % (ablock, content, entity.absolute_url(), entity.creation_date))
+
+
+class MicroBlogEntryPrimaryView(primary.PrimaryView):
+    __select__ = primary.PrimaryView.__select__ & is_instance('MicroBlogEntry')
+
+    def cell_call(self, row, col):
+        self._cw.add_css('cubes.blog.css')
+        entity = self.cw_rset.get_entity(row, col)
+        self.w(format_microblog(entity))
+
+
+class MicroBlogEntrySameETypeListView(baseviews.SameETypeListView):
+    __select__ = baseviews.SameETypeListView.__select__ & is_instance(
+        'MicroBlogEntry')
+
+    def cell_call(self, row, col):
+        self._cw.add_css('cubes.blog.css')
+        entity = self.cw_rset.get_entity(row, col)
+        self.w(format_microblog(entity))
+
+
+_CLEAN_STYLE_RE = re.compile(r'<style .*?</style>', re.MULTILINE | re.DOTALL)
+
+
+class BlogEntryRSSItemView(RSSItemView):
+    __select__ = is_instance('BlogEntry')
+
+    def render_description(self, entity):
+        """
+        Make sure generated XML for RSS is "valid", ie. do not
+        contains <style> tags
+
+        Note that this *should* not happen (style tags there), but
+        browser tolerate them.
+        """
+        htmlcontent = entity.dc_description(format='text/html')
+        htmlcontent = _CLEAN_STYLE_RE.sub('', htmlcontent)
+        self._marker('description', htmlcontent)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_blog/views/urlpublishing.py	Sun Nov 11 20:37:51 2018 +0000
@@ -0,0 +1,47 @@
+from cubicweb.web.views.urlrewrite import SimpleReqRewriter, rgx
+
+
+class BlogReqRewriter(SimpleReqRewriter):
+    rules = [
+        # links generated by archives by date/author boxes
+        (rgx('/(micro)?blog/([0-9]+)/blogentries'),
+         dict(rql='Any B,BD ORDERBY BD DESC WHERE B creation_date BD, '
+                  'B entry_of BL, BL eid %(eid)s' % {'eid': r'\2'},
+              )),
+        (rgx('/(micro)?blog/([0-9]+)/blogentries/([a-z_]+)'),
+         dict(rql='Any B,BD ORDERBY BD DESC WHERE B creation_date BD, '
+                  'B entry_of BL, BL eid %(eid)s, '
+                  'B created_by U, U login "%(user)s"' % {'eid': r'\2', 'user': r'\3'},
+              user=r'\2')),
+        (rgx('/(micro)?blog/([0-9]+)/blogentries/([0-9]{4})'),
+         dict(rql='Any B,BD ORDERBY BD DESC WHERE B creation_date BD, B entry_of BL, BL eid %(eid)s'
+              'HAVING YEAR(BD)= %(year)s' % {'eid': r'\2', 'year': r'\3'},
+              )),
+        (rgx('/(micro)?blog/([0-9]+)/blogentries/([0-9]{4})/([0-9]{2})'),
+         dict(rql='Any B,BD ORDERBY BD DESC WHERE B creation_date BD, '
+                  'B entry_of BL, BL eid %(eid)s '
+                  'HAVING YEAR(BD)= %(year)s, MONTH(BD)=%(month)s' % {'eid': r'\2',
+                                                                      'year': r'\3',
+                                                                      'month': r'\4'},
+              )),
+
+        # XXX use or kill
+        (rgx('/blogentry/([a-z_]+)'),
+         dict(rql='Any B,BD ORDERBY BD DESC WHERE B is BlogEntry, '
+                  'B creation_date BD, B created_by U, U login "%(user)s"' % {'user': r'\1'},
+              user=r'\1')),
+        (rgx('/blogentry/([a-z_]+)\.rss'),
+         dict(rql='Any B,BD ORDERBY BD DESC LIMIT 20 WHERE B is BlogEntry, '
+                  'B creation_date BD, B created_by U, U login "%(user)s"' % {'user': r'\1'},
+              vid='rss')),
+
+
+        (rgx('/blogentries/([0-9]{4})'),
+         dict(rql='Any B,BD ORDERBY BD DESC WHERE B is BlogEntry, B creation_date BD '
+                  'HAVING YEAR(BD) = %(year)s' % {'year': r'\1'})),
+        (rgx('/blogentries/([0-9]{4})/([0-9]{2})'),
+         dict(rql='Any B,BD ORDERBY BD DESC WHERE B is BlogEntry, B creation_date BD '
+                  'HAVING YEAR(BD) = %(year)s, MONTH(BD) = %(month)s' % {'year': r'\1',
+                                                                         'month': r'\2'})),
+
+    ]
--- a/data/cubes.blog.css	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-div.blogwrapper {
-    font-size: 14px;
-}
-div.blogwrapper img {
-    padding: 1em;
-    /* the following 3 lines taken verbatim from bootstrap's .img-responsive */
-    max-width: 100%;
-    display: block;
-    height: auto;
-}
-ul.invisible li {
-    background: none repeat scroll 0 0 rgba(0, 0, 0, 0);
-}
-span.previousmonth {
-    float: left;
-}
-span.nextmonth {
-    float: right;
-}
-div.author_date div {
-    color: #999999;
-    float: right;
-    padding-top: 3px;
-}
-div.author_date {
-    border-top: thin ridge #ccc;
-    font-size: 1.2em;
-    font-style: italic;
-}
-div.microblog {
-    clear: both;
-}
-div.microblog span.author {
-    float: left;
-    margin: 0 10px 5px 0;
-}
-div.microblog span.msgtxt {
-    margin: 0 0 10px;
-}
-div.microblog span.meta {
-    color: #999;
-    display: block;
-}
-div.microblog span.meta a {
-    color: #999;
-}
Binary file data/icon_blog.gif has changed
Binary file data/postdatabg.jpg has changed
--- a/entities.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-"""entity classes for Blog entities
-
-:organization: Logilab
-:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE)
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: Lesser General Public License version 2 or above - http://www.gnu.org/
-"""
-# __docformat__ = "restructuredtext en"
-
-from logilab.common.date import todate
-
-from cubicweb.entities import AnyEntity, fetch_config
-from cubicweb.view import EntityAdapter
-from cubicweb.predicates import is_instance
-
-__docformat__ = "restructuredtext en"
-
-
-class BlogIFeedAdapter(EntityAdapter):
-    __regid__ = 'IFeed'
-    __select__ = is_instance('Blog', 'MicroBlog')
-
-    def rss_feed_url(self):
-        if getattr(self.entity, 'rss_url', None):
-            return self.entity.rss_url
-        rql = ('Any E ORDERBY D DESC '
-               'WHERE E entry_of X, X eid %s, E creation_date D'
-               )
-        return self._cw.build_url(rql=rql % self.entity.eid, vid='rss',
-                                  vtitle=self.entity.dc_title())
-
-
-class BlogISiocContainerAdapter(EntityAdapter):
-    __regid__ = 'ISIOCContainer'
-    __select__ = is_instance('Blog')
-
-    def isioc_type(self):
-        return 'Weblog'
-
-    def isioc_items(self):
-        return self.entity.reverse_entry_of
-
-
-class BlogEntry(AnyEntity):
-    """customized class for BlogEntry entities"""
-    __regid__ = 'BlogEntry'
-    fetch_attrs, cw_fetch_order = fetch_config(
-        ['creation_date', 'title'], order='DESC')
-
-    def dc_title(self):
-        return self.title
-
-    def dc_description(self, format='text/plain'):
-        return self.printable_value('content', format=format)
-
-    def dc_date(self, date_format=None):
-        dc_date = self.creation_date
-        for tr_info in self.reverse_wf_info_for:
-            if tr_info.new_state.name == 'published':
-                dc_date = tr_info.creation_date
-                break
-        return self._cw.format_date(dc_date, date_format=date_format)
-
-
-class BlogEntryICalendarableAdapter(EntityAdapter):
-    __regid__ = 'ICalendarable'
-    __select__ = is_instance('BlogEntry')
-
-    @property
-    def start(self):
-        return self.entity.creation_date
-
-    @property
-    def stop(self):
-        return self.entity.creation_date
-
-
-class BlogEntryICalendarViewsAdapter(EntityAdapter):
-    __regid__ = 'ICalendarViews'
-    __select__ = is_instance('BlogEntry')
-
-    def matching_dates(self, begin, end):
-        """calendar views interface"""
-        mydate = self.entity.creation_date
-        if not mydate:
-            return []
-        mydate = todate(mydate)
-        if begin < mydate < end:
-            return [mydate]
-        return []
-
-
-class BlogEntryISiocItemAdapter(EntityAdapter):
-    __regid__ = 'ISIOCItem'
-    __select__ = is_instance('BlogEntry')
-
-    def isioc_content(self):
-        return self.entity.content
-
-    def isioc_container(self):
-        return self.entity.entry_of and self.entity.entry_of[0] or None
-
-    def isioc_type(self):
-        return 'BlogPost'
-
-    def isioc_replies(self):
-        # XXX link to comments
-        return []
-
-    def isioc_topics(self):
-        # XXX link to tags, folders?
-        return []
--- a/hooks.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-from cubicweb.predicates import is_instance, is_in_state
-from cubicweb.sobjects.notification import NotificationView, StatusChangeMixIn
-
-
-class BlogEntryPublishedView(StatusChangeMixIn, NotificationView):
-    """get notified from published blogs"""
-    __select__ = is_instance('BlogEntry',) & is_in_state('published')
-    content_attr = 'content'
-
-    def subject(self):
-        entity = self.cw_rset.get_entity(0, 0)
-        return '[%s] %s' % (self._cw.vreg.config.appid, entity.dc_title())
--- a/i18n/en.po	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,315 +0,0 @@
-# cubicweb-blog i18n catalog
-# Copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# Logilab <contact@logilab.fr>
-msgid ""
-msgstr ""
-"Project-Id-Version: cubicweb-blog 0.1.0\n"
-"PO-Revision-Date: 2010-10-12 11:59+0200\n"
-"Last-Translator: Logilab Team <contact@logilab.fr>\n"
-"Language-Team: en <contact@logilab.fr>\n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-# schema pot file, generated on 2007-09-28 17:24:46
-#
-# singular and plural forms for each entity type
-msgid "Blog"
-msgstr "Blog"
-
-msgid "BlogEntry"
-msgstr "Blog entry"
-
-msgid "BlogEntry_plural"
-msgstr "Blog entries"
-
-msgid "Blog_plural"
-msgstr "Blogs"
-
-msgid "Comment"
-msgstr "Comment"
-
-msgid "MicroBlog"
-msgstr "Micro Blog"
-
-msgid "MicroBlogEntry"
-msgstr "Micro Blog Entry"
-
-msgid "MicroBlogEntry_plural"
-msgstr "Micro Blog Entries"
-
-msgid "MicroBlog_plural"
-msgstr "Micro Blogs"
-
-msgid "New Blog"
-msgstr "New blog"
-
-msgid "New BlogEntry"
-msgstr "New blog entry"
-
-msgid "New MicroBlog"
-msgstr "New Micro Blog"
-
-msgid "New MicroBlogEntry"
-msgstr "New Micro Blog Entry"
-
-msgid "New UserAccount"
-msgstr "New User Account"
-
-msgid "This Blog"
-msgstr "This blog"
-
-msgid "This BlogEntry"
-msgstr "This blog entry"
-
-msgid "This MicroBlog"
-msgstr "This micro blog"
-
-msgid "This MicroBlogEntry"
-msgstr "This micro blog entry"
-
-msgid "This UserAccount"
-msgstr "This user account"
-
-msgid "UserAccount"
-msgstr "User Account"
-
-msgid "UserAccount_plural"
-msgstr "User Accounts"
-
-# add related box generated message
-msgid "add BlogEntry entry_of Blog object"
-msgstr "blog entry"
-
-msgid "add MicroBlogEntry entry_of MicroBlog object"
-msgstr ""
-
-#, python-format
-msgid "blog entries created by %s"
-msgstr ""
-
-msgid "blog's rss url (useful for when using external site such as feedburner)"
-msgstr ""
-
-msgid "blog.archives_by_author"
-msgstr "posts by author"
-
-msgid "blog.archives_by_date"
-msgstr "posts by date"
-
-msgid "blog.latest_blogs"
-msgstr "latest posts"
-
-msgid "blogged in "
-msgstr ""
-
-msgid "by"
-msgstr "by"
-
-msgctxt "BlogEntry"
-msgid "content"
-msgstr ""
-
-msgctxt "MicroBlogEntry"
-msgid "content"
-msgstr ""
-
-msgctxt "BlogEntry"
-msgid "content_format"
-msgstr "format"
-
-msgctxt "MicroBlogEntry"
-msgid "content_format"
-msgstr "format"
-
-msgid "creating BlogEntry (BlogEntry entry_of Blog %(linkto)s)"
-msgstr "creating blogentry in blog %(linkto)s"
-
-msgid "creating MicroBlogEntry (MicroBlogEntry entry_of MicroBlog %(linkto)s)"
-msgstr "creating blogentry in micro-blog %(linkto)s"
-
-msgid "ctxcomponents_blog.archives_by_author"
-msgstr "blog archives by author"
-
-msgid "ctxcomponents_blog.archives_by_author_description"
-msgstr "box displaying posts by author"
-
-msgid "ctxcomponents_blog.archives_by_date"
-msgstr "blog archives by date"
-
-msgid "ctxcomponents_blog.archives_by_date_description"
-msgstr "box displaying posts by date"
-
-msgid "ctxcomponents_blog.latest_blogs"
-msgstr "latest blogs box"
-
-msgid "ctxcomponents_blog.latest_blogs_description"
-msgstr "box displaying latest blog entries posted"
-
-msgid "ctxcomponents_blogsubscribe"
-msgstr "blog subscribtion icon"
-
-msgid "ctxcomponents_blogsubscribe_description"
-msgstr ""
-
-msgid "default BlogEntry workflow"
-msgstr ""
-
-msgctxt "Blog"
-msgid "description"
-msgstr ""
-
-msgctxt "MicroBlog"
-msgid "description"
-msgstr ""
-
-msgctxt "Blog"
-msgid "description_format"
-msgstr "format"
-
-msgctxt "MicroBlog"
-msgid "description_format"
-msgstr "format"
-
-msgid "draft"
-msgstr ""
-
-# subject and object forms for each relation type
-# (no object form for final relation types)
-msgid "entry_of"
-msgstr "blog entry of"
-
-msgctxt "BlogEntry"
-msgid "entry_of"
-msgstr "blog entry of"
-
-msgctxt "MicroBlogEntry"
-msgid "entry_of"
-msgstr "micro blog entry of"
-
-msgid "entry_of_object"
-msgstr "posts"
-
-msgctxt "Blog"
-msgid "entry_of_object"
-msgstr "posts"
-
-msgctxt "MicroBlog"
-msgid "entry_of_object"
-msgstr "posts"
-
-msgid "facets_cwuri-facet"
-msgstr ""
-
-msgid "facets_cwuri-facet_description"
-msgstr ""
-
-msgid "has_avatar"
-msgstr "has avatar"
-
-msgctxt "UserAccount"
-msgid "has_avatar"
-msgstr ""
-
-msgid "has_avatar_object"
-msgstr "avatar of"
-
-msgctxt "ExternalUri"
-msgid "has_avatar_object"
-msgstr ""
-
-msgid "has_creator"
-msgstr "has creator"
-
-msgctxt "BlogEntry"
-msgid "has_creator"
-msgstr ""
-
-msgctxt "MicroBlogEntry"
-msgid "has_creator"
-msgstr ""
-
-msgid "has_creator_object"
-msgstr "creator of"
-
-msgctxt "UserAccount"
-msgid "has_creator_object"
-msgstr ""
-
-msgctxt "UserAccount"
-msgid "name"
-msgstr ""
-
-msgid "next month"
-msgstr ""
-
-msgid "previous month"
-msgstr ""
-
-msgid "publish"
-msgstr ""
-
-msgid "published"
-msgstr ""
-
-msgid "rss icon"
-msgstr ""
-
-msgid "rss_url"
-msgstr "rss feed url"
-
-msgctxt "Blog"
-msgid "rss_url"
-msgstr "rss feed url"
-
-msgctxt "BlogEntry"
-msgid "same_as"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "same_as"
-msgstr ""
-
-msgctxt "MicroBlogEntry"
-msgid "same_as"
-msgstr ""
-
-msgid "see more"
-msgstr "see more"
-
-msgid "subscribe"
-msgstr "subscribe"
-
-msgid "subscribe to this blog"
-msgstr ""
-
-msgid "tags"
-msgstr "tags"
-
-msgctxt "Blog"
-msgid "title"
-msgstr ""
-
-msgctxt "BlogEntry"
-msgid "title"
-msgstr ""
-
-msgctxt "MicroBlog"
-msgid "title"
-msgstr ""
-
-#~ msgid "boxes_blog_summary_box"
-#~ msgstr "posts by author"
-
-#~ msgid "boxes_blog_summary_box_description"
-#~ msgstr ""
-#~ "this box contains a list of authors with the number of blog entries they "
-#~ "posted"
-
-#~ msgid "boxes_latest_blogs_box"
-#~ msgstr "latest blogs"
-
-# add related box generated message
-#~ msgid "boxes_latest_blogs_box_description"
-#~ msgstr "this box contains the latest blog entries posted"
--- a/i18n/es.po	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,299 +0,0 @@
-msgid ""
-msgstr ""
-"Project-Id-Version: cubicweb 2.99.1\n"
-"PO-Revision-Date: 2009-01-27 10:06+0100\n"
-"Last-Translator: Logilab Team <contact@logilab.fr>\n"
-"Language-Team: fr <contact@logilab.fr>\n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: cubicweb-devtools\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-# schema pot file, generated on 2008-11-27 07:53:37
-#
-# singular and plural forms for each entity type
-msgid "Blog"
-msgstr "Blog"
-
-msgid "BlogEntry"
-msgstr "Entrada de blog"
-
-msgid "BlogEntry_plural"
-msgstr "Entradas de Blog"
-
-msgid "Blog_plural"
-msgstr "Blogs"
-
-msgid "Comment"
-msgstr "Participa"
-
-msgid "MicroBlog"
-msgstr ""
-
-msgid "MicroBlogEntry"
-msgstr ""
-
-msgid "MicroBlogEntry_plural"
-msgstr ""
-
-msgid "MicroBlog_plural"
-msgstr ""
-
-msgid "New Blog"
-msgstr "Nuevo Blog"
-
-msgid "New BlogEntry"
-msgstr "Nueva Entrada de Blog"
-
-msgid "New MicroBlog"
-msgstr ""
-
-msgid "New MicroBlogEntry"
-msgstr ""
-
-msgid "New UserAccount"
-msgstr ""
-
-msgid "This Blog"
-msgstr "Este Blog"
-
-msgid "This BlogEntry"
-msgstr "Esta Entrada de Blog"
-
-msgid "This MicroBlog"
-msgstr ""
-
-msgid "This MicroBlogEntry"
-msgstr ""
-
-msgid "This UserAccount"
-msgstr ""
-
-msgid "UserAccount"
-msgstr ""
-
-msgid "UserAccount_plural"
-msgstr ""
-
-# add related box generated message
-msgid "add BlogEntry entry_of Blog object"
-msgstr "entrada de blog"
-
-msgid "add MicroBlogEntry entry_of MicroBlog object"
-msgstr ""
-
-#, python-format
-msgid "blog entries created by %s"
-msgstr "Entradas de blog creadas por %s"
-
-msgid "blog's rss url (useful for when using external site such as feedburner)"
-msgstr ""
-
-msgid "blog.archives_by_author"
-msgstr ""
-
-msgid "blog.archives_by_date"
-msgstr "espacio archivo de blogs"
-
-msgid "blog.latest_blogs"
-msgstr "último"
-
-msgid "blogged in "
-msgstr ""
-
-msgid "by"
-msgstr "por"
-
-msgctxt "BlogEntry"
-msgid "content"
-msgstr ""
-
-msgctxt "MicroBlogEntry"
-msgid "content"
-msgstr ""
-
-msgctxt "BlogEntry"
-msgid "content_format"
-msgstr ""
-
-msgctxt "MicroBlogEntry"
-msgid "content_format"
-msgstr ""
-
-msgid "creating BlogEntry (BlogEntry entry_of Blog %(linkto)s)"
-msgstr "creación de una entrada de blog %(linkto)s"
-
-msgid "creating MicroBlogEntry (MicroBlogEntry entry_of MicroBlog %(linkto)s)"
-msgstr ""
-
-msgid "ctxcomponents_blog.archives_by_author"
-msgstr ""
-
-msgid "ctxcomponents_blog.archives_by_author_description"
-msgstr ""
-
-msgid "ctxcomponents_blog.archives_by_date"
-msgstr "espacio archivo de blogs"
-
-msgid "ctxcomponents_blog.archives_by_date_description"
-msgstr "espacio hacia los archivos del blog en meses anteriores"
-
-msgid "ctxcomponents_blog.latest_blogs"
-msgstr "último blogs"
-
-msgid "ctxcomponents_blog.latest_blogs_description"
-msgstr "espacio que contiene la lista de los últimos blogs"
-
-msgid "ctxcomponents_blogsubscribe"
-msgstr ""
-
-msgid "ctxcomponents_blogsubscribe_description"
-msgstr ""
-
-msgid "default BlogEntry workflow"
-msgstr ""
-
-msgctxt "Blog"
-msgid "description"
-msgstr ""
-
-msgctxt "MicroBlog"
-msgid "description"
-msgstr ""
-
-msgctxt "Blog"
-msgid "description_format"
-msgstr ""
-
-msgctxt "MicroBlog"
-msgid "description_format"
-msgstr ""
-
-msgid "draft"
-msgstr ""
-
-# subject and object forms for each relation type
-# (no object form for final relation types)
-msgid "entry_of"
-msgstr "entrada de blog"
-
-msgctxt "BlogEntry"
-msgid "entry_of"
-msgstr ""
-
-msgctxt "MicroBlogEntry"
-msgid "entry_of"
-msgstr ""
-
-msgid "entry_of_object"
-msgstr "contiene las entradas"
-
-msgctxt "Blog"
-msgid "entry_of_object"
-msgstr ""
-
-msgctxt "MicroBlog"
-msgid "entry_of_object"
-msgstr ""
-
-msgid "facets_cwuri-facet"
-msgstr ""
-
-msgid "facets_cwuri-facet_description"
-msgstr ""
-
-msgid "has_avatar"
-msgstr ""
-
-msgctxt "UserAccount"
-msgid "has_avatar"
-msgstr ""
-
-msgid "has_avatar_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "has_avatar_object"
-msgstr ""
-
-msgid "has_creator"
-msgstr ""
-
-msgctxt "BlogEntry"
-msgid "has_creator"
-msgstr ""
-
-msgctxt "MicroBlogEntry"
-msgid "has_creator"
-msgstr ""
-
-msgid "has_creator_object"
-msgstr ""
-
-msgctxt "UserAccount"
-msgid "has_creator_object"
-msgstr ""
-
-msgctxt "UserAccount"
-msgid "name"
-msgstr ""
-
-msgid "next month"
-msgstr ""
-
-msgid "previous month"
-msgstr ""
-
-msgid "publish"
-msgstr ""
-
-msgid "published"
-msgstr ""
-
-msgid "rss icon"
-msgstr "ícono rss"
-
-msgid "rss_url"
-msgstr ""
-
-msgctxt "Blog"
-msgid "rss_url"
-msgstr ""
-
-msgctxt "BlogEntry"
-msgid "same_as"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "same_as"
-msgstr ""
-
-msgctxt "MicroBlogEntry"
-msgid "same_as"
-msgstr ""
-
-msgid "see more"
-msgstr "ver más"
-
-msgid "subscribe"
-msgstr "suscribirse"
-
-msgid "subscribe to this blog"
-msgstr ""
-
-msgid "tags"
-msgstr "palabras clave"
-
-msgctxt "Blog"
-msgid "title"
-msgstr ""
-
-msgctxt "BlogEntry"
-msgid "title"
-msgstr ""
-
-msgctxt "MicroBlog"
-msgid "title"
-msgstr ""
--- a/i18n/fr.po	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,302 +0,0 @@
-# cubicweb-blog i18n catalog
-# Copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# Logilab <contact@logilab.fr>
-msgid ""
-msgstr ""
-"Project-Id-Version: cubicweb-blog 0.1.0\n"
-"PO-Revision-Date: 2010-10-12 12:03+0200\n"
-"Last-Translator: Logilab Team <contact@logilab.fr>\n"
-"Language-Team: fr <contact@logilab.fr>\n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-# schema pot file, generated on 2007-09-28 17:24:46
-#
-# singular and plural forms for each entity type
-msgid "Blog"
-msgstr "Blog"
-
-msgid "BlogEntry"
-msgstr "Billet"
-
-msgid "BlogEntry_plural"
-msgstr "Billets"
-
-msgid "Blog_plural"
-msgstr "Blogs"
-
-msgid "Comment"
-msgstr "Commentaire"
-
-msgid "MicroBlog"
-msgstr "Microblog"
-
-msgid "MicroBlogEntry"
-msgstr "Micro-billet"
-
-msgid "MicroBlogEntry_plural"
-msgstr "Micro-billets"
-
-msgid "MicroBlog_plural"
-msgstr "Microblogs"
-
-msgid "New Blog"
-msgstr "Nouveau blog"
-
-msgid "New BlogEntry"
-msgstr "Nouveau billet"
-
-msgid "New MicroBlog"
-msgstr "Nouveau microblog"
-
-msgid "New MicroBlogEntry"
-msgstr "Nouveau microbillet"
-
-msgid "New UserAccount"
-msgstr "Nouveau compte utilisateur"
-
-msgid "This Blog"
-msgstr "Ce blog"
-
-msgid "This BlogEntry"
-msgstr "Ce billet"
-
-msgid "This MicroBlog"
-msgstr "Ce microblog"
-
-msgid "This MicroBlogEntry"
-msgstr "Ce microbillet"
-
-msgid "This UserAccount"
-msgstr "Ce compte utilisateur"
-
-msgid "UserAccount"
-msgstr "Compte utilisateur"
-
-msgid "UserAccount_plural"
-msgstr "Comptes utilisateurs"
-
-# add related box generated message
-msgid "add BlogEntry entry_of Blog object"
-msgstr "billet"
-
-msgid "add MicroBlogEntry entry_of MicroBlog object"
-msgstr "billet"
-
-#, python-format
-msgid "blog entries created by %s"
-msgstr "billets créés par %s"
-
-msgid "blog's rss url (useful for when using external site such as feedburner)"
-msgstr ""
-"url du flux rss du blog (peut être utile lorsque l'on utilise un service tel "
-"que feedburner)"
-
-msgid "blog.archives_by_author"
-msgstr "billets par auteur"
-
-msgid "blog.archives_by_date"
-msgstr "billets par date"
-
-msgid "blog.latest_blogs"
-msgstr "derniers billets"
-
-msgid "blogged in "
-msgstr "bloggué dans "
-
-msgid "by"
-msgstr "par"
-
-msgctxt "BlogEntry"
-msgid "content"
-msgstr "contenu"
-
-msgctxt "MicroBlogEntry"
-msgid "content"
-msgstr "contenu"
-
-msgctxt "BlogEntry"
-msgid "content_format"
-msgstr "format"
-
-msgctxt "MicroBlogEntry"
-msgid "content_format"
-msgstr "format"
-
-msgid "creating BlogEntry (BlogEntry entry_of Blog %(linkto)s)"
-msgstr "création d'un billet dans le blog %(linkto)s"
-
-msgid "creating MicroBlogEntry (MicroBlogEntry entry_of MicroBlog %(linkto)s)"
-msgstr "création d'un billet dans le microblog %(linkto)s"
-
-msgid "ctxcomponents_blog.archives_by_author"
-msgstr "boîte des billets par auteur"
-
-msgid "ctxcomponents_blog.archives_by_author_description"
-msgstr "boîte contenant la liste des auteurs et leur nombre de billets"
-
-msgid "ctxcomponents_blog.archives_by_date"
-msgstr "boîte des billets par date"
-
-msgid "ctxcomponents_blog.archives_by_date_description"
-msgstr "boîte permettant d'accéder aux archives des blogs pour les mois passés"
-
-msgid "ctxcomponents_blog.latest_blogs"
-msgstr "boîte des derniers billets"
-
-msgid "ctxcomponents_blog.latest_blogs_description"
-msgstr "boîte contenant la liste des derniers billets postés dans un blog ou micro-blog"
-
-msgid "ctxcomponents_blogsubscribe"
-msgstr "souscrire au blog"
-
-msgid "ctxcomponents_blogsubscribe_description"
-msgstr "icône permettant de souscrire à un blog"
-
-msgid "default BlogEntry workflow"
-msgstr "workflow des billets par défaut"
-
-msgctxt "Blog"
-msgid "description"
-msgstr "description"
-
-msgctxt "MicroBlog"
-msgid "description"
-msgstr "description"
-
-msgctxt "Blog"
-msgid "description_format"
-msgstr "format"
-
-msgctxt "MicroBlog"
-msgid "description_format"
-msgstr "format"
-
-msgid "draft"
-msgstr "brouillon"
-
-# subject and object forms for each relation type
-# (no object form for final relation types)
-msgid "entry_of"
-msgstr "dans le blog"
-
-msgctxt "BlogEntry"
-msgid "entry_of"
-msgstr "posté dans"
-
-msgctxt "MicroBlogEntry"
-msgid "entry_of"
-msgstr "posté dans"
-
-msgid "entry_of_object"
-msgstr "billets"
-
-msgctxt "Blog"
-msgid "entry_of_object"
-msgstr "billets"
-
-msgctxt "MicroBlog"
-msgid "entry_of_object"
-msgstr "micro-billets"
-
-msgid "facets_cwuri-facet"
-msgstr ""
-
-msgid "facets_cwuri-facet_description"
-msgstr ""
-
-msgid "has_avatar"
-msgstr "a pour avatar"
-
-msgctxt "UserAccount"
-msgid "has_avatar"
-msgstr "a pour avatar"
-
-msgid "has_avatar_object"
-msgstr "est l'avatar de"
-
-msgctxt "ExternalUri"
-msgid "has_avatar_object"
-msgstr "est l'avatar de"
-
-msgid "has_creator"
-msgstr "a pour créateur"
-
-msgctxt "BlogEntry"
-msgid "has_creator"
-msgstr "a pour créateur"
-
-msgctxt "MicroBlogEntry"
-msgid "has_creator"
-msgstr "a pour créateur"
-
-msgid "has_creator_object"
-msgstr "est le créateur de"
-
-msgctxt "UserAccount"
-msgid "has_creator_object"
-msgstr "est le créateur de"
-
-msgctxt "UserAccount"
-msgid "name"
-msgstr "nom"
-
-msgid "next month"
-msgstr "mois suivant"
-
-msgid "previous month"
-msgstr "mois précédent"
-
-msgid "publish"
-msgstr "publier"
-
-msgid "published"
-msgstr "publié"
-
-msgid "rss icon"
-msgstr "icône rss"
-
-msgid "rss_url"
-msgstr "url du fil rss"
-
-msgctxt "Blog"
-msgid "rss_url"
-msgstr "url du flux RSS"
-
-msgctxt "BlogEntry"
-msgid "same_as"
-msgstr "identique à"
-
-msgctxt "ExternalUri"
-msgid "same_as"
-msgstr "identique à"
-
-msgctxt "MicroBlogEntry"
-msgid "same_as"
-msgstr "identique à"
-
-msgid "see more"
-msgstr "voir plus"
-
-msgid "subscribe"
-msgstr "souscrire"
-
-msgid "subscribe to this blog"
-msgstr "souscrire à ce blog"
-
-msgid "tags"
-msgstr "étiquettes"
-
-msgctxt "Blog"
-msgid "title"
-msgstr "titre"
-
-msgctxt "BlogEntry"
-msgid "title"
-msgstr "titre"
-
-msgctxt "MicroBlog"
-msgid "title"
-msgstr "titre"
--- a/migration/1.2.0_Any.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-rename_entity_type('Blog', u'BlogEntry')
-add_entity_type('Blog')
-rql('INSERT Blog B: B title "main blog"')
-rql('SET BE entry_of B WHERE BE is BlogEntry, B is Blog')
-checkpoint()
--- a/migration/1.4.2_Any.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-add_attribute('Blog', 'description_format')
-add_attribute('Blog', 'rss_url')
-checkpoint()
--- a/migration/1.5.0_Any.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-change_relation_props('BlogEntry', 'entry_of', 'Blog',
-                      commit=True, cardinality='**')
--- a/migration/1.7.0_Any.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-add_relation_definition('BlogEntry',  'in_state', 'State')
-add_relation_definition('TrInfo',  'wf_info_for', 'BlogEntry')
-add_relation_definition('BlogEntry',  'custom_workflow', 'Workflow')
-
-
-# add BlogEntry workflow
-if confirm('add blog entry workflow'):
-    bwf = add_workflow(_('default BlogEntry workflow'), 'BlogEntry')
-
-    draft = bwf.add_state(_('draft'), initial=True)
-    published = bwf.add_state(_('published'))
-
-    publish = bwf.add_transition(_('publish'), draft, published,
-                                 ('managers',))
-    commit()
-
-# set state to published for already existing blog entries
-blogentries = rql('Any B WHERE B is BlogEntry')
-
-for eid, in blogentries:
-    session.unsafe_execute('SET B in_state S WHERE S name "published", '
-                           'S state_of WF, WF name "default BlogEntry workflow", '
-                           'B eid %(b)s',
-                           {'b': eid}, 'b')
-
-commit()
--- a/migration/1.7.2_Any.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-sync_schema_props_perms('BlogEntry')
--- a/migration/1.7.3_Any.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-# add group "owners" in transition "publish" of BlogEntry workflow permissions
-rql('SET T require_group G WHERE G name "owners", T transition_of W, '
-    'T name "publish", W workflow_of ET, ET name "BlogEntry"')
-commit()
--- a/migration/1.9.0_Any.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-add_entity_type('MicroBlog')
-add_entity_type('MicroBlogEntry')
-add_entity_type('UserAccount')
--- a/migration/postcreate.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-# postcreate script. You could setup a workflow here for example
-
-try:
-    from cubicweb import _
-except ImportError:
-    _ = unicode
-
-# BlogEntry workflow
-bwf = add_workflow(_('default BlogEntry workflow'), 'BlogEntry')
-
-draft = bwf.add_state(_('draft'), initial=True)
-published = bwf.add_state(_('published'))
-
-publish = bwf.add_transition(_('publish'), draft, published,
-                             ('managers', 'owners'))
--- a/schema.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-from six import text_type as unicode
-
-try:
-    from cubicweb import _
-except ImportError:
-    _ = unicode
-
-from yams.buildobjs import EntityType, String, RichString, SubjectRelation, RelationDefinition
-from cubicweb.schema import WorkflowableEntityType, ERQLExpression
-
-
-class Blog(EntityType):
-    title = String(maxsize=50, required=True)
-    description = RichString()
-    rss_url = String(maxsize=128, description=_(
-        'blog\'s rss url (useful for when using external site such as feedburner)'))
-
-
-class BlogEntry(WorkflowableEntityType):
-    __permissions__ = {
-        'read': ('managers', 'users', ERQLExpression('X in_state S, S name "published"'),),
-        'add': ('managers', 'users'),
-        'update': ('managers', 'owners'),
-        'delete': ('managers', 'owners')
-    }
-    title = String(required=True, fulltextindexed=True, maxsize=256)
-    content = RichString(required=True, fulltextindexed=True)
-    entry_of = SubjectRelation('Blog')
-    same_as = SubjectRelation('ExternalUri')
-
-
-class MicroBlog(EntityType):
-    title = String(maxsize=50, required=True)
-    description = RichString()
-
-
-class MicroBlogEntry(EntityType):
-    __permissions__ = {
-        'read': ('managers', 'users'),
-        'add': ('managers', 'users'),
-        'update': ('managers', 'owners'),
-        'delete': ('managers', 'owners')
-    }
-    content = RichString(required=True, fulltextindexed=True)
-    entry_of = SubjectRelation('MicroBlog')
-    same_as = SubjectRelation('ExternalUri')
-
-
-class UserAccount(EntityType):
-    name = String(required=True)  # see foaf:accountName
-
-
-class has_creator(RelationDefinition):
-    subject = ('BlogEntry', 'MicroBlogEntry')
-    object = 'UserAccount'
-
-
-class has_avatar(RelationDefinition):
-    subject = 'UserAccount'
-    object = 'ExternalUri'
--- a/setup.py	Tue Jan 17 12:14:00 2017 +0100
+++ b/setup.py	Sun Nov 11 20:37:51 2018 +0000
@@ -18,220 +18,66 @@
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""Generic Setup script, takes package info from __pkginfo__.py file
+"""cubicweb_blog setup module using data from
+cubicweb_blog/__pkginfo__.py file
 """
 
-import os
-import sys
-import shutil
-try:
-    from os.path import exists, join, walk, dirname
-except ImportError:  # PY3
-    from os.path import exists, join, dirname
-    from os import walk
-
-__docformat__ = "restructuredtext en"
+from os.path import join, dirname
+from setuptools import find_packages, setup
 
-try:
-    if os.environ.get('NO_SETUPTOOLS'):
-        raise ImportError()  # do as there is no setuptools
-    from setuptools import setup
-    from setuptools.command import install_lib
-    USE_SETUPTOOLS = True
-except ImportError:
-    from distutils.core import setup
-    from distutils.command import install_lib
-    USE_SETUPTOOLS = False
-from distutils.command import install_data
 
-if exists('README'):
-    long_description = open('README').read()
-else:
-    long_description = ''
+here = dirname(__file__)
+
 # load metadata from the __pkginfo__.py file so there is no risk of conflict
 # see https://packaging.python.org/en/latest/single_source_version.html
-base_dir = dirname(__file__)
-pkginfo = {}
-with open(join(base_dir, "__pkginfo__.py")) as f:
-    exec(f.read(), pkginfo)
+pkginfo = join(here, 'cubicweb_blog', '__pkginfo__.py')
+__pkginfo__ = {}
+with open(pkginfo) as f:
+    exec(f.read(), __pkginfo__)
 
 # get required metadatas
-modname = pkginfo['modname']
-version = pkginfo['version']
-license = pkginfo['license']
-description = pkginfo['description']
-web = pkginfo['web']
-author = pkginfo['author']
-author_email = pkginfo['author_email']
-classifiers = pkginfo['classifiers']
+distname = __pkginfo__['distname']
+version = __pkginfo__['version']
+license = __pkginfo__['license']
+description = __pkginfo__['description']
+web = __pkginfo__['web']
+author = __pkginfo__['author']
+author_email = __pkginfo__['author_email']
+classifiers = __pkginfo__['classifiers']
 
-with open(join(base_dir, 'README')) as f:
+with open(join(here, 'README')) as f:
     long_description = f.read()
 
 # get optional metadatas
-distname = pkginfo.get('distname', modname)
-scripts = pkginfo.get('scripts', ())
-include_dirs = pkginfo.get('include_dirs', ())
-data_files = pkginfo.get('data_files', None)
-ext_modules = pkginfo.get('ext_modules', None)
-dependency_links = pkginfo.get('dependency_links', ())
-
-if USE_SETUPTOOLS:
-    requires = {}
-    for entry in ("__depends__",):  # "__recommends__"):
-        requires.update(pkginfo.get(entry, {}))
-    install_requires = [("%s %s" % (d, v and v or "")).strip()
-                        for d, v in requires.items()]
-else:
-    install_requires = []
-
-BASE_BLACKLIST = ('CVS', '.svn', '.hg', '.git', 'debian', 'dist', 'build')
-IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc', '~')
-
-
-def ensure_scripts(linux_scripts):
-    """
-    Creates the proper script names required for each platform
-    (taken from 4Suite)
-    """
-    from distutils import util
-    if util.get_platform()[:3] == 'win':
-        scripts_ = [script + '.bat' for script in linux_scripts]
-    else:
-        scripts_ = linux_scripts
-    return scripts_
-
+data_files = __pkginfo__.get('data_files', None)
+dependency_links = __pkginfo__.get('dependency_links', ())
 
-def export(from_dir, to_dir,
-           blacklist=BASE_BLACKLIST,
-           ignore_ext=IGNORED_EXTENSIONS,
-           verbose=True):
-    try:
-        os.mkdir(to_dir)
-    except OSError as ex:
-        # file exists ?
-        import errno
-        if ex.errno != errno.EEXIST:
-            raise
-    for dirpath, dirnames, filenames in os.walk(from_dir):
-        for norecurs in blacklist:
-            try:
-                dirnames.remove(norecurs)
-            except ValueError:
-                pass
-        for dir_name in dirnames:
-            dest = join(to_dir, dir_name)
-            if not exists(dest):
-                os.mkdir(dest)
-        for filename in filenames:
-            # don't include binary files
-            src = join(dirpath, filename)
-            dest = to_dir + src[len(from_dir):]
-            if filename[-4:] in ignore_ext:
-                continue
-            if filename[-1] == '~':
-                continue
-            if exists(dest):
-                os.remove(dest)
-            if verbose:
-                sys.stderr.write('%s -> %s\n' % (src, dest))
-            if os.path.isdir(src):
-                if not exists(dest):
-                    os.mkdir(dest)
-            else:
-                if exists(dest):
-                    os.remove(dest)
-                shutil.copy2(src, dest)
-    try:
-        os.mkdir(to_dir)
-    except OSError as ex:
-        # file exists ?
-        import errno
-        if ex.errno != errno.EEXIST:
-            raise
-    walk(from_dir, make_mirror, None)
+requires = {}
+for entry in ("__depends__",):  # "__recommends__"):
+    requires.update(__pkginfo__.get(entry, {}))
+
+install_requires = ["{0} {1}".format(d, v and v or "").strip()
+                    for d, v in requires.items()]
 
 
-class MyInstallLib(install_lib.install_lib):
-    """extend install_lib command to handle  package __init__.py and
-    include_dirs variable if necessary
-    """
-
-    def run(self):
-        """overridden from install_lib class"""
-        install_lib.install_lib.run(self)
-        # manually install included directories if any
-        if include_dirs:
-            base = modname
-            for directory in include_dirs:
-                dest = join(self.install_dir, base, directory)
-                export(directory, dest, verbose=False)
-
-
-# re-enable copying data files in sys.prefix
-old_install_data = install_data.install_data
-if USE_SETUPTOOLS:
-    # overwrite InstallData to use sys.prefix instead of the egg directory
-    class MyInstallData(old_install_data):
-        """A class that manages data files installation"""
-
-        def run(self):
-            _old_install_dir = self.install_dir
-            if self.install_dir.endswith('egg'):
-                self.install_dir = sys.prefix
-            old_install_data.run(self)
-            self.install_dir = _old_install_dir
-    try:
-        # only if easy_install available
-        import setuptools.command.easy_install  # noqa
-        # monkey patch: Crack SandboxViolation verification
-        from setuptools.sandbox import DirectorySandbox as DS
-        old_ok = DS._ok
+setup(
+    name=distname,
+    version=version,
+    license=license,
+    description=description,
+    long_description=long_description,
+    author=author,
+    author_email=author_email,
+    url=web,
+    classifiers=classifiers,
+    packages=find_packages(exclude=['test']),
+    install_requires=install_requires,
+    include_package_data=True,
+    entry_points={
+        'cubicweb.cubes': [
+            'blog=cubicweb_blog',
+        ],
+    },
 
-        def _ok(self, path):
-            """Return True if ``path`` can be written during installation."""
-            out = old_ok(self, path)  # here for side effect from setuptools
-            realpath = os.path.normcase(os.path.realpath(path))
-            allowed_path = os.path.normcase(sys.prefix)
-            if realpath.startswith(allowed_path):
-                out = True
-            return out
-        DS._ok = _ok
-    except ImportError:
-        pass
-
-
-def install(**kwargs):
-    """setup entry point"""
-    if USE_SETUPTOOLS:
-        if '--force-manifest' in sys.argv:
-            sys.argv.remove('--force-manifest')
-    # install-layout option was introduced in 2.5.3-1~exp1
-    elif sys.version_info < (2, 5, 4) and '--install-layout=deb' in sys.argv:
-        sys.argv.remove('--install-layout=deb')
-    cmdclass = {'install_lib': MyInstallLib}
-    if USE_SETUPTOOLS:
-        kwargs['install_requires'] = install_requires
-        kwargs['dependency_links'] = dependency_links
-        kwargs['zip_safe'] = False
-        cmdclass['install_data'] = MyInstallData
-
-    return setup(name=distname,
-                 version=version,
-                 license=license,
-                 description=description,
-                 long_description=long_description,
-                 author=author,
-                 author_email=author_email,
-                 url=web,
-                 scripts=ensure_scripts(scripts),
-                 data_files=data_files,
-                 ext_modules=ext_modules,
-                 cmdclass=cmdclass,
-                 classifiers=classifiers,
-                 **kwargs
-                 )
-
-
-if __name__ == '__main__':
-    install()
+    zip_safe=False,
+)
--- a/site_cubicweb.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-# XML <-> yams equivalence
-from cubicweb.xy import xy
-xy.add_equivalence('Blog', 'sioc:Weblog')
-xy.add_equivalence('BlogEntry', 'sioc:BlogPost')
-xy.add_equivalence('BlogEntry title', 'dcterms:title')
-xy.add_equivalence('BlogEntry content', 'sioc:content')
--- a/sobjects.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import print_function
-
-from datetime import datetime
-from six import text_type as unicode
-
-from lxml.html import fromstring, tostring
-
-try:
-    import feedparser
-except ImportError:
-    feedparser = None
-
-try:
-    import rdflib
-except ImportError:
-    rdflib = None
-else:
-    RDF = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
-    SIOC = rdflib.Namespace('http://rdfs.org/sioc/ns#')
-    DCTERMS = rdflib.Namespace('http://purl.org/dc/terms/')
-
-try:
-    from cubes.datafeed.sobjects import DataFeedParser
-except ImportError:
-    DataFeedParser = None
-
-
-def get_subject(g, pred, obj):
-    subjects = list(g.subjects(pred, obj))
-    assert len(subjects) == 1
-    return subjects[0]
-
-
-def get_object(g, subj, pred):
-    objects = list(g.objects(subj, pred))
-    assert len(objects) == 1
-    return objects[0]
-
-
-def parse_blogpost_sioc(url):
-    g = rdflib.ConjunctiveGraph()
-    g.parse(url)
-    for post, type_, blogpost_ in g.triples((None, RDF.type, SIOC.BlogPost)):
-        item = {'uri': unicode(post)}
-        item['title'] = unicode(get_object(g, post, DCTERMS.title))
-        item['content'] = unicode(get_object(g, post, SIOC.content))
-        yield item
-
-
-format_map = {'application/xhtml+xml': u'text/html',
-              'text/html': u'text/html',
-              'text/plain': u'text/plain',
-              }
-
-IMG_SPIES = ['http://feeds.feedburner.com',
-             'http://creatives.commindo-media',
-             'http://imp.constantcontact.com',
-             'https://blogger.googleusercontent.com/tracker',
-             'http://stats.wordpress.com/',
-             ]
-
-
-def is_img_spy(node):
-    if node.tag != 'img':
-        return False
-    for url in IMG_SPIES:
-        if node.get('src').startswith(url):
-            return True
-    return False
-
-
-def is_tweetmeme_spy(node):
-    href = node.get('href')
-    if href and href.startswith('http://api.tweetmeme.com/share'):
-        return True
-    return False
-
-
-def remove_content_spies(content):
-    root = fromstring(content)
-    if is_img_spy(root):
-        return u''
-    for img in root.findall('.//img'):
-        if is_img_spy(img):
-            img.drop_tree()
-        elif img.get('height') == '1' and img.get('width') == '1':
-            print(tostring(img), 'is probably a spy')
-    for anchor in root.findall('.//a'):
-        if is_tweetmeme_spy(anchor):
-            anchor.drop_tree()
-    return unicode(tostring(root))
-
-
-def parse_blogpost_rss(url):
-    data = feedparser.parse(url)
-    feed = data.feed
-    for entry in data.entries:
-        item = {}
-        if 'feedburner_origlink' in entry:
-            item['uri'] = entry.feedburner_origlink
-        else:
-            item['uri'] = entry.link
-        item['title'] = entry.title
-        if hasattr(entry, 'content'):
-            content = entry.content[0].value
-            mimetype = entry.content[0].type
-        elif hasattr(entry, 'summary_detail'):
-            content = entry.summary_detail.value
-            mimetype = entry.summary_detail.type
-        else:
-            content = u''  # XXX entry.description?
-            mimetype = u'text/plain'
-        if mimetype == u'text/html':
-            content = remove_content_spies(content)
-        item['content'] = content
-        item['content_format'] = format_map.get(mimetype, u'text/plain')
-        if hasattr(entry, 'date_parsed'):
-            item['creation_date'] = datetime(*entry.date_parsed[:6])
-        if hasattr(entry, 'author_detail') and hasattr(entry.author_detail, 'href'):
-            item['author'] = entry.author_detail.href
-        elif hasattr(feed, 'author_detail') and hasattr(feed.author_detail, 'href'):
-            item['author'] = feed.author_detail.href
-        elif hasattr(feed, 'author'):
-            item['author'] = feed.author
-        elif hasattr(feed, 'image') and hasattr(feed.image, 'link'):
-            item['author'] = feed.image.link
-        else:
-            item['author'] = url
-        item['cwuri'] = feed.link
-        yield item
-
-
-def parse_microblogpost_rss(url):
-    feed = feedparser.parse(url)
-    for entry in feed.entries:
-        item = {}
-        item['uri'] = entry.id
-        # fix weird parsing
-        if hasattr(entry, 'content'):
-            content = entry.content[0].value
-            mimetype = entry.content[0].type
-        else:
-            content = entry.description
-            mimetype = u'text/plain'
-        if ': ' in content:
-            author, text = content.split(': ', 1)
-            if ' ' not in author:
-                content = text
-        item['content'] = content
-        item['content_format'] = format_map.get(mimetype, u'text/plain')
-        item['creation_date'] = datetime(*entry.date_parsed[:6])
-        item['modification_date'] = datetime(*entry.date_parsed[:6])
-        item['author'] = feed.channel.link  # true for twitter
-        item['cwuri'] = feed.channel.link
-        for link in entry.links:
-            if link.type.startswith('image/') and link.rel == 'image':
-                item['avatar'] = link.href
-                break
-        else:
-            screen_name = feed.channel.link.split('/')[-1]
-            item['avatar'] = get_twitter_avatar(screen_name)
-        yield item
-
-
-def search_twitter(word):
-    import urllib2
-    from simplejson import loads
-    data = urllib2.urlopen(
-        'http://search.twitter.com/search.json?q=%s&rpp=100' % word).read()
-    loads(data)
-    # process results
-    # print results
-    return []
-
-
-AVATAR_CACHE = {}
-
-
-def get_twitter_avatar(screen_name):
-    if screen_name not in AVATAR_CACHE:
-        from urllib2 import urlopen
-        import simplejson
-        data = urlopen(
-            'http://api.twitter.com/1/users/show.json?screen_name=%s' % screen_name).read()
-        user = simplejson.loads(data)
-        AVATAR_CACHE[screen_name] = user['profile_image_url']
-    return AVATAR_CACHE[screen_name]
-
-
-if DataFeedParser is not None:
-    class BlogPostParser(DataFeedParser):
-        __abstract__ = True
-        entity_type = 'BlogEntry'
-
-        def process(self, url):
-            stats = {'update': 0, 'creation': 0}
-            for item in self.parse(url):
-                author = item.pop('author', None)
-                avatar = item.pop('avatar', None)
-                euri = self.sget_entity('ExternalUri', uri=item.pop('uri'))
-                if euri.same_as:
-                    # sys.stdout.write('.')
-                    stats['update'] += 1
-                    post = self.update_blogpost(euri.same_as[0], item)
-                else:
-                    # sys.stdout.write('+')
-                    stats['creation'] += 1
-                    post = self.create_blogpost(item, euri)
-                if author:
-                    account = self.sget_entity('UserAccount', name=author)
-                    self.sget_relation(post.eid, 'has_creator', account.eid)
-                    if avatar:
-                        auri = self.sget_entity('ExternalUri', uri=avatar)
-                        self.sget_relation(account.eid, 'has_avatar', auri.eid)
-                # sys.stdout.flush()
-            return stats
-
-        def create_blogpost(self, item, uri):
-            entity = self._cw.create_entity(self.entity_type, **item)
-            entity.set_relations(same_as=uri)
-            return entity
-
-        def update_blogpost(self, entity, item):
-            entity.set_attributes(**item)
-            return entity
-
-    if rdflib is not None:
-        class BlogPostSiocParser(BlogPostParser):
-            __regid__ = 'blogpost-sioc'
-            parse = staticmethod(parse_blogpost_sioc)
-
-    if feedparser is not None:
-        class BlogPostRSSParser(BlogPostParser):
-            __regid__ = 'blogpost-rss'
-            parse = staticmethod(parse_blogpost_rss)
-
-        class MicroBlogPostRSSParser(BlogPostParser):
-            __regid__ = 'microblogpost-rss'
-            entity_type = 'MicroBlogEntry'
-            parse = staticmethod(parse_microblogpost_rss)
-
-
-if __name__ == '__main__':
-    import sys
-    from pprint import pprint
-
-    name = sys.argv[1]
-    url = sys.argv[2]
-
-    parser = globals()[name]
-    pprint(list(parser(url)))
--- a/tox.ini	Tue Jan 17 12:14:00 2017 +0100
+++ b/tox.ini	Sun Nov 11 20:37:51 2018 +0000
@@ -31,4 +31,4 @@
 format = pylint
 max-line-length = 100
 ignore = E731,W503
-exclude = __pkginfo__.py,migration/*,test/data/*,setup.py,.tox/*
+exclude = cubicweb_blog/__pkginfo__.py,cubicweb_blog/migration/*,test/data/*,.tox/*
--- a/uiprops.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-BLOG_ICON = data('icon_blog.gif') # noqa
--- a/views/__init__.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-from cubicweb.predicates import is_instance
-from cubicweb.view import EntityAdapter
-from cubicweb.web.views import ibreadcrumbs
-from cubicweb.web.views.autoform import AutomaticEntityForm
-
-
-class BlogEntryIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
-    __select__ = is_instance('BlogEntry', 'MicroBlogEntry')
-
-    def parent_entity(self):
-        return self.entity.entry_of and self.entity.entry_of[0] or None
-
-
-class BlogEntryIPrevNextAdapter(EntityAdapter):
-    __regid__ = 'IPrevNext'
-    __select__ = is_instance('BlogEntry', 'MicroBlogEntry')
-
-    def next_entity(self):
-        return self._sibling_entry('ASC', '>')
-
-    def previous_entity(self):
-        return self._sibling_entry('DESC', '<')
-
-    def _sibling_entry(self, order, operator):
-        if self.entity.entry_of:
-            rql = ('Any B ORDERBY B %s LIMIT 1 '
-                   'WHERE B entry_of BL, BL eid %%(blog)s, '
-                   'B eid %s %%(eid)s')
-            rset = self._cw.execute(rql % (order, operator),
-                                    {'blog': self.entity.entry_of[0].eid,
-                                     'eid': self.entity.eid})
-        else:
-            rql = ('Any B ORDERBY B %s LIMIT 1 '
-                   'WHERE B eid %s %%(eid)s, NOT B entry_of BL, '
-                   'B is ET, ET name IN ("BlogEntry", "MicroBlogEntry")')
-            rset = self._cw.execute(rql % (order, operator),
-                                    {'eid': self.entity.eid})
-        if rset:
-            return rset.get_entity(0, 0)
-
-
-def registration_callback(vreg):
-    vreg.register(BlogEntryIBreadCrumbsAdapter)
-    vreg.register(BlogEntryIPrevNextAdapter)
-
-    loaded_cubes = vreg.config.cubes()
-
-    if 'seo' in loaded_cubes:
-        from cubes.seo.views import SitemapRule
-
-        class BlogEntrySitemapRule(SitemapRule):
-            __regid__ = 'blogentry'
-            query = 'Any X WHERE X is BlogEntry'
-            priority = 1.0
-            chfreq = 'yearly'
-
-        class MicroBlogEntrySitemapRule(SitemapRule):
-            __regid__ = 'microblogentry'
-            query = 'Any X WHERE X is MicroBlogEntry'
-            priority = 1.0
-            chfreq = 'yearly'
-
-        vreg.register(BlogEntrySitemapRule)
-        vreg.register(MicroBlogEntrySitemapRule)
-
-    if 'preview' in loaded_cubes:
-        from cubes.preview.views.forms import PreviewFormMixin
-
-        class PreviewAutomaticEntityForm(PreviewFormMixin, AutomaticEntityForm):
-            preview_mode = 'inline'
-            __select__ = AutomaticEntityForm.__select__ & is_instance('Blog', 'BlogEntry',
-                                                                      'MicroBlog', 'MicroBlogEntry')
-        vreg.register(PreviewAutomaticEntityForm)
--- a/views/blog.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-"""Primary views for blogs
-
-:organization: Logilab
-:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-from logilab.mtconverter import xml_escape
-
-from cubicweb.utils import UStringIO
-from cubicweb.predicates import is_instance
-from cubicweb.web import component
-from cubicweb.web.views import uicfg, primary
-
-__docformat__ = "restructuredtext en"
-
-_pvs = uicfg.primaryview_section
-_pvdc = uicfg.primaryview_display_ctrl
-_abaa = uicfg.actionbox_appearsin_addmenu
-for etype in ('Blog', 'MicroBlog'):
-    _pvs.tag_attribute((etype, 'title'), 'hidden')
-    _pvs.tag_object_of(('*', 'entry_of', etype), 'hidden')
-    _pvdc.tag_attribute((etype, 'description'), {'showlabel': False})
-    _abaa.tag_object_of(('*', 'entry_of', etype), True)
-_pvs.tag_attribute(('Blog', 'rss_url'), 'hidden')
-
-_pvs.tag_object_of(('*', 'has_creator', 'UserAccount'), 'relations')
-_pvs.tag_attribute(('UserAccount', 'name'), 'hidden')
-
-
-class BlogPrimaryView(primary.PrimaryView):
-    __select__ = is_instance('Blog', 'MicroBlog')
-
-    def entity_call(self, entity):
-        self.w(u'<div class="blogwrapper">')
-        super(BlogPrimaryView, self).entity_call(entity)
-        self.w(u'</div>')
-
-    def render_entity_relations(self, entity):
-        super(BlogPrimaryView, self).render_entity_relations(entity)
-        rset = entity.related('entry_of', 'object')
-        if rset:
-            strio = UStringIO()
-            self.paginate(w=strio.write, page_size=10, rset=rset)
-            self.w(strio.getvalue())
-            self.wview('sameetypelist', rset, showtitle=False)
-            self.w(strio.getvalue())
-
-
-class SubscribeToBlogComponent(component.EntityCtxComponent):
-    __regid__ = 'blogsubscribe'
-    __select__ = component.EntityVComponent.__select__ & is_instance(
-        'Blog', 'MicroBlog')
-    context = 'ctxtoolbar'
-
-    def render_body(self, w):
-        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        w('<a href="%s"><img src="%s" alt="%s"/></a>' % (
-            xml_escape(entity.cw_adapt_to('IFeed').rss_feed_url()),
-            self._cw.uiprops['RSS_LOGO_16'],
-            self._cw._(u'subscribe to this blog')))
--- a/views/boxes.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-"""Various blog boxes: archive, per author, etc...
-
-:organization: Logilab
-:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-from six import text_type as unicode
-
-from cubicweb import tags
-from cubicweb.predicates import (none_rset, one_line_rset, is_instance,
-                                 has_related_entities, match_view)
-from cubicweb.web import component
-
-try:
-    from cubicweb import _
-except ImportError:
-    _ = unicode
-
-__docformat__ = "restructuredtext en"
-
-
-class BlogArchivesBox(component.CtxComponent):
-    """blog side box displaying a Blog Archive"""
-    __regid__ = 'blog.archives_by_date'
-    __select__ = (component.CtxComponent.__select__
-                  & is_instance('Blog', 'MicroBlog')
-                  & has_related_entities('entry_of', 'object'))
-    title = _('blog.archives_by_date')
-    order = 35
-    context = 'left'
-
-    def render_body(self, w):
-        # FIXME doesn't handle (yet) multiple blogs
-        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        rset = entity.related('entry_of', 'object')
-        self._cw.view('cw.archive.by_date', rset, maxentries=6,
-                      basepath=entity.rest_path() + '/blogentries',
-                      w=w)
-
-
-class BlogEntryArchivesBox(BlogArchivesBox):
-    __regid__ = 'blog.entry_archives_by_date'
-    __select__ = (component.CtxComponent.__select__
-                  & is_instance('BlogEntry', 'MicroBlogEntry')
-                  & has_related_entities('entry_of', 'subject'))
-
-    def render_body(self, w):
-        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        box = self._cw.vreg['ctxcomponents'].select('blog.archives_by_date', self._cw, w=w,
-                                                    rset=entity.related('entry_of', 'subject'))
-        box.render_body(w)
-
-
-class BlogByAuthorBox(component.CtxComponent):
-    __regid__ = 'blog.archives_by_author'
-    __select__ = (component.CtxComponent.__select__
-                  & is_instance('Blog', 'MicroBlog')
-                  & has_related_entities('entry_of', 'object'))
-    title = _('blog.archives_by_author')
-    order = 36
-    context = 'left'
-
-    def render_body(self, w):
-        # FIXME doesn't handle (yet) multiple blogs
-        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        rset = entity.related('entry_of', 'object')
-        self._cw.view('cw.archive.by_author', rset,
-                      basepath=entity.rest_path() + '/blogentries',
-                      w=w)
-
-
-class BlogEntryByAuthorBox(BlogByAuthorBox):
-    __regid__ = 'blog.entry_archives_by_author'
-    __select__ = (component.CtxComponent.__select__
-                  & is_instance('BlogEntry', 'MicroBlogEntry')
-                  & has_related_entities('entry_of', 'subject'))
-
-    def render_body(self, w):
-        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        box = self._cw.vreg['ctxcomponents'].select('blog.archives_by_author', self._cw, w=w,
-                                                    rset=entity.related('entry_of', 'subject'))
-        box.render_body(w)
-
-
-class LatestBlogsBox(component.CtxComponent):
-    """display a box with latest blogs and rss"""
-    __regid__ = 'blog.latest_blogs'
-    __select__ = (component.CtxComponent.__select__
-                  & none_rset() & match_view('index'))
-    title = _('blog.latest_blogs')
-    order = 34
-    display_see_more_link = True
-    contextual = False
-
-    def latest_blogs_rset(self):
-        return self._cw.execute(
-            'Any X,T,CD ORDERBY CD DESC LIMIT 5 WHERE X is IN (MicroBlogEntry, BlogEntry), '
-            'X title T, X creation_date CD')
-
-    def render_body(self, w):
-        # XXX turn into a predicate
-        rset = self.latest_blogs_rset()
-        if not rset:
-            return
-        w(u'<ul class="boxListing">')
-        for entity in rset.entities():
-            w(u'<li>%s</li>\n' %
-              tags.a(entity.dc_title(), href=entity.absolute_url()))
-        rqlst = rset.syntax_tree()
-        rqlst.set_limit(None)
-        rql = rqlst.as_string(kwargs=rset.args)
-        if self.display_see_more_link:
-            url = self._cw.build_url('view', rql=rql, page_size=10)
-            w(u'<li>%s</li>\n' %
-              tags.a(u'[%s]' % self._cw._(u'see more'), href=url))
-        rss_icon = self._cw.uiprops['RSS_LOGO_16']
-        # FIXME - could use rss_url defined as a property if available
-        rss_label = u'%s <img src="%s" alt="%s"/>' % (
-            self._cw._(u'subscribe'), rss_icon, self._cw._('rss icon'))
-        blogs = self._cw.execute('Any B,RSS WHERE B is Blog, B rss_url RSS')
-        if len(blogs) == 1:
-            rss_url = blogs[0][1]
-        else:
-            rss_url = self._cw.build_url('view', vid='rss', rql=rql)
-        w(u'<li>%s</li>\n' %
-          tags.a(rss_label, href=rss_url, escapecontent=False))
-        w(u'</ul>\n')
-
-
-class LatestBlogsBlogBox(LatestBlogsBox):
-    """display a box with latest blogs and rss, filtered for a particular blog
-    """
-    __select__ = (component.CtxComponent.__select__
-                  & one_line_rset() & is_instance('Blog'))
-    display_see_more_link = False
-    contextual = True
-
-    def latest_blogs_rset(self):
-        blog = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        return self._cw.execute(
-            'Any X,T,CD ORDERBY CD DESC LIMIT 5 WHERE '
-            'X title T, X creation_date CD, X entry_of B, B eid %(b)s',
-            {'b': blog.eid})
-
-
-class LatestBlogsBlogEntryBox(LatestBlogsBox):
-    """display a box with latest blogs and rss, filtered for a particular blog
-    """
-    __select__ = (component.CtxComponent.__select__
-                  & is_instance('BlogEntry')
-                  & has_related_entities('entry_of', 'subject'))
-    display_see_more_link = False
-    contextual = True
-
-    def latest_blogs_rset(self):
-        blogentry = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        # FIXME doesn't handle (yet) multiple blogs
-        blog = blogentry.related('entry_of', 'subject').get_entity(0, 0)
-        return self._cw.execute(
-            'Any X,T,CD ORDERBY CD DESC LIMIT 5 WHERE '
-            'X title T, X creation_date CD, X entry_of B, B eid %(b)s',
-            {'b': blog.eid})
--- a/views/entry.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,278 +0,0 @@
-"""Secondary views for blogs
-
-:organization: Logilab
-:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-import re
-
-from calendar import monthrange
-from datetime import datetime
-
-from six import PY2
-from six import text_type as unicode
-
-from logilab.mtconverter import xml_escape
-
-from cubicweb.schema import display_name
-from cubicweb.view import EntityView
-from cubicweb.predicates import paginated_rset, sorted_rset, is_instance
-from cubicweb.web.views import uicfg
-from cubicweb.web.views import primary, baseviews, calendar, navigation, workflow
-from cubicweb.web.views.xmlrss import RSSItemView
-try:
-    from cubicweb import _
-except ImportError:
-    _ = unicode
-
-__docformat__ = "restructuredtext en"
-
-_pvs = uicfg.primaryview_section
-_pvs.tag_attribute(('BlogEntry', 'title'), 'hidden')
-_pvs.tag_attribute(('BlogEntry', 'content'), 'hidden')
-_pvs.tag_subject_of(('BlogEntry', 'entry_of', '*'), 'relations')
-_afs = uicfg.autoform_section
-_afs.tag_subject_of(('BlogEntry', 'entry_of', 'Blog'), 'main', 'attributes')
-
-# blog entries ###########################################################
-
-
-def render_blogentry_title(req, w, entity):
-    w(u'<h1>%s</h1>' % entity.view('incontext'))
-    w(u'<div class="author_date"><div>%s' %
-      req.format_date(entity.creation_date))
-    rql = None
-    if entity.has_creator:
-        creator = entity.has_creator[0]
-        name = creator.name
-        rql = 'Any X ORDERBY D DESC WHERE X is BlogEntry, X has_creator Y, '\
-              'Y eid %s, X creation_date D' % creator.eid
-    elif entity.creator:
-        creator = entity.creator
-        name = creator.name()
-        rql = 'Any X ORDERBY D DESC WHERE X is BlogEntry, X created_by Y, '\
-              'Y eid %s, X creation_date D' % creator.eid
-    if rql:
-        vtitle = _('blog entries created by %s') % name
-        url = req.build_url('view', rql=rql, vtitle=vtitle, page_size=10)
-        w(u' %s <a title="%s" href="%s">%s</a>' % (
-            _('by'), xml_escape(vtitle), xml_escape(url), xml_escape(name)))
-    w(u'</div></div>')
-
-
-class BlogEntryPrimaryView(primary.PrimaryView):
-    __select__ = is_instance('BlogEntry')
-    show_attr_label = False
-
-    def render_entity_attributes(self, entity):
-        super(BlogEntryPrimaryView, self).render_entity_attributes(entity)
-        # render the actual blog entry content outside the attributes table
-        # which causes major CSS headaches, see bug #5450612.
-        self.w(u'<div class="blogwrapper">')
-        self.w(entity.printable_value('content'))
-        self.w(u'</div>')
-
-    def render_entity_title(self, entity):
-        self._cw.add_css('cubes.blog.css')
-        w = self.w
-        w(u'<div class="blogentry_title">')
-        render_blogentry_title(self._cw, w, entity)
-        w(u'</div>')
-        w(u'<br class="clear"/>')
-
-
-# don't show workflow history for blog entry
-class BlogEntryWFHistoryVComponent(workflow.WFHistoryVComponent):
-    __select__ = workflow.WFHistoryVComponent.__select__ & is_instance(
-        'BlogEntry')
-
-    def render(self, w, **kwargs):
-        pass
-
-
-class BlogEntrySameETypeListView(baseviews.SameETypeListView):
-    __select__ = baseviews.SameETypeListView.__select__ & is_instance(
-        'BlogEntry')
-    countrql = ('Any COUNT(B) WHERE B is BlogEntry, '
-                'B creation_date >= %(firstday)s, B creation_date <= %(lastday)s')
-    item_vid = 'blog'
-
-    def call(self, **kwargs):
-        self._cw.add_css('cubes.blog.css')
-        super(BlogEntrySameETypeListView, self).call(**kwargs)
-        # XXX Iirk, IPrevNext
-        if 'year' in self._cw.form and 'month' in self._cw.form:
-            self.render_next_previous(
-                int(self._cw.form['year']), int(self._cw.form['month']))
-
-    def render_next_previous(self, year, month):
-        if month == 12:
-            nextmonth = 1
-            year = year + 1
-        else:
-            nextmonth = month + 1
-        if month == 1:
-            previousmonth = 12
-            year = year - 1
-        else:
-            previousmonth = month - 1
-        self.w(u'<div class="prevnext">')
-        self.w(u'<span class="previousmonth">%s</span>'
-               % self.render_link(year, previousmonth,
-                                  xml_escape(u'<< ' + self._cw._(u'previous month'))))
-        self.w(u'<span class="nextmonth">%s</span>'
-               % self.render_link(year, nextmonth,
-                                  xml_escape(self._cw._(u'next month') + u' >>')))
-        self.w(u'</div>')
-
-    def render_link(self, year, month, atitle):
-        firstday = datetime(year, month, 1)
-        lastday = datetime(year, month, monthrange(year, month)[1])
-        args = {'firstday': firstday, 'lastday': lastday}
-        nmb_entries = self._cw.execute(self.countrql, args)[0][0]
-        if not nmb_entries:
-            return
-        rql = ('Any B, BD ORDERBY BD DESC '
-               'WHERE B is BlogEntry, B creation_date BD, '
-               'B creation_date >= "%s", B creation_date <= "%s"' %
-               (firstday.strftime('%Y-%m-%d'), lastday.strftime('%Y-%m-%d')))
-        label = u'%s %s [%s]' % (self._cw._(calendar.MONTHNAMES[month - 1]), year,
-                                 nmb_entries)
-        vtitle = '%s %s' % (display_name(
-            self._cw, 'BlogEntry', 'plural'), label)
-        url = self._cw.build_url('view', rql=rql, vtitle=vtitle,
-                                 month=month, year=year)
-        return u'<a href="%s">%s</a>' % (xml_escape(url), atitle)
-
-
-class BlogEntryBlogView(EntityView):
-    __regid__ = 'blog'
-    __select__ = is_instance('BlogEntry')
-
-    toolbar_components = (primary.PrimaryView.content_navigation_components.im_func if PY2 else
-                          primary.PrimaryView.content_navigation_components)
-
-    def cell_call(self, row, col, **kwargs):
-        entity = self.cw_rset.get_entity(row, col)
-        w = self.w
-        w(u'<div class="post">')
-        self.toolbar_components('ctxtoolbar')
-        render_blogentry_title(self._cw, w, entity)
-        w(u'<div class="entry">')
-        body = entity.printable_value('content')
-        w(body)
-        w(u'</div>')
-        w(u'<br class="clear"/>')
-        w(u'<div class="postmetadata">%s</div>' % entity.view('post-reldata'))
-        w(u'</div>')
-
-
-class BlogEntryPostMetaData(EntityView):
-    __regid__ = 'post-reldata'
-    __select__ = is_instance('BlogEntry')
-
-    def cell_call(self, row, col):
-        entity = self.cw_rset.get_entity(row, col)
-        _ = lambda ertype, form='': display_name(self._cw, ertype, form)
-        reldata = []
-        w = reldata.append
-        schema = self._cw.vreg.schema
-        if 'comments' in schema and \
-                'BlogEntry' in schema.rschema('comments').objects():
-            from cubes.comment.entities import subcomments_count
-            count = subcomments_count(entity)
-            if count:
-                url = xml_escape(entity.absolute_url())
-                if count > 1:
-                    label = _('Comment', 'plural')
-                else:
-                    label = _('Comment')
-                w(u'<a href="%s">%s %s</a>' % (url, count, label))
-            else:
-                w(u'%s %s' % (count, _('Comment')))
-        if 'tags' in schema and 'BlogEntry' in schema.rschema('tags').objects():
-            tag_rset = entity.related('tags', 'object')
-            if tag_rset:
-                w(u'%s %s' % (_('tags', 'object'), self._cw.view('csv', tag_rset)))
-        rset = entity.related('entry_of', 'subject')
-        if rset:
-            w(u'%s %s' % (self._cw._('blogged in '),
-                          self._cw.view('csv', rset, 'null')))
-        self.w(u' | '.join(reldata))
-
-
-class BlogNavigation(navigation.PageNavigation):
-    __select__ = paginated_rset() & sorted_rset() & is_instance('BlogEntry')
-
-    def index_display(self, start, stop):
-        return unicode(int(start / self.page_size) + 1)
-
-
-# micro blog entries #####################################################
-
-def format_microblog(entity):
-    if entity.has_creator:
-        author = entity.has_creator[0]
-        if author.has_avatar:
-            imgurl = author.has_avatar[0].uri
-            ablock = u'<a href="%s"><img src="%s" alt="avatar"/></a>' % (
-                author.absolute_url(), xml_escape(imgurl))
-        else:
-            ablock = entity.has_creator[0].view('outofcontext')
-    else:
-        ablock = entity.dc_creator()
-    if entity.content_format == 'text/html':
-        content = entity.content
-    else:
-        words = []
-        for word in entity.content.split():
-            if word.startswith('http://'):
-                word = u'<a href="%s">%s</a>' % (word, word)
-            else:
-                word = xml_escape(word)
-            words.append(word)
-        content = u' '.join(words)
-    return (u'<div class="microblog">'
-            u'<span class="author">%s</span>'
-            u'<span class="msgtxt">%s</span>'
-            u'<span class="meta"><a href="%s">%s</a></span>'
-            u'</div>' % (ablock, content, entity.absolute_url(), entity.creation_date))
-
-
-class MicroBlogEntryPrimaryView(primary.PrimaryView):
-    __select__ = primary.PrimaryView.__select__ & is_instance('MicroBlogEntry')
-
-    def cell_call(self, row, col):
-        self._cw.add_css('cubes.blog.css')
-        entity = self.cw_rset.get_entity(row, col)
-        self.w(format_microblog(entity))
-
-
-class MicroBlogEntrySameETypeListView(baseviews.SameETypeListView):
-    __select__ = baseviews.SameETypeListView.__select__ & is_instance(
-        'MicroBlogEntry')
-
-    def cell_call(self, row, col):
-        self._cw.add_css('cubes.blog.css')
-        entity = self.cw_rset.get_entity(row, col)
-        self.w(format_microblog(entity))
-
-
-_CLEAN_STYLE_RE = re.compile(r'<style .*?</style>', re.MULTILINE | re.DOTALL)
-
-
-class BlogEntryRSSItemView(RSSItemView):
-    __select__ = is_instance('BlogEntry')
-
-    def render_description(self, entity):
-        """
-        Make sure generated XML for RSS is "valid", ie. do not
-        contains <style> tags
-
-        Note that this *should* not happen (style tags there), but
-        browser tolerate them.
-        """
-        htmlcontent = entity.dc_description(format='text/html')
-        htmlcontent = _CLEAN_STYLE_RE.sub('', htmlcontent)
-        self._marker('description', htmlcontent)
--- a/views/urlpublishing.py	Tue Jan 17 12:14:00 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-from cubicweb.web.views.urlrewrite import SimpleReqRewriter, rgx
-
-
-class BlogReqRewriter(SimpleReqRewriter):
-    rules = [
-        # links generated by archives by date/author boxes
-        (rgx('/(micro)?blog/([0-9]+)/blogentries'),
-         dict(rql='Any B,BD ORDERBY BD DESC WHERE B creation_date BD, '
-                  'B entry_of BL, BL eid %(eid)s' % {'eid': r'\2'},
-              )),
-        (rgx('/(micro)?blog/([0-9]+)/blogentries/([a-z_]+)'),
-         dict(rql='Any B,BD ORDERBY BD DESC WHERE B creation_date BD, '
-                  'B entry_of BL, BL eid %(eid)s, '
-                  'B created_by U, U login "%(user)s"' % {'eid': r'\2', 'user': r'\3'},
-              user=r'\2')),
-        (rgx('/(micro)?blog/([0-9]+)/blogentries/([0-9]{4})'),
-         dict(rql='Any B,BD ORDERBY BD DESC WHERE B creation_date BD, B entry_of BL, BL eid %(eid)s'
-              'HAVING YEAR(BD)= %(year)s' % {'eid': r'\2', 'year': r'\3'},
-              )),
-        (rgx('/(micro)?blog/([0-9]+)/blogentries/([0-9]{4})/([0-9]{2})'),
-         dict(rql='Any B,BD ORDERBY BD DESC WHERE B creation_date BD, '
-                  'B entry_of BL, BL eid %(eid)s '
-                  'HAVING YEAR(BD)= %(year)s, MONTH(BD)=%(month)s' % {'eid': r'\2',
-                                                                      'year': r'\3',
-                                                                      'month': r'\4'},
-              )),
-
-        # XXX use or kill
-        (rgx('/blogentry/([a-z_]+)'),
-         dict(rql='Any B,BD ORDERBY BD DESC WHERE B is BlogEntry, '
-                  'B creation_date BD, B created_by U, U login "%(user)s"' % {'user': r'\1'},
-              user=r'\1')),
-        (rgx('/blogentry/([a-z_]+)\.rss'),
-         dict(rql='Any B,BD ORDERBY BD DESC LIMIT 20 WHERE B is BlogEntry, '
-                  'B creation_date BD, B created_by U, U login "%(user)s"' % {'user': r'\1'},
-              vid='rss')),
-
-
-        (rgx('/blogentries/([0-9]{4})'),
-         dict(rql='Any B,BD ORDERBY BD DESC WHERE B is BlogEntry, B creation_date BD '
-                  'HAVING YEAR(BD) = %(year)s' % {'year': r'\1'})),
-        (rgx('/blogentries/([0-9]{4})/([0-9]{2})'),
-         dict(rql='Any B,BD ORDERBY BD DESC WHERE B is BlogEntry, B creation_date BD '
-                  'HAVING YEAR(BD) = %(year)s, MONTH(BD) = %(month)s' % {'year': r'\1',
-                                                                         'month': r'\2'})),
-
-    ]