Removing deprecated Swift UI code
The move to the angularJS based UI for swift as the default occurred two releases ago in Mitaka. This patch removes the dead code. Change-Id: I0322808aeacab5406c0dd3f4b19bed50bc0ff089
This commit is contained in:
parent
11fbb4df2d
commit
9f75312b07
@ -1,31 +0,0 @@
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import browsers
|
||||
|
||||
from openstack_dashboard.dashboards.project.containers import tables
|
||||
|
||||
|
||||
class ContainerBrowser(browsers.ResourceBrowser):
|
||||
name = "swift"
|
||||
verbose_name = _("Swift")
|
||||
navigation_table_class = tables.ContainersTable
|
||||
content_table_class = tables.ObjectsTable
|
||||
navigable_item_name = _("Container")
|
||||
navigation_kwarg_name = "container_name"
|
||||
content_kwarg_name = "subfolder_path"
|
||||
has_breadcrumb = True
|
||||
breadcrumb_url = "horizon:project:containers:index"
|
@ -1,244 +0,0 @@
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core import validators
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.project.containers import utils
|
||||
|
||||
|
||||
no_slash_validator = validators.RegexValidator(r'^(?u)[^/]+$',
|
||||
_("Slash is not an allowed "
|
||||
"character."),
|
||||
code="noslash")
|
||||
no_begin_or_end_slash = validators.RegexValidator(r'^[^\/](?u).+[^\/]$',
|
||||
_("Slash is not allowed at "
|
||||
"the beginning or end of "
|
||||
"your string."),
|
||||
code="nobeginorendslash")
|
||||
|
||||
|
||||
class CreateContainer(forms.SelfHandlingForm):
|
||||
ACCESS_CHOICES = (
|
||||
("private", _("Private")),
|
||||
("public", _("Public")),
|
||||
)
|
||||
|
||||
parent = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
widget=forms.HiddenInput)
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Container Name"),
|
||||
validators=[no_slash_validator])
|
||||
access = forms.ThemableChoiceField(label=_("Container Access"),
|
||||
choices=ACCESS_CHOICES)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
if not data['parent']:
|
||||
is_public = data["access"] == "public"
|
||||
metadata = ({'is_public': is_public})
|
||||
# Create a container
|
||||
api.swift.swift_create_container(request,
|
||||
data["name"],
|
||||
metadata=metadata)
|
||||
messages.success(request, _("Container created successfully."))
|
||||
else:
|
||||
# Create a pseudo-folder
|
||||
container, slash, remainder = data['parent'].partition("/")
|
||||
remainder = remainder.rstrip("/")
|
||||
subfolder_name = "/".join([bit for bit
|
||||
in (remainder, data['name'])
|
||||
if bit])
|
||||
api.swift.swift_create_subfolder(request,
|
||||
container,
|
||||
subfolder_name)
|
||||
messages.success(request, _("Folder created successfully."))
|
||||
return True
|
||||
except Exception:
|
||||
exceptions.handle(request, _('Unable to create container.'))
|
||||
|
||||
|
||||
class UploadObject(forms.SelfHandlingForm):
|
||||
path = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
widget=forms.HiddenInput)
|
||||
object_file = forms.FileField(label=_("File"),
|
||||
required=False,
|
||||
allow_empty_file=True)
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Object Name"),
|
||||
help_text=_("Slashes are allowed, and are treated "
|
||||
"as pseudo-folders by the Object "
|
||||
"Store."),
|
||||
widget=forms.TextInput(
|
||||
attrs={"ng-model": "name",
|
||||
"not-blank": ""}
|
||||
))
|
||||
container_name = forms.CharField(widget=forms.HiddenInput())
|
||||
|
||||
def _set_object_path(self, data):
|
||||
if data['path']:
|
||||
object_path = "/".join([data['path'].rstrip("/"), data['name']])
|
||||
else:
|
||||
object_path = data['name']
|
||||
return object_path
|
||||
|
||||
def clean(self):
|
||||
data = super(UploadObject, self).clean()
|
||||
if 'object_file' not in self.files:
|
||||
self.files['object_file'] = None
|
||||
|
||||
return data
|
||||
|
||||
def handle(self, request, data):
|
||||
object_file = self.files['object_file']
|
||||
object_path = self._set_object_path(data)
|
||||
try:
|
||||
obj = api.swift.swift_upload_object(request,
|
||||
data['container_name'],
|
||||
object_path,
|
||||
object_file)
|
||||
msg = force_text(_("Object was successfully uploaded."))
|
||||
messages.success(request, msg)
|
||||
return obj
|
||||
except Exception:
|
||||
exceptions.handle(request, _("Unable to upload object."))
|
||||
|
||||
|
||||
class UpdateObject(UploadObject):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UpdateObject, self).__init__(*args, **kwargs)
|
||||
self.fields['name'].widget = forms.TextInput(
|
||||
attrs={"readonly": "readonly"})
|
||||
self.fields['name'].help_text = None
|
||||
|
||||
def handle(self, request, data):
|
||||
object_file = self.files.get('object_file')
|
||||
if object_file:
|
||||
object_path = self._set_object_path(data)
|
||||
try:
|
||||
obj = api.swift.swift_upload_object(request,
|
||||
data['container_name'],
|
||||
object_path,
|
||||
object_file)
|
||||
messages.success(
|
||||
request, _("Object was successfully updated."))
|
||||
return obj
|
||||
except Exception:
|
||||
exceptions.handle(request, _("Unable to update object."))
|
||||
return False
|
||||
else:
|
||||
# If object file is not provided, then a POST method is needed
|
||||
# to update ONLY metadata. This must be implemented when
|
||||
# object metadata can be updated from this panel.
|
||||
return True
|
||||
|
||||
|
||||
class CreatePseudoFolder(forms.SelfHandlingForm):
|
||||
path = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
widget=forms.HiddenInput)
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Pseudo-folder Name"),
|
||||
validators=[no_begin_or_end_slash])
|
||||
container_name = forms.CharField(widget=forms.HiddenInput())
|
||||
|
||||
def _set_pseudo_folder_path(self, data):
|
||||
if data['path']:
|
||||
pseudo_folder_path = "/".join([data['path'].rstrip("/"),
|
||||
data['name']]) + "/"
|
||||
else:
|
||||
pseudo_folder_path = data['name'] + "/"
|
||||
return pseudo_folder_path
|
||||
|
||||
def handle(self, request, data):
|
||||
pseudo_folder_path = self._set_pseudo_folder_path(data)
|
||||
try:
|
||||
obj = api.swift.swift_create_pseudo_folder(request,
|
||||
data['container_name'],
|
||||
pseudo_folder_path)
|
||||
messages.success(request,
|
||||
_("Pseudo-folder was successfully created."))
|
||||
return obj
|
||||
|
||||
except Exception:
|
||||
exceptions.handle(request, _("Unable to create pseudo-folder."))
|
||||
|
||||
|
||||
class CopyObject(forms.SelfHandlingForm):
|
||||
new_container_name = forms.ChoiceField(label=_("Destination container"),
|
||||
validators=[no_slash_validator])
|
||||
path = forms.CharField(
|
||||
label=pgettext_lazy("Swift pseudo folder path", u"Path"),
|
||||
max_length=255, required=False)
|
||||
new_object_name = forms.CharField(max_length=255,
|
||||
label=_("Destination object name"),
|
||||
validators=[no_slash_validator])
|
||||
orig_container_name = forms.CharField(widget=forms.HiddenInput())
|
||||
orig_object_name = forms.CharField(widget=forms.HiddenInput())
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
containers = kwargs.pop('containers')
|
||||
super(CopyObject, self).__init__(*args, **kwargs)
|
||||
self.fields['new_container_name'].choices = containers
|
||||
|
||||
def handle(self, request, data):
|
||||
index = "horizon:project:containers:index"
|
||||
orig_container = data['orig_container_name']
|
||||
orig_object = data['orig_object_name']
|
||||
new_container = data['new_container_name']
|
||||
new_object = data['new_object_name']
|
||||
path = data['path']
|
||||
if path and not path.endswith("/"):
|
||||
path = path + "/"
|
||||
new_path = "%s%s" % (path, new_object)
|
||||
|
||||
# Now copy the object itself.
|
||||
try:
|
||||
api.swift.swift_copy_object(request,
|
||||
orig_container,
|
||||
orig_object,
|
||||
new_container,
|
||||
new_path)
|
||||
dest = "%s/%s" % (new_container, path)
|
||||
vals = {"dest": dest.rstrip("/"),
|
||||
"orig": orig_object.split("/")[-1],
|
||||
"new": new_object}
|
||||
messages.success(request,
|
||||
_('Copied "%(orig)s" to "%(dest)s" as "%(new)s".')
|
||||
% vals)
|
||||
return True
|
||||
except exceptions.HorizonException as exc:
|
||||
messages.error(request, exc)
|
||||
raise exceptions.Http302(
|
||||
reverse(index, args=[utils.wrap_delimiter(orig_container)]))
|
||||
except Exception:
|
||||
redirect = reverse(index,
|
||||
args=[utils.wrap_delimiter(orig_container)])
|
||||
exceptions.handle(request,
|
||||
_("Unable to copy object."),
|
||||
redirect=redirect)
|
@ -1,432 +0,0 @@
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import shortcuts
|
||||
from django import template
|
||||
from django.template import defaultfilters as filters
|
||||
from django.utils import http
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import messages
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import swift
|
||||
from openstack_dashboard.dashboards.project.containers import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ViewContainer(tables.LinkAction):
|
||||
name = "view"
|
||||
verbose_name = _("View Details")
|
||||
url = "horizon:project:containers:container_detail"
|
||||
classes = ("ajax-modal", "btn-view")
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
obj_id = self.table.get_object_id(datum)
|
||||
return reverse(self.url, args=(obj_id,))
|
||||
|
||||
|
||||
class MakePublicContainer(tables.Action):
|
||||
name = "make_public"
|
||||
verbose_name = _("Make Public")
|
||||
icon = "pencil"
|
||||
|
||||
def allowed(self, request, container):
|
||||
# Container metadata have not been loaded
|
||||
if not hasattr(container, 'is_public'):
|
||||
return False
|
||||
return not container.is_public
|
||||
|
||||
def single(self, table, request, obj_id):
|
||||
try:
|
||||
api.swift.swift_update_container(request,
|
||||
obj_id,
|
||||
metadata=({'is_public': True}))
|
||||
LOG.info('Updating container "%s" access to public.' % obj_id)
|
||||
messages.success(request,
|
||||
_('Successfully updated container access to '
|
||||
'public.'))
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to update container access.'))
|
||||
return shortcuts.redirect('horizon:project:containers:index')
|
||||
|
||||
|
||||
class MakePrivateContainer(tables.Action):
|
||||
name = "make_private"
|
||||
verbose_name = _("Make Private")
|
||||
icon = "pencil"
|
||||
|
||||
def allowed(self, request, container):
|
||||
# Container metadata have not been loaded
|
||||
if not hasattr(container, 'is_public'):
|
||||
return False
|
||||
return container.is_public
|
||||
|
||||
def single(self, table, request, obj_id):
|
||||
try:
|
||||
api.swift.swift_update_container(request,
|
||||
obj_id,
|
||||
metadata=({'is_public': False}))
|
||||
LOG.info('Updating container "%s" access to private.' % obj_id)
|
||||
messages.success(request,
|
||||
_('Successfully updated container access to '
|
||||
'private.'))
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to update container access.'))
|
||||
return shortcuts.redirect('horizon:project:containers:index')
|
||||
|
||||
|
||||
class DeleteContainer(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Container",
|
||||
u"Delete Containers",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Container",
|
||||
u"Deleted Containers",
|
||||
count
|
||||
)
|
||||
|
||||
success_url = "horizon:project:containers:index"
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
try:
|
||||
api.swift.swift_delete_container(request, obj_id)
|
||||
except exceptions.Conflict as exc:
|
||||
exceptions.handle(request, exc, redirect=self.success_url)
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to delete container.'),
|
||||
redirect=self.success_url)
|
||||
|
||||
def get_success_url(self, request=None):
|
||||
"""Returns the URL to redirect to after a successful action.
|
||||
"""
|
||||
current_container = self.table.kwargs.get("container_name", None)
|
||||
|
||||
# If the current_container is deleted, then redirect to the default
|
||||
# completion url
|
||||
if current_container in self.success_ids:
|
||||
return self.success_url
|
||||
return request.get_full_path()
|
||||
|
||||
|
||||
class CreateContainer(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Container")
|
||||
url = "horizon:project:containers:create"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
|
||||
|
||||
class ListObjects(tables.LinkAction):
|
||||
name = "list_objects"
|
||||
verbose_name = _("View Container")
|
||||
url = "horizon:project:containers:index"
|
||||
icon = "list"
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
container_name = http.urlquote(datum.name)
|
||||
args = (utils.wrap_delimiter(container_name),)
|
||||
return reverse(self.url, args=args)
|
||||
|
||||
|
||||
class CreatePseudoFolder(tables.LinkAction):
|
||||
name = "create_pseudo_folder"
|
||||
verbose_name = _("Create Pseudo-folder")
|
||||
url = "horizon:project:containers:create_pseudo_folder"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
# Usable for both the container and object tables
|
||||
if getattr(datum, 'container', datum):
|
||||
container_name = http.urlquote(datum.name)
|
||||
else:
|
||||
container_name = self.table.kwargs['container_name']
|
||||
subfolders = self.table.kwargs.get('subfolder_path', '')
|
||||
args = (bit for bit in (container_name, subfolders) if bit)
|
||||
return reverse(self.url, args=args)
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
if self.table.kwargs.get('container_name', None):
|
||||
return True
|
||||
return False
|
||||
|
||||
def update(self, request, obj):
|
||||
# This will only be called for the row, so we can remove the button
|
||||
# styles meant for the table action version.
|
||||
self.attrs = {'class': 'ajax-modal'}
|
||||
|
||||
|
||||
class UploadObject(tables.LinkAction):
|
||||
name = "upload"
|
||||
verbose_name = _("Upload Object")
|
||||
url = "horizon:project:containers:object_upload"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "upload"
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
# Usable for both the container and object tables
|
||||
if getattr(datum, 'container', datum):
|
||||
# This is a container
|
||||
container_name = datum.name
|
||||
else:
|
||||
# This is a table action, and we already have the container name
|
||||
container_name = self.table.kwargs['container_name']
|
||||
subfolders = self.table.kwargs.get('subfolder_path', '')
|
||||
args = (bit for bit in (container_name, subfolders) if bit)
|
||||
return reverse(self.url, args=args)
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
if self.table.kwargs.get('container_name', None):
|
||||
return True
|
||||
return False
|
||||
|
||||
def update(self, request, obj):
|
||||
# This will only be called for the row, so we can remove the button
|
||||
# styles meant for the table action version.
|
||||
self.attrs = {'class': 'ajax-modal'}
|
||||
|
||||
|
||||
def get_size_used(container):
|
||||
return filters.filesizeformat(container.bytes)
|
||||
|
||||
|
||||
def get_container_link(container):
|
||||
return reverse("horizon:project:containers:index",
|
||||
args=(utils.wrap_delimiter(container.name),))
|
||||
|
||||
|
||||
class ContainerAjaxUpdateRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, container_name):
|
||||
container = api.swift.swift_get_container(request,
|
||||
container_name,
|
||||
with_data=False)
|
||||
return container
|
||||
|
||||
|
||||
def get_metadata(container):
|
||||
# If the metadata has not been loading, display a loading image
|
||||
if not hasattr(container, 'is_public'):
|
||||
return template.loader.render_to_string(
|
||||
'project/containers/_container_loader.html'
|
||||
)
|
||||
template_name = 'project/containers/_container_metadata.html'
|
||||
context = {"container": container}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
def get_metadata_loaded(container):
|
||||
# Determine if metadata has been loaded if the attribute is already set.
|
||||
return hasattr(container, 'is_public') and container.is_public is not None
|
||||
|
||||
|
||||
class ContainersTable(tables.DataTable):
|
||||
METADATA_LOADED_CHOICES = (
|
||||
(False, None),
|
||||
(True, True),
|
||||
)
|
||||
name = tables.Column("name",
|
||||
link=get_container_link,
|
||||
verbose_name=_("Container Name"))
|
||||
metadata = tables.Column(get_metadata,
|
||||
verbose_name=_("Container Details"),
|
||||
classes=('nowrap-col', ),)
|
||||
metadata_loaded = tables.Column(get_metadata_loaded,
|
||||
status=True,
|
||||
status_choices=METADATA_LOADED_CHOICES,
|
||||
hidden=True)
|
||||
|
||||
class Meta(object):
|
||||
name = "containers"
|
||||
verbose_name = _("Containers")
|
||||
row_class = ContainerAjaxUpdateRow
|
||||
status_columns = ['metadata_loaded', ]
|
||||
table_actions = (CreateContainer,)
|
||||
row_actions = (ViewContainer, MakePublicContainer,
|
||||
MakePrivateContainer, DeleteContainer,)
|
||||
browser_table = "navigation"
|
||||
footer = False
|
||||
|
||||
def get_object_id(self, container):
|
||||
return container.name
|
||||
|
||||
|
||||
class ViewObject(tables.LinkAction):
|
||||
name = "view"
|
||||
verbose_name = _("View Details")
|
||||
url = "horizon:project:containers:object_detail"
|
||||
classes = ("ajax-modal", "btn-view")
|
||||
allowed_data_types = ("objects",)
|
||||
|
||||
def get_link_url(self, obj):
|
||||
container_name = self.table.kwargs['container_name']
|
||||
return reverse(self.url, args=(container_name, obj.name))
|
||||
|
||||
|
||||
class UpdateObject(tables.LinkAction):
|
||||
name = "update_object"
|
||||
verbose_name = _("Edit")
|
||||
url = "horizon:project:containers:object_update"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "pencil"
|
||||
allowed_data_types = ("objects",)
|
||||
|
||||
def get_link_url(self, obj):
|
||||
container_name = self.table.kwargs['container_name']
|
||||
return reverse(self.url, args=(container_name, obj.name))
|
||||
|
||||
|
||||
class DeleteObject(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Object",
|
||||
u"Delete Objects",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Object",
|
||||
u"Deleted Objects",
|
||||
count
|
||||
)
|
||||
|
||||
name = "delete_object"
|
||||
allowed_data_types = ("objects", "subfolders",)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
obj = self.table.get_object_by_id(obj_id)
|
||||
container_name = obj.container_name
|
||||
datum_type = getattr(obj, self.table._meta.data_type_name, None)
|
||||
if datum_type == 'subfolders':
|
||||
obj_id = obj_id[(len(container_name) + 1):] + "/"
|
||||
api.swift.swift_delete_object(request, container_name, obj_id)
|
||||
|
||||
|
||||
class DeleteMultipleObjects(DeleteObject):
|
||||
name = "delete_multiple_objects"
|
||||
|
||||
|
||||
class CopyObject(tables.LinkAction):
|
||||
name = "copy"
|
||||
verbose_name = _("Copy")
|
||||
url = "horizon:project:containers:object_copy"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "circle-arrow-right"
|
||||
allowed_data_types = ("objects",)
|
||||
|
||||
def get_link_url(self, obj):
|
||||
container_name = self.table.kwargs['container_name']
|
||||
return reverse(self.url, args=(container_name, obj.name))
|
||||
|
||||
|
||||
class DownloadObject(tables.LinkAction):
|
||||
name = "download"
|
||||
verbose_name = _("Download")
|
||||
url = "horizon:project:containers:object_download"
|
||||
icon = "download"
|
||||
allowed_data_types = ("objects",)
|
||||
|
||||
def get_link_url(self, obj):
|
||||
container_name = self.table.kwargs['container_name']
|
||||
return reverse(self.url, args=(container_name, obj.name))
|
||||
|
||||
def allowed(self, request, object):
|
||||
return object.bytes and object.bytes > 0
|
||||
|
||||
|
||||
class ObjectFilterAction(tables.FilterAction):
|
||||
def _filtered_data(self, table, filter_string):
|
||||
request = table.request
|
||||
container = self.table.kwargs['container_name']
|
||||
subfolder = self.table.kwargs['subfolder_path']
|
||||
prefix = utils.wrap_delimiter(subfolder) if subfolder else ''
|
||||
self.filtered_data = api.swift.swift_filter_objects(request,
|
||||
filter_string,
|
||||
container,
|
||||
prefix=prefix)
|
||||
return self.filtered_data
|
||||
|
||||
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/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/pseudo-folder"]
|
||||
|
||||
def allowed(self, request, datum=None):
|
||||
if self.table.kwargs.get('container_name', None):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def sanitize_name(name):
|
||||
return name.split(swift.FOLDER_DELIMITER)[-1]
|
||||
|
||||
|
||||
def get_size(obj):
|
||||
if obj.bytes is None:
|
||||
return _("pseudo-folder")
|
||||
return filters.filesizeformat(obj.bytes)
|
||||
|
||||
|
||||
def get_link_subfolder(subfolder):
|
||||
container_name = subfolder.container_name
|
||||
return reverse("horizon:project:containers:index",
|
||||
args=(utils.wrap_delimiter(container_name),
|
||||
utils.wrap_delimiter(subfolder.name)))
|
||||
|
||||
|
||||
class ObjectsTable(tables.DataTable):
|
||||
name = tables.Column("name",
|
||||
link=get_link_subfolder,
|
||||
allowed_data_types=("subfolders",),
|
||||
verbose_name=_("Object Name"),
|
||||
filters=(sanitize_name,))
|
||||
|
||||
size = tables.Column(get_size, verbose_name=_('Size'))
|
||||
|
||||
class Meta(object):
|
||||
name = "objects"
|
||||
verbose_name = _("Objects")
|
||||
table_actions = (ObjectFilterAction, CreatePseudoFolder, UploadObject,
|
||||
DeleteMultipleObjects)
|
||||
row_actions = (DownloadObject, UpdateObject, CopyObject,
|
||||
ViewObject, DeleteObject)
|
||||
data_types = ("subfolders", "objects")
|
||||
browser_table = "content"
|
||||
footer = False
|
@ -1,29 +0,0 @@
|
||||
{% extends "horizon/common/_modal.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-header %}{% trans "Container Details" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="info detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Container Name" %}</dt>
|
||||
<dd>{{ container.name }}</dd>
|
||||
<dt>{% trans "Container Access" %}</dt>
|
||||
{% if container.public_url %}
|
||||
<dd>{% trans "Public" %}</dd>
|
||||
<dt>{% trans "Public URL" %}</dt>
|
||||
<dd>{{ container.public_url }}</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "Private" %}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Object Count" %}</dt>
|
||||
<dd>{{ container.container_object_count }}</dd>
|
||||
<dt>{% trans "Size" %}</dt>
|
||||
<dd>{{ container.container_bytes_used|filesizeformat }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<a href="{% url 'horizon:project:containers:index' %}" class="btn btn-default cancel">{% trans "Close" %}</a>
|
||||
{% endblock %}
|
@ -1,5 +0,0 @@
|
||||
<div class="horizon-loading-bar container-pending-bar">
|
||||
<div class="progress progress-striped active">
|
||||
<div class="progress-bar"></div>
|
||||
</div>
|
||||
</div>
|
@ -1,12 +0,0 @@
|
||||
{% load i18n %}
|
||||
<ul>
|
||||
<li>{% trans "Object Count: " %}{{ container.container_object_count }}</li>
|
||||
<li>{% trans "Size: " %}{{ container.container_bytes_used|filesizeformat }}</li>
|
||||
<li>{% trans "Access: " %}
|
||||
{% if container.public_url %}
|
||||
<a href="{{ container.public_url }}">{% trans "Public" %}</a>
|
||||
{% else %}
|
||||
{% trans "Private" %}
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
@ -1,24 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}copy_object_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:containers:object_copy' container_name object_name %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% blocktrans %}Copy Object: {{ object_name }}{% endblocktrans %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "Make a new copy of an existing object to store in this or another container. You may additionally specify the path within the selected container where the new copy should be stored." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Copy Object" %}" />
|
||||
<a href="{% url 'horizon:project:containers:index' container_name|add:'/' %}" class="btn btn-default cancel">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -1,20 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}create_container_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:containers:create' %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Create Container" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "A container is a storage compartment for your data and provides a way for you to organize your data. You can think of a container as a folder in Windows ® or a directory in UNIX ®. The primary difference between a container and these other file system concepts is that containers cannot be nested. You can, however, create an unlimited number of containers within your account. Data must be stored in a container so you must have at least one container defined in your account prior to uploading data." %}</p>
|
||||
<p>{% trans "Note: A Public Container will allow anyone with the Public URL to gain access to your objects in the container." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,26 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}create_directory_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:containers:create_pseudo_folder' container_name %}{% endblock %}
|
||||
|
||||
{% block modal-header %}
|
||||
{% blocktrans %}Create pseudo-folder in container {{ container_name }}{% endblocktrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<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 %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<a href="{% url 'horizon:project:containers:index' container_name|add:'/' %}" class="btn btn-default cancel">{% trans "Cancel" %}</a>
|
||||
<input class="btn btn-primary" type="submit" value="{% trans "Create" %}" />
|
||||
{% endblock %}
|
@ -1,25 +0,0 @@
|
||||
{% extends "horizon/common/_modal.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-header %}{% trans "Object Details" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="info detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ object.name }}</dd>
|
||||
<dt>{% trans "Hash" %}</dt>
|
||||
<dd>{{ object.etag }}</dd>
|
||||
<dt>{% trans "Content Type" %}</dt>
|
||||
<dd>{{ object.content_type }}</dd>
|
||||
<dt>{% trans "Last Modified" %}</dt>
|
||||
<dd>{{ object.timestamp|parse_isotime }}</dd>
|
||||
<dt>{% trans "Size" %}</dt>
|
||||
<dd>{{ object.bytes|filesizeformat }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<a href="{% url 'horizon:project:containers:index' %}" class="btn btn-default cancel">{% trans "Close" %}</a>
|
||||
{% endblock %}
|
@ -1,28 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}update_object_form{% endblock %}
|
||||
{% block ng_controller %}DummyController{% endblock %}
|
||||
{% block form_name %}updateForm{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:containers:object_update' container_name subfolder_path object_path %}{% endblock %}
|
||||
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Edit Object" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<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 "File:" %}</strong> {% trans "A new uploaded file will replace the content of the current object" %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<a href="{% url 'horizon:project:containers:index' container_name|add:'/' %}" class="btn btn-default cancel">{% trans "Cancel" %}</a>
|
||||
<input class="btn btn-primary" type="submit" ng-disabled="updateForm.$invalid" value="{% trans "Update Object" %}" />
|
||||
{% endblock %}
|
@ -1,29 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}upload_object_form{% endblock %}
|
||||
{% block form_name %}uploadForm{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:containers:object_upload' container_name %}{% endblock %}
|
||||
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
||||
|
||||
{% block modal-header %}
|
||||
{% blocktrans %}Upload Object To Container: {{ container_name }}{% endblocktrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<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 %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<a href="{% url 'horizon:project:containers:index' container_name|add:'/' %}" class="btn btn-default cancel">{% trans "Cancel" %}</a>
|
||||
<input class="btn btn-primary" type="submit" ng-disabled="uploadForm.$invalid || uploadForm.$pristine" value="{% trans "Upload Object" %}" />
|
||||
{% endblock %}
|
@ -1,11 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Container Details" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% include 'project/containers/_container_detail.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Copy Object" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/containers/_copy.html' %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Container" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "project/containers/_create.html" %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Pseudo-folder" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/containers/_create_pseudo_folder.html' %}
|
||||
{% endblock %}
|
@ -1,24 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Containers" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% if subfolders %}
|
||||
<div class="page_title table_header">
|
||||
<div>
|
||||
<h3>
|
||||
<a href="{% url 'horizon:project:containers:index' %}{{ container_name }}/">{{ container_name }}</a> : /
|
||||
{% for folder in subfolders %}
|
||||
{% if forloop.last %}
|
||||
{{ folder.0 }}
|
||||
{% else %}
|
||||
<a href="{% url 'horizon:project:containers:index' %}{{ container_name }}/{{ folder.1 }}">{{ folder.0 }}</a> /
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ swift_browser.render }}
|
||||
{% endblock %}
|
@ -1,11 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Object Details" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% include 'project/containers/_object_detail.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Update Object" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/containers/_update.html' %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Upload Object" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/containers/_upload.html' %}
|
||||
{% endblock %}
|
@ -1,565 +0,0 @@
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import email.header
|
||||
import tempfile
|
||||
|
||||
import django
|
||||
from django.core.files.uploadedfile import InMemoryUploadedFile # noqa
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import http
|
||||
from django.utils import http as utils_http
|
||||
|
||||
from mox3.mox import IsA # noqa
|
||||
import six
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.project.containers import forms
|
||||
from openstack_dashboard.dashboards.project.containers import tables
|
||||
from openstack_dashboard.dashboards.project.containers import utils
|
||||
from openstack_dashboard.dashboards.project.containers import views
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
CONTAINER_NAME_1 = u"container one%\u6346"
|
||||
CONTAINER_NAME_2 = u"container_two\u6346"
|
||||
CONTAINER_NAME_1_QUOTED = utils_http.urlquote(CONTAINER_NAME_1)
|
||||
CONTAINER_NAME_2_QUOTED = utils_http.urlquote(CONTAINER_NAME_2)
|
||||
INVALID_CONTAINER_NAME_1 = utils_http.urlquote(CONTAINER_NAME_1_QUOTED)
|
||||
INVALID_CONTAINER_NAME_2 = utils_http.urlquote(CONTAINER_NAME_2_QUOTED)
|
||||
CONTAINER_INDEX_URL = reverse('horizon:project:containers:index')
|
||||
|
||||
INVALID_PATHS = []
|
||||
|
||||
|
||||
def invalid_paths():
|
||||
if not INVALID_PATHS:
|
||||
for x in (CONTAINER_NAME_1_QUOTED, CONTAINER_NAME_2_QUOTED):
|
||||
y = reverse('horizon:project:containers:index',
|
||||
args=(utils.wrap_delimiter(x), ))
|
||||
INVALID_PATHS.append(y)
|
||||
for x in (CONTAINER_NAME_1, CONTAINER_NAME_2):
|
||||
INVALID_PATHS.append(CONTAINER_INDEX_URL + x)
|
||||
return INVALID_PATHS
|
||||
|
||||
|
||||
class SwiftTests(test.TestCase):
|
||||
|
||||
def _test_invalid_paths(self, response):
|
||||
for x in invalid_paths():
|
||||
self.assertNotContains(response, x)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_get_containers',)})
|
||||
def test_index_no_container_selected(self):
|
||||
containers = self.containers.list()
|
||||
api.swift.swift_get_containers(IsA(http.HttpRequest), marker=None) \
|
||||
.AndReturn((containers, False))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(CONTAINER_INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(res, 'project/containers/index.html')
|
||||
self.assertIn('table', res.context)
|
||||
resp_containers = res.context['table'].data
|
||||
self.assertEqual(len(resp_containers), len(containers))
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_delete_container', )})
|
||||
def test_delete_container(self):
|
||||
for container in self.containers.list():
|
||||
self.mox.ResetAll() # mandatory in a for loop
|
||||
api.swift.swift_delete_container(IsA(http.HttpRequest),
|
||||
container.name)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
action_string = u"containers__delete__%s" % container.name
|
||||
form_data = {"action": action_string}
|
||||
req = self.factory.post(CONTAINER_INDEX_URL, form_data)
|
||||
table = tables.ContainersTable(req, self.containers.list())
|
||||
handled = table.maybe_handle()
|
||||
self.assertEqual(handled['location'], CONTAINER_INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_get_objects', )})
|
||||
def test_delete_container_nonempty(self):
|
||||
container = self.containers.first()
|
||||
objects = self.objects.list()
|
||||
api.swift.swift_get_objects(IsA(http.HttpRequest),
|
||||
container.name).AndReturn([objects, False])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
action_string = u"containers__delete__%s" % container.name
|
||||
form_data = {"action": action_string}
|
||||
req = self.factory.post(CONTAINER_INDEX_URL, form_data)
|
||||
req.META['HTTP_REFERER'] = '%s/%s' % (CONTAINER_INDEX_URL,
|
||||
container.name)
|
||||
table = tables.ContainersTable(req, self.containers.list())
|
||||
handled = table.maybe_handle()
|
||||
|
||||
self.assertEqual(handled.status_code, 302)
|
||||
self.assertEqual(six.text_type(list(req._messages)[0].message),
|
||||
u"The container cannot be deleted "
|
||||
u"since it is not empty.")
|
||||
|
||||
def test_create_container_get(self):
|
||||
res = self.client.get(reverse('horizon:project:containers:create'))
|
||||
self.assertTemplateUsed(res, 'project/containers/create.html')
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_create_container',)})
|
||||
def test_create_container_post(self):
|
||||
for container in self.containers.list():
|
||||
self.mox.ResetAll() # mandatory in a for loop
|
||||
api.swift.swift_create_container(IsA(http.HttpRequest),
|
||||
container.name,
|
||||
metadata=({'is_public': False}))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'name': container.name,
|
||||
'access': "private",
|
||||
'method': forms.CreateContainer.__name__}
|
||||
res = self.client.post(
|
||||
reverse('horizon:project:containers:create'), formData)
|
||||
args = (utils.wrap_delimiter(container.name),)
|
||||
url = reverse('horizon:project:containers:index', args=args)
|
||||
self.assertRedirectsNoFollow(res, url)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_update_container', )})
|
||||
def test_update_container_to_public(self):
|
||||
container = self.containers.get(name=u"container one%\u6346")
|
||||
api.swift.swift_update_container(IsA(http.HttpRequest),
|
||||
container.name,
|
||||
metadata=({'is_public': True}))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
action_string = u"containers__make_public__%s" % container.name
|
||||
form_data = {"action": action_string}
|
||||
req = self.factory.post(CONTAINER_INDEX_URL, form_data)
|
||||
table = tables.ContainersTable(req, self.containers.list())
|
||||
handled = table.maybe_handle()
|
||||
self.assertEqual(handled['location'], CONTAINER_INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_update_container', )})
|
||||
def test_update_container_to_private(self):
|
||||
container = self.containers.get(name=u"container_two\u6346")
|
||||
api.swift.swift_update_container(IsA(http.HttpRequest),
|
||||
container.name,
|
||||
metadata=({'is_public': False}))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
action_string = u"containers__make_private__%s" % container.name
|
||||
form_data = {"action": action_string}
|
||||
req = self.factory.post(CONTAINER_INDEX_URL, form_data)
|
||||
table = tables.ContainersTable(req, self.containers.list())
|
||||
handled = table.maybe_handle()
|
||||
self.assertEqual(handled['location'], CONTAINER_INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_get_containers',
|
||||
'swift_get_objects')})
|
||||
def test_index_container_selected(self):
|
||||
containers = (self.containers.list(), False)
|
||||
ret = (self.objects.list(), False)
|
||||
api.swift.swift_get_containers(IsA(http.HttpRequest),
|
||||
marker=None).AndReturn(containers)
|
||||
api.swift.swift_get_objects(IsA(http.HttpRequest),
|
||||
self.containers.first().name,
|
||||
marker=None,
|
||||
prefix=None).AndReturn(ret)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
container_name = self.containers.first().name
|
||||
res = self.client.get(
|
||||
reverse('horizon:project:containers:index',
|
||||
args=[utils.wrap_delimiter(container_name)]))
|
||||
self.assertTemplateUsed(res, 'project/containers/index.html')
|
||||
# UTF8 encoding here to ensure there aren't problems with Nose output.
|
||||
expected = [obj.name.encode('utf8') for obj in self.objects.list()]
|
||||
self.assertQuerysetEqual(res.context['objects_table'].data,
|
||||
expected,
|
||||
lambda obj: obj.name.encode('utf8'))
|
||||
# Check if the two forms' URL are properly 'urlquote()d'.
|
||||
form_action = ' action="%s%s/" ' % (CONTAINER_INDEX_URL,
|
||||
CONTAINER_NAME_1_QUOTED)
|
||||
self.assertContains(res, form_action, count=2)
|
||||
self._test_invalid_paths(res)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_upload_object',)})
|
||||
def test_upload(self):
|
||||
container = self.containers.first()
|
||||
obj = self.objects.first()
|
||||
OBJECT_DATA = b'objectData'
|
||||
|
||||
temp_file = tempfile.NamedTemporaryFile()
|
||||
temp_file.write(OBJECT_DATA)
|
||||
temp_file.flush()
|
||||
temp_file.seek(0)
|
||||
|
||||
api.swift.swift_upload_object(IsA(http.HttpRequest),
|
||||
container.name,
|
||||
obj.name,
|
||||
IsA(InMemoryUploadedFile)).AndReturn(obj)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
upload_url = reverse('horizon:project:containers:object_upload',
|
||||
args=[container.name])
|
||||
|
||||
res = self.client.get(upload_url)
|
||||
self.assertTemplateUsed(res, 'project/containers/upload.html')
|
||||
self.assertContains(res, 'enctype="multipart/form-data"')
|
||||
self._test_invalid_paths(res)
|
||||
|
||||
formData = {'method': forms.UploadObject.__name__,
|
||||
'container_name': container.name,
|
||||
'name': obj.name,
|
||||
'object_file': temp_file}
|
||||
res = self.client.post(upload_url, formData)
|
||||
|
||||
args = (utils.wrap_delimiter(container.name),)
|
||||
index_url = reverse('horizon:project:containers:index', args=args)
|
||||
self.assertRedirectsNoFollow(res, index_url)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_upload_object',)})
|
||||
def test_upload_without_file(self):
|
||||
container = self.containers.first()
|
||||
obj = self.objects.first()
|
||||
|
||||
api.swift.swift_upload_object(IsA(http.HttpRequest),
|
||||
container.name,
|
||||
obj.name,
|
||||
None).AndReturn(obj)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
upload_url = reverse('horizon:project:containers:object_upload',
|
||||
args=[container.name])
|
||||
|
||||
res = self.client.get(upload_url)
|
||||
self.assertTemplateUsed(res, 'project/containers/upload.html')
|
||||
|
||||
res = self.client.get(upload_url)
|
||||
self.assertContains(res, 'enctype="multipart/form-data"')
|
||||
self.assertNotContains(res, INVALID_CONTAINER_NAME_1)
|
||||
self.assertNotContains(res, INVALID_CONTAINER_NAME_2)
|
||||
|
||||
formData = {'method': forms.UploadObject.__name__,
|
||||
'container_name': container.name,
|
||||
'name': obj.name,
|
||||
'object_file': None}
|
||||
res = self.client.post(upload_url, formData)
|
||||
|
||||
args = (utils.wrap_delimiter(container.name),)
|
||||
index_url = reverse('horizon:project:containers:index', args=args)
|
||||
self.assertRedirectsNoFollow(res, index_url)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_create_pseudo_folder',)})
|
||||
def test_create_pseudo_folder(self):
|
||||
container = self.containers.first()
|
||||
obj = self.objects.first()
|
||||
|
||||
api.swift.swift_create_pseudo_folder(IsA(http.HttpRequest),
|
||||
container.name,
|
||||
obj.name + "/").AndReturn(obj)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
create_pseudo_folder_url = reverse('horizon:project:containers:'
|
||||
'create_pseudo_folder',
|
||||
args=[container.name])
|
||||
|
||||
res = self.client.get(create_pseudo_folder_url)
|
||||
self.assertTemplateUsed(res,
|
||||
'project/containers/create_pseudo_folder.html')
|
||||
self._test_invalid_paths(res)
|
||||
|
||||
formData = {'method': forms.CreatePseudoFolder.__name__,
|
||||
'container_name': container.name,
|
||||
'name': obj.name}
|
||||
res = self.client.post(create_pseudo_folder_url, formData)
|
||||
|
||||
index_url = reverse('horizon:project:containers:index',
|
||||
args=[utils.wrap_delimiter(container.name)])
|
||||
|
||||
self.assertRedirectsNoFollow(res, index_url)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_delete_object',)})
|
||||
def test_delete(self):
|
||||
container = self.containers.first()
|
||||
obj = self.objects.first()
|
||||
args = (utils.wrap_delimiter(container.name),)
|
||||
index_url = reverse('horizon:project:containers:index', args=args)
|
||||
api.swift.swift_delete_object(IsA(http.HttpRequest),
|
||||
container.name,
|
||||
obj.name)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
action_string = "objects__delete_object__%s" % obj.name
|
||||
form_data = {"action": action_string}
|
||||
req = self.factory.post(index_url, form_data)
|
||||
kwargs = {"container_name": container.name}
|
||||
table = tables.ObjectsTable(req, self.objects.list(), **kwargs)
|
||||
handled = table.maybe_handle()
|
||||
self.assertEqual(handled['location'], index_url)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_delete_object',)})
|
||||
def test_delete_pseudo_folder(self):
|
||||
container = self.containers.first()
|
||||
folder = self.folder.first()
|
||||
args = (utils.wrap_delimiter(container.name),)
|
||||
index_url = reverse('horizon:project:containers:index', args=args)
|
||||
api.swift.swift_delete_object(IsA(http.HttpRequest),
|
||||
container.name,
|
||||
folder.name + '/')
|
||||
self.mox.ReplayAll()
|
||||
|
||||
action_string = "objects__delete_object__%s/%s" % (container.name,
|
||||
folder.name)
|
||||
form_data = {"action": action_string}
|
||||
req = self.factory.post(index_url, form_data)
|
||||
kwargs = {"container_name": container.name}
|
||||
table = tables.ObjectsTable(req, self.folder.list(), **kwargs)
|
||||
handled = table.maybe_handle()
|
||||
self.assertEqual(handled['location'], index_url)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_get_object',)})
|
||||
def test_download(self):
|
||||
for container in self.containers.list():
|
||||
for obj in self.objects.list():
|
||||
self.mox.ResetAll() # mandatory in a for loop
|
||||
obj = copy.copy(obj)
|
||||
_data = obj.data
|
||||
|
||||
def make_iter():
|
||||
yield _data
|
||||
|
||||
obj.data = make_iter()
|
||||
api.swift.swift_get_object(
|
||||
IsA(http.HttpRequest),
|
||||
container.name,
|
||||
obj.name,
|
||||
resp_chunk_size=api.swift.CHUNK_SIZE).AndReturn(obj)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
download_url = reverse(
|
||||
'horizon:project:containers:object_download',
|
||||
args=[container.name, obj.name])
|
||||
res = self.client.get(download_url)
|
||||
|
||||
self.assertTrue(res.has_header('Content-Disposition'))
|
||||
self.assertEqual(b''.join(res.streaming_content), _data)
|
||||
self.assertNotContains(res, INVALID_CONTAINER_NAME_1)
|
||||
self.assertNotContains(res, INVALID_CONTAINER_NAME_2)
|
||||
|
||||
# Check that the returned Content-Disposition filename is
|
||||
# correct - some have commas which must be removed
|
||||
expected_name = obj.name.replace(',', '')
|
||||
|
||||
# some have a path which must be removed
|
||||
if '/' in expected_name:
|
||||
expected_name = expected_name.split('/')[-1]
|
||||
|
||||
# There will also be surrounding double quotes
|
||||
expected_name = '"' + expected_name + '"'
|
||||
|
||||
expected = 'attachment; filename=%s' % expected_name
|
||||
content = res.get('Content-Disposition')
|
||||
|
||||
if six.PY3:
|
||||
header = email.header.decode_header(content)
|
||||
content = header[0][0]
|
||||
if isinstance(content, str):
|
||||
content = content.encode('utf-8')
|
||||
expected = expected.encode('utf-8')
|
||||
|
||||
self.assertEqual(content, expected)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_get_containers',)})
|
||||
def test_copy_index(self):
|
||||
ret = (self.containers.list(), False)
|
||||
api.swift.swift_get_containers(IsA(http.HttpRequest)).AndReturn(ret)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:project:containers:object_copy',
|
||||
args=[self.containers.first().name,
|
||||
self.objects.first().name]))
|
||||
self.assertTemplateUsed(res, 'project/containers/copy.html')
|
||||
self.assertNotContains(res, INVALID_CONTAINER_NAME_1)
|
||||
self.assertNotContains(res, INVALID_CONTAINER_NAME_2)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_get_containers',
|
||||
'swift_copy_object')})
|
||||
def test_copy(self):
|
||||
container_1 = self.containers.get(name=CONTAINER_NAME_1)
|
||||
container_2 = self.containers.get(name=CONTAINER_NAME_2)
|
||||
obj = self.objects.first()
|
||||
|
||||
ret = (self.containers.list(), False)
|
||||
api.swift.swift_get_containers(IsA(http.HttpRequest)).AndReturn(ret)
|
||||
api.swift.swift_copy_object(IsA(http.HttpRequest),
|
||||
container_1.name,
|
||||
obj.name,
|
||||
container_2.name,
|
||||
obj.name)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'method': forms.CopyObject.__name__,
|
||||
'new_container_name': container_2.name,
|
||||
'new_object_name': obj.name,
|
||||
'orig_container_name': container_1.name,
|
||||
'orig_object_name': obj.name}
|
||||
copy_url = reverse('horizon:project:containers:object_copy',
|
||||
args=[container_1.name, obj.name])
|
||||
res = self.client.post(copy_url, formData)
|
||||
args = (utils.wrap_delimiter(container_2.name),)
|
||||
index_url = reverse('horizon:project:containers:index', args=args)
|
||||
self.assertRedirectsNoFollow(res, index_url)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_get_containers',
|
||||
'swift_copy_object')})
|
||||
def test_copy_get(self):
|
||||
original_name = u"test folder%\u6346/test.txt"
|
||||
copy_name = u"test.copy.txt"
|
||||
container = self.containers.first()
|
||||
obj = self.objects.get(name=original_name)
|
||||
ret = (self.containers.list(), False)
|
||||
api.swift.swift_get_containers(IsA(http.HttpRequest)).AndReturn(ret)
|
||||
self.mox.ReplayAll()
|
||||
copy_url = reverse('horizon:project:containers:object_copy',
|
||||
args=[container.name, obj.name])
|
||||
res = self.client.get(copy_url)
|
||||
# The copy's name must appear in initial data
|
||||
if django.VERSION >= (1, 10):
|
||||
pattern = ('<input id="id_new_object_name" value="%s" '
|
||||
'name="new_object_name" type="text" '
|
||||
'class="form-control" '
|
||||
'maxlength="255" required/>' % copy_name)
|
||||
else:
|
||||
pattern = ('<input id="id_new_object_name" value="%s" '
|
||||
'name="new_object_name" type="text" '
|
||||
'class="form-control" '
|
||||
'maxlength="255"/>' % copy_name)
|
||||
self.assertContains(res, pattern, html=True)
|
||||
|
||||
def test_get_copy_name(self):
|
||||
self.assertEqual(views.CopyView.get_copy_name('test.txt'),
|
||||
'test.copy.txt')
|
||||
self.assertEqual(views.CopyView.get_copy_name('test'),
|
||||
'test.copy')
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_upload_object',)})
|
||||
def test_update_with_file(self):
|
||||
container = self.containers.first()
|
||||
obj = self.objects.first()
|
||||
OBJECT_DATA = b'objectData'
|
||||
|
||||
temp_file = tempfile.NamedTemporaryFile()
|
||||
temp_file.write(OBJECT_DATA)
|
||||
temp_file.flush()
|
||||
temp_file.seek(0)
|
||||
|
||||
api.swift.swift_upload_object(IsA(http.HttpRequest),
|
||||
container.name,
|
||||
obj.name,
|
||||
IsA(InMemoryUploadedFile)).AndReturn(obj)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
update_url = reverse('horizon:project:containers:object_update',
|
||||
args=[container.name, obj.name])
|
||||
|
||||
res = self.client.get(update_url)
|
||||
self.assertTemplateUsed(res, 'project/containers/update.html')
|
||||
self.assertContains(res, 'enctype="multipart/form-data"')
|
||||
self._test_invalid_paths(res)
|
||||
|
||||
formData = {'method': forms.UpdateObject.__name__,
|
||||
'container_name': container.name,
|
||||
'name': obj.name,
|
||||
'object_file': temp_file}
|
||||
res = self.client.post(update_url, formData)
|
||||
|
||||
args = (utils.wrap_delimiter(container.name),)
|
||||
index_url = reverse('horizon:project:containers:index', args=args)
|
||||
self.assertRedirectsNoFollow(res, index_url)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_upload_object',)})
|
||||
def test_update_without_file(self):
|
||||
container = self.containers.first()
|
||||
obj = self.objects.first()
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
update_url = reverse('horizon:project:containers:object_update',
|
||||
args=[container.name, obj.name])
|
||||
|
||||
res = self.client.get(update_url)
|
||||
self.assertTemplateUsed(res, 'project/containers/update.html')
|
||||
self.assertContains(res, 'enctype="multipart/form-data"')
|
||||
self._test_invalid_paths(res)
|
||||
|
||||
formData = {'method': forms.UpdateObject.__name__,
|
||||
'container_name': container.name,
|
||||
'name': obj.name}
|
||||
res = self.client.post(update_url, formData)
|
||||
|
||||
args = (utils.wrap_delimiter(container.name),)
|
||||
index_url = reverse('horizon:project:containers:index', args=args)
|
||||
self.assertRedirectsNoFollow(res, index_url)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_get_container', )})
|
||||
def test_view_container(self):
|
||||
for container in self.containers.list():
|
||||
self.mox.ResetAll() # mandatory in a for loop
|
||||
api.swift.swift_get_container(IsA(http.HttpRequest),
|
||||
container.name,
|
||||
with_data=False) \
|
||||
.AndReturn(container)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
view_url = reverse('horizon:project:containers:container_detail',
|
||||
args=[container.name])
|
||||
res = self.client.get(view_url)
|
||||
|
||||
self.assertTemplateUsed(res,
|
||||
'project/containers/container_detail.html')
|
||||
self.assertContains(res, container.name, 1, 200)
|
||||
self.assertNotContains(res, INVALID_CONTAINER_NAME_1)
|
||||
self.assertNotContains(res, INVALID_CONTAINER_NAME_2)
|
||||
|
||||
@test.create_stubs({api.swift: ('swift_get_object', )})
|
||||
def test_view_object(self):
|
||||
for container in self.containers.list():
|
||||
for obj in self.objects.list():
|
||||
self.mox.ResetAll() # mandatory in a for loop
|
||||
api.swift.swift_get_object(IsA(http.HttpRequest),
|
||||
container.name,
|
||||
obj.name,
|
||||
with_data=False) \
|
||||
.AndReturn(obj)
|
||||
self.mox.ReplayAll()
|
||||
view_url = reverse('horizon:project:containers:object_detail',
|
||||
args=[container.name, obj.name])
|
||||
res = self.client.get(view_url)
|
||||
|
||||
self.assertTemplateUsed(
|
||||
res, 'project/containers/object_detail.html')
|
||||
self.assertContains(res, obj.name, 1, 200)
|
||||
self._test_invalid_paths(res)
|
||||
|
||||
def test_wrap_delimiter(self):
|
||||
expected = {
|
||||
'containerA': 'containerA/',
|
||||
'containerB%': 'containerB%/', # no urlquote() should occur
|
||||
'containerC/': 'containerC/', # already wrapped name
|
||||
'containerD/objectA': 'containerD/objectA/'
|
||||
}
|
||||
for name, expected_name in expected.items():
|
||||
self.assertEqual(utils.wrap_delimiter(name), expected_name)
|
@ -16,13 +16,10 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
|
||||
from openstack_dashboard.dashboards.project.containers import views
|
||||
|
||||
if settings.HORIZON_CONFIG['swift_panel'] == 'angular':
|
||||
# New angular containers and objects
|
||||
urlpatterns = [
|
||||
url(r'^container/((?P<container_name>.+?)/)?'
|
||||
'(?P<subfolder_path>.+)?$',
|
||||
@ -30,46 +27,3 @@ if settings.HORIZON_CONFIG['swift_panel'] == 'angular':
|
||||
url(r'^$',
|
||||
views.NgIndexView.as_view(), name='index')
|
||||
]
|
||||
else:
|
||||
# Legacy swift containers and objects
|
||||
urlpatterns = [
|
||||
url(r'^((?P<container_name>.+?)/)?(?P<subfolder_path>(.+/)+)?$',
|
||||
views.ContainerView.as_view(), name='index'),
|
||||
|
||||
url(r'^(?P<container_name>(.+/)+)?create$',
|
||||
views.CreateView.as_view(),
|
||||
name='create'),
|
||||
|
||||
url(r'^(?P<container_name>.+?)/(?P<subfolder_path>(.+/)+)'
|
||||
'?container_detail$',
|
||||
views.ContainerDetailView.as_view(),
|
||||
name='container_detail'),
|
||||
|
||||
url(r'^(?P<container_name>[^/]+)/(?P<object_path>.+)/object_detail$',
|
||||
views.ObjectDetailView.as_view(),
|
||||
name='object_detail'),
|
||||
|
||||
url(r'^(?P<container_name>[^/]+)/(?P<subfolder_path>(.+/)+)?'
|
||||
'(?P<object_name>.+)/update$',
|
||||
views.UpdateObjectView.as_view(),
|
||||
name='object_update'),
|
||||
|
||||
url(r'^(?P<container_name>.+?)/(?P<subfolder_path>(.+/)+)?upload$',
|
||||
views.UploadView.as_view(),
|
||||
name='object_upload'),
|
||||
|
||||
url(r'^(?P<container_name>.+?)/(?P<subfolder_path>(.+/)+)'
|
||||
'?create_pseudo_folder',
|
||||
views.CreatePseudoFolderView.as_view(),
|
||||
name='create_pseudo_folder'),
|
||||
|
||||
url(r'^(?P<container_name>[^/]+)/'
|
||||
r'(?P<subfolder_path>(.+/)+)?'
|
||||
r'(?P<object_name>.+)/copy$',
|
||||
views.CopyView.as_view(),
|
||||
name='object_copy'),
|
||||
|
||||
url(r'^(?P<container_name>[^/]+)/(?P<object_path>.+)/download$',
|
||||
views.object_download,
|
||||
name='object_download'),
|
||||
]
|
||||
|
@ -10,9 +10,23 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core import validators
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from openstack_dashboard.api import swift
|
||||
|
||||
|
||||
no_slash_validator = validators.RegexValidator(r'^(?u)[^/]+$',
|
||||
_("Slash is not an allowed "
|
||||
"character."),
|
||||
code="noslash")
|
||||
no_begin_or_end_slash = validators.RegexValidator(r'^[^\/](?u).+[^\/]$',
|
||||
_("Slash is not allowed at "
|
||||
"the beginning or end of "
|
||||
"your string."),
|
||||
code="nobeginorendslash")
|
||||
|
||||
|
||||
def wrap_delimiter(name):
|
||||
if name and not name.endswith(swift.FOLDER_DELIMITER):
|
||||
return name + swift.FOLDER_DELIMITER
|
||||
|
@ -16,327 +16,10 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Views for managing Swift containers.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import http
|
||||
from django.utils.functional import cached_property # noqa
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views import generic
|
||||
import six
|
||||
|
||||
from horizon import browsers
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import swift
|
||||
from openstack_dashboard.dashboards.project.containers \
|
||||
import browsers as project_browsers
|
||||
from openstack_dashboard.dashboards.project.containers \
|
||||
import forms as project_forms
|
||||
from openstack_dashboard.dashboards.project.containers import utils
|
||||
|
||||
|
||||
class NgIndexView(generic.TemplateView):
|
||||
"""View for managing Swift containers."""
|
||||
template_name = 'project/containers/ngindex.html'
|
||||
|
||||
|
||||
class ContainerView(browsers.ResourceBrowserView):
|
||||
browser_class = project_browsers.ContainerBrowser
|
||||
template_name = "project/containers/index.html"
|
||||
page_title = _("Containers")
|
||||
|
||||
def get_containers_data(self):
|
||||
containers = []
|
||||
self._more = None
|
||||
marker = self.request.GET.get('marker', None)
|
||||
try:
|
||||
containers, self._more = api.swift.swift_get_containers(
|
||||
self.request, marker=marker)
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve container list.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return containers
|
||||
|
||||
@cached_property
|
||||
def objects(self):
|
||||
"""Returns a list of objects given the subfolder's path.
|
||||
|
||||
The path is from the kwargs of the request.
|
||||
"""
|
||||
objects = []
|
||||
self._more = None
|
||||
marker = self.request.GET.get('marker', None)
|
||||
container_name = self.kwargs['container_name']
|
||||
subfolder = self.kwargs['subfolder_path']
|
||||
prefix = None
|
||||
if container_name:
|
||||
self.navigation_selection = True
|
||||
if subfolder:
|
||||
prefix = subfolder
|
||||
try:
|
||||
objects, self._more = api.swift.swift_get_objects(
|
||||
self.request,
|
||||
container_name,
|
||||
marker=marker,
|
||||
prefix=prefix)
|
||||
except Exception:
|
||||
self._more = None
|
||||
objects = []
|
||||
msg = _('Unable to retrieve object list.')
|
||||
exceptions.handle(self.request, msg)
|
||||
return objects
|
||||
|
||||
def is_subdir(self, item):
|
||||
content_type = "application/pseudo-folder"
|
||||
return getattr(item, "content_type", None) == content_type
|
||||
|
||||
def is_placeholder(self, item):
|
||||
object_name = getattr(item, "name", "")
|
||||
return object_name.endswith(api.swift.FOLDER_DELIMITER)
|
||||
|
||||
def get_objects_data(self):
|
||||
"""Returns a list of objects within the current folder."""
|
||||
filtered_objects = [item for item in self.objects
|
||||
if (not self.is_subdir(item) and
|
||||
not self.is_placeholder(item))]
|
||||
return filtered_objects
|
||||
|
||||
def get_subfolders_data(self):
|
||||
"""Returns a list of subfolders within the current folder."""
|
||||
filtered_objects = [item for item in self.objects
|
||||
if self.is_subdir(item)]
|
||||
return filtered_objects
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ContainerView, self).get_context_data(**kwargs)
|
||||
context['container_name'] = self.kwargs["container_name"]
|
||||
context['subfolders'] = []
|
||||
if self.kwargs["subfolder_path"]:
|
||||
(parent, slash, folder) = self.kwargs["subfolder_path"] \
|
||||
.strip('/').rpartition('/')
|
||||
while folder:
|
||||
path = "%s%s%s/" % (parent, slash, folder)
|
||||
context['subfolders'].insert(0, (folder, path))
|
||||
(parent, slash, folder) = parent.rpartition('/')
|
||||
return context
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = project_forms.CreateContainer
|
||||
template_name = 'project/containers/create.html'
|
||||
success_url = "horizon:project:containers:index"
|
||||
page_title = _("Create Container")
|
||||
|
||||
def get_success_url(self):
|
||||
parent = self.request.POST.get('parent', None)
|
||||
if parent:
|
||||
container, slash, remainder = parent.partition(
|
||||
swift.FOLDER_DELIMITER)
|
||||
args = (utils.wrap_delimiter(container),
|
||||
utils.wrap_delimiter(remainder))
|
||||
return reverse(self.success_url, args=args)
|
||||
else:
|
||||
container = utils.wrap_delimiter(self.request.POST['name'])
|
||||
return reverse(self.success_url, args=[container])
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(CreateView, self).get_initial()
|
||||
initial['parent'] = self.kwargs['container_name']
|
||||
return initial
|
||||
|
||||
|
||||
class CreatePseudoFolderView(forms.ModalFormView):
|
||||
form_class = project_forms.CreatePseudoFolder
|
||||
template_name = 'project/containers/create_pseudo_folder.html'
|
||||
success_url = "horizon:project:containers:index"
|
||||
page_title = _("Create Pseudo-folder")
|
||||
|
||||
def get_success_url(self):
|
||||
container_name = self.request.POST['container_name']
|
||||
return reverse(self.success_url,
|
||||
args=(utils.wrap_delimiter(container_name),
|
||||
self.request.POST.get('path', '')))
|
||||
|
||||
def get_initial(self):
|
||||
return {"container_name": self.kwargs["container_name"],
|
||||
"path": self.kwargs['subfolder_path']}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreatePseudoFolderView, self). \
|
||||
get_context_data(**kwargs)
|
||||
context['container_name'] = self.kwargs["container_name"]
|
||||
return context
|
||||
|
||||
|
||||
class UploadView(forms.ModalFormView):
|
||||
form_class = project_forms.UploadObject
|
||||
template_name = 'project/containers/upload.html'
|
||||
success_url = "horizon:project:containers:index"
|
||||
page_title = _("Upload Objects")
|
||||
|
||||
def get_success_url(self):
|
||||
container = utils.wrap_delimiter(self.request.POST['container_name'])
|
||||
path = utils.wrap_delimiter(self.request.POST.get('path', ''))
|
||||
args = (container, path)
|
||||
return reverse(self.success_url, args=args)
|
||||
|
||||
def get_initial(self):
|
||||
return {"container_name": self.kwargs["container_name"],
|
||||
"path": self.kwargs['subfolder_path']}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UploadView, self).get_context_data(**kwargs)
|
||||
context['container_name'] = self.kwargs["container_name"]
|
||||
return context
|
||||
|
||||
|
||||
def object_download(request, container_name, object_path):
|
||||
try:
|
||||
obj = api.swift.swift_get_object(request, container_name, object_path,
|
||||
resp_chunk_size=swift.CHUNK_SIZE)
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:containers:index")
|
||||
exceptions.handle(request,
|
||||
_("Unable to retrieve object."),
|
||||
redirect=redirect)
|
||||
# Add the original file extension back on if it wasn't preserved in the
|
||||
# name given to the object.
|
||||
filename = object_path.rsplit(swift.FOLDER_DELIMITER)[-1]
|
||||
if not os.path.splitext(obj.name)[1] and obj.orig_name:
|
||||
name, ext = os.path.splitext(obj.orig_name)
|
||||
filename = "%s%s" % (filename, ext)
|
||||
response = http.StreamingHttpResponse(obj.data)
|
||||
safe_name = filename.replace(",", "")
|
||||
if six.PY2:
|
||||
safe_name = safe_name.encode('utf-8')
|
||||
response['Content-Disposition'] = 'attachment; filename="%s"' % safe_name
|
||||
response['Content-Type'] = 'application/octet-stream'
|
||||
response['Content-Length'] = obj.bytes
|
||||
return response
|
||||
|
||||
|
||||
class CopyView(forms.ModalFormView):
|
||||
form_class = project_forms.CopyObject
|
||||
template_name = 'project/containers/copy.html'
|
||||
success_url = "horizon:project:containers:index"
|
||||
page_title = _("Copy Object")
|
||||
|
||||
def get_success_url(self):
|
||||
container = utils.wrap_delimiter(
|
||||
self.request.POST['new_container_name'])
|
||||
path = utils.wrap_delimiter(self.request.POST.get('path', ''))
|
||||
args = (container, path)
|
||||
return reverse(self.success_url, args=args)
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(CopyView, self).get_form_kwargs()
|
||||
try:
|
||||
containers = api.swift.swift_get_containers(self.request)
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:containers:index")
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to list containers.'),
|
||||
redirect=redirect)
|
||||
kwargs['containers'] = [(c.name, c.name) for c in containers[0]]
|
||||
return kwargs
|
||||
|
||||
@staticmethod
|
||||
def get_copy_name(object_name):
|
||||
filename, ext = os.path.splitext(object_name)
|
||||
return "%s.copy%s" % (filename, ext)
|
||||
|
||||
def get_initial(self):
|
||||
path = self.kwargs["subfolder_path"]
|
||||
object_name = self.kwargs["object_name"]
|
||||
orig = "%s%s" % (path or '', object_name)
|
||||
|
||||
return {"new_container_name": self.kwargs["container_name"],
|
||||
"orig_container_name": self.kwargs["container_name"],
|
||||
"orig_object_name": orig,
|
||||
"path": path,
|
||||
"new_object_name": self.get_copy_name(object_name)}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CopyView, self).get_context_data(**kwargs)
|
||||
context['container_name'] = self.kwargs["container_name"]
|
||||
context['object_name'] = self.kwargs["object_name"]
|
||||
return context
|
||||
|
||||
|
||||
class ContainerDetailView(forms.ModalFormMixin, generic.TemplateView):
|
||||
template_name = 'project/containers/container_detail.html'
|
||||
page_title = _("Container Details")
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_object(self):
|
||||
try:
|
||||
return api.swift.swift_get_container(
|
||||
self.request,
|
||||
self.kwargs["container_name"],
|
||||
with_data=False)
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:containers:index")
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details.'),
|
||||
redirect=redirect)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ContainerDetailView, self).get_context_data(**kwargs)
|
||||
context['container'] = self.get_object()
|
||||
return context
|
||||
|
||||
|
||||
class ObjectDetailView(forms.ModalFormMixin, generic.TemplateView):
|
||||
template_name = 'project/containers/object_detail.html'
|
||||
page_title = _("Object Details")
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_object(self):
|
||||
try:
|
||||
return api.swift.swift_get_object(
|
||||
self.request,
|
||||
self.kwargs["container_name"],
|
||||
self.kwargs["object_path"],
|
||||
with_data=False)
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:containers:index")
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details.'),
|
||||
redirect=redirect)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ObjectDetailView, self).get_context_data(**kwargs)
|
||||
context['object'] = self.get_object()
|
||||
return context
|
||||
|
||||
|
||||
class UpdateObjectView(forms.ModalFormView):
|
||||
form_class = project_forms.UpdateObject
|
||||
template_name = 'project/containers/update.html'
|
||||
success_url = "horizon:project:containers:index"
|
||||
page_title = _("Update Object")
|
||||
|
||||
def get_success_url(self):
|
||||
container = utils.wrap_delimiter(self.request.POST['container_name'])
|
||||
path = utils.wrap_delimiter(self.request.POST.get('path', ''))
|
||||
args = (container, path)
|
||||
return reverse(self.success_url, args=args)
|
||||
|
||||
def get_initial(self):
|
||||
return {"container_name": self.kwargs["container_name"],
|
||||
"path": self.kwargs["subfolder_path"],
|
||||
"name": self.kwargs["object_name"]}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateObjectView, self).get_context_data(**kwargs)
|
||||
context['container_name'] = self.kwargs["container_name"]
|
||||
context['subfolder_path'] = self.kwargs["subfolder_path"]
|
||||
context['object_name'] = self.kwargs["object_name"]
|
||||
return context
|
||||
|
@ -74,7 +74,7 @@ class MappingsTests(test.TestCase):
|
||||
'AWS::EC2::Instance',
|
||||
'aaa')
|
||||
assertMappingUrl(
|
||||
'/project/containers/aaa/',
|
||||
'/project/containers/container/aaa/',
|
||||
'OS::Swift::Container',
|
||||
'aaa')
|
||||
assertMappingUrl(
|
||||
|
@ -26,7 +26,7 @@ from horizon import messages
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.project.containers \
|
||||
import forms as containers_forms
|
||||
import utils as containers_utils
|
||||
|
||||
|
||||
class CreateBackupForm(forms.SelfHandlingForm):
|
||||
@ -37,7 +37,7 @@ class CreateBackupForm(forms.SelfHandlingForm):
|
||||
container_name = forms.CharField(
|
||||
max_length=255,
|
||||
label=_("Container Name"),
|
||||
validators=[containers_forms.no_slash_validator],
|
||||
validators=[containers_utils.no_slash_validator],
|
||||
required=False)
|
||||
volume_id = forms.CharField(widget=forms.HiddenInput())
|
||||
|
||||
|
@ -25,13 +25,6 @@ ADD_PANEL = ('openstack_dashboard.dashboards.project.'
|
||||
|
||||
DISABLED = False
|
||||
|
||||
# Which implementation of the panel should we use? Valid options
|
||||
# here are 'angular' (new implementation) and 'legacy' (old
|
||||
# implementation.)
|
||||
UPDATE_HORIZON_CONFIG = {
|
||||
'swift_panel': 'angular'
|
||||
}
|
||||
|
||||
ADD_SCSS_FILES = [
|
||||
'dashboard/project/containers/_containers.scss',
|
||||
]
|
||||
|
@ -121,10 +121,6 @@ settings_utils.update_dashboards(
|
||||
INSTALLED_APPS,
|
||||
)
|
||||
|
||||
# Remove this when the legacy panel is removed, along with its tests and
|
||||
# the stacks MappingsTests are updated with the new URL path.
|
||||
HORIZON_CONFIG['swift_panel'] = 'legacy'
|
||||
|
||||
settings_utils.find_static_files(HORIZON_CONFIG, AVAILABLE_THEMES,
|
||||
THEME_COLLECTION_DIR, ROOT_PATH)
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
upgrade:
|
||||
- Any past use of the Django based Swift UI is no longer
|
||||
supported and the code is being removed. The new
|
||||
angularJS based version should be used instead.
|
||||
deprecations:
|
||||
- Removing formerly deprecated Swift UI code that was
|
||||
replaced with an improved Angular version in Mitaka.
|
Loading…
Reference in New Issue
Block a user