Merge pull request #240 from OGAWAHirofumi/add-edit
Add edit dialog to modify metadata (for now)master
commit
21ca683ec8
|
@ -17,6 +17,7 @@ passphrases) as credentials - there are no separate user names.
|
|||
The site admin can assign permissions to login credentials (and also to the anonymous, not logged-in user):
|
||||
|
||||
* create: be able to create pastebins
|
||||
* modify: be able to modify pastebins
|
||||
* read: be able to read / download pastebins
|
||||
* delete: be able to delete pastebins
|
||||
* list: be able to list (discover) all pastebins
|
||||
|
|
2
setup.py
2
setup.py
|
@ -42,7 +42,7 @@ setup(
|
|||
'Pygments',
|
||||
'xstatic',
|
||||
'XStatic-asciinema-player',
|
||||
'xstatic-bootbox',
|
||||
'xstatic-bootbox>=5.4.0',
|
||||
'xstatic-bootstrap>=4.0.0.0,<5.0.0.0',
|
||||
'xstatic-font-awesome',
|
||||
'xstatic-jquery',
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from flask import Blueprint
|
||||
|
||||
from .lodgeit import LodgeitUpload
|
||||
from .rest import ItemDetailView, ItemDownloadView, ItemUploadView, InfoView, \
|
||||
ItemDeleteView, ItemLockView, ItemUnlockView
|
||||
from .rest import ItemDetailView, ItemDownloadView, ItemModifyView, \
|
||||
ItemUploadView, InfoView, ItemDeleteView, ItemLockView, ItemUnlockView
|
||||
|
||||
|
||||
blueprint = Blueprint('bepasty_apis', __name__, url_prefix='/apis')
|
||||
|
@ -13,5 +13,6 @@ blueprint.add_url_rule('/rest/items', view_func=ItemUploadView.as_view('items'))
|
|||
blueprint.add_url_rule('/rest/items/<itemname:name>', view_func=ItemDetailView.as_view('items_detail'))
|
||||
blueprint.add_url_rule('/rest/items/<itemname:name>/download', view_func=ItemDownloadView.as_view('items_download'))
|
||||
blueprint.add_url_rule('/rest/items/<itemname:name>/delete', view_func=ItemDeleteView.as_view('items_delete'))
|
||||
blueprint.add_url_rule('/rest/items/<itemname:name>/modify', view_func=ItemModifyView.as_view('items_modify'))
|
||||
blueprint.add_url_rule('/rest/items/<itemname:name>/lock', view_func=ItemLockView.as_view('items_lock'))
|
||||
blueprint.add_url_rule('/rest/items/<itemname:name>/unlock', view_func=ItemUnlockView.as_view('items_unlock'))
|
||||
|
|
|
@ -17,6 +17,7 @@ from ..utils.upload import Upload, filter_internal, background_compute_hash
|
|||
from ..views.filelist import file_infos
|
||||
from ..views.delete import DeleteView
|
||||
from ..views.download import DownloadView
|
||||
from ..views.modify import ModifyView
|
||||
from ..views.setkv import LockView, UnlockView
|
||||
|
||||
|
||||
|
@ -248,6 +249,28 @@ class ItemDownloadView(ItemDetailView):
|
|||
return super(ItemDetailView, self).get(name)
|
||||
|
||||
|
||||
class ItemModifyView(ModifyView, RestBase):
|
||||
def error(self, item, error):
|
||||
raise Conflict(description=error)
|
||||
|
||||
def response(self, name):
|
||||
return make_response('{}', {'Content-Type': 'application/json'})
|
||||
|
||||
def get_params(self):
|
||||
json = request.json
|
||||
if json is None:
|
||||
raise BadRequest(description='Content-Type or JSON format is invalid')
|
||||
|
||||
return {
|
||||
FILENAME: json.get(FILENAME),
|
||||
TYPE: json.get(TYPE),
|
||||
}
|
||||
|
||||
@rest_errorhandler
|
||||
def post(self, name):
|
||||
return super(ItemModifyView, self).post(name)
|
||||
|
||||
|
||||
class ItemDeleteView(DeleteView, RestBase):
|
||||
def error(self, item, error):
|
||||
raise Conflict(description=error)
|
||||
|
|
|
@ -18,6 +18,7 @@ from .utils.permissions import (
|
|||
ADMIN,
|
||||
CREATE,
|
||||
DELETE,
|
||||
MODIFY,
|
||||
LIST,
|
||||
READ,
|
||||
get_permission_icons,
|
||||
|
@ -142,6 +143,7 @@ def create_app():
|
|||
app.jinja_env.globals['ADMIN'] = ADMIN
|
||||
app.jinja_env.globals['LIST'] = LIST
|
||||
app.jinja_env.globals['CREATE'] = CREATE
|
||||
app.jinja_env.globals['MODIFY'] = MODIFY
|
||||
app.jinja_env.globals['READ'] = READ
|
||||
app.jinja_env.globals['DELETE'] = DELETE
|
||||
|
||||
|
|
|
@ -123,12 +123,12 @@ class Config(object):
|
|||
#: ::
|
||||
#:
|
||||
#: PERMISSIONS = {
|
||||
#: 'myadminsecret_1.21d-3!wdar34': 'admin,list,create,read,delete',
|
||||
#: 'myadminsecret_1.21d-3!wdar34': 'admin,list,create,modify,read,delete',
|
||||
#: 'uploadersecret_rtghtrbrrrfsd': 'create,read',
|
||||
#: 'joe_doe_89359299887711335537': 'create,read,delete',
|
||||
#: }
|
||||
PERMISSIONS = {
|
||||
# 'foo': 'admin,list,create,read,delete',
|
||||
# 'foo': 'admin,list,create,modify,read,delete',
|
||||
}
|
||||
|
||||
#: not-logged-in users get these permissions -
|
||||
|
|
|
@ -131,3 +131,8 @@ table.highlighttable {
|
|||
.linenos {
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
/* autocomplete for modify dialog */
|
||||
.ui-autocomplete {
|
||||
z-index: 1065;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,37 @@ $(function () {
|
|||
});
|
||||
});
|
||||
|
||||
// Show a modify dialog box when trying to edit metadata.
|
||||
$("#modify-btn").click(function() {
|
||||
var form_name = "modify-frm"
|
||||
var hidden_name_id = "#hidden-" + form_name
|
||||
// Read form template from html
|
||||
var modal_form = $(hidden_name_id).html();
|
||||
var modal_title = $(hidden_name_id).attr('modalTitle');
|
||||
var modal_focus = $(hidden_name_id).attr('modalFocus');
|
||||
// A bit of a hack to avoid implementing this from scratch
|
||||
// using .dialog().
|
||||
var box = bootbox.confirm({
|
||||
title: modal_title,
|
||||
message: modal_form,
|
||||
centerVertical: true,
|
||||
onShown: function(e) {
|
||||
if (modal_focus) {
|
||||
$(this).find("#" + modal_focus).trigger('focus');
|
||||
}
|
||||
// Support jquery-ui autocomplete
|
||||
contenttype_autocomplete(this)
|
||||
},
|
||||
callback: function(result) {
|
||||
// Please note that this is not called when hitting
|
||||
// the Enter key on the input box.
|
||||
if (result == true) {
|
||||
$(this).find("#" + form_name).submit();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Bind on click event to all line number anchor tags
|
||||
$('td.linenos a').on('click', function(e) {
|
||||
remove_highlights();
|
||||
|
|
|
@ -80,3 +80,16 @@
|
|||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro input_filename(value) -%}
|
||||
<input class="form-control" type="text" id="filename" name="filename" size="40" placeholder="optional download-filename"{% if value is defined %} value="{{ value }}"{% endif %}>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro input_contenttype(value) -%}
|
||||
<input class="form-control" type="text" id="contenttype" name="contenttype" size="30" placeholder="Content-Type"{% if value is defined %} value="{{ value }}"{% endif %}>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro contenttype_autocomplete(selector, contenttypes) -%}
|
||||
var availableTypes = ["{{ contenttypes | join('","') | safe}}"];
|
||||
{{ selector|safe }}.autocomplete({source: availableTypes});
|
||||
{%- endmacro %}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{% extends "_layout.html" %}
|
||||
|
||||
{%- import '_utils.html' as utils -%}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
|
@ -18,6 +20,29 @@
|
|||
<span class="fa fa-asterisk"></span> Inline
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if may(MODIFY) %}
|
||||
<div id="hidden-modify-frm" class="d-none" modalTitle="Modify Metadata" modalFocus="filename">
|
||||
<!-- modify form that used by utils.js -->
|
||||
<form id="modify-frm" action="{{ url_for('bepasty.modify', name=name) }}" method="post">
|
||||
<div class="form-group row">
|
||||
<label for="filename" class="col-2 form-label">Filename</label>
|
||||
<div class="col-10">
|
||||
{{ utils.input_filename(item.meta['filename']) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="contenttype" class="col-2 form-label">Type</label>
|
||||
<div class="col-10">
|
||||
{{ utils.input_contenttype(item.meta['type']) }}
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary d-none">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
<button id="modify-btn" type="button" class="btn btn-info">
|
||||
<span class="fa fa-edit"></span> Modify
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if may(DELETE) %}
|
||||
<form id="del-frm" action="{{ url_for('bepasty.delete', name=name) }}" method="post" class="btn-group">
|
||||
<input type="hidden" name="next" value="{{ url_for('bepasty.index') }}">
|
||||
|
@ -76,4 +101,12 @@
|
|||
<script src="{{ url_for('bepasty.xstatic', name='bootbox', filename='bootbox.min.js') }}" type="text/javascript"></script>
|
||||
<script src="{{ url_for('bepasty.xstatic', name='asciinema_player', filename='asciinema-player.js') }}" type="text/javascript"></script>
|
||||
<script src="{{ url_for('static', filename='app/js/utils.js') }}" type="text/javascript"></script>
|
||||
{% if may(MODIFY) %}
|
||||
<script>
|
||||
<!-- function that used by utils.js -->
|
||||
function contenttype_autocomplete(modal_box) {
|
||||
{{ utils.contenttype_autocomplete('$(modal_box).find("#contenttype")', contenttypes) }}
|
||||
};
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock extra_script %}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{% extends "_layout.html" %}
|
||||
|
||||
{%- import '_utils.html' as utils %}
|
||||
|
||||
{% macro maximum_lifetime() -%}
|
||||
<div class="row">
|
||||
<div class="col-lg-3 form-group">
|
||||
|
@ -29,10 +31,10 @@
|
|||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3 form-group">
|
||||
<input class="form-control" type="text" id="contenttype" name="contenttype" size="30" placeholder="Content-Type">
|
||||
{{ utils.input_contenttype() }}
|
||||
</div>
|
||||
<div class="col-6 form-group">
|
||||
<input class="form-control" type="text" id="filename" name="filename" size="40" placeholder="optional download-filename">
|
||||
{{ utils.input_filename() }}
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<button id="formupload-submit" class="btn btn-primary btn-block">Submit</button>
|
||||
|
@ -148,8 +150,7 @@
|
|||
|
||||
<script>
|
||||
$(function() {
|
||||
var availableTypes = ["{{ contenttypes | join('","') | safe}}"];
|
||||
$("#contenttype").autocomplete({source: availableTypes});
|
||||
{{ utils.contenttype_autocomplete('$("#contenttype")', contenttypes) }}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -17,6 +17,7 @@ class TestScreenShots(object):
|
|||
# bootstrap4 breakpoints
|
||||
screenshot_dir = 'screenshots'
|
||||
screen_sizes = [(450, 700), (576, 800), (768, 600), (992, 768), (1200, 1024)]
|
||||
screenshot_seq = 1
|
||||
|
||||
def setup_class(self):
|
||||
"""
|
||||
|
@ -39,7 +40,8 @@ class TestScreenShots(object):
|
|||
if not os.path.isdir(self.screenshot_dir):
|
||||
os.mkdir(self.screenshot_dir)
|
||||
self.browser.save_screenshot(
|
||||
'{}/{}-{}x{}.png'.format(self.screenshot_dir, name, w, h)
|
||||
'{}/{:02d}-{}-{}x{}.png'.format(self.screenshot_dir,
|
||||
self.screenshot_seq, name, w, h)
|
||||
)
|
||||
|
||||
def screen_shots(self, name):
|
||||
|
@ -89,13 +91,15 @@ class TestScreenShots(object):
|
|||
|
||||
def error_404(self):
|
||||
# NOTE: 404 error
|
||||
self.screen_shots("01-error404")
|
||||
self.screen_shots("error404")
|
||||
self.screenshot_seq += 1
|
||||
|
||||
def login(self):
|
||||
self.browser.get(self.url_base)
|
||||
|
||||
# NOTE: login screen, 1 - close hamburger, 2 - open hamburger
|
||||
self.top_screen_shots("02-top")
|
||||
self.top_screen_shots("top")
|
||||
self.screenshot_seq += 1
|
||||
|
||||
token = self.browser.find_element_by_name("token")
|
||||
password = "foo"
|
||||
|
@ -105,7 +109,8 @@ class TestScreenShots(object):
|
|||
self.wait_present("//input[@value='Logout']")
|
||||
|
||||
# NOTE: upload screen, 1 - close hamburger, 2 - open hamburger
|
||||
self.top_screen_shots("03-upload")
|
||||
self.top_screen_shots("upload")
|
||||
self.screenshot_seq += 1
|
||||
|
||||
try:
|
||||
self.browser.find_element_by_xpath("//input[@value='Logout']")
|
||||
|
@ -141,7 +146,7 @@ echo "hello, world!"
|
|||
self.scroll_to_bottom()
|
||||
|
||||
# NOTE: uploaded screen
|
||||
self.screen_shots("04-uploading1")
|
||||
self.screen_shots("uploading1")
|
||||
|
||||
# big file
|
||||
with tempfile.NamedTemporaryFile(suffix=".bin") as fp:
|
||||
|
@ -151,7 +156,8 @@ echo "hello, world!"
|
|||
self.scroll_to_bottom()
|
||||
|
||||
# NOTE: in-progress uploading screen
|
||||
self.screen_shots("05-uploading2")
|
||||
self.screen_shots("uploading2")
|
||||
self.screenshot_seq += 1
|
||||
|
||||
# click abort
|
||||
abort = self.browser.find_element_by_id('fileupload-abort')
|
||||
|
@ -159,7 +165,8 @@ echo "hello, world!"
|
|||
time.sleep(.5)
|
||||
|
||||
# NOTE: abort bootbox
|
||||
self.screen_shots("06-abort")
|
||||
self.screen_shots("abort")
|
||||
self.screenshot_seq += 1
|
||||
|
||||
ok = self.browser.find_element_by_class_name('bootbox-accept')
|
||||
ok.click()
|
||||
|
@ -167,12 +174,14 @@ echo "hello, world!"
|
|||
self.scroll_to_bottom()
|
||||
|
||||
# NOTE: aborted upload screen
|
||||
self.screen_shots("07-uploading3")
|
||||
self.screen_shots("uploading3")
|
||||
self.screenshot_seq += 1
|
||||
|
||||
def list_view(self):
|
||||
self.browser.get(self.url_base + '/+list')
|
||||
# NOTE: list screen
|
||||
self.screen_shots("08-list")
|
||||
self.screen_shots("list")
|
||||
self.screenshot_seq += 1
|
||||
|
||||
def display_view(self):
|
||||
self.browser.get(self.url_base + '/+list')
|
||||
|
@ -183,17 +192,32 @@ echo "hello, world!"
|
|||
self.browser.get(self.browser.current_url + '#L-4')
|
||||
|
||||
# NOTE: display screen
|
||||
self.screen_shots("09-display")
|
||||
self.screen_shots("display")
|
||||
self.screenshot_seq += 1
|
||||
|
||||
modify = self.browser.find_element_by_id('modify-btn')
|
||||
modify.click()
|
||||
time.sleep(.5)
|
||||
|
||||
# NOTE: modify bootbox
|
||||
self.screen_shots("modify")
|
||||
self.screenshot_seq += 1
|
||||
|
||||
modify_cancel = self.browser.find_element_by_class_name('bootbox-cancel')
|
||||
modify_cancel.click()
|
||||
time.sleep(.5)
|
||||
|
||||
lock = self.browser.find_element_by_id('lock-btn')
|
||||
lock.click()
|
||||
# NOTE: display with lock screen
|
||||
self.screen_shots("10-lock")
|
||||
self.screen_shots("lock")
|
||||
self.screenshot_seq += 1
|
||||
|
||||
qr = self.browser.find_element_by_id('qr-btn')
|
||||
qr.click()
|
||||
# NOTE: QR code screen
|
||||
self.screen_shots("11-qr")
|
||||
self.screen_shots("qr")
|
||||
self.screenshot_seq += 1
|
||||
|
||||
def test(self):
|
||||
self.error_404()
|
||||
|
|
|
@ -10,8 +10,7 @@ import copy
|
|||
import hashlib
|
||||
import re
|
||||
from requests.auth import _basic_auth_str
|
||||
from flask import current_app
|
||||
from flask import url_for
|
||||
from flask import current_app, url_for, json
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -19,7 +18,7 @@ from ..app import create_app
|
|||
from ..config import Config
|
||||
from ..constants import FILENAME, TYPE, LOCKED, SIZE, COMPLETE, HASH, \
|
||||
TIMESTAMP_DOWNLOAD, TIMESTAMP_UPLOAD, TIMESTAMP_MAX_LIFE, TRANSACTION_ID
|
||||
from ..utils.date_funcs import time_unit_to_sec
|
||||
from ..utils.date_funcs import get_maxlife
|
||||
|
||||
UPLOAD_DATA = b"""\
|
||||
#!/usr/bin/python3
|
||||
|
@ -66,8 +65,8 @@ def wait_background():
|
|||
def client_fixture(tmp_path):
|
||||
with FakeTime() as faketime:
|
||||
Config.PERMISSIONS = {
|
||||
'admin': 'admin,list,create,read,delete',
|
||||
'full': 'list,create,read,delete',
|
||||
'admin': 'admin,list,create,modify,read,delete',
|
||||
'full': 'list,create,modify,read,delete',
|
||||
'none': '',
|
||||
}
|
||||
Config.STORAGE_FILESYSTEM_DIRECTORY = str(tmp_path)
|
||||
|
@ -128,6 +127,11 @@ class RestUrl:
|
|||
with current_app.test_request_context():
|
||||
return url_for('bepasty_apis.items_delete', name=self.item_id)
|
||||
|
||||
@property
|
||||
def modify(self):
|
||||
with current_app.test_request_context():
|
||||
return url_for('bepasty_apis.items_modify', name=self.item_id)
|
||||
|
||||
@property
|
||||
def lock(self):
|
||||
with current_app.test_request_context():
|
||||
|
@ -286,7 +290,12 @@ def make_meta(data, filename=None, ftype=None, lifetime=None, uri=None):
|
|||
if lifetime is None:
|
||||
# default maxlife is 1 MONTHS
|
||||
lifetime = [1, 'MONTHS']
|
||||
maxlife = int(time.time()) + time_unit_to_sec(lifetime[0], lifetime[1])
|
||||
|
||||
maxtime = get_maxlife({
|
||||
'maxlife_value': lifetime[0],
|
||||
'maxlife_unit': lifetime[1]
|
||||
}, True)
|
||||
maxlife = int(time.time()) + maxtime if maxtime > 0 else maxtime
|
||||
|
||||
meta = {
|
||||
'file-meta': {
|
||||
|
@ -800,6 +809,56 @@ def test_download_range(client_fixture):
|
|||
total_size=len(data))
|
||||
|
||||
|
||||
def test_modify(client_fixture):
|
||||
app, client, _ = client_fixture
|
||||
|
||||
meta = upload(client, UPLOAD_DATA, token='full', filename='test.py',
|
||||
ftype='text/x-python', lifetime=[1, 'FOREVER'])
|
||||
|
||||
item_id = os.path.basename(meta['uri'])
|
||||
url = RestUrl(item_id)
|
||||
|
||||
check_detail_or_download(app, client, item_id, meta, None)
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
|
||||
# no permission
|
||||
response = client.post(url.modify, headers=headers, data='{}')
|
||||
check_err_response(response, 403)
|
||||
|
||||
headers = add_auth('user', 'full', headers)
|
||||
|
||||
# invalid name
|
||||
response = client.post(RestUrl('abcdefgh').modify, headers=headers, data='{}')
|
||||
check_err_response(response, 404)
|
||||
|
||||
# invalid Content-Type
|
||||
response = client.post(url.modify, headers=add_auth('user', 'full'), data='{}')
|
||||
check_err_response(response, 400)
|
||||
|
||||
# invalid json
|
||||
response = client.post(url.modify, headers=headers, data='')
|
||||
check_err_response(response, 400)
|
||||
|
||||
# change filename
|
||||
filename = 'test2.py'
|
||||
meta['file-meta'][FILENAME] = filename
|
||||
data = json.dumps({FILENAME: filename})
|
||||
response = client.post(url.modify, headers=headers, data=data)
|
||||
check_json_response(response, {})
|
||||
|
||||
check_detail_or_download(app, client, item_id, meta, None)
|
||||
|
||||
# change type
|
||||
content_type = 'text/plain'
|
||||
meta['file-meta'][TYPE] = content_type
|
||||
data = json.dumps({TYPE: content_type})
|
||||
response = client.post(url.modify, headers=headers, data=data)
|
||||
check_json_response(response, {})
|
||||
|
||||
check_detail_or_download(app, client, item_id, meta, None)
|
||||
|
||||
|
||||
def test_delete_basic(client_fixture):
|
||||
app, client, faketime = client_fixture
|
||||
|
||||
|
@ -871,6 +930,16 @@ def test_lock_basic(client_fixture):
|
|||
response = client.get(url.download, headers=add_auth('user', 'admin'))
|
||||
check_data_response(response, metas[item_id], datas[item_id])
|
||||
|
||||
# modify locked item (should fail)
|
||||
headers = add_auth('user', 'full', {'Content-Type': 'application/json'})
|
||||
response = client.post(url.modify, headers=headers, data='{}')
|
||||
check_err_response(response, 403)
|
||||
|
||||
# modify locked item with admin (should succeed)
|
||||
headers = add_auth('user', 'admin', {'Content-Type': 'application/json'})
|
||||
response = client.post(url.modify, headers=headers, data='{}')
|
||||
check_json_response(response, {})
|
||||
|
||||
# delete locked item (should fail)
|
||||
response = client.post(url.delete, headers=add_auth('user', 'full'))
|
||||
check_err_response(response, 403)
|
||||
|
@ -918,6 +987,11 @@ def test_incomplete(client_fixture):
|
|||
response = client.get(url.download, headers=add_auth('user', 'full'))
|
||||
check_err_response(response, 409)
|
||||
|
||||
# modify should error with incomplete
|
||||
headers = add_auth('user', 'full', {'Content-Type': 'application/json'})
|
||||
response = client.post(url.modify, headers=headers, data='{}')
|
||||
check_err_response(response, 409)
|
||||
|
||||
# lock should error with incomplete
|
||||
response = client.post(url.lock, headers=add_auth('user', 'admin'))
|
||||
check_err_response(response, 409)
|
||||
|
|
|
@ -67,7 +67,7 @@ class TestMaxlifeFeature(object):
|
|||
def delete_current_file(self):
|
||||
self.browser.find_element_by_id("del-btn").click()
|
||||
time.sleep(.2)
|
||||
self.browser.find_element_by_class_name("btn-primary").click()
|
||||
self.browser.find_element_by_class_name("bootbox-accept").click()
|
||||
|
||||
def test_paste_keep_forever(self):
|
||||
self.browser.find_element_by_xpath("//select[@name='maxlife-unit']/option[@value='forever']").click()
|
||||
|
|
|
@ -6,6 +6,7 @@ from flask import g as flaskg
|
|||
ADMIN = 'admin'
|
||||
LIST = 'list'
|
||||
CREATE = 'create'
|
||||
MODIFY = 'modify'
|
||||
READ = 'read'
|
||||
DELETE = 'delete'
|
||||
|
||||
|
@ -17,6 +18,7 @@ permission_icons = {
|
|||
'admin': 'user',
|
||||
'list': 'list',
|
||||
'create': 'plus',
|
||||
'modify': 'edit',
|
||||
'read': 'book',
|
||||
'delete': 'trash'
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ from flask import Blueprint
|
|||
from .delete import DeleteView
|
||||
from .display import DisplayView
|
||||
from .download import DownloadView, InlineView
|
||||
from .modify import ModifyView
|
||||
from .qr import QRView
|
||||
from .filelist import FileListView
|
||||
from .index import index
|
||||
|
@ -24,6 +25,7 @@ blueprint.add_url_rule('/<itemname:name>', view_func=DisplayView.as_view('displa
|
|||
blueprint.add_url_rule('/<itemname:name>/+delete', view_func=DeleteView.as_view('delete'))
|
||||
blueprint.add_url_rule('/<itemname:name>/+download', view_func=DownloadView.as_view('download'))
|
||||
blueprint.add_url_rule('/<itemname:name>/+inline', view_func=InlineView.as_view('inline'))
|
||||
blueprint.add_url_rule('/<itemname:name>/+modify', view_func=ModifyView.as_view('modify'))
|
||||
blueprint.add_url_rule('/<itemname:name>/+qr', view_func=QRView.as_view('qr'))
|
||||
blueprint.add_url_rule('/<itemname:name>/+lock', view_func=LockView.as_view('lock'))
|
||||
blueprint.add_url_rule('/<itemname:name>/+unlock', view_func=UnlockView.as_view('unlock'))
|
||||
|
|
|
@ -14,6 +14,7 @@ from ..utils.date_funcs import delete_if_lifetime_over
|
|||
from ..utils.formatters import CustomHtmlFormatter
|
||||
from ..utils.permissions import ADMIN, READ, may
|
||||
|
||||
from .index import contenttypes_list
|
||||
from .filelist import file_infos
|
||||
|
||||
|
||||
|
@ -136,4 +137,5 @@ class DisplayView(MethodView):
|
|||
rendered_content = u"Rendering not allowed (too big?). Try download"
|
||||
|
||||
return render_template('display.html', name=name, item=item,
|
||||
rendered_content=rendered_content)
|
||||
rendered_content=rendered_content,
|
||||
contenttypes=contenttypes_list())
|
||||
|
|
|
@ -3,10 +3,14 @@ from flask import render_template
|
|||
from pygments.lexers import get_all_lexers
|
||||
|
||||
|
||||
def index():
|
||||
def contenttypes_list():
|
||||
contenttypes = [
|
||||
'text/x-bepasty-redirect', # redirect / link shortener service
|
||||
]
|
||||
for lexer_info in get_all_lexers():
|
||||
contenttypes.extend(lexer_info[3])
|
||||
return render_template('index.html', contenttypes=contenttypes)
|
||||
return contenttypes
|
||||
|
||||
|
||||
def index():
|
||||
return render_template('index.html', contenttypes=contenttypes_list())
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import errno
|
||||
|
||||
from flask import current_app, request, render_template
|
||||
from flask.views import MethodView
|
||||
from werkzeug.exceptions import Forbidden, NotFound
|
||||
|
||||
from ..constants import COMPLETE, FILENAME, LOCKED, TYPE
|
||||
from ..utils.date_funcs import delete_if_lifetime_over
|
||||
from ..utils.http import redirect_next_referrer
|
||||
from ..utils.permissions import ADMIN, CREATE, may
|
||||
from ..utils.upload import Upload
|
||||
|
||||
|
||||
class ModifyView(MethodView):
|
||||
def error(self, item, error):
|
||||
return render_template('error.html', heading=item.meta[FILENAME], body=error), 409
|
||||
|
||||
def response(self, name):
|
||||
return redirect_next_referrer('bepasty.display', name=name)
|
||||
|
||||
def get_params(self):
|
||||
return {
|
||||
FILENAME: request.form.get('filename'),
|
||||
TYPE: request.form.get('contenttype'),
|
||||
}
|
||||
|
||||
def post(self, name):
|
||||
if not may(CREATE):
|
||||
raise Forbidden()
|
||||
|
||||
try:
|
||||
with current_app.storage.openwrite(name) as item:
|
||||
if not item.meta[COMPLETE] and not may(ADMIN):
|
||||
error = 'Upload incomplete. Try again later.'
|
||||
return self.error(item, error)
|
||||
|
||||
if item.meta[LOCKED] and not may(ADMIN):
|
||||
raise Forbidden()
|
||||
|
||||
if delete_if_lifetime_over(item, name):
|
||||
raise NotFound()
|
||||
|
||||
params = self.get_params()
|
||||
if params[FILENAME]:
|
||||
item.meta[FILENAME] = Upload.filter_filename(
|
||||
params[FILENAME], name, params[TYPE], item.meta[TYPE]
|
||||
)
|
||||
if params[TYPE]:
|
||||
item.meta[TYPE], _ = Upload.filter_type(
|
||||
params[TYPE], item.meta[TYPE]
|
||||
)
|
||||
|
||||
return self.response(name)
|
||||
|
||||
except (OSError, IOError) as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
raise NotFound()
|
||||
raise
|
Loading…
Reference in New Issue