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
This commit is contained in:
Brian Waldon 2013-01-29 17:58:21 -08:00 committed by Gabriel Hurley
parent 4bd2204809
commit b36a285938
6 changed files with 29 additions and 98 deletions

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -15,7 +15,8 @@
</div>
<div class="right">
<h3>{% trans "Description" %}:</h3>
<p>{% 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." %}</p>
<p><strong>{% trans "Object" %}</strong>: {% 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." %}</p>
<p><strong>{% trans "Pseudo-folder" %}</strong>: {% 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." %}</p>
</div>
{% endblock %}

View File

@ -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()

View File

@ -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. """