Commit 4c29a7bb by Szabolcs Gelencser

Implement template share with users

parent 0ec3899c
...@@ -433,11 +433,17 @@ class TemplateForm(forms.ModelForm): ...@@ -433,11 +433,17 @@ class TemplateForm(forms.ModelForm):
# })) # }))
name = forms.TextInput() name = forms.TextInput()
flavor = forms.ChoiceField(['a','b','c']) # flavor = forms.ChoiceField(['a','b','c'])
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None) self.user = kwargs.pop("user", None)
super(TemplateForm, self).__init__(*args, **kwargs) super(TemplateForm, self).__init__(*args, **kwargs)
self.allowed_fields = (
'name',
# 'lease',
)
# #
# self.fields['networks'].queryset = ()#Vlan.get_objects_with_level('user', self.user) # self.fields['networks'].queryset = ()#Vlan.get_objects_with_level('user', self.user)
# #
...@@ -487,34 +493,34 @@ class TemplateForm(forms.ModelForm): ...@@ -487,34 +493,34 @@ class TemplateForm(forms.ModelForm):
def clean_max_ram_size(self): def clean_max_ram_size(self):
return self.cleaned_data.get("ram_size", 0) return self.cleaned_data.get("ram_size", 0)
def _clean_fields(self): # def _clean_fields(self):
try: # try:
old = InstanceTemplate.objects.get(pk=self.instance.pk) # old = InstanceTemplate.objects.get(pk=self.instance.pk)
except InstanceTemplate.DoesNotExist: # except InstanceTemplate.DoesNotExist:
old = None # old = None
for name, field in self.fields.items(): # for name, field in self.fields.items():
if name in self.allowed_fields: # if name in self.allowed_fields:
value = field.widget.value_from_datadict( # value = field.widget.value_from_datadict(
self.data, self.files, self.add_prefix(name)) # self.data, self.files, self.add_prefix(name))
try: # try:
value = field.clean(value) # value = field.clean(value)
self.cleaned_data[name] = value # self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name): # if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)() # value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value # self.cleaned_data[name] = value
except ValidationError as e: # except ValidationError as e:
self._errors[name] = self.error_class(e.messages) # self._errors[name] = self.error_class(e.messages)
if name in self.cleaned_data: # if name in self.cleaned_data:
del self.cleaned_data[name] # del self.cleaned_data[name]
elif old: # elif old:
if name == 'networks': # if name == 'networks':
self.cleaned_data[name] = [ # self.cleaned_data[name] = [
i.vlan for i in self.instance.interface_set.all()] # i.vlan for i in self.instance.interface_set.all()]
else: # else:
self.cleaned_data[name] = getattr(old, name) # self.cleaned_data[name] = getattr(old, name)
if "req_traits" not in self.allowed_fields: # if "req_traits" not in self.allowed_fields:
self.cleaned_data['req_traits'] = self.instance.req_traits.all() # self.cleaned_data['req_traits'] = self.instance.req_traits.all()
def save(self, commit=True): def save(self, commit=True):
data = self.cleaned_data data = self.cleaned_data
...@@ -1300,12 +1306,7 @@ class UserEditForm(forms.ModelForm): ...@@ -1300,12 +1306,7 @@ class UserEditForm(forms.ModelForm):
class AclUserOrGroupAddForm(forms.Form): class AclUserOrGroupAddForm(forms.Form):
name = forms.CharField( name = forms.CharField(required=False)
widget=autocomplete.ListSelect2(
url='autocomplete.acl.user-group',
attrs={'class': 'form-control',
'data-html': 'true',
'data-placeholder': _("Name of group or user")}))
class TransferOwnershipForm(forms.Form): class TransferOwnershipForm(forms.Form):
......
{% load i18n %} {% 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}}"> <table class="table table-striped table-with-form-fields acl-table" id="{{table_id}}">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th>{% trans "Who" %}</th> <th>{% trans "Who" %}</th>
<th>{% trans "What" %}</th>
<th><i id="manage-access-select-all" class="fa fa-times"></i></th> <th><i id="manage-access-select-all" class="fa fa-times"></i></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for i in acl.users %} {% for member in shared_with_users %}
<tr> <tr>
<td> <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>
<td> <td>
<a href="{% url "dashboard.views.profile" username=i.user.username %}" {# <a href="{% url "dashboard.views.profile" username=i.user.username %}"#}
title="{{ i.user.username }}"> {# title="{{ i.user.username }}">#}
{% include "dashboard/_display-name.html" with user=i.user show_org=True %} {# {% include "dashboard/_display-name.html" with user=i.user show_org=True %}#}
</a> {# </a>#}
</td> {{ member.username }}
<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>
</td> </td>
<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> </td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -57,15 +48,10 @@ ...@@ -57,15 +48,10 @@
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
<tr><td><i class="fa fa-plus"></i></td> <tr>
<td>{{aclform.name }}</td> <td><i class="fa fa-plus"></i></td>
<td><select class="form-control" name="level"> <td>{{aclform.name }}</td>
{% for id, name in acl.levels %} <td></td>
{% if id in acl.allowed_levels %}
<option value="{{id}}">{{name}}</option>
{% endif %}
{% endfor %}
</select></td><td></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
......
...@@ -42,8 +42,8 @@ ...@@ -42,8 +42,8 @@
{# {{ form.max_ram_size|as_crispy_field }}#} {# {{ form.max_ram_size|as_crispy_field }}#}
</fieldset> </fieldset>
<fieldset> {# <fieldset>#}
<legend>{% trans "Virtual machine settings" %}</legend> {# <legend>{% trans "Virtual machine settings" %}</legend>#}
{# {{ form.arch|as_crispy_field }}#} {# {{ form.arch|as_crispy_field }}#}
{# {{ form.access_method|as_crispy_field }}#} {# {{ form.access_method|as_crispy_field }}#}
{# {{ form.boot_menu|as_crispy_field }}#} {# {{ form.boot_menu|as_crispy_field }}#}
...@@ -52,11 +52,11 @@ ...@@ -52,11 +52,11 @@
{# {{ form.description|as_crispy_field }}#} {# {{ form.description|as_crispy_field }}#}
{# {{ form.system|as_crispy_field }}#} {# {{ form.system|as_crispy_field }}#}
{# {{ form.has_agent|as_crispy_field }}#} {# {{ form.has_agent|as_crispy_field }}#}
</fieldset> {# </fieldset>#}
<fieldset> <fieldset>
<legend>{% trans "External resources" %}</legend> <legend>{% trans "External resources" %}</legend>
{# {{ form.networks|as_crispy_field }}#} {# {{ form.networks|as_crispy_field }}#}
{# {{ form.lease|as_crispy_field }}#} {{ form.lease|as_crispy_field }}
{##} {##}
{# {{ form.tags|as_crispy_field }}#} {# {{ form.tags|as_crispy_field }}#}
</fieldset> </fieldset>
...@@ -82,26 +82,6 @@ ...@@ -82,26 +82,6 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <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> <h4 class="no-margin"><i class="fa fa-group"></i> {% trans "Manage access" %}</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
...@@ -111,40 +91,6 @@ ...@@ -111,40 +91,6 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <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> <h4 class="no-margin"><i class="fa fa-file"></i> {% trans "Disk list" %}</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
......
...@@ -19,7 +19,8 @@ from __future__ import absolute_import ...@@ -19,7 +19,8 @@ from __future__ import absolute_import
from dashboard.views.autocomplete import AclUserGroupAutocomplete, AclUserAutocomplete from dashboard.views.autocomplete import AclUserGroupAutocomplete, AclUserAutocomplete
from dashboard.views.template import TemplateList, TemplateChoose, TemplateDetail, TemplateDelete, \ 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 dashboard.views.vm import VmDetailView, VmList, VmCreate, vm_activity, vm_ops, FavouriteView, VmPlainImageCreate
from django.conf.urls import url from django.conf.urls import url
...@@ -47,8 +48,8 @@ urlpatterns = [ ...@@ -47,8 +48,8 @@ urlpatterns = [
# name="dashboard.views.template-create"), # name="dashboard.views.template-create"),
url(r'^template/choose/$', TemplateChoose.as_view(), url(r'^template/choose/$', TemplateChoose.as_view(),
name="dashboard.views.template-choose"), name="dashboard.views.template-choose"),
# url(r'template/(?P<pk>\d+)/acl/$', TemplateAclUpdateView.as_view(), url(r'template/(?P<pk>\d+)/acl/$', TemplateAclUpdateView.as_view(),
# name='dashboard.views.template-acl'), name='dashboard.views.template-acl'),
url(r'^template/(?P<pk>\d+)/$', TemplateDetail.as_view(), url(r'^template/(?P<pk>\d+)/$', TemplateDetail.as_view(),
name='dashboard.views.template-detail'), name='dashboard.views.template-detail'),
url(r"^template/list/$", TemplateList.as_view(), url(r"^template/list/$", TemplateList.as_view(),
......
...@@ -89,10 +89,9 @@ class AclUserAutocomplete(autocomplete.Select2ListView): ...@@ -89,10 +89,9 @@ class AclUserAutocomplete(autocomplete.Select2ListView):
}), content_type="application/json") }), content_type="application/json")
class AclUserGroupAutocomplete(AclUserAutocomplete): class AclUserGroupAutocomplete(autocomplete.Select2ListView): # TODO: was AclUserAutocomplete inherited
group_search_fields = ('name', 'groupprofile__org_id')
def get_list(self): def get_list(self):
groups = AclUpdateView.get_allowed_groups(self.request.user) from openstack_api import keystone
groups = self.filter(groups, self.group_search_fields) groups = keystone.group_list(request=self.request, user=self.request.user)
return super(AclUserGroupAutocomplete, self).get_list() + groups group_names = [g.name for g in groups]
return super(AclUserGroupAutocomplete, self).get_list() + group_names
...@@ -41,6 +41,9 @@ from braces.views import ( ...@@ -41,6 +41,9 @@ from braces.views import (
) )
from django.views.generic.edit import BaseUpdateView, ModelFormMixin, FormView from django.views.generic.edit import BaseUpdateView, ModelFormMixin, FormView
from django_tables2 import SingleTableView 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 ( from vm.models import (
InstanceTemplate, InterfaceTemplate, Instance, Lease, InstanceActivity InstanceTemplate, InterfaceTemplate, Instance, Lease, InstanceActivity
...@@ -289,6 +292,8 @@ class TemplateDelete(DeleteViewBase): ...@@ -289,6 +292,8 @@ class TemplateDelete(DeleteViewBase):
object.delete() object.delete()
class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, UpdateView): class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, UpdateView):
model = InstanceTemplate model = InstanceTemplate
template_name = "dashboard/template-edit.html" template_name = "dashboard/template-edit.html"
...@@ -325,11 +330,57 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update ...@@ -325,11 +330,57 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
else: else:
return super(TemplateDetail, self).get(request, *args, **kwargs) 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): def get_context_data(self, **kwargs):
template = self.get_object() template = self.get_object()
context = super(TemplateDetail, self).get_context_data(**kwargs) context = super(TemplateDetail, self).get_context_data(**kwargs)
# context['acl'] = AclUpdateView.get_acl_data( # context['acl'] = AclUpdateView.get_acl_data(
# template, self.request.user, 'dashboard.views.template-acl') # 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['disks'] = template.disks.all()
context['is_owner'] = template.owner_id == self.request.user.id context['is_owner'] = template.owner_id == self.request.user.id
context['aclform'] = AclUserOrGroupAddForm() context['aclform'] = AclUserOrGroupAddForm()
...@@ -341,13 +392,14 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update ...@@ -341,13 +392,14 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
return reverse_lazy("dashboard.views.template-detail", return reverse_lazy("dashboard.views.template-detail",
kwargs=self.kwargs) kwargs=self.kwargs)
def post(self, request, *args, **kwargs): def post(self, request):
template = self.get_object() template = self.get_object()
if not template.has_level(request.user, 'owner'): # TODO: multiple users
if template.owner_id != request.user.id:
raise PermissionDenied() raise PermissionDenied()
return super(TemplateDetail, self).post(self, request, args, kwargs) 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 = super(TemplateDetail, self).get_form_kwargs()
kwargs['user'] = self.request.user kwargs['user'] = self.request.user
return kwargs return kwargs
......
...@@ -593,7 +593,7 @@ def group_delete(request, group_id): ...@@ -593,7 +593,7 @@ def group_delete(request, group_id):
def group_list(request, domain=None, project=None, user=None, filters=None): 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 = [] groups = []
kwargs = { kwargs = {
"domain": domain, "domain": domain,
...@@ -834,7 +834,7 @@ def remove_tenant_user(request, project=None, user=None, domain=None): ...@@ -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): 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) return manager.list(group=group, domain=domain, project=project)
......
...@@ -119,6 +119,15 @@ class VirtualMachineDescModel(BaseResourceConfigModel): ...@@ -119,6 +119,15 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
abstract = True 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): class InstanceTemplate(TimeStampedModel):
"""Virtual machine template. """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