Commit 51a45b5a by Kálmán Viktor

Merge branch 'master' into feature-profile-rework

parents 287cedde e561b9eb
...@@ -31,13 +31,13 @@ def create_levels(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, ...@@ -31,13 +31,13 @@ def create_levels(app, created_models, verbosity, db=DEFAULT_DB_ALIAS,
for klass in app_models: for klass in app_models:
# Force looking up the content types in the current database # Force looking up the content types in the current database
# before creating foreign keys to them. # before creating foreign keys to them.
ctype = ContentType.objects.db_manager(db).get_for_model(klass) ctype1 = ContentType.objects.db_manager(db).get_for_model(klass)
ctypes.add(ctype) ctypes.add(ctype1)
weight = 0 weight = 0
try: try:
for codename, name in klass.ACL_LEVELS: for codename, name in klass.ACL_LEVELS:
searched_levels.append((ctype, (codename, name))) searched_levels.append((ctype1, (codename, name)))
level_weights.append((ctype, codename, weight)) level_weights.append((ctype1, codename, weight))
weight += 1 weight += 1
except AttributeError: except AttributeError:
raise ImproperlyConfigured( raise ImproperlyConfigured(
......
...@@ -18,8 +18,10 @@ ...@@ -18,8 +18,10 @@
"""Common settings and globals.""" """Common settings and globals."""
# flake8: noqa # flake8: noqa
from os import environ from os import environ
from os.path import abspath, basename, dirname, join, normpath, isfile from os.path import (abspath, basename, dirname, join, normpath, isfile,
expanduser)
from sys import path from sys import path
from subprocess import check_output
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -418,6 +420,16 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE': ...@@ -418,6 +420,16 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
LOGIN_REDIRECT_URL = "/" LOGIN_REDIRECT_URL = "/"
AGENT_DIR = get_env_variable(
'DJANGO_AGENT_DIR', join(unicode(expanduser("~")), 'agent'))
try:
git_env = {'GIT_DIR': join(AGENT_DIR, '.git')}
AGENT_VERSION = check_output(
('git', 'log', '-1', r'--pretty=format:%h', 'HEAD'), env=git_env)
except:
AGENT_VERSION = None
LOCALE_PATHS = (join(SITE_ROOT, 'locale'), ) LOCALE_PATHS = (join(SITE_ROOT, 'locale'), )
COMPANY_NAME = "BME IK 2014" COMPANY_NAME = "BME IK 2014"
SOUTH_MIGRATION_MODULES = { SOUTH_MIGRATION_MODULES = {
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
"""Development settings and globals.""" """Development settings and globals."""
# flake8: noqa
from base import * # noqa from base import * # noqa
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
"""Production settings and globals.""" """Production settings and globals."""
# flake8: noqa
from os import environ from os import environ
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
from .base import * # noqa from .base import * # noqa
# flake8: noqa
########## IN-MEMORY TEST DATABASE ########## IN-MEMORY TEST DATABASE
DATABASES = { DATABASES = {
"default": { "default": {
......
...@@ -30,7 +30,7 @@ admin.autodiscover() ...@@ -30,7 +30,7 @@ admin.autodiscover()
urlpatterns = patterns( urlpatterns = patterns(
'', '',
#url(r'^$', TemplateView.as_view(template_name='base.html')), # url(r'^$', TemplateView.as_view(template_name='base.html')),
# Examples: # Examples:
# url(r'^$', 'circle.views.home', name='home'), # url(r'^$', 'circle.views.home', name='home'),
......
...@@ -33,6 +33,7 @@ class Operation(object): ...@@ -33,6 +33,7 @@ class Operation(object):
required_perms = () required_perms = ()
do_not_call_in_templates = True do_not_call_in_templates = True
abortable = False abortable = False
has_percentage = False
def __call__(self, **kwargs): def __call__(self, **kwargs):
return self.call(**kwargs) return self.call(**kwargs)
......
...@@ -38,25 +38,9 @@ ...@@ -38,25 +38,9 @@
"datastore": 1, "datastore": 1,
"dev_num": "a", "dev_num": "a",
"type": "qcow2-norm", "type": "qcow2-norm",
"size": 8589934592 "size": 8589934592,
} "is_ready": true
}, }
{
"pk": 1,
"model": "storage.diskactivity",
"fields":{
"activity_code": "storage.Disk.create",
"succeeded": true,
"parent": null,
"created": "2014-03-18T15:44:37.671Z",
"started": "2014-03-18T15:44:37.671Z",
"finished": "2014-03-18T15:44:37.677Z",
"modified": "2014-03-18T15:44:37.679Z",
"task_uuid": null,
"user": 1,
"disk": 1,
"result":null
}
}, },
{ {
"pk": 1, "pk": 1,
......
...@@ -24,6 +24,7 @@ from django.contrib.auth.forms import ( ...@@ -24,6 +24,7 @@ from django.contrib.auth.forms import (
PasswordChangeForm, PasswordChangeForm,
) )
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.core.validators import URLValidator
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import ( from crispy_forms.layout import (
...@@ -40,9 +41,9 @@ from django.utils.translation import ugettext as _ ...@@ -40,9 +41,9 @@ from django.utils.translation import ugettext as _
from sizefield.widgets import FileSizeWidget from sizefield.widgets import FileSizeWidget
from firewall.models import Vlan, Host from firewall.models import Vlan, Host
from storage.models import Disk, DataStore from storage.models import Disk
from vm.models import ( from vm.models import (
InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance InstanceTemplate, Lease, InterfaceTemplate, Node, Trait
) )
from .models import Profile, GroupProfile from .models import Profile, GroupProfile
from circle.settings.base import LANGUAGES from circle.settings.base import LANGUAGES
...@@ -852,20 +853,12 @@ class LeaseForm(forms.ModelForm): ...@@ -852,20 +853,12 @@ class LeaseForm(forms.ModelForm):
model = Lease model = Lease
class DiskAddForm(forms.Form): class VmCreateDiskForm(forms.Form):
name = forms.CharField() name = forms.CharField(max_length=100, label=_("Name"))
size = forms.CharField(widget=FileSizeWidget, required=False) size = forms.CharField(
url = forms.CharField(required=False) widget=FileSizeWidget, initial=(10 << 30), label=_('Size'),
is_template = forms.CharField() help_text=_('Size of disk to create in bytes or with units '
object_pk = forms.CharField() 'like MB or GB.'))
def __init__(self, *args, **kwargs):
self.is_template = kwargs.pop("is_template")
self.object_pk = kwargs.pop("object_pk")
self.user = kwargs.pop("user")
super(DiskAddForm, self).__init__(*args, **kwargs)
self.initial['is_template'] = 1 if self.is_template else 0
self.initial['object_pk'] = self.object_pk
def clean_size(self): def clean_size(self):
size_in_bytes = self.cleaned_data.get("size") size_in_bytes = self.cleaned_data.get("size")
...@@ -874,66 +867,23 @@ class DiskAddForm(forms.Form): ...@@ -874,66 +867,23 @@ class DiskAddForm(forms.Form):
" GB or MB!")) " GB or MB!"))
return size_in_bytes return size_in_bytes
def clean(self): @property
cleaned_data = self.cleaned_data def helper(self):
size = cleaned_data.get("size") helper = FormHelper(self)
url = cleaned_data.get("url") helper.form_tag = False
return helper
if not size and not url:
msg = _("You have to either specify size or URL")
self._errors[_("Global")] = self.error_class([msg])
return cleaned_data
def save(self, commit=True):
data = self.cleaned_data
if self.is_template:
inst = InstanceTemplate.objects.get(pk=self.object_pk)
else:
inst = Instance.objects.get(pk=self.object_pk)
if data['size']:
kwargs = {
'name': data['name'],
'type': "qcow2-norm",
'datastore': DataStore.objects.all()[0],
'size': data['size'],
}
d = Disk.create_empty(instance=inst, user=self.user, **kwargs)
else:
kwargs = {
'name': data['name'],
'url': data['url'],
}
Disk.create_from_url_async(instance=inst, user=self.user,
**kwargs)
d = None
return d class VmDownloadDiskForm(forms.Form):
name = forms.CharField(max_length=100, label=_("Name"))
url = forms.CharField(label=_('URL'), validators=[URLValidator(), ])
@property @property
def helper(self): def helper(self):
helper = FormHelper() helper = FormHelper(self)
helper.form_show_labels = False helper.add_input(Submit("submit", _("Create"),
helper.layout = Layout(
Field("is_template", type="hidden"),
Field("object_pk", type="hidden"),
Field("name", placeholder=_("Name")),
Field("size", placeholder=_("Disk size (for example: 20GB, "
"1500MB)")),
Field("url", placeholder=_("URL to an ISO image")),
AnyTag(
"div",
HTML(
_("Either specify the size for an empty disk or a URL "
"to an ISO image!")
),
css_class="alert alert-info",
style="padding: 5px; text-align: justify;",
),
)
helper.add_input(Submit("submit", _("Add"),
css_class="btn btn-success")) css_class="btn btn-success"))
helper.form_tag = False
return helper return helper
......
...@@ -705,6 +705,7 @@ textarea[name="list-new-namelist"] { ...@@ -705,6 +705,7 @@ textarea[name="list-new-namelist"] {
#group-detail-user-table td:nth-child(2) a, #group-detail-user-table td:nth-child(2) a,
#group-detail-perm-table td:nth-child(2) a, #group-detail-perm-table td:nth-child(2) a,
#template-access-table td:nth-child(2) a,
#vm-access-table td:nth-child(2) a, #vm-access-table td:nth-child(2) a,
.no-style-link, .no-style-link:hover { .no-style-link, .no-style-link:hover {
color: #555 !important; color: #555 !important;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
$(function() { $(function() {
/* vm operations */ /* vm operations */
$('#ops').on('click', '.operation.btn', function(e) { $('#ops, #vm-details-resources-disk').on('click', '.operation.btn', function(e) {
var icon = $(this).children("i").addClass('icon-spinner icon-spin'); var icon = $(this).children("i").addClass('icon-spinner icon-spin');
$.ajax({ $.ajax({
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<div id="dashboard-template-list"> <div id="dashboard-template-list">
{% for t in templates %} {% for t in templates %}
<a href="{% url "dashboard.views.template-detail" pk=t.pk %}" class="list-group-item <a href="{% url "dashboard.views.template-detail" pk=t.pk %}" class="list-group-item
{% if forloop.last and nodes|length < 5 %} list-group-item-last{% endif %}"> {% if forloop.last and templates|length < 5 %} list-group-item-last{% endif %}">
<span class="index-template-list-name"> <span class="index-template-list-name">
<i class="icon-{{ t.os_type }}"></i> {{ t.name }} <i class="icon-{{ t.os_type }}"></i> {{ t.name }}
</span> </span>
......
...@@ -37,48 +37,62 @@ ...@@ -37,48 +37,62 @@
<th>{% trans "Who" %}</th> <th>{% trans "Who" %}</th>
<th>{% trans "What" %}</th> <th>{% trans "What" %}</th>
<th><i class="icon-remove"></i></th> <th><i class="icon-remove"></i></th>
</tr></thead> </tr>
</thead>
<tbody> <tbody>
{% for i in acl.users %} {% for i in acl.users %}
<tr> <tr>
<td><i class="icon-user"></i></td><td>{{i.user}}</td> <td>
<td> <i class="icon-user"></i>
<select class="form-control" name="perm-u-{{i.user.id}}"> </td>
{% for id, name in acl.levels %} <td>
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option> <a href="{% url "dashboard.views.profile" username=i.user.username %}"
{% endfor %} title="{{ i.user.username }}">
</select> {% include "dashboard/_display-name.html" with user=i.user show_org=True %}
</td> </a>
<td> </td>
<input type="checkbox" name="remove-u-{{i.user.id}}" title="{% trans "Remove" %}"/> <td>
</td> <select class="form-control" name="perm-u-{{i.user.id}}">
</tr> {% for id, name in acl.levels %}
{% endfor %} <option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% for i in acl.groups %} {% endfor %}
<tr> </select>
<td><i class="icon-group"></i></td><td>{{i.group}}</td> </td>
<td> <td>
<select class="form-control" name="perm-g-{{i.group.id}}"> <input type="checkbox" name="remove-u-{{i.user.id}}" title="{% trans "Remove" %}"/>
{% for id, name in acl.levels %} </td>
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option> </tr>
{% endfor %} {% endfor %}
</select> {% for i in acl.groups %}
</td> <tr>
<td> <td><i class="icon-group"></i></td>
<input type="checkbox" name="remove-g-{{i.group.id}}" title="{% trans "Remove" %}"/> <td>
</td> <a href="{% url "dashboard.views.group-detail" pk=i.group.pk %}">
</tr> {{i.group}}
{% endfor %} </a>
<tr><td><i class="icon-plus"></i></td> </td>
<td><input type="text" class="form-control" name="perm-new-name" <td>
placeholder="{% trans "Name of group or user" %}"></td> <select class="form-control" name="perm-g-{{i.group.id}}">
<td><select class="form-control" name="perm-new"> {% for id, name in acl.levels %}
{% for id, name in acl.levels %} <option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
<option value="{{id}}">{{name}}</option> {% endfor %}
{% endfor %} </select>
</select></td><td></td> </td>
</tr> <td>
</tbody> <input type="checkbox" name="remove-g-{{i.group.id}}" title="{% trans "Remove" %}"/>
</td>
</tr>
{% endfor %}
<tr><td><i class="icon-plus"></i></td>
<td><input type="text" class="form-control" name="perm-new-name"
placeholder="{% trans "Name of group or user" %}"></td>
<td><select class="form-control" name="perm-new">
{% for id, name in acl.levels %}
<option value="{{id}}">{{name}}</option>
{% endfor %}
</select></td><td></td>
</tr>
</tbody>
</table> </table>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button> <button type="submit" class="btn btn-success">{% trans "Save" %}</button>
...@@ -105,17 +119,6 @@ ...@@ -105,17 +119,6 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="no-margin"><i class="icon-folder-open"></i> {% trans "Create new disk" %}</h4>
</div>
<div class="panel-body">
<form action="{% url "dashboard.views.disk-add" %}" method="POST">
{% crispy disk_add_form %}
</form>
</div>
</div>
</div><!-- .col-md-4 --> </div><!-- .col-md-4 -->
</div><!-- .row --> </div><!-- .row -->
......
...@@ -13,6 +13,9 @@ ...@@ -13,6 +13,9 @@
{% include "dashboard/_display-name.html" with user=a.user show_org=True %} {% include "dashboard/_display-name.html" with user=a.user show_org=True %}
</a> </a>
{% endif %} {% endif %}
{% if a.has_percent %}
{{ a.percentage }}%
{% endif %}
{% if a.is_abortable_for_user %} {% if a.is_abortable_for_user %}
<form action="{{ a.instance.get_absolute_url }}" method="POST" class="pull-right"> <form action="{{ a.instance.get_absolute_url }}" method="POST" class="pull-right">
{% csrf_token %} {% csrf_token %}
......
{% load i18n %} {% load i18n %}
{% for op in ops %} {% for op in ops %}
{% if op.show_in_toolbar %}
<a href="{{op.get_url}}" class="operation operation-{{op.op}} btn btn-default btn-xs" <a href="{{op.get_url}}" class="operation operation-{{op.op}} btn btn-default btn-xs"
title="{{op.name}}: {{op.description}}"> title="{{op.name}}: {{op.description}}">
<i class="icon-{{op.icon}}"></i> <i class="icon-{{op.icon}}"></i>
<span class="sr-only">{{op.name}}</span> <span class="sr-only">{{op.name}}</span>
</a> </a>
{% endif %}
{% endfor %} {% endfor %}
...@@ -47,9 +47,18 @@ ...@@ -47,9 +47,18 @@
<h3> <h3>
{% trans "Disks" %} {% trans "Disks" %}
<div class="pull-right"> <div class="pull-right">
<a href="#" id="vm-details-disk-add" class="btn btn-success btn-xs"> {% if op.download_disk %}
<i class="icon-plus"></i> {% trans "Add new disk" %} <a href="{{op.download_disk.get_url}}" class="btn btn-success btn-xs
</a> operation operation-{{op.download_disk.op}} btn btn-default">
<i class="icon-{{op.download_disk.icon}}"></i>
{{op.download_disk.name}} </a>
{% endif %}
{% if op.create_disk %}
<a href="{{op.create_disk.get_url}}" class="btn btn-success btn-xs
operation operation-{{op.create_disk.op}} btn btn-default">
<i class="icon-{{op.create_disk.icon}}"></i>
{{op.create_disk.name}} </a>
{% endif %}
</div> </div>
</h3> </h3>
...@@ -68,17 +77,6 @@ ...@@ -68,17 +77,6 @@
</div> </div>
</div> </div>
<div class="js-hidden row" id="vm-details-disk-add-form">
<div class="col-md-12">
<div>
<hr />
<form method="POST" action="{% url "dashboard.views.disk-add" %}" style="max-width: 350px;">
{% crispy forms.disk_add_form %}
</form>
<hr />
</div>
</div>
</div>
{% block extra_js %} {% block extra_js %}
<style> <style>
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
import json import json
from unittest import skip # from unittest import skip
from django.test import TestCase from django.test import TestCase
from django.test.client import Client from django.test.client import Client
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
...@@ -333,38 +333,6 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -333,38 +333,6 @@ class VmDetailTest(LoginMixin, TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(leases, Lease.objects.count()) self.assertEqual(leases, Lease.objects.count())
def test_unpermitted_vm_disk_add(self):
c = Client()
self.login(c, "user2")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
disks = inst.disks.count()
response = c.post("/dashboard/disk/add/", {
'disk-name': "a",
'disk-size': 1,
'disk-is_template': 0,
'disk-object_pk': 1,
})
self.assertEqual(response.status_code, 403)
self.assertEqual(disks, inst.disks.count())
@skip("until fix merged")
def test_permitted_vm_disk_add(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
# disks = inst.disks.count()
response = c.post("/dashboard/disk/add/", {
'disk-name': "a",
'disk-size': 1,
'disk-is_template': 0,
'disk-object_pk': 1,
})
self.assertEqual(response.status_code, 302)
# mancelery is needed TODO
# self.assertEqual(disks + 1, inst.disks.count())
def test_notification_read(self): def test_notification_read(self):
c = Client() c = Client()
self.login(c, "user1") self.login(c, "user1")
......
...@@ -20,7 +20,7 @@ from django.conf.urls import patterns, url, include ...@@ -20,7 +20,7 @@ from django.conf.urls import patterns, url, include
from vm.models import Instance from vm.models import Instance
from .views import ( from .views import (
AclUpdateView, DiskAddView, FavouriteView, GroupAclUpdateView, GroupDelete, AclUpdateView, FavouriteView, GroupAclUpdateView, GroupDelete,
GroupDetailView, GroupList, IndexView, GroupDetailView, GroupList, IndexView,
InstanceActivityDetail, LeaseCreate, LeaseDelete, LeaseDetail, InstanceActivityDetail, LeaseCreate, LeaseDelete, LeaseDetail,
MyPreferencesView, NodeAddTraitView, NodeCreate, NodeDelete, MyPreferencesView, NodeAddTraitView, NodeCreate, NodeDelete,
...@@ -128,8 +128,6 @@ urlpatterns = patterns( ...@@ -128,8 +128,6 @@ urlpatterns = patterns(
url(r'^notifications/$', NotificationView.as_view(), url(r'^notifications/$', NotificationView.as_view(),
name="dashboard.views.notifications"), name="dashboard.views.notifications"),
url(r'^disk/add/$', DiskAddView.as_view(),
name="dashboard.views.disk-add"),
url(r'^disk/(?P<pk>\d+)/remove/$', DiskRemoveView.as_view(), url(r'^disk/(?P<pk>\d+)/remove/$', DiskRemoveView.as_view(),
name="dashboard.views.disk-remove"), name="dashboard.views.disk-remove"),
url(r'^disk/(?P<pk>\d+)/status/$', get_disk_download_status, url(r'^disk/(?P<pk>\d+)/status/$', get_disk_download_status,
......
...@@ -44,7 +44,6 @@ from django.views.generic import (TemplateView, DetailView, View, DeleteView, ...@@ -44,7 +44,6 @@ from django.views.generic import (TemplateView, DetailView, View, DeleteView,
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ungettext as __ from django.utils.translation import ungettext as __
from django.template.defaultfilters import title as title_filter
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.template import RequestContext from django.template import RequestContext
...@@ -55,10 +54,11 @@ from braces.views import (LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -55,10 +54,11 @@ from braces.views import (LoginRequiredMixin, SuperuserRequiredMixin,
from braces.views._access import AccessMixin from braces.views._access import AccessMixin
from .forms import ( from .forms import (
CircleAuthenticationForm, DiskAddForm, HostForm, LeaseForm, MyProfileForm, CircleAuthenticationForm, HostForm, LeaseForm, MyProfileForm,
NodeForm, TemplateForm, TraitForm, VmCustomizeForm, GroupCreateForm, NodeForm, TemplateForm, TraitForm, VmCustomizeForm, GroupCreateForm,
UserCreationForm, GroupProfileUpdateForm, UnsubscribeForm, UserCreationForm, GroupProfileUpdateForm, UnsubscribeForm,
CirclePasswordChangeForm, VmSaveForm, VmSaveForm,
CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm,
) )
from .tables import ( from .tables import (
...@@ -126,7 +126,11 @@ class FilterMixin(object): ...@@ -126,7 +126,11 @@ class FilterMixin(object):
filters = {} filters = {}
for item in self.allowed_filters: for item in self.allowed_filters:
if item in self.request.GET: if item in self.request.GET:
filters[self.allowed_filters[item]] = self.request.GET[item] filters[self.allowed_filters[item]] = (
self.request.GET[item].split(",")
if self.allowed_filters[item].endswith("__in") else
self.request.GET[item])
return filters return filters
def get_queryset(self): def get_queryset(self):
...@@ -275,12 +279,6 @@ class VmDetailView(CheckedDetailView): ...@@ -275,12 +279,6 @@ class VmDetailView(CheckedDetailView):
instance=self.get_object()).values_list("vlan", flat=True) instance=self.get_object()).values_list("vlan", flat=True)
).all() ).all()
context['acl'] = get_vm_acl_data(instance) context['acl'] = get_vm_acl_data(instance)
context['forms'] = {
'disk_add_form': DiskAddForm(
user=self.request.user,
is_template=False, object_pk=self.get_object().pk,
prefix="disk"),
}
context['os_type_icon'] = instance.os_type.replace("unknown", context['os_type_icon'] = instance.os_type.replace("unknown",
"question") "question")
# ipv6 infos # ipv6 infos
...@@ -600,6 +598,22 @@ class FormOperationMixin(object): ...@@ -600,6 +598,22 @@ class FormOperationMixin(object):
return self.get(request) return self.get(request)
class VmCreateDiskView(FormOperationMixin, VmOperationView):
op = 'create_disk'
form_class = VmCreateDiskForm
show_in_toolbar = False
icon = 'hdd'
class VmDownloadDiskView(FormOperationMixin, VmOperationView):
op = 'download_disk'
form_class = VmDownloadDiskForm
show_in_toolbar = False
icon = 'download'
class VmMigrateView(VmOperationView): class VmMigrateView(VmOperationView):
op = 'migrate' op = 'migrate'
...@@ -639,6 +653,8 @@ vm_ops = { ...@@ -639,6 +653,8 @@ vm_ops = {
'destroy': VmOperationView.factory(op='destroy', icon='remove'), 'destroy': VmOperationView.factory(op='destroy', icon='remove'),
'sleep': VmOperationView.factory(op='sleep', icon='moon'), 'sleep': VmOperationView.factory(op='sleep', icon