diff --git a/kotti_blog/populate.py b/kotti_blog/populate.py
index d9a9a22..1a3c958 100644
--- a/kotti_blog/populate.py
+++ b/kotti_blog/populate.py
@@ -54,5 +54,55 @@ class KottiBlogSettingsSchema(colander.MappingSchema):
}
+class UseSidebarCategories(colander.SchemaNode):
+ name = 'use_sidebar_categories'
+ title = _(u'Show categories in sidebar')
+ description = _(u'Show categories sorted by number of posts on which you '
+ u'can filter.')
+ missing = True
+ default = True
+
+
+class SidebarCategoriesNumber(colander.SchemaNode):
+ name = 'sidebar_categories_number'
+ title = _(u'The number of categories shown')
+ description = _(u'Choose how many categories are shown in the sidebar. ' +
+ u'Set to 0 to show all categories.')
+ default = 5
+
+
+class UseSidebarArchives(colander.SchemaNode):
+ name = 'use_sidebar_archives'
+ title = _(u'Show archives in sidebar')
+ description = _(u'Show categories sorted by date on which you can filter.')
+ missing = True
+ default = True
+
+
+class SidebarArchivesNumber(colander.SchemaNode):
+ name = 'sidebar_archives_number'
+ title = _(u'The number of archives shown')
+ description = _(u'Choose how many archives are shown in the sidebar. ' +
+ u'Set to 0 to show all archives.')
+ default = 5
+
+
+class KottiBlogSidebarSettingsSchema(colander.MappingSchema):
+ use_sidebar_categories = UseSidebarCategories(colander.Boolean())
+ sidebar_categories_number = SidebarCategoriesNumber(colander.Integer())
+ use_sidebar_archives = UseSidebarArchives(colander.Boolean())
+ sidebar_archives_number = SidebarArchivesNumber(colander.Integer())
+
+
+KottiBlogSidebarSettings = {
+ 'name': 'kotti_blog_sidebar_settings',
+ 'title': _(u'Blog sidebar settings'),
+ 'description': _(u"Settings for kotti_blog sidebar"),
+ 'success_message': _(u"Successfully saved blog sidebar settings."),
+ 'schema_factory': KottiBlogSidebarSettingsSchema,
+}
+
+
def populate_settings():
add_settings(KottiBlogSettings)
+ add_settings(KottiBlogSidebarSettings)
diff --git a/kotti_blog/resources.py b/kotti_blog/resources.py
index c70e33d..0c479bb 100644
--- a/kotti_blog/resources.py
+++ b/kotti_blog/resources.py
@@ -1,4 +1,5 @@
import datetime
+from datetime import date
from dateutil.tz import tzutc
from sqlalchemy import Column
from sqlalchemy import ForeignKey
@@ -33,6 +34,38 @@ class Blog(Document):
addable_to=[u'Document'],
)
+ def get_children_with_permission(self, request):
+ return self.children_with_permission(request)
+
+ def get_unique_tags(self, request):
+ children = self.get_children_with_permission(request)
+
+ unique_tags_dict = {}
+ for child in children:
+ for tag in child.tags:
+ try:
+ unique_tags_dict[tag] += 1
+ except KeyError:
+ unique_tags_dict[tag] = 1
+
+ return sorted(
+ unique_tags_dict.items(),
+ key=lambda x: x[1],
+ reverse=True
+ )
+
+ def get_archives(self, request):
+ children = self.get_children_with_permission(request)
+
+ dates_dict = {}
+ for child in children:
+ try:
+ dates_dict[date(child.date.year, child.date.month, 1)] += 1
+ except KeyError:
+ dates_dict[date(child.date.year, child.date.month, 1)] = 1
+
+ return sorted(dates_dict.items(), reverse=True)
+
class BlogEntry(Document):
id = Column(Integer, ForeignKey('documents.id'), primary_key=True)
diff --git a/kotti_blog/static/style.css b/kotti_blog/static/style.css
index e43a694..c7a0e66 100644
--- a/kotti_blog/static/style.css
+++ b/kotti_blog/static/style.css
@@ -1,9 +1,19 @@
.blog-view .blogentry {
margin-top: 2em;
}
-.blogentry-view h1,
.blog-view .blogentry h2 {
border-top: 1px solid black;
margin-top: 0.75em;
padding-top: 0.5em;
}
+.blogentry-view h1 {
+ margin-top: 0.5em;
+}
+.blog-view .description,
+.blogentry-view .description {
+ font-style: italic;
+}
+.blog-metadata {
+ margin-bottom: 20px;
+ border-bottom: 1px solid #EEE;
+}
diff --git a/kotti_blog/templates/blog-archives.pt b/kotti_blog/templates/blog-archives.pt
new file mode 100644
index 0000000..8d5a0d8
--- /dev/null
+++ b/kotti_blog/templates/blog-archives.pt
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ ${context.description}
+
+
+ ${structure: context.body}
+
+
+
+
+
+
diff --git a/kotti_blog/templates/blog-categories.pt b/kotti_blog/templates/blog-categories.pt
new file mode 100644
index 0000000..54e235e
--- /dev/null
+++ b/kotti_blog/templates/blog-categories.pt
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ ${context.description}
+
+
+ ${structure: context.body}
+
+
+
+
+
+
diff --git a/kotti_blog/templates/blog-sidebar.pt b/kotti_blog/templates/blog-sidebar.pt
new file mode 100644
index 0000000..e0f3484
--- /dev/null
+++ b/kotti_blog/templates/blog-sidebar.pt
@@ -0,0 +1,22 @@
+
+
diff --git a/kotti_blog/templates/blog-view.pt b/kotti_blog/templates/blog-view.pt
index 243df80..4fb8c46 100644
--- a/kotti_blog/templates/blog-view.pt
+++ b/kotti_blog/templates/blog-view.pt
@@ -7,7 +7,7 @@
- ${api.context.title}
+
${api.context.description}
@@ -17,12 +17,19 @@
-
+
+
+ |
categories:
+
+
+ ${tag},
+
+
${structure: item.body}
diff --git a/kotti_blog/templates/blogentry-view.pt b/kotti_blog/templates/blogentry-view.pt
index 964ead3..6c31a1d 100644
--- a/kotti_blog/templates/blogentry-view.pt
+++ b/kotti_blog/templates/blogentry-view.pt
@@ -8,8 +8,15 @@
-
${api.context.title}
+
+
+ |
categories:
+
+
+ ${tag},
+
+
${api.context.description}
diff --git a/kotti_blog/templates/macros.pt b/kotti_blog/templates/macros.pt
index f9b8ddd..2c5a213 100644
--- a/kotti_blog/templates/macros.pt
+++ b/kotti_blog/templates/macros.pt
@@ -13,33 +13,33 @@
tal:condition="batch.multiple_pages">
-
-
+
Previous n items
-
- 1
+ 1
...
-
-
+
-
Current page number
-
+ tal:attributes="href string:${url}${url_parameters}${page}"/>
-
...
last page
-
-
+
Next n items
diff --git a/kotti_blog/views.py b/kotti_blog/views.py
index b2e37d1..b56afc4 100644
--- a/kotti_blog/views.py
+++ b/kotti_blog/views.py
@@ -1,26 +1,24 @@
-import datetime
from dateutil.tz import tzutc
-
-import colander
from deform.widget import DateTimeInputWidget
+from pyramid.exceptions import PredicateMismatch
from pyramid.renderers import get_renderer
+from pyramid.view import render_view_to_response
from pyramid.view import view_config
from pyramid.view import view_defaults
+import colander
+import datetime
-
-from kotti import DBSession
-from kotti.security import has_permission
from kotti.views.edit import DocumentSchema
from kotti.views.form import AddFormView
from kotti.views.form import EditFormView
+from kotti.views.slots import assign_slot
from kotti.views.util import template_api
-
from kotti_settings.util import get_setting
+from kotti_blog import _
from kotti_blog.batch import Batch
from kotti_blog.resources import Blog
from kotti_blog.resources import BlogEntry
-from kotti_blog import _
class BlogSchema(DocumentSchema):
@@ -77,29 +75,197 @@ def __init__(self, context, request):
self.context = context
self.request = request
+ self.selected_tag = self.request.GET.get("selected-tag")
+ self.selected_month = self.selected_year = None
+ selected_date = self.request.GET.get("selected-date")
+
+ if selected_date:
+ self.selected_year, self.selected_month = map(
+ int,
+ selected_date.split('_')
+ )
+
+ def filter_blog_entry(self, entry):
+ # Filter on date
+ if self.selected_year and self.selected_month:
+ if(entry.date.year != self.selected_year or
+ entry.date.month != self.selected_month):
+ return False
+
+ # Filter on the tag
+ if self.selected_tag:
+ if self.selected_tag not in entry.tags:
+ return False
+
+ return True
+
+ @view_config(context=Blog)
+ def view_blog_super(self):
+ """A super view for the blog, used to handle pagination.
+ """
+ api = template_api(self.context, self.request)
+
+ # Remove everything but the extra parameters
+ page = self.request.url.replace(api.url(self.context).strip('/'), '')
+ page = page.strip('/')
+
+ # Pagination only works when specifying the view
+ if page.startswith('view/'):
+ page = page[5:]
+
+ if page:
+ self.request.GET['page'] = page
+
+ url_parameters_get = 'view/'
+ self.request.GET['url_parameters'] = url_parameters_get
+
+ return render_view_to_response(
+ self.context,
+ self.request,
+ name='blog-view'
+ )
+
@view_config(context=Blog,
+ name='blog-view',
renderer='kotti_blog:templates/blog-view.pt')
def view_blog(self):
macros = get_renderer('templates/macros.pt').implementation()
- query = DBSession.query(BlogEntry)
- query = query.filter(BlogEntry.parent_id == self.context.id)
- query = query.order_by(BlogEntry.date.desc())
- items = query.all()
- items = [item for item in items if has_permission('view', item, self.request)]
+
+ items = [child for child in
+ self.context.get_children_with_permission(self.request) if
+ self.filter_blog_entry(child)]
+ items.sort(key=lambda x: x.date, reverse=True)
+
page = self.request.params.get('page', 1)
use_pagination = get_setting('use_pagination')
if use_pagination:
items = Batch.fromPagenumber(items,
pagesize=get_setting('pagesize'),
pagenumber=int(page))
+
return {
'api': template_api(self.context, self.request),
'macros': macros,
'items': items,
'use_pagination': use_pagination,
'link_headline': get_setting('link_headline'),
+ 'url_parameters': self.request.GET.get("url_parameters")
}
+ @view_config(context=Blog,
+ name="categories")
+ def view_categories_super(self):
+ """A super view that either shows the list or filters by the provided
+ category/tag, which we get from the URL. We use this so we can have
+ URL's like:
+
+ blog/categories (shows the list)
+ blog/categories/tag1 (shows articles, filtered by tag 'tag1')
+ ...
+
+ """
+ api = template_api(self.context, self.request)
+
+ # Remove everything but the extra parameters
+ url_parameters = self.request.url.replace(
+ api.url(self.context) + 'categories',
+ ''
+ )
+ url_parameters = url_parameters.strip('/')
+
+ # If we have url parameters, take first one as tag, second one as page,
+ # ignore the rest
+ if url_parameters:
+ url_parameters = url_parameters.split('/')
+ url_parameters_get = 'categories/'
+ try:
+ self.request.GET['selected-tag'] = url_parameters[0]
+ url_parameters_get += url_parameters[0] + '/'
+ self.request.GET['page'] = url_parameters[1]
+ except IndexError:
+ pass
+
+ self.request.GET['url_parameters'] = url_parameters_get
+
+ return render_view_to_response(
+ self.context,
+ self.request,
+ name='blog-view'
+ )
+
+ # If no tag was provided, return the categories list
+ return render_view_to_response(
+ self.context,
+ self.request,
+ name='categories-list'
+ )
+
+ @view_config(context=Blog,
+ name="categories-list",
+ renderer='kotti_blog:templates/blog-categories.pt')
+ def view_categories_list(self):
+ return {
+ 'api': template_api(self.context, self.request),
+ 'items': self.context.get_unique_tags(self.request)
+ }
+
+ @view_config(context=Blog,
+ name="archives")
+ def view_archives_super(self):
+ """A super view that either shows the list or filters by the provided
+ category/tag, which we get from the URL. We use this so we can have
+ URL's like:
+
+ blog/categories (shows the list)
+ blog/categories/tag1 (shows articles, filtered by tag 'tag1')
+ ...
+
+ """
+ api = template_api(self.context, self.request)
+
+ # Remove everything but the extra parameters
+ url_parameters = self.request.url.replace(
+ api.url(self.context) + 'archives',
+ ''
+ )
+ url_parameters = url_parameters.strip('/')
+
+ # If we have url parameters, take first one as tag, second one as page,
+ # ignore the rest
+ if url_parameters:
+ url_parameters = url_parameters.split('/')
+ url_parameters_get = 'archives/'
+ try:
+ self.request.GET['selected-date'] = url_parameters[0]
+ url_parameters_get += url_parameters[0] + '/'
+ self.request.GET['page'] = url_parameters[1]
+ except IndexError:
+ pass
+
+ self.request.GET['url_parameters'] = url_parameters_get
+
+ return render_view_to_response(
+ self.context,
+ self.request,
+ name='blog-view'
+ )
+
+ # If no tag was provided, return the categories list
+ return render_view_to_response(
+ self.context,
+ self.request,
+ name='archives-list'
+ )
+
+ @view_config(context=Blog,
+ name="archives-list",
+ renderer='kotti_blog:templates/blog-archives.pt')
+ def view_archives_list(self):
+ return {
+ 'api': template_api(self.context, self.request),
+ 'items': self.context.get_archives(self.request)
+ }
+
@view_config(context=BlogEntry,
renderer='kotti_blog:templates/blogentry-view.pt')
def view_blogentry(self):
@@ -113,6 +279,49 @@ def use_auto_pagination(context, request):
return {'use_auto_pagination': get_setting('use_auto_pagination')}
+@view_config(name="blog_sidebar",
+ renderer="kotti_blog:templates/blog-sidebar.pt")
+def blog_sidebar_view(context, request):
+ # Only show sidebar on Blog and Blog Entries
+ if not (isinstance(context, Blog) or isinstance(context, BlogEntry)):
+ raise PredicateMismatch()
+
+ # Find the blog
+
+ if isinstance(context, Blog):
+ blog = context
+ else:
+ blog = context.parent
+
+ api = template_api(context, request)
+
+ use_categories = get_setting('use_sidebar_categories')
+ unique_tags = None
+ if use_categories:
+ number = get_setting('sidebar_categories_number')
+ if number:
+ unique_tags = blog.get_unique_tags(request)[:number]
+ else:
+ unique_tags = blog.get_unique_tags(request)
+
+ use_archives = get_setting('use_sidebar_archives')
+ archives = None
+ if use_archives:
+ number = get_setting('sidebar_archives_number')
+ if number:
+ archives = blog.get_archives(request)[:number]
+ else:
+ archives = blog.get_archives(request)
+
+ return {
+ 'blog_url': api.url(blog),
+ 'unique_tags': unique_tags,
+ 'use_categories': use_categories,
+ 'archives': archives,
+ 'use_archives': use_archives,
+ }
+
+
def includeme_edit(config):
config.add_view(
@@ -155,4 +364,5 @@ def includeme(config):
config.override_asset(to_override='kotti_blog', override_with=override)
includeme_edit(config)
config.add_static_view('static-kotti_blog', 'kotti_blog:static')
+ assign_slot('blog_sidebar', 'right')
config.scan(__name__)