Skip to content
Browse files

HUE-838 [jb] Create new file button

Create a new 'touch' operation for creating a text file.
Group 'Create Directory' and 'Create File' in a dropdown button.
Put icons and refactor 'Upload file/archive' buttons.
Add a new test_touch tests with no superuser permissions.
  • Loading branch information...
1 parent 8b4a015 commit ae0e8ba62ba8f4e74553ee8327ff183701dfc46b @romainr romainr committed Oct 11, 2012
View
7 apps/filebrowser/src/filebrowser/forms.py
@@ -33,7 +33,7 @@ def __init__(self, label, help_text=None, **kwargs):
kwargs.setdefault('required', True)
kwargs.setdefault('min_length', 1)
forms.CharField.__init__(self, label=label, help_text=help_text, **kwargs)
-
+
def clean(self, value):
return normpath(CharField.clean(self, value))
@@ -81,6 +81,11 @@ class MkDirForm(forms.Form):
path = PathField(label=_("Path in which to create the directory"))
name = PathField(label=_("Directory Name"))
+class TouchForm(forms.Form):
+ op = "touch"
+ path = PathField(label=_("Path in which to create the file"))
+ name = PathField(label=_("File Name"))
+
class ChownForm(forms.Form):
op = "chown"
path = PathField(label=_("Path to change user/group ownership"))
View
2 apps/filebrowser/src/filebrowser/static/help/index.html
@@ -280,6 +280,6 @@
</td>
</tr>
</table>
-
+
</body>
</html>
View
27 apps/filebrowser/src/filebrowser/templates/listdir.mako
@@ -27,10 +27,12 @@ ${commonheader(_('File Browser'), 'filebrowser', user)}
<div class="container-fluid">
<h1>${_('File Browser')}</h1>
+
<%actionbar:render>
<%def name="search()">
<input type="text" class="input-xlarge search-query" placeholder="${_('Search for file name')}" data-bind="value: searchQuery">
</%def>
+
<%def name="actions()">
<button class="btn fileToolbarBtn" title="${_('Rename')}" data-bind="click: renameFile, enable: selectedFiles().length == 1"><i class="icon-font"></i> ${_('Rename')}</button>
<button class="btn fileToolbarBtn" title="${_('Move')}" data-bind="click: move, enable: selectedFiles().length == 1"><i class="icon-random"></i> ${_('Move')}</button>
@@ -40,21 +42,36 @@ ${commonheader(_('File Browser'), 'filebrowser', user)}
<button class="btn fileToolbarBtn" title="${_('Change Permissions')}" data-bind="click: changePermissions, enable: selectedFiles().length == 1"><i class="icon-list-alt"></i> ${_('Change Permissions')}</button>
<button class="btn fileToolbarBtn" title="${_('Delete')}" data-bind="click: deleteSelected, enable: selectedFiles().length == 1"><i class="icon-trash"></i> ${_('Delete')}</button>
</%def>
+
<%def name="creation()">
+ <div class="btn-group" style="display: inline-block;">
+ <a href="#" data-toggle="dropdown" class="btn dropdown-toggle">
+ <i class="icon-plus-sign"></i> ${_('New')}
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a href="#" class="create-file-link" title="${_('File')}"><i class="icon-file"></i> ${_('File')}</a></li>
+ <li><a href="#" class="create-directory-link" title="${_('Directory')}"><i class="icon-folder-close"></i> ${_('Directory')}</a></li>
+ </ul>
+ </div>
+
<div id="upload-dropdown" class="btn-group" style="display: inline-block;">
- <a href="#" class="btn upload-link dropdown-toggle" title="${_('Upload')}" data-toggle="dropdown"><i class="icon-upload"></i> ${_('Upload')}</a>
+ <a href="#" class="btn upload-link dropdown-toggle" title="${_('Upload')}" data-toggle="dropdown">
+ <i class="icon-upload"></i> ${_('Upload')}
+ <span class="caret"></span>
+ </a>
<ul class="dropdown-menu">
- <li><a href="#" tabindex="-1" class="upload-link" title="${_('Upload files')}" data-bind="click: uploadFile">${_('Upload files')}</a></li>
- <li class="divider"></li>
- <li><a href="#" tabindex="-1" class="upload-link" title="${_('Upload archive')}" data-bind="click: uploadArchive">${_('Upload archive')}</a></li>
+ <li><a href="#" class="upload-link" title="${_('Files')}" data-bind="click: uploadFile"><i class="icon-file"></i> ${_('Files')}</a></li>
+ <li><a href="#" class="upload-link" title="${_('Archive')}" data-bind="click: uploadArchive"><i class="icon-gift"></i> ${_('Archives')}</a></li>
</ul>
</div>
- <a href="#" class="btn create-directory-link" title="${_('New directory')}"><i class="icon-folder-close"></i> ${_('New directory')}</a>
</%def>
</%actionbar:render>
+
% if breadcrumbs:
${fb_components.breadcrumbs(path, breadcrumbs, True)}
%endif
+
<div class="scrollable">
${dir.list_table_browser(files, path_enc, current_request_path, cwd_set)}
</div>
View
40 apps/filebrowser/src/filebrowser/templates/listdir_components.mako
@@ -220,6 +220,27 @@ from django.utils.translation import ugettext as _
</form>
</div>
+ <!-- new directory modal -->
+ <div id="createFileModal" class="modal hide fade">
+ <form id="createFileForm" data-bind="submit: createFile" method="POST" enctype="multipart/form-data" class="form-inline form-padding-fix">
+ <div class="modal-header">
+ <a href="#" class="close" data-dismiss="modal">&times;</a>
+ <h3>${_('Create File')}</h3>
+ </div>
+ <div class="modal-body">
+ <label>${_('File Name')} <input id="newFileNameInput" name="name" value="" type="text" class="input-xlarge"/></label>
+ <input type="hidden" name="path" type="text" data-bind="value: currentPath"/>
+ </div>
+ <div class="modal-footer">
+ <div id="fileNameRequiredAlert" class="alert-message error hide" style="position: absolute; left: 10;">
+ <p><strong>${_('Sorry, file name is required.')}</strong>
+ </div>
+ <a id="cancelCreateFileBtn" class="btn" href="#">${_('Cancel')}</a>
+ <input class="btn primary" type="submit" value="${_('Submit')}" />
+ </div>
+ </form>
+ </div>
+
<script type="text/javascript" charset="utf-8">
// ajax modal windows
function openChownWindow(path, user, group, next){
@@ -313,18 +334,28 @@ from django.utils.translation import ugettext as _
$("#moveForm").find("input[name='dest_path']").removeClass("fieldError");
});
- //create directory handlers
$(".create-directory-link").click(function(){
$("#createDirectoryModal").modal({
keyboard: true,
show: true
});
});
+ $(".create-file-link").click(function(){
+ $("#createFileModal").modal({
+ keyboard: true,
+ show: true
+ });
+ });
+
$("#cancelCreateDirectoryBtn").click(function(){
$("#createDirectoryModal").modal("hide");
});
+ $("#cancelCreateFileBtn").click(function(){
+ $("#createFileModal").modal("hide");
+ });
+
$("#createDirectoryForm").submit(function(){
if ($.trim($("#newDirectoryNameInput").val())==""){
$("#directoryNameRequiredAlert").show();
@@ -567,7 +598,7 @@ from django.utils.translation import ugettext as _
self.currentPath(currentDirPath);
$('.uploader').trigger('fb:updatePath', {dest: self.currentPath()});
-
+
self.isLoading(false);
$(".scrollable").jHueTableScroller();
};
@@ -669,6 +700,11 @@ from django.utils.translation import ugettext as _
return true;
};
+ self.createFile = function (formElement) {
+ $(formElement).attr("action", "/filebrowser/touch?next=${url('filebrowser.views.view', path=urlencode('/'))}"+ "." + self.currentPath());
+ return true;
+ };
+
self.uploadFile = (function() {
var num_of_pending_uploads = 0;
var uploader = new qq.FileUploader({
View
1 apps/filebrowser/src/filebrowser/urls.py
@@ -39,6 +39,7 @@
url(r'upload/archive$', 'upload_archive', name='upload_archive'),
url(r'rename', 'rename', name='rename'),
url(r'mkdir', 'mkdir', name='mkdir'),
+ url(r'touch', 'touch', name='touch'),
url(r'^move', 'move', name='move'),
url(r'remove', 'remove', name='remove'),
url(r'rmdir', 'rmdir', name='rmdir'),
View
18 apps/filebrowser/src/filebrowser/views.py
@@ -28,15 +28,14 @@
import shutil
import stat as stat_module
import os
-from types import MethodType
try:
import json
except ImportError:
import simplejson as json
from django.contrib import messages
-from django.core import urlresolvers, serializers
+from django.core import urlresolvers
from django.template.defaultfilters import stringformat, filesizeformat
from django.http import Http404, HttpResponse, HttpResponseNotModified
from django.views.static import was_modified_since
@@ -55,7 +54,7 @@
from filebrowser.lib.rwx import filetype, rwx
from filebrowser.lib import xxd
from filebrowser.forms import RenameForm, UploadFileForm, UploadArchiveForm, MkDirForm,\
- RmDirForm, RmTreeForm, RemoveForm, ChmodForm, ChownForm, EditorForm
+ RmDirForm, RmTreeForm, RemoveForm, ChmodForm, ChownForm, EditorForm, TouchForm
from hadoop.fs.hadoopfs import Hdfs
from hadoop.fs.exceptions import WebHdfsException
@@ -834,6 +833,17 @@ def smart_mkdir(path, name):
return generic_op(MkDirForm, request, smart_mkdir, ["path", "name"], "path")
+def touch(request):
+ def smart_touch(path, name):
+ # Make sure only the filename is specified.
+ # No absolute path specification allowed.
+ if posixpath.sep in name:
+ raise PopupException(_("Sorry, could not name file \"%s\": Slashes are not allowed in filenames." % name))
+ request.fs.create(os.path.join(path, name))
+
+ return generic_op(TouchForm, request, smart_touch, ["path", "name"], "path")
+
+
def remove(request):
return generic_op(RemoveForm, request, request.fs.remove, ["path"], None)
@@ -865,7 +875,7 @@ def chown(request):
op = curry(request.fs.chown, recursive=request.POST.get('recursive', False))
return generic_op(ChownForm, request, op, args, "path", template="chown.mako",
- extra_params=dict(current_user=request.user,
+ extra_params=dict(current_user=request.user,
superuser=request.fs.superuser))
View
42 apps/filebrowser/src/filebrowser/views_test.py
@@ -37,7 +37,6 @@
from hadoop import pseudo_hdfs4
from avro import schema, datafile, io
-from lib.archives import archive_factory
from lib.rwx import expand_mode
@@ -47,16 +46,15 @@
@attr('requires_hadoop')
def test_mkdir_singledir():
cluster = pseudo_hdfs4.shared_cluster()
+ cluster.fs.setuser('test')
+ c = make_logged_in_client()
try:
- c = make_logged_in_client(cluster.superuser)
- cluster.fs.setuser(cluster.superuser)
-
# We test that mkdir fails when a non-relative path is provided and a multi-level path is provided.
success_path = 'mkdir_singledir'
path_absolute = '/mkdir_singledir'
path_fail = 'fail/foo'
- prefix = '/test-filebrowser/'
+ prefix = '/tmp/test-filebrowser/'
# Two of the following post requests should throw exceptions.
# See https://issues.cloudera.org/browse/HUE-793.
c.post('/filebrowser/mkdir', dict(path=prefix, name=path_fail))
@@ -77,6 +75,40 @@ def test_mkdir_singledir():
@attr('requires_hadoop')
+def test_touch():
+ cluster = pseudo_hdfs4.shared_cluster()
+ cluster.fs.setuser('test')
+ c = make_logged_in_client()
+
+ try:
+ success_path = 'touch_file'
+ path_absolute = '/touch_file'
+ path_fail = 'touch_fail/file'
+ prefix = '/tmp/test-filebrowser-touch/'
+
+ cluster.fs.mkdir(prefix)
+
+ resp = c.post('/filebrowser/touch', dict(path=prefix, name=path_fail))
+ assert_equal(500, resp.status_code)
+ resp = c.post('/filebrowser/touch', dict(path=prefix, name=path_absolute))
+ assert_equal(500, resp.status_code)
+ resp = c.post('/filebrowser/touch', dict(path=prefix, name=success_path))
+ assert_equal(200, resp.status_code)
+
+ # Read the parent dir and make sure we created 'success_path' only.
+ response = c.get('/filebrowser/view' + prefix)
+ file_listing = response.context['files']
+ assert_equal(2, len(file_listing))
+ assert_equal(file_listing[1]['name'], success_path)
+
+ finally:
+ try:
+ cluster.fs.rmtree(prefix)
+ except:
+ pass
+
+
+@attr('requires_hadoop')
def test_chmod():
cluster = pseudo_hdfs4.shared_cluster()

0 comments on commit ae0e8ba

Please sign in to comment.
Something went wrong with that request. Please try again.