diff --git a/openstack_dashboard/api/swift.py b/openstack_dashboard/api/swift.py index 1cd059ccda..860c492974 100644 --- a/openstack_dashboard/api/swift.py +++ b/openstack_dashboard/api/swift.py @@ -23,7 +23,6 @@ import logging import swiftclient from django.conf import settings -from django.utils.translation import ugettext as _ from horizon import exceptions @@ -45,56 +44,47 @@ class StorageObject(APIDictWrapper): self.orig_name = orig_name self.data = data + @property + def id(self): + return self.name + class PseudoFolder(APIDictWrapper): - """ - Wrapper to smooth out discrepencies between swift "subdir" items - and swift pseudo-folder objects. - """ - def __init__(self, apidict, container_name): super(PseudoFolder, self).__init__(apidict) self.container_name = container_name - def _has_content_type(self): - content_type = self._apidict.get("content_type", None) - return content_type == "application/directory" + @property + def id(self): + return '%s/%s' % (self.container_name, self.name) @property def name(self): - if self._has_content_type(): - return self._apidict['name'] return self.subdir.rstrip(FOLDER_DELIMITER) @property def bytes(self): - if self._has_content_type(): - return self._apidict['bytes'] return None @property def content_type(self): - return "application/directory" + return "application/pseudo-folder" def _objectify(items, container_name): """ Splits a listing of objects into their appropriate wrapper classes. """ - objects = {} - subdir_markers = [] + objects = [] # Deal with objects and object pseudo-folders first, save subdirs for later for item in items: - if item.get("content_type", None) == "application/directory": - objects[item['name']] = PseudoFolder(item, container_name) - elif item.get("subdir", None) is not None: - subdir_markers.append(PseudoFolder(item, container_name)) + if item.get("subdir", None) is not None: + object_cls = PseudoFolder else: - objects[item['name']] = StorageObject(item, container_name) - # Revisit subdirs to see if we have any non-duplicates - for item in subdir_markers: - if item.name not in objects.keys(): - objects[item.name] = item - return objects.values() + object_cls = StorageObject + + objects.append(object_cls(item, container_name)) + + return objects def swift_api(request): @@ -215,17 +205,6 @@ def swift_copy_object(request, orig_container_name, orig_object_name, headers=headers) -def swift_create_subfolder(request, container_name, folder_name): - headers = {'content-type': 'application/directory', - 'content-length': 0} - etag = swift_api(request).put_object(container_name, - folder_name, - None, - headers=headers) - obj_info = {'subdir': folder_name, 'etag': etag} - return PseudoFolder(obj_info, container_name) - - def swift_upload_object(request, container_name, object_name, object_file): headers = {} headers['X-Object-Meta-Orig-Filename'] = object_file.name diff --git a/openstack_dashboard/dashboards/project/containers/forms.py b/openstack_dashboard/dashboards/project/containers/forms.py index f94135ae9d..5e38c12be9 100644 --- a/openstack_dashboard/dashboards/project/containers/forms.py +++ b/openstack_dashboard/dashboards/project/containers/forms.py @@ -77,7 +77,9 @@ class UploadObject(forms.SelfHandlingForm): widget=forms.HiddenInput) name = forms.CharField(max_length=255, label=_("Object Name"), - validators=[no_slash_validator]) + help_text=_("Slashes are allowed, and are treated " + "as pseudo-folders by the Object " + "Store.")) object_file = forms.FileField(label=_("File"), allow_empty_file=True) container_name = forms.CharField(widget=forms.HiddenInput()) @@ -124,23 +126,6 @@ class CopyObject(forms.SelfHandlingForm): path = path + "/" new_path = "%s%s" % (path, new_object) - # Iteratively make sure all the directory markers exist. - if path: - path_component = "" - for bit in [i for i in path.split("/") if i]: - path_component += bit - try: - api.swift.swift_create_subfolder(request, - new_container, - path_component) - except: - redirect = reverse(index, - args=(wrap_delimiter(orig_container),)) - exceptions.handle(request, - _("Unable to copy object."), - redirect=redirect) - path_component += "/" - # Now copy the object itself. try: api.swift.swift_copy_object(request, diff --git a/openstack_dashboard/dashboards/project/containers/tables.py b/openstack_dashboard/dashboards/project/containers/tables.py index dfcdbc7fde..ccb9d1bfdc 100644 --- a/openstack_dashboard/dashboards/project/containers/tables.py +++ b/openstack_dashboard/dashboards/project/containers/tables.py @@ -144,18 +144,11 @@ class DeleteObject(tables.DeleteAction): api.swift.swift_delete_object(request, container_name, obj_id) -class DeleteSubfolder(DeleteObject): - name = "delete_subfolder" - data_type_singular = _("Folder") - data_type_plural = _("Folders") - allowed_data_types = ("subfolders",) - - class DeleteMultipleObjects(DeleteObject): name = "delete_multiple_objects" data_type_singular = _("Object") data_type_plural = _("Objects") - allowed_data_types = ("subfolders", "objects",) + allowed_data_types = ("objects",) class CopyObject(tables.LinkAction): @@ -199,12 +192,12 @@ class ObjectFilterAction(tables.FilterAction): def filter_subfolders_data(self, table, objects, filter_string): data = self._filtered_data(table, filter_string) return [datum for datum in data if - datum.content_type == "application/directory"] + datum.content_type == "application/pseudo-folder"] def filter_objects_data(self, table, objects, filter_string): data = self._filtered_data(table, filter_string) return [datum for datum in data if - datum.content_type != "application/directory"] + datum.content_type != "application/pseudo-folder"] def allowed(self, request, datum=None): if self.table.kwargs.get('container_name', None): @@ -228,24 +221,6 @@ def get_link_subfolder(subfolder): http.urlquote(wrap_delimiter(subfolder.name)))) -class CreateSubfolder(CreateContainer): - verbose_name = _("Create Folder") - url = "horizon:project:containers:create" - - def get_link_url(self): - container = self.table.kwargs['container_name'] - subfolders = self.table.kwargs['subfolder_path'] - parent = FOLDER_DELIMITER.join((bit for bit in [container, - subfolders] if bit)) - parent = parent.rstrip(FOLDER_DELIMITER) - return reverse(self.url, args=[http.urlquote(wrap_delimiter(parent))]) - - def allowed(self, request, datum=None): - if self.table.kwargs.get('container_name', None): - return True - return False - - class ObjectsTable(tables.DataTable): name = tables.Column("name", link=get_link_subfolder, @@ -255,16 +230,12 @@ class ObjectsTable(tables.DataTable): size = tables.Column(get_size, verbose_name=_('Size')) - def get_object_id(self, obj): - return obj.name - class Meta: name = "objects" verbose_name = _("Objects") - table_actions = (ObjectFilterAction, CreateSubfolder, - UploadObject, DeleteMultipleObjects) - row_actions = (DownloadObject, CopyObject, DeleteObject, - DeleteSubfolder) + table_actions = (ObjectFilterAction, UploadObject, + DeleteMultipleObjects) + row_actions = (DownloadObject, CopyObject, DeleteObject) data_types = ("subfolders", "objects") browser_table = "content" footer = False diff --git a/openstack_dashboard/dashboards/project/containers/templates/containers/_upload.html b/openstack_dashboard/dashboards/project/containers/templates/containers/_upload.html index 503bc97783..fe27e2adbc 100644 --- a/openstack_dashboard/dashboards/project/containers/templates/containers/_upload.html +++ b/openstack_dashboard/dashboards/project/containers/templates/containers/_upload.html @@ -15,7 +15,8 @@

