From b36a285938a0d98978eeb992f5144788d5ce11ff Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 29 Jan 2013 17:58:21 -0800 Subject: [PATCH] Remove placeholder objects for subfolders A subfolder in a container is now represented solely by the prefix of an object name delimited by a forward-slash (/). If an object exists in a container that matches the implied subfolder of another object, each will be displayed in the objects table. Implements bp swift-folder-prefix Change-Id: I05252c1db34fdf6584a71e8827ff6f8363bf0488 --- openstack_dashboard/api/swift.py | 53 ++++++------------- .../dashboards/project/containers/forms.py | 21 ++------ .../dashboards/project/containers/tables.py | 41 +++----------- .../templates/containers/_upload.html | 3 +- .../dashboards/project/containers/tests.py | 6 --- .../dashboards/project/containers/views.py | 3 +- 6 files changed, 29 insertions(+), 98 deletions(-) diff --git a/openstack_dashboard/api/swift.py b/openstack_dashboard/api/swift.py index 1cd059ccd..860c49297 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 f94135ae9..5e38c12be 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 dfcdbc7fd..ccb9d1bfd 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 503bc9778..fe27e2adb 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 efb7bb195..f4ebe822d 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 dd5048829..c93b7de68 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. """