return list(files)
- def get_file_by_path(self, path):
+ def get_file_by_path(self, path, revision=None):
path, filename = os.path.dirname(path), os.path.basename(path)
+ if revision:
+ # Fetch a specific revision
+ return self._get_file("SELECT * FROM wiki_files \
+ WHERE path = %s AND filename = %s AND created_at <= %s \
+ ORDER BY created_at DESC LIMIT 1", path, filename, revision)
+
+ # Fetch latest version
+ return self._get_file("SELECT * FROM wiki_files \
+ WHERE path = %s AND filename = %s AND deleted_at IS NULL",
+ path, filename)
+
+ def get_file_by_path_and_filename(self, path, filename):
return self._get_file("SELECT * FROM wiki_files \
- WHERE path = %s AND filename = %s AND deleted_at IS NULL", path, filename)
+ WHERE path = %s AND filename = %s AND deleted_at IS NULL",
+ path, filename)
def upload(self, path, filename, data, mimetype, author, address):
+ # Replace any existing files
+ file = self.get_file_by_path_and_filename(path, filename)
+ if file:
+ file.delete(author)
+
# Upload the blob first
blob = self.db.get("INSERT INTO wiki_blobs(data) VALUES(%s) RETURNING id", data)
self.id = id
self.data = data
+ def __eq__(self, other):
+ if isinstance(other, self.__class__):
+ return self.id == other.id
+
@property
def url(self):
return os.path.join(self.path, self.filename)
def created_at(self):
return self.data.created_at
+ def delete(self, author):
+ # XXX handle author
+ self.db.execute("UPDATE wiki_files SET deleted_at = NOW() \
+ WHERE id = %s", self.id)
+
+ @property
+ def deleted_at(self):
+ return self.data.deleted_at
+
+ def get_latest_revision(self):
+ revisions = self.get_revisions()
+
+ # Return first object
+ for rev in revisions:
+ return rev
+
+ def get_revisions(self):
+ revisions = self.backend.wiki._get_files("SELECT * FROM wiki_files \
+ WHERE path = %s ORDER BY created_at DESC", self.path)
+
+ return list(revisions)
+
def is_pdf(self):
return self.mimetype in ("application/pdf", "application/x-pdf")
<div class="card-body">
{% if file.is_image() %}
<p class="text-center">
- <img class="img-fluid img-thumbnail" src="{{ file.url }}?s=768" alt="{{ file.filename }}">
+ <img class="img-fluid img-thumbnail" src="{{ file.url }}?revision={{ file.created_at.isoformat() }}&s=768" alt="{{ file.filename }}">
</p>
{% elif file.is_pdf() %}
- <object class="pdf-viewer" data="{{ file.url }}"
+ <object class="pdf-viewer" data="{{ file.url }}?revision={{ file.created_at.isoformat() }}"
title="{{ file.filename }}" type="{{ file.mimetype }}">
<p>
{{ _("This PDF attachment could not be displayed.") }}
- <a href="{{ file.url }}">{{ _("Click here to download") }}</a>
+ <a href="{{ file.url }}?revision={{ file.created_at.isoformat() }}">{{ _("Click here to download") }}</a>
</p>
</object>
{% end %}
- <a class="btn btn-primary btn-lg btn-block my-3" href="{{ file.url }}">
+ <a class="btn btn-primary btn-lg btn-block my-3" href="{{ file.url }}?revision={{ file.created_at.isoformat() }}">
<span class="fas fa-file-download"></span>
{{ _("Download") }} ({{ format_size(file.size) }})
</a>
<dt class="col-sm-3">{{ _("Uploaded at") }}</dt>
<dd class="col-sm-9">{{ locale.format_date(file.created_at) }}</dd>
+
+ {% if file.deleted_at %}
+ <dt class="col-sm-3">{{ _("Deleted at") }}</dt>
+ <dd class="col-sm-9">{{ locale.format_date(file.deleted_at) }}</dd>
+ {% end %}
</dl>
{% if file.is_image() %}
<pre><code>![](./{{ file.filename }} "{{ _("Caption") }}")</code></pre>
{% end %}
+
+ {% set revisions = file.get_revisions() %}
+ {% if len(revisions) > 1 %}
+ <h6>{{ _("Other Revisions") }}</h6>
+
+ <ul>
+ {% for r in revisions %}
+ {% if not file == r %}
+ <li>
+ <a href="{{ r.url }}?action=detail&revision={{ r.created_at.isoformat() }}">
+ {{ _("Uploaded %(time)s by %(author)s") % { "time" : locale.format_date(r.created_at), "author" : r.author } }}
+ </a>
+ </li>
+ {% end %}
+ {% end %}
+ </ul>
+ {% end %}
+
+ <form method="POST" action="/actions/upload" enctype="multipart/form-data">
+ {% raw xsrf_form_html() %}
+
+ <input type="hidden" name="path" value="{{ file.path }}">
+ <input type="hidden" name="filename" value="{{ file.filename }}">
+
+ <div class="form-group">
+ <div class="custom-file">
+ <input type="file" class="custom-file-input" name="file" required>
+ <label class="custom-file-label" for="customFile">{{ _("Choose a file to upload") }}</label>
+ </div>
+ </div>
+
+ <input class="btn btn-primary btn-block" type="submit" value="{{ _("Upload") }}">
+ </form>
</div>
</div>
{% end block %}
try:
filename, data, mimetype = self.get_file("file")
+ # Use filename from request if any
+ filename = self.get_argument("filename", filename)
+
# XXX check valid mimetypes
with self.db.transaction():
if not self.backend.wiki.check_acl(path, self.current_user):
raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
+ # Check if we are asked to render a certain revision
+ revision = self.get_argument("revision", None)
+
# Fetch the file
- file = self.backend.wiki.get_file_by_path(path)
+ file = self.backend.wiki.get_file_by_path(path, revision=revision)
if not file:
raise tornado.web.HTTPError(404, "Could not find %s" % path)