{% trans "Description" %}:

-

{% trans "An object is the basic storage entity and any optional metadata that represents the files you store in the OpenStack Object Storage system. When you upload data to OpenStack Object Storage, the data is stored as-is (no compression or encryption) and consists of a location (container), the object's name, and any metadata consisting of key/value pairs." %}

+

{% trans "Object" %}: {% trans "An object is the basic storage entity that represents a file you store in the OpenStack Object Storage system. When you upload data to OpenStack Object Storage, the data is stored as-is (no compression or encryption) and consists of a location (container), the object's name, and any metadata consisting of key/value pairs." %}

+

{% trans "Pseudo-folder" %}: {% trans "Within a container you can group your objects into pseudo-folders, which behave similarly to folders in your desktop operating system, with the exception that they are virtual collections defined by a common prefix on the object's name. A slash (/) character is used as the delimiter for pseudo-folders in the Object Store." %}

{% endblock %} diff --git a/openstack_dashboard/dashboards/project/containers/tests.py b/openstack_dashboard/dashboards/project/containers/tests.py index efb7bb1957..f4ebe822dc 100644 --- a/openstack_dashboard/dashboards/project/containers/tests.py +++ b/openstack_dashboard/dashboards/project/containers/tests.py @@ -157,12 +157,6 @@ class SwiftTests(test.TestCase): args=[wrap_delimiter(container.name)]) self.assertRedirectsNoFollow(res, index_url) - # Test invalid filename - formData['name'] = "contains/a/slash" - res = self.client.post(upload_url, formData) - self.assertNoMessages() - self.assertContains(res, "Slash is not an allowed character.") - @test.create_stubs({api.swift: ('swift_delete_object',)}) def test_delete(self): container = self.containers.first() diff --git a/openstack_dashboard/dashboards/project/containers/views.py b/openstack_dashboard/dashboards/project/containers/views.py index dd5048829a..c93b7de68e 100644 --- a/openstack_dashboard/dashboards/project/containers/views.py +++ b/openstack_dashboard/dashboards/project/containers/views.py @@ -86,7 +86,8 @@ class ContainerView(browsers.ResourceBrowserView): return self._objects def is_subdir(self, item): - return getattr(item, "content_type", None) == "application/directory" + content_type = "application/pseudo-folder" + return getattr(item, "content_type", None) == content_type def get_objects_data(self): """ Returns a list of objects within the current folder. """