Commit 4c29a7bb by Szabolcs Gelencser

Implement template share with users

parent 0ec3899c
......@@ -433,11 +433,17 @@ class TemplateForm(forms.ModelForm):
# }))
name = forms.TextInput()
flavor = forms.ChoiceField(['a','b','c'])
# flavor = forms.ChoiceField(['a','b','c'])
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
super(TemplateForm, self).__init__(*args, **kwargs)
self.allowed_fields = (
'name',
# 'lease',
)
#
# self.fields['networks'].queryset = ()#Vlan.get_objects_with_level('user', self.user)
#
......@@ -487,34 +493,34 @@ class TemplateForm(forms.ModelForm):
def clean_max_ram_size(self):
return self.cleaned_data.get("ram_size", 0)
def _clean_fields(self):
try:
old = InstanceTemplate.objects.get(pk=self.instance.pk)
except InstanceTemplate.DoesNotExist:
old = None
for name, field in self.fields.items():
if name in self.allowed_fields:
value = field.widget.value_from_datadict(
self.data, self.files, self.add_prefix(name))
try:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self._errors[name] = self.error_class(e.messages)
if name in self.cleaned_data:
del self.cleaned_data[name]
elif old:
if name == 'networks':
self.cleaned_data[name] = [
i.vlan for i in self.instance.interface_set.all()]
else:
self.cleaned_data[name] = getattr(old, name)
if "req_traits" not in self.allowed_fields:
self.cleaned_data['req_traits'] = self.instance.req_traits.all()
# def _clean_fields(self):
# try:
# old = InstanceTemplate.objects.get(pk=self.instance.pk)
# except InstanceTemplate.DoesNotExist:
# old = None
# for name, field in self.fields.items():
# if name in self.allowed_fields:
# value = field.widget.value_from_datadict(
# self.data, self.files, self.add_prefix(name))
# try:
# value = field.clean(value)
# self.cleaned_data[name] = value
# if hasattr(self, 'clean_%s' % name):
# value = getattr(self, 'clean_%s' % name)()
# self.cleaned_data[name] = value
# except ValidationError as e:
# self._errors[name] = self.error_class(e.messages)
# if name in self.cleaned_data:
# del self.cleaned_data[name]
# elif old:
# if name == 'networks':
# self.cleaned_data[name] = [
# i.vlan for i in self.instance.interface_set.all()]
# else:
# self.cleaned_data[name] = getattr(old, name)
# if "req_traits" not in self.allowed_fields:
# self.cleaned_data['req_traits'] = self.instance.req_traits.all()
def save(self, commit=True):
data = self.cleaned_data
......@@ -1300,12 +1306,7 @@ class UserEditForm(forms.ModelForm):
class AclUserOrGroupAddForm(forms.Form):
name = forms.CharField(
widget=autocomplete.ListSelect2(
url='autocomplete.acl.user-group',
attrs={'class': 'form-control',
'data-html': 'true',
'data-placeholder': _("Name of group or user")}))
name = forms.CharField(required=False)
class TransferOwnershipForm(forms.Form):
......
{% load i18n %}
<form action="{{ acl.url }}" method="post">{% csrf_token %}
<form action="{{ manage_access_url }}" method="post">{% csrf_token %}
<table class="table table-striped table-with-form-fields acl-table" id="{{table_id}}">
<thead>
<tr>
<th></th>
<th>{% trans "Who" %}</th>
<th>{% trans "What" %}</th>
<th><i id="manage-access-select-all" class="fa fa-times"></i></th>
</tr>
</thead>
<tbody>
{% for i in acl.users %}
{% for member in shared_with_users %}
<tr>
<td>
<img class="profile-avatar" src="{{ i.user.profile.get_avatar_url }}"/>
{# <img class="profile-avatar" src="{{ i.user.profile.get_avatar_url }}"/>#}
</td>
<td>
<a href="{% url "dashboard.views.profile" username=i.user.username %}"
title="{{ i.user.username }}">
{% include "dashboard/_display-name.html" with user=i.user show_org=True %}
</a>
</td>
<td>
<select class="form-control" name="perm-u-{{i.user.id}}"{% if i.level not in acl.allowed_levels %} disabled{% endif %}>
{% for id, name in acl.levels %}
<option{%if id == i.level%} selected="selected"{%endif%}
{% if id not in acl.allowed_levels %} disabled{% endif %}
value="{{id}}">{{name}}</option>
{% endfor %}
</select>
{# <a href="{% url "dashboard.views.profile" username=i.user.username %}"#}
{# title="{{ i.user.username }}">#}
{# {% include "dashboard/_display-name.html" with user=i.user show_org=True %}#}
{# </a>#}
{{ member.username }}
</td>
<td>
<input type="checkbox" name="remove-u-{{i.user.id}}" title="{% trans "Remove" %}"/>
<input type="checkbox" name="remove-u-{{ member.member_id }}" title="{% trans "Remove" %}"/>
</td>
</tr>
{% endfor %}
......@@ -57,15 +48,10 @@
</td>
</tr>
{% endfor %}
<tr><td><i class="fa fa-plus"></i></td>
<td>{{aclform.name }}</td>
<td><select class="form-control" name="level">
{% for id, name in acl.levels %}
{% if id in acl.allowed_levels %}
<option value="{{id}}">{{name}}</option>
{% endif %}
{% endfor %}
</select></td><td></td>
<tr>
<td><i class="fa fa-plus"></i></td>
<td>{{aclform.name }}</td>
<td></td>
</tr>
</tbody>
</table>
......
......@@ -42,8 +42,8 @@
{# {{ form.max_ram_size|as_crispy_field }}#}
</fieldset>
<fieldset>
<legend>{% trans "Virtual machine settings" %}</legend>
{# <fieldset>#}
{# <legend>{% trans "Virtual machine settings" %}</legend>#}
{# {{ form.arch|as_crispy_field }}#}
{# {{ form.access_method|as_crispy_field }}#}
{# {{ form.boot_menu|as_crispy_field }}#}
......@@ -52,11 +52,11 @@
{# {{ form.description|as_crispy_field }}#}
{# {{ form.system|as_crispy_field }}#}
{# {{ form.has_agent|as_crispy_field }}#}
</fieldset>
{# </fieldset>#}
<fieldset>
<legend>{% trans "External resources" %}</legend>
{# {{ form.networks|as_crispy_field }}#}
{# {{ form.lease|as_crispy_field }}#}
{{ form.lease|as_crispy_field }}
{##}
{# {{ form.tags|as_crispy_field }}#}
</fieldset>
......@@ -82,26 +82,6 @@
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="no-margin"><i class="fa fa-user"></i> {% trans "Owner" %}</h4>
</div>
<div class="panel-body">
{% if user == object.owner %}
{% blocktrans %}You are the current owner of this template.{% endblocktrans %}
{% else %}
{% url "dashboard.views.profile" username=object.owner.username as url %}
{% blocktrans with owner=object.owner name=object.owner.get_full_name%}
The current owner of this template is <a href="{{url}}">{{name}} ({{owner}})</a>.
{% endblocktrans %}
{% endif %}
{% if user == object.owner or user.is_superuser %}
<a href="{% url "dashboard.views.template-transfer-ownership" object.pk %}"
class="btn btn-link tx-tpl-ownership">{% trans "Transfer ownership..." %}</a>
{% endif %}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="no-margin"><i class="fa fa-group"></i> {% trans "Manage access" %}</h4>
</div>
<div class="panel-body">
......@@ -111,40 +91,6 @@
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="no-margin">
<i class="fa fa-question-circle"></i>
{% trans "Access level rights" %}
</h4>
</div>
<div class="panel-body">
<dl>
<dt>{% trans "User" %}</dt>
<dd>
{% blocktrans %}
User can deploy instances from this template.
{% endblocktrans %}
</dd>
<dt>{% trans "Operator" %}</dt>
<dd>
{% blocktrans %}
Operators are able to deploy and grant/revoke User level access to this template.
{% endblocktrans %}
</dd>
<dt>{% trans "Owner" %}</dt>
<dd>
{% blocktrans %}
Owners can edit attributes or delete the template.
Owners are able to grant/revoke User, Operator and Owner level access to the template.
The accountable owner (the one who created the template) can not be demoted.
The accountable ownership can be transferred to other User via the "Transfer onwership" button.
{% endblocktrans %}
</dd>
</dl>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="no-margin"><i class="fa fa-file"></i> {% trans "Disk list" %}</h4>
</div>
<div class="panel-body">
......
......@@ -19,7 +19,8 @@ from __future__ import absolute_import
from dashboard.views.autocomplete import AclUserGroupAutocomplete, AclUserAutocomplete
from dashboard.views.template import TemplateList, TemplateChoose, TemplateDetail, TemplateDelete, \
TransferTemplateOwnershipConfirmView, TransferTemplateOwnershipView, LeaseCreate, LeaseDetail, LeaseDelete
TransferTemplateOwnershipConfirmView, TransferTemplateOwnershipView, LeaseCreate, LeaseDetail, LeaseDelete, \
TemplateAclUpdateView
from dashboard.views.vm import VmDetailView, VmList, VmCreate, vm_activity, vm_ops, FavouriteView, VmPlainImageCreate
from django.conf.urls import url
......@@ -47,8 +48,8 @@ urlpatterns = [
# name="dashboard.views.template-create"),
url(r'^template/choose/$', TemplateChoose.as_view(),
name="dashboard.views.template-choose"),
# url(r'template/(?P<pk>\d+)/acl/$', TemplateAclUpdateView.as_view(),
# name='dashboard.views.template-acl'),
url(r'template/(?P<pk>\d+)/acl/$', TemplateAclUpdateView.as_view(),
name='dashboard.views.template-acl'),
url(r'^template/(?P<pk>\d+)/$', TemplateDetail.as_view(),
name='dashboard.views.template-detail'),
url(r"^template/list/$", TemplateList.as_view(),
......
......@@ -89,10 +89,9 @@ class AclUserAutocomplete(autocomplete.Select2ListView):
}), content_type="application/json")
class AclUserGroupAutocomplete(AclUserAutocomplete):
group_search_fields = ('name', 'groupprofile__org_id')
class AclUserGroupAutocomplete(autocomplete.Select2ListView): # TODO: was AclUserAutocomplete inherited
def get_list(self):
groups = AclUpdateView.get_allowed_groups(self.request.user)
groups = self.filter(groups, self.group_search_fields)
return super(AclUserGroupAutocomplete, self).get_list() + groups
from openstack_api import keystone
groups = keystone.group_list(request=self.request, user=self.request.user)
group_names = [g.name for g in groups]
return super(AclUserGroupAutocomplete, self).get_list() + group_names
......@@ -41,6 +41,9 @@ from braces.views import (
)
from django.views.generic.edit import BaseUpdateView, ModelFormMixin, FormView
from django_tables2 import SingleTableView
from keystoneauth1 import session
from keystoneauth1.identity import v3
from openstack_auth.utils import fix_auth_url_version
from vm.models import (
InstanceTemplate, InterfaceTemplate, Instance, Lease, InstanceActivity
......@@ -289,6 +292,8 @@ class TemplateDelete(DeleteViewBase):
object.delete()
class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, UpdateView):
model = InstanceTemplate
template_name = "dashboard/template-edit.html"
......@@ -325,11 +330,57 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
else:
return super(TemplateDetail, self).get(request, *args, **kwargs)
def __get_glance_admin_client(self, request):
from keystoneauth1 import session
from glanceclient import Client
auth = v3.Password(
auth_url=fix_auth_url_version(settings.OPENSTACK_KEYSTONE_URL),
user_id=settings.OPENSTACK_CIRCLE_USERID,
password=settings.OPENSTACK_CIRCLE_PASSWORD,
project_id=request.user.tenant_id,
)
session = session.Session(auth=auth, verify=False)
return Client('2', session=session)
def __get_members_shared_with(self):
template = self.get_object()
glance = self.__get_glance_admin_client(self.request)
members_generator = glance.image_members.list(template.image_id)
return [m for m in members_generator]
def __get_project_client(self, project_id):
from keystoneclient.v3 import client
auth = v3.Password(
auth_url=fix_auth_url_version(settings.OPENSTACK_KEYSTONE_URL),
user_id=settings.OPENSTACK_CIRCLE_USERID,
password=settings.OPENSTACK_CIRCLE_PASSWORD,
project_id=project_id,
)
sess = session.Session(auth=auth, verify=False)
return client.Client(session=sess, interface='public')
def __get_username_for(self, project_id):
client = self.__get_project_client(project_id)
project = client.projects.get(project_id)
return project.name # as username = project's name
def __get_users_shared_with(self):
members = self.__get_members_shared_with()
for m in members:
m['username'] = self.__get_username_for(m.member_id)
return members
def get_context_data(self, **kwargs):
template = self.get_object()
context = super(TemplateDetail, self).get_context_data(**kwargs)
# context['acl'] = AclUpdateView.get_acl_data(
# template, self.request.user, 'dashboard.views.template-acl')
context['manage_access_url'] = reverse_lazy('dashboard.views.template-acl',
kwargs={'pk': template.id})
context['shared_with_users'] = self.__get_users_shared_with()
# context['disks'] = template.disks.all()
context['is_owner'] = template.owner_id == self.request.user.id
context['aclform'] = AclUserOrGroupAddForm()
......@@ -341,13 +392,14 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
return reverse_lazy("dashboard.views.template-detail",
kwargs=self.kwargs)
def post(self, request, *args, **kwargs):
def post(self, request):
template = self.get_object()
if not template.has_level(request.user, 'owner'):
# TODO: multiple users
if template.owner_id != request.user.id:
raise PermissionDenied()
return super(TemplateDetail, self).post(self, request, args, kwargs)
def get_form_kwargs(self):
def get_form_kwargs(self, *args, **kwargs):
kwargs = super(TemplateDetail, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
......
......@@ -593,7 +593,7 @@ def group_delete(request, group_id):
def group_list(request, domain=None, project=None, user=None, filters=None):
manager = keystoneclient(request, admin=True).groups
manager = keystoneclient(request).groups # TODO: was admin API
groups = []
kwargs = {
"domain": domain,
......@@ -834,7 +834,7 @@ def remove_tenant_user(request, project=None, user=None, domain=None):
def roles_for_group(request, group, domain=None, project=None):
manager = keystoneclient(request, admin=True).roles
manager = keystoneclient(request).roles
return manager.list(group=group, domain=domain, project=project)
......
......@@ -119,6 +119,15 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
abstract = True
class TemplateMemberGroup(Model):
group_id = CharField(blank=False, max_length=100, unique=True)
class Meta:
app_label = 'vm'
db_table = 'vm_template_member_group'
class InstanceTemplate(TimeStampedModel):
"""Virtual machine template.
"""
......
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