]> git.ipfire.org Git - ipfire.org.git/commitdiff
wiki: Add file gallery and allow uploading files
authorMichael Tremer <michael.tremer@ipfire.org>
Sun, 18 Nov 2018 18:03:09 +0000 (18:03 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sun, 18 Nov 2018 18:03:09 +0000 (18:03 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/backend/wiki.py
src/templates/wiki/files/index.html [new file with mode: 0644]
src/web/__init__.py
src/web/wiki.py

index 263bc9eefef77e5105d7f932ec4dfd76aa184297..1e5d43cfc3681e80bc4e4a78a3dab9cc282f0332 100644 (file)
@@ -267,6 +267,11 @@ templates_wiki_DATA = \
 
 templates_wikidir = $(templatesdir)/wiki
 
+templates_wiki_files_DATA = \
+       src/templates/wiki/files/index.html
+
+templates_wiki_filesdir = $(templates_wikidir)/files
+
 templates_wiki_modules_DATA = \
        src/templates/wiki/modules/list.html \
        src/templates/wiki/modules/navbar.html
index 311d85359d8970b63ce607ed03189462c384f527..2b65d017bb67fdd35083b0397d629f3050e9c4a4 100644 (file)
@@ -85,6 +85,41 @@ class Wiki(misc.Object):
                """
                self.db.execute("REFRESH MATERIALIZED VIEW wiki_search_index")
 
+       # Files
+
+       def _get_files(self, query, *args):
+               res = self.db.query(query, *args)
+
+               for row in res:
+                       yield File(self.backend, row.id, data=row)
+
+       def _get_file(self, query, *args):
+               res = self.db.get(query, *args)
+
+               if res:
+                       return File(self.backend, res.id, data=res)
+
+       def get_files(self, path):
+               files = self._get_files("SELECT * FROM wiki_files \
+                       WHERE path = %s AND deleted_at IS NULL ORDER BY filename", path)
+
+               return list(files)
+
+       def get_file_by_path(self, path):
+               path, filename = os.path.dirname(path), os.path.basename(path)
+
+               return self._get_file("SELECT * FROM wiki_files \
+                       WHERE path = %s AND filename = %s AND deleted_at IS NULL", path, filename)
+
+       def upload(self, path, filename, data, mimetype, author, address):
+               # Upload the blob first
+               blob = self.db.get("INSERT INTO wiki_blobs(data) VALUES(%s) RETURNING id", data)
+
+               # Create entry for file
+               return self._get_file("INSERT INTO wiki_files(path, filename, author_uid, address, \
+                       mimetype, blob_id, size) VALUES(%s,  %s, %s, %s, %s, %s, %s) RETURNING *", path,
+                       filename, author.uid, address, mimetype, blob.id, len(data))
+
 
 class Page(misc.Object):
        def init(self, id, data=None):
@@ -218,3 +253,40 @@ class Page(misc.Object):
                                return sidebar
 
                        parts.pop()
+
+
+class File(misc.Object):
+       def init(self, id, data):
+               self.id   = id
+               self.data = data
+
+       @property
+       def url(self):
+               return os.path.join(self.path, self.filename)
+
+       @property
+       def path(self):
+               return self.data.path
+
+       @property
+       def filename(self):
+               return self.data.filename
+
+       @property
+       def mimetype(self):
+               return self.data.mimetype
+
+       @property
+       def size(self):
+               return self.data.size
+
+       def is_image(self):
+               return self.mimetype.startswith("image/")
+
+       @lazy_property
+       def blob(self):
+               res = self.db.get("SELECT data FROM wiki_blobs \
+                       WHERE id = %s", self.data.blob_id)
+
+               if res:
+                       return bytes(res.data)
diff --git a/src/templates/wiki/files/index.html b/src/templates/wiki/files/index.html
new file mode 100644 (file)
index 0000000..407ed4f
--- /dev/null
@@ -0,0 +1,72 @@
+{% extends "../base.html" %}
+
+{% block title %}{{ _("Files") }}{% end block %}
+
+{% block sidebar %}
+       {% set help = backend.wiki.get_page("/wiki/media") %}
+
+       {% if help %}
+               {% raw help.html %}
+       {% end %}
+{% end block %}
+
+{% block main %}
+       {% if files %}
+               <div class="card mb-4">
+                       <div class="card-body">
+                               <h4 class="card-title">{{ _("Files") }}</h4>
+
+                               <div class="row">    
+                                       {% for f in files %}
+                                               {% if f.is_image() %}
+                                                       <div class="col-sm-6 col-md-4">
+                                                               <figure class="figure">
+                                                                       <img class="figure-img img-fluid img-thumbnail" src="{{ f.url }}" alt="{{ f.filename }}">
+                                                                       <figcaption class="figure-caption">{{ f.filename }}</figcaption>
+                                                               </figure>     
+                                                       </div>
+                                               {% end %}
+                                       {% end %}
+                               </div>
+
+                               <ul class="list-inline">
+                                       {% for f in files %}
+                                               {% if not f.is_image() %}
+                                                       <li class="list-inline-item">
+                                                               {% if "pdf" in f.mimetype %}
+                                                                       <span class="fas fa-file-pdf fa-fw"></span>
+                                                               {% else %}
+                                                                       <span class="fas fa-file fa-fw"></span>
+                                                               {% end %}
+
+                                                               <a href="{{ f.url }}">{{ f.filename }}</a>
+                                                       </li>
+                                               {% end %}
+                                       {% end %}
+                               </ul>
+                       </div>
+               </div>
+       {% end %}
+
+       <div class="card">
+               <div class="card-body">
+                       <h6 class="card-title">{{ _("Upload File") }}</h6>
+
+                       <form method="POST" action="/actions/upload" enctype="multipart/form-data">
+                               {% raw xsrf_form_html() %}
+
+                               <input type="hidden" name="path" value="{{ path }}">
+
+                               <div class="form-group">
+                                       <input type="file" class="form-control-file" name="file" required>
+
+                                       <small class="form-text text-muted">
+                                               {{ _("Choose a file to upload") }}
+                                       </small>
+                               </div>
+
+                               <input class="btn btn-primary btn-block" type="submit" value="{{ _("Upload") }}">
+                       </form>
+               </div>
+       </div>
+{% end block %}
index 504b8a46bcbda2d9a8905eb793709b93550eb3db..926a46f9210247fd5ef3ee6e84bac222a5783c32 100644 (file)
@@ -287,10 +287,17 @@ class Application(tornado.web.Application):
                self.add_handlers(r"wiki(\.dev)?\.ipfire\.org",
                        authentication_handlers + [
 
+                       # Actions
+                       (r"/actions/upload", wiki.ActionUploadHandler),
+
                        # Handlers
                        (r"/recent\-changes", wiki.RecentChangesHandler),
                        (r"/search", wiki.SearchHandler),
 
+                       # Media
+                       (r"([A-Za-z0-9\-_\/]+)?/files", wiki.FilesHandler),
+                       (r"((?!/static)(?:[A-Za-z0-9\-_\/]+)?(?:.*)\.(?:\w+))$", wiki.FileHandler),
+
                        # Render pages
                        (r"([A-Za-z0-9\-_\/]+)?", wiki.PageHandler),
                ])
index 90da993c030af123d9e068caa3614b9d8b3f5ce3..2afe1ea7c3db502aedc1ec6347bc3b527174ba96 100644 (file)
@@ -6,6 +6,48 @@ from . import auth
 from . import base
 from . import ui_modules
 
+class ActionUploadHandler(auth.CacheMixin, base.BaseHandler):
+       @tornado.web.authenticated
+       def post(self):
+               path = self.get_argument("path")
+
+               try:
+                       filename, data, mimetype = self.get_file("file")
+
+                       # XXX check valid mimetypes
+
+                       with self.db.transaction():
+                               file = self.backend.wiki.upload(path, filename, data,
+                                       mimetype=mimetype, author=self.current_user,
+                                       address=self.get_remote_ip())
+
+               except TypeError as e:
+                       raise e
+
+               self.redirect("%s/files" % path)
+
+
+class FilesHandler(auth.CacheMixin, base.BaseHandler):
+       @tornado.web.authenticated
+       def get(self, path):
+               files = self.backend.wiki.get_files(path)
+
+               self.render("wiki/files/index.html", path=path, files=files)
+
+
+class FileHandler(auth.CacheMixin, base.BaseHandler):
+       def get(self, path):
+               file = self.backend.wiki.get_file_by_path(path)
+               if not file:
+                       raise tornado.web.HTTPError(404, "Could not find %s" % path)
+
+               # Set headers
+               self.set_header("Content-Type", file.mimetype or "application/octet-stream")
+               self.set_header("Content-Length", file.size)
+
+               self.finish(file.blob)
+
+
 class PageHandler(auth.CacheMixin, base.BaseHandler):
        @property
        def action(self):
@@ -102,9 +144,15 @@ class WikiListModule(ui_modules.UIModule):
 
 class WikiNavbarModule(ui_modules.UIModule):
        def render(self, suffix=None):
+               _ = self.locale.translate
+
                breadcrumbs = self.backend.wiki.make_breadcrumbs(self.request.path)
 
-               title = self.backend.wiki.get_page_title(self.request.path)
+               # Don't search for a title for the file manager
+               if self.request.path.endswith("/files"):
+                       title = _("Files")
+               else:
+                       title = self.backend.wiki.get_page_title(self.request.path)
 
                return self.render_string("wiki/modules/navbar.html",
                        breadcrumbs=breadcrumbs, page_title=title, suffix=suffix)