templates_blog_DATA = \
src/templates/blog/author.html \
src/templates/blog/base.html \
+ src/templates/blog/compose.html \
src/templates/blog/feed.xml \
src/templates/blog/index.html \
src/templates/blog/post.html \
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.dn)
- def __cmp__(self, other):
- return cmp(self.name, other.name)
+ def __eq__(self, other):
+ if isinstance(other, self.__class__):
+ return self.dn == other.dn
+
+ def __lt__(self, other):
+ if isinstance(other, self.__class__):
+ return self.name < other.name
@property
def ldap(self):
#!/usr/bin/python
+import datetime
import feedparser
import re
import textile
ORDER BY ts_rank(search_index.document, to_tsquery('english', %s)) DESC \
LIMIT %s", query, query, limit)
+ def create_post(self, title, text, author, tags=[]):
+ """
+ Creates a new post and returns the resulting Post object
+ """
+ return self._get_post("INSERT INTO blog(title, slug, text, author_uid, tags) \
+ VALUES(%s, %s, %s, %s, %s) RETURNING *", title, self._make_slug(title),
+ text, author.uid, list(tags))
+
def _make_slug(self, s):
# Remove any non-ASCII characters
try:
self.id = id
self.data = data
- @property
- def title(self):
+ # Title
+
+ def get_title(self):
return self.data.title
+ def set_title(self, title):
+ self.db.execute("UPDATE blog SET title = %s \
+ WHERE id = %s", title, self.id)
+
+ # Update slug if post is not published, yet
+ if not self.is_published():
+ self.db.execute("UPDATE blog SET slug = %s \
+ WHERE id = %s", self.backend.blog._make_slug(title), self.id)
+
+ title = property(get_title, set_title)
+
@property
def slug(self):
return self.data.slug
def created_at(self):
return self.data.created_at
+ # Published?
+
@property
def published_at(self):
return self.data.published_at
+ def is_published(self):
+ """
+ Returns True if the post is already published
+ """
+ return self.published_at and self.published_at <= datetime.datetime.now()
+
+ def publish(self):
+ if self.is_published():
+ return
+
+ self.db.execute("UPDATE blog SET published_at = NOW() \
+ WHERE id = %s", self.id)
+
+ # Update search indices
+ self.backend.blog.refresh()
+
+ # Updated?
+
@property
- def markdown(self):
- return self.data.markdown
+ def updated_at(self):
+ return self.data.updated_at
+
+ def updated(self):
+ self.db.execute("UPDATE blog SET updated_at = NOW() \
+ WHERE id = %s", self.id)
+
+ # Update search indices
+ self.backend.blog.refresh()
+
+ # Text
+
+ def get_text(self):
+ return self.data.text
+
+ def set_text(self, text):
+ self.db.execute("UPDATE blog SET text = %s \
+ WHERE id = %s", text, self.id)
+
+ text = property(get_text, set_text)
+
+ # HTML
@property
def html(self):
"""
Returns this post as rendered HTML
"""
- return self.data.html or textile.textile(self.markdown.decode("utf-8"))
+ return self.data.html or textile.textile(self.text.decode("utf-8"))
- @property
- def tags(self):
+ # Tags
+
+ def get_tags(self):
return self.data.tags
+ def set_tags(self, tags):
+ self.db.execute("UPDATE blog SET tags = %s \
+ WHERE id = %s", list(tags), self.id)
+
+ tags = property(get_tags, set_tags)
+
@property
def link(self):
return self.data.link
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{% if post %}{{ _("Edit %s") % post.title }}{% else %}{{ _("Compose A New Article") }}{% end %}{% end block %}
+
+{% block main %}
+ <div class="card">
+ <div class="card-body">
+ <form action="" method="POST">
+ {% raw xsrf_form_html() %}
+
+ <div class="form-group">
+ <input type="text" class="form-control" name="title" placeholder="{{ _("Title") }}"
+ {% if post %}value="{{ post.title }}"{% end %} required>
+ </div>
+
+ <div class="form-group">
+ <textarea class="form-control" rows="16" name="text" placeholder="{{ _("Text") }}"
+ required>{% if post %}{{ post.text }}{% end %}</textarea>
+ </div>
+
+ <div class="form-group row">
+ <label class="col-sm-2 col-form-label">{{ _("Tags") }}</label>
+ <div class="col-sm-10">
+ <input type="text" class="form-control" name="tags" placeholder="{{ _("(Comma-sepated list)") }}"
+ {% if post %}value="{{ ", ".join(post.tags) }}"{% end %}>
+ </div>
+ </div>
+
+ <button type="submit" class="btn btn-primary btn-block">
+ {{ _("Save") }}
+ </button>
+ </form>
+ </div>
+ </div>
+{% end block %}
{% end %},
{{ locale.format_date(post.published_at, shorter=True, relative=False) }}
+
+ {% if current_user and current_user == post.author %}
+ <a href="/post/{{ post.slug }}/edit">{{ _("Edit") }}</a>
+ {% end %}
</p>
</div>
self.add_handlers(r"blog(\.dev)?\.ipfire\.org", [
(r"/", blog.IndexHandler),
(r"/authors/(\w+)", blog.AuthorHandler),
- (r"/post/(.*)", blog.PostHandler),
+ (r"/compose", blog.ComposeHandler),
+ (r"/post/([0-9a-z\-\.]+)", blog.PostHandler),
+ (r"/post/([0-9a-z\-\.]+)/edit", blog.EditHandler),
(r"/search", blog.SearchHandler),
(r"/tags/([0-9a-z\-\.]+)", blog.TagHandler),
(r"/years/([0-9]+)", blog.YearHandler),
self.render("blog/year.html", posts=posts, year=year)
+class ComposeHandler(base.BaseHandler):
+ @tornado.web.authenticated
+ def get(self):
+ self.render("blog/compose.html", post=None)
+
+ @tornado.web.authenticated
+ def post(self):
+ title = self.get_argument("title")
+ text = self.get_argument("text")
+ tags = self.get_argument("tags", None)
+
+ with self.db.transaction():
+ post = self.backend.blog.create_post(title, text,
+ author=self.current_user, tags=tags)
+
+ self.redirect("/posts")
+
+
+class EditHandler(base.BaseHandler):
+ @tornado.web.authenticated
+ def get(self, slug):
+ post = self.backend.blog.get_by_slug(slug)
+ if not post:
+ raise tornado.web.HTTPError(404)
+
+ # XXX check if post is editable
+
+ self.render("blog/compose.html", post=post)
+
+ @tornado.web.authenticated
+ def post(self, slug):
+ post = self.backend.blog.get_by_slug(slug)
+ if not post:
+ raise tornado.web.HTTPError(404)
+
+ # XXX check if post is editable
+
+ with self.db.transaction():
+ # Update title
+ post.title = self.get_argument("title")
+
+ # Update text
+ post.text = self.get_argument("text")
+
+ # Update tags
+ post.tags = (t.strip() for t in self.get_argument("tags", "").split(","))
+
+ # Mark post as updated
+ post.updated()
+
+ # Return to blog if the post is already published
+ if post.is_published():
+ self.redirect("/post/%s" % post.slug)
+ return
+
+ # Otherwise return to list of unpublished posts
+ self.redirect("/posts")
+
+
class HistoryNavigationModule(ui_modules.UIModule):
def render(self):
return self.render_string("blog/modules/history-navigation.html",