]> git.ipfire.org Git - ipfire.org.git/commitdiff
blog: Allow to edit and create new posts
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 1 Sep 2018 14:04:45 +0000 (15:04 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 1 Sep 2018 14:04:45 +0000 (15:04 +0100)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/backend/accounts.py
src/backend/blog.py
src/templates/blog/compose.html [new file with mode: 0644]
src/templates/blog/modules/post.html
src/web/__init__.py
src/web/blog.py

index 2eae162e052904343f167afcda61ac03fd25b14b..6ebd321cd05099cbcabc256fb7f251658248b798 100644 (file)
@@ -112,6 +112,7 @@ templates_authdir = $(templatesdir)/auth
 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 \
index ffbf2a0cabb0c86763d0288e4e16cfce4c58ccbd..faafc859c5b81a4ba363e278ce297b058b8886f2 100644 (file)
@@ -138,8 +138,13 @@ class Account(Object):
        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):
index 980cc676265115c41d4865d096d3675250c39a66..fc1f0dc719058dfc2c0f836a879670c143d0b6c1 100644 (file)
@@ -1,5 +1,6 @@
 #!/usr/bin/python
 
+import datetime
 import feedparser
 import re
 import textile
@@ -63,6 +64,14 @@ class Blog(misc.Object):
                                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:
@@ -167,10 +176,22 @@ class Post(misc.Object):
                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
@@ -187,25 +208,72 @@ class Post(misc.Object):
        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
diff --git a/src/templates/blog/compose.html b/src/templates/blog/compose.html
new file mode 100644 (file)
index 0000000..fdfec35
--- /dev/null
@@ -0,0 +1,35 @@
+{% 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 %}
index 69f0a05edcd21cf1e7806af9d73aefe2ac321314..8a5aa075770fe7d9108e4b0b9033618d9d416d27 100644 (file)
                        {% 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>
 
index 1089ed8ff216da8a05c8f6a91025d0b29e8474d0..9e30f4f582b5308cf1fae3bfbc328dcac636906a 100644 (file)
@@ -124,7 +124,9 @@ class Application(tornado.web.Application):
                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),
index f4a8fd94775ebcbb8d73f0e2ac79db8f58272020..a6b7224bbd5b6b6dc1edfefb3c141e436e870187 100644 (file)
@@ -89,6 +89,65 @@ class YearHandler(base.BaseHandler):
                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",