Commit fb4f60b6 by Máhonfai Bálint

Get list of importable disks from store and show them on import form

parent 6b634da4
...@@ -17,56 +17,51 @@ ...@@ -17,56 +17,51 @@
from __future__ import absolute_import from __future__ import absolute_import
from datetime import timedelta
from urlparse import urlparse from urlparse import urlparse
import os
import pyotp import pyotp
from crispy_forms.bootstrap import FormActions
from django.forms import ModelForm
from django.contrib.auth.forms import (
AuthenticationForm, PasswordResetForm, SetPasswordForm,
PasswordChangeForm,
)
from django.contrib.auth.models import User, Group
from django.core.validators import URLValidator
from django.core.exceptions import PermissionDenied, ValidationError
from dal import autocomplete
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import ( from crispy_forms.layout import (
Layout, Div, BaseInput, Field, HTML, Submit, TEMPLATE_PACK, Fieldset Layout, Div, BaseInput, Field, HTML, Submit, TEMPLATE_PACK, Fieldset
) )
from crispy_forms.utils import render_field from crispy_forms.utils import render_field
from crispy_forms.bootstrap import FormActions from dal import autocomplete
from datetime import timedelta
from django import forms from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.forms import (
AuthenticationForm, PasswordResetForm, SetPasswordForm,
PasswordChangeForm,
)
from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User, Group
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.urlresolvers import reverse_lazy
from django.core.validators import URLValidator
from django.forms import ModelForm
from django.forms.widgets import TextInput, HiddenInput from django.forms.widgets import TextInput, HiddenInput
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.html import escape, format_html from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_sshkey.models import UserKey
from sizefield.widgets import FileSizeWidget from sizefield.widgets import FileSizeWidget
from django.core.urlresolvers import reverse_lazy
from django_sshkey.models import UserKey from circle.settings.base import LANGUAGES, MAX_NODE_RAM, MAX_NODE_CPU_CORE
from dashboard.models import ConnectCommand, create_profile
from dashboard.store_api import Store
from firewall.models import Vlan, Host from firewall.models import Vlan, Host
from storage.models import DataStore, Disk
from vm.models import ( from vm.models import (
InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance
) )
from storage.models import DataStore, Disk
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import Permission
from .models import Profile, GroupProfile, Message from .models import Profile, GroupProfile, Message
from circle.settings.base import LANGUAGES, MAX_NODE_RAM, MAX_NODE_CPU_CORE
from django.utils.translation import string_concat
from .validators import domain_validator from .validators import domain_validator
from dashboard.models import ConnectCommand, create_profile
LANGUAGES_WITH_CODE = ((l[0], string_concat(l[1], " (", l[0], ")")) LANGUAGES_WITH_CODE = ((l[0], string_concat(l[1], " (", l[0], ")"))
for l in LANGUAGES) for l in LANGUAGES)
...@@ -189,7 +184,7 @@ class VmCustomizeForm(forms.Form): ...@@ -189,7 +184,7 @@ class VmCustomizeForm(forms.Form):
self.initial['ram_size'] = self.template.ram_size self.initial['ram_size'] = self.template.ram_size
else: else:
self.allowed_fields = ("name", "template", "customized", ) self.allowed_fields = ("name", "template", "customized",)
# initial name and template pk # initial name and template pk
self.initial['name'] = self.template.name self.initial['name'] = self.template.name
...@@ -214,7 +209,6 @@ class VmCustomizeForm(forms.Form): ...@@ -214,7 +209,6 @@ class VmCustomizeForm(forms.Form):
class GroupCreateForm(NoFormTagMixin, forms.ModelForm): class GroupCreateForm(NoFormTagMixin, forms.ModelForm):
description = forms.CharField(label=_("Description"), required=False, description = forms.CharField(label=_("Description"), required=False,
widget=forms.Textarea(attrs={'rows': 3})) widget=forms.Textarea(attrs={'rows': 3}))
...@@ -258,7 +252,7 @@ class GroupCreateForm(NoFormTagMixin, forms.ModelForm): ...@@ -258,7 +252,7 @@ class GroupCreateForm(NoFormTagMixin, forms.ModelForm):
class Meta: class Meta:
model = Group model = Group
fields = ('name', ) fields = ('name',)
class GroupProfileUpdateForm(NoFormTagMixin, forms.ModelForm): class GroupProfileUpdateForm(NoFormTagMixin, forms.ModelForm):
...@@ -513,7 +507,7 @@ class TemplateForm(forms.ModelForm): ...@@ -513,7 +507,7 @@ class TemplateForm(forms.ModelForm):
self.allowed_fields += tuple(set(self.fields.keys()) - self.allowed_fields += tuple(set(self.fields.keys()) -
set(['raw_data'])) set(['raw_data']))
if self.user.is_superuser: if self.user.is_superuser:
self.allowed_fields += ('raw_data', ) self.allowed_fields += ('raw_data',)
for name, field in self.fields.items(): for name, field in self.fields.items():
if name not in self.allowed_fields: if name not in self.allowed_fields:
field.widget.attrs['disabled'] = 'disabled' field.widget.attrs['disabled'] = 'disabled'
...@@ -525,8 +519,8 @@ class TemplateForm(forms.ModelForm): ...@@ -525,8 +519,8 @@ class TemplateForm(forms.ModelForm):
self.initial['max_ram_size'] = 512 self.initial['max_ram_size'] = 512
lease_queryset = ( lease_queryset = (
Lease.get_objects_with_level("operator", self.user).distinct() | Lease.get_objects_with_level("operator", self.user).distinct() |
Lease.objects.filter(pk=self.instance.lease_id).distinct()) Lease.objects.filter(pk=self.instance.lease_id).distinct())
self.fields["lease"].queryset = lease_queryset self.fields["lease"].queryset = lease_queryset
...@@ -602,7 +596,7 @@ class TemplateForm(forms.ModelForm): ...@@ -602,7 +596,7 @@ class TemplateForm(forms.ModelForm):
class Meta: class Meta:
model = InstanceTemplate model = InstanceTemplate
exclude = ('state', 'disks', ) exclude = ('state', 'disks',)
widgets = { widgets = {
'system': forms.TextInput, 'system': forms.TextInput,
'max_ram_size': forms.HiddenInput, 'max_ram_size': forms.HiddenInput,
...@@ -745,7 +739,6 @@ class LeaseForm(forms.ModelForm): ...@@ -745,7 +739,6 @@ class LeaseForm(forms.ModelForm):
class VmRenewForm(OperationForm): class VmRenewForm(OperationForm):
force = forms.BooleanField(required=False, label=_( force = forms.BooleanField(required=False, label=_(
"Set expiration times even if they are shorter than " "Set expiration times even if they are shorter than "
"the current value.")) "the current value."))
...@@ -785,11 +778,10 @@ class VmMigrateForm(forms.Form): ...@@ -785,11 +778,10 @@ class VmMigrateForm(forms.Form):
class VmStateChangeForm(OperationForm): class VmStateChangeForm(OperationForm):
interrupt = forms.BooleanField(required=False, label=_( interrupt = forms.BooleanField(required=False, label=_(
"Forcibly interrupt all running activities."), "Forcibly interrupt all running activities."),
help_text=_("Set all activities to finished state, " help_text=_("Set all activities to finished state, "
"but don't interrupt any tasks.")) "but don't interrupt any tasks."))
new_state = forms.ChoiceField(Instance.STATUS, label=_( new_state = forms.ChoiceField(Instance.STATUS, label=_(
"New status")) "New status"))
reset_node = forms.BooleanField(required=False, label=_("Reset node")) reset_node = forms.BooleanField(required=False, label=_("Reset node"))
...@@ -932,8 +924,17 @@ class VmDiskRemoveForm(OperationForm): ...@@ -932,8 +924,17 @@ class VmDiskRemoveForm(OperationForm):
class VmImportDiskForm(OperationForm): class VmImportDiskForm(OperationForm):
name = forms.CharField(max_length=50, label=_('Name')) def __init__(self, *args, **kwargs):
disk_file = forms.FileField(label=_('File')) self.user = kwargs.pop('user')
super(VmImportDiskForm, self).__init__(*args, **kwargs)
disk_paths = Store(self.user).get_disk_images()
disk_filenames = [os.path.basename(item) for item in disk_paths]
self.choices = zip(disk_paths, disk_filenames)
self.fields['name'] = forms.CharField(max_length=100, label=_('Name'))
self.fields['disk_path'] = forms.ChoiceField(label=_('Disk image'),
choices=self.choices)
class VmDownloadDiskForm(OperationForm): class VmDownloadDiskForm(OperationForm):
...@@ -1175,7 +1176,6 @@ class CircleSetPasswordForm(SetPasswordForm): ...@@ -1175,7 +1176,6 @@ class CircleSetPasswordForm(SetPasswordForm):
class LinkButton(BaseInput): class LinkButton(BaseInput):
""" """
Used to create a link button descriptor for the {% crispy %} template tag:: Used to create a link button descriptor for the {% crispy %} template tag::
...@@ -1261,7 +1261,7 @@ class MyProfileForm(forms.ModelForm): ...@@ -1261,7 +1261,7 @@ class MyProfileForm(forms.ModelForm):
class Meta: class Meta:
fields = ('preferred_language', 'email_notifications', fields = ('preferred_language', 'email_notifications',
'desktop_notifications', 'use_gravatar', ) 'desktop_notifications', 'use_gravatar',)
model = Profile model = Profile
@property @property
...@@ -1276,9 +1276,8 @@ class MyProfileForm(forms.ModelForm): ...@@ -1276,9 +1276,8 @@ class MyProfileForm(forms.ModelForm):
class UnsubscribeForm(forms.ModelForm): class UnsubscribeForm(forms.ModelForm):
class Meta: class Meta:
fields = ('email_notifications', ) fields = ('email_notifications',)
model = Profile model = Profile
@property @property
...@@ -1351,14 +1350,14 @@ class UserEditForm(forms.ModelForm): ...@@ -1351,14 +1350,14 @@ class UserEditForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ('email', 'first_name', 'last_name', 'instance_limit', fields = ('email', 'first_name', 'last_name', 'instance_limit',
'is_active', "two_factor_secret", ) 'is_active', "two_factor_secret",)
def save(self, commit=True): def save(self, commit=True):
user = super(UserEditForm, self).save() user = super(UserEditForm, self).save()
user.profile.instance_limit = ( user.profile.instance_limit = (
self.cleaned_data['instance_limit'] or None) self.cleaned_data['instance_limit'] or None)
user.profile.two_factor_secret = ( user.profile.two_factor_secret = (
self.cleaned_data['two_factor_secret'] or None) self.cleaned_data['two_factor_secret'] or None)
user.profile.save() user.profile.save()
return user return user
...@@ -1442,10 +1441,9 @@ class ConnectCommandForm(forms.ModelForm): ...@@ -1442,10 +1441,9 @@ class ConnectCommandForm(forms.ModelForm):
class TraitsForm(forms.ModelForm): class TraitsForm(forms.ModelForm):
class Meta: class Meta:
model = Instance model = Instance
fields = ('req_traits', ) fields = ('req_traits',)
@property @property
def helper(self): def helper(self):
...@@ -1465,7 +1463,7 @@ class RawDataForm(forms.ModelForm): ...@@ -1465,7 +1463,7 @@ class RawDataForm(forms.ModelForm):
class Meta: class Meta:
model = Instance model = Instance
fields = ('raw_data', ) fields = ('raw_data',)
@property @property
def helper(self): def helper(self):
...@@ -1527,7 +1525,7 @@ class GroupPermissionForm(forms.ModelForm): ...@@ -1527,7 +1525,7 @@ class GroupPermissionForm(forms.ModelForm):
class Meta: class Meta:
model = Group model = Group
fields = ('permissions', ) fields = ('permissions',)
@property @property
def helper(self): def helper(self):
...@@ -1575,7 +1573,7 @@ class VmResourcesForm(forms.ModelForm): ...@@ -1575,7 +1573,7 @@ class VmResourcesForm(forms.ModelForm):
class Meta: class Meta:
model = Instance model = Instance
fields = ('num_cores', 'priority', 'ram_size', ) fields = ('num_cores', 'priority', 'ram_size',)
class VmRenameForm(forms.Form): class VmRenameForm(forms.Form):
...@@ -1666,7 +1664,7 @@ class DataStoreForm(ModelForm): ...@@ -1666,7 +1664,7 @@ class DataStoreForm(ModelForm):
class Meta: class Meta:
model = DataStore model = DataStore
fields = ("name", "path", "hostname", ) fields = ("name", "path", "hostname",)
class DiskForm(ModelForm): class DiskForm(ModelForm):
...@@ -1684,7 +1682,7 @@ class DiskForm(ModelForm): ...@@ -1684,7 +1682,7 @@ class DiskForm(ModelForm):
class Meta: class Meta:
model = Disk model = Disk
fields = ("name", "filename", "datastore", "type", "bus", "size", fields = ("name", "filename", "datastore", "type", "bus", "size",
"base", "dev_num", "destroyed", "is_ready", ) "base", "dev_num", "destroyed", "is_ready",)
class MessageForm(ModelForm): class MessageForm(ModelForm):
......
...@@ -14,19 +14,20 @@ ...@@ -14,19 +14,20 @@
# #
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from os.path import splitext
import json import json
import logging import logging
from urlparse import urljoin from urlparse import urljoin
from datetime import datetime
from django.http import Http404 import os
from datetime import datetime
from django.conf import settings from django.conf import settings
from django.http import Http404
from os.path import splitext
from requests import get, post, codes from requests import get, post, codes
from requests.exceptions import Timeout # noqa
from sizefield.utils import filesizeformat from sizefield.utils import filesizeformat
from storage.models import Disk
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -102,6 +103,15 @@ class Store(object): ...@@ -102,6 +103,15 @@ class Store(object):
else: else:
return result return result
def get_disk_images(self, path='/'):
images = []
file_list = self.list(path, process=False)
export_formats = [item[0] for item in Disk.EXPORT_FORMATS]
for item in file_list:
if os.path.splitext(item['NAME'])[1].strip('.') in export_formats:
images.append(os.path.join(path, item['NAME']))
return images
def request_download(self, path): def request_download(self, path):
r = self._request_cmd("DOWNLOAD", PATH=path, timeout=10) r = self._request_cmd("DOWNLOAD", PATH=path, timeout=10)
return r.json()['LINK'] return r.json()['LINK']
......
...@@ -167,8 +167,8 @@ class VmDetailView(GraphMixin, CheckedDetailView): ...@@ -167,8 +167,8 @@ class VmDetailView(GraphMixin, CheckedDetailView):
# resources forms # resources forms
can_edit = ( can_edit = (
instance.has_level(user, "owner") and instance.has_level(user, "owner") and
self.request.user.has_perm("vm.change_resources")) self.request.user.has_perm("vm.change_resources"))
context['resources_form'] = VmResourcesForm( context['resources_form'] = VmResourcesForm(
can_edit=can_edit, instance=instance) can_edit=can_edit, instance=instance)
...@@ -302,7 +302,6 @@ class VmRawDataUpdate(SuperuserRequiredMixin, UpdateView): ...@@ -302,7 +302,6 @@ class VmRawDataUpdate(SuperuserRequiredMixin, UpdateView):
class VmOperationView(AjaxOperationMixin, OperationView): class VmOperationView(AjaxOperationMixin, OperationView):
model = Instance model = Instance
context_object_name = 'instance' # much simpler to mock object context_object_name = 'instance' # much simpler to mock object
...@@ -351,7 +350,6 @@ class VmRemoveInterfaceView(FormOperationMixin, VmOperationView): ...@@ -351,7 +350,6 @@ class VmRemoveInterfaceView(FormOperationMixin, VmOperationView):
class VmAddInterfaceView(FormOperationMixin, VmOperationView): class VmAddInterfaceView(FormOperationMixin, VmOperationView):
op = 'add_interface' op = 'add_interface'
form_class = VmAddInterfaceForm form_class = VmAddInterfaceForm
show_in_toolbar = False show_in_toolbar = False
...@@ -392,7 +390,6 @@ class VmDiskModifyView(FormOperationMixin, VmOperationView): ...@@ -392,7 +390,6 @@ class VmDiskModifyView(FormOperationMixin, VmOperationView):
class VmCreateDiskView(FormOperationMixin, VmOperationView): class VmCreateDiskView(FormOperationMixin, VmOperationView):
op = 'create_disk' op = 'create_disk'
form_class = VmCreateDiskForm form_class = VmCreateDiskForm
show_in_toolbar = False show_in_toolbar = False
...@@ -410,7 +407,6 @@ class VmCreateDiskView(FormOperationMixin, VmOperationView): ...@@ -410,7 +407,6 @@ class VmCreateDiskView(FormOperationMixin, VmOperationView):
class VmImportDiskView(FormOperationMixin, VmOperationView): class VmImportDiskView(FormOperationMixin, VmOperationView):
op = 'import_disk' op = 'import_disk'
form_class = VmImportDiskForm form_class = VmImportDiskForm
show_in_toolbar = False show_in_toolbar = False
...@@ -419,9 +415,13 @@ class VmImportDiskView(FormOperationMixin, VmOperationView): ...@@ -419,9 +415,13 @@ class VmImportDiskView(FormOperationMixin, VmOperationView):
is_disk_operation = True is_disk_operation = True
with_reload = True with_reload = True
def get_form_kwargs(self):
val = super(VmImportDiskView, self).get_form_kwargs()
val.update({'user': self.request.user})
return val
class VmDownloadDiskView(FormOperationMixin, VmOperationView):
class VmDownloadDiskView(FormOperationMixin, VmOperationView):
op = 'download_disk' op = 'download_disk'
form_class = VmDownloadDiskForm form_class = VmDownloadDiskForm
show_in_toolbar = False show_in_toolbar = False
...@@ -432,7 +432,6 @@ class VmDownloadDiskView(FormOperationMixin, VmOperationView): ...@@ -432,7 +432,6 @@ class VmDownloadDiskView(FormOperationMixin, VmOperationView):
class VmMigrateView(FormOperationMixin, VmOperationView): class VmMigrateView(FormOperationMixin, VmOperationView):
op = 'migrate' op = 'migrate'
icon = 'truck' icon = 'truck'
effect = 'info' effect = 'info'
...@@ -462,7 +461,7 @@ class VmMigrateView(FormOperationMixin, VmOperationView): ...@@ -462,7 +461,7 @@ class VmMigrateView(FormOperationMixin, VmOperationView):
nodes_w_traits = [ nodes_w_traits = [
n.pk for n in Node.objects.filter(enabled=True) n.pk for n in Node.objects.filter(enabled=True)
if n.online and if n.online and
has_traits(inst.req_traits.all(), n) has_traits(inst.req_traits.all(), n)
] ]
ctx['nodes_w_traits'] = nodes_w_traits ctx['nodes_w_traits'] = nodes_w_traits
...@@ -470,7 +469,6 @@ class VmMigrateView(FormOperationMixin, VmOperationView): ...@@ -470,7 +469,6 @@ class VmMigrateView(FormOperationMixin, VmOperationView):
class VmPortRemoveView(FormOperationMixin, VmOperationView): class VmPortRemoveView(FormOperationMixin, VmOperationView):
template_name = 'dashboard/_vm-remove-port.html' template_name = 'dashboard/_vm-remove-port.html'
op = 'remove_port' op = 'remove_port'
show_in_toolbar = False show_in_toolbar = False
...@@ -499,7 +497,6 @@ class VmPortRemoveView(FormOperationMixin, VmOperationView): ...@@ -499,7 +497,6 @@ class VmPortRemoveView(FormOperationMixin, VmOperationView):
class VmPortAddView(FormOperationMixin, VmOperationView): class VmPortAddView(FormOperationMixin, VmOperationView):
op = 'add_port' op = 'add_port'
show_in_toolbar = False show_in_toolbar = False
with_reload = True with_reload = True
...@@ -526,7 +523,6 @@ class VmPortAddView(FormOperationMixin, VmOperationView): ...@@ -526,7 +523,6 @@ class VmPortAddView(FormOperationMixin, VmOperationView):
class VmSaveView(FormOperationMixin, VmOperationView): class VmSaveView(FormOperationMixin, VmOperationView):
op = 'save_as_template' op = 'save_as_template'
icon = 'save' icon = 'save'
effect = 'info' effect = 'info'
...@@ -582,7 +578,7 @@ class TokenOperationView(OperationView): ...@@ -582,7 +578,7 @@ class TokenOperationView(OperationView):
User can do the action with a valid token instead of logging in. User can do the action with a valid token instead of logging in.
""" """
token_max_age = 3 * 24 * 3600 token_max_age = 3 * 24 * 3600
redirect_exception_classes = (PermissionDenied, SuspiciousOperation, ) redirect_exception_classes = (PermissionDenied, SuspiciousOperation,)
@classmethod @classmethod
def get_salt(cls): def get_salt(cls):
...@@ -654,7 +650,6 @@ class TokenOperationView(OperationView): ...@@ -654,7 +650,6 @@ class TokenOperationView(OperationView):
class VmRenewView(FormOperationMixin, TokenOperationView, VmOperationView): class VmRenewView(FormOperationMixin, TokenOperationView, VmOperationView):
op = 'renew' op = 'renew'
icon = 'calendar' icon = 'calendar'
effect = 'success' effect = 'success'
...@@ -1030,7 +1025,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView): ...@@ -1030,7 +1025,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
# remove "-" that means descending order # remove "-" that means descending order
# also check if the column name is valid # also check if the column name is valid
if (sort and if (sort and
(sort[1:] if sort[0] == "-" else sort) (sort[1:] if sort[0] == "-" else sort)
in [i.name for i in Instance._meta.fields] + ["pk"]): in [i.name for i in Instance._meta.fields] + ["pk"]):
queryset = queryset.order_by(sort) queryset = queryset.order_by(sort)
...@@ -1041,7 +1036,6 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView): ...@@ -1041,7 +1036,6 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
class VmCreate(LoginRequiredMixin, TemplateView): class VmCreate(LoginRequiredMixin, TemplateView):
form_class = VmCustomizeForm form_class = VmCustomizeForm
form = None form = None
...@@ -1150,7 +1144,7 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -1150,7 +1144,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
messages.success(request, ungettext_lazy( messages.success(request, ungettext_lazy(
"Successfully created %(count)d VM.", # this should not happen "Successfully created %(count)d VM.", # this should not happen
"Successfully created %(count)d VMs.", len(instances)) % { "Successfully created %(count)d VMs.", len(instances)) % {
'count': len(instances)}) 'count': len(instances)})
path = "%s?stype=owned" % reverse("dashboard.views.vm-list") path = "%s?stype=owned" % reverse("dashboard.views.vm-list")
else: else:
messages.success(request, _("VM successfully created.")) messages.success(request, _("VM successfully created."))
...@@ -1342,7 +1336,7 @@ class TransferInstanceOwnershipConfirmView(TransferOwnershipConfirmView): ...@@ -1342,7 +1336,7 @@ class TransferInstanceOwnershipConfirmView(TransferOwnershipConfirmView):
def change_owner(self, instance, new_owner): def change_owner(self, instance, new_owner):
with instance.activity( with instance.activity(
code_suffix='ownership-transferred', code_suffix='ownership-transferred',
readable_name=ugettext_noop("transfer ownership"), readable_name=ugettext_noop("transfer ownership"),
concurrency_check=False, user=new_owner): concurrency_check=False, user=new_owner):
super(TransferInstanceOwnershipConfirmView, self).change_owner( super(TransferInstanceOwnershipConfirmView, self).change_owner(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment