Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Gelencsér Szabolcs
/
circlestack
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
4c29a7bb
authored
May 31, 2018
by
Szabolcs Gelencser
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement template share with users
parent
0ec3899c
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
128 additions
and
134 deletions
+128
-134
circle/dashboard/forms.py
+36
-35
circle/dashboard/templates/dashboard/_manage_access.html
+13
-27
circle/dashboard/templates/dashboard/template-edit.html
+4
-58
circle/dashboard/urls.py
+4
-3
circle/dashboard/views/autocomplete.py
+5
-6
circle/dashboard/views/template.py
+55
-3
circle/dashboard/views/util.py
+0
-0
circle/openstack_api/keystone.py
+2
-2
circle/vm/models/instance.py
+9
-0
No files found.
circle/dashboard/forms.py
View file @
4c29a7bb
...
...
@@ -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
):
...
...
circle/dashboard/templates/dashboard/_manage_access.html
View file @
4c29a7bb
{% 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>
...
...
circle/dashboard/templates/dashboard/template-edit.html
View file @
4c29a7bb
...
...
@@ -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"
>
...
...
circle/dashboard/urls.py
View file @
4c29a7bb
...
...
@@ -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
(),
...
...
circle/dashboard/views/autocomplete.py
View file @
4c29a7bb
...
...
@@ -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
circle/dashboard/views/template.py
View file @
4c29a7bb
...
...
@@ -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
...
...
circle/dashboard/views/util.py
View file @
4c29a7bb
This diff is collapsed.
Click to expand it.
circle/openstack_api/keystone.py
View file @
4c29a7bb
...
...
@@ -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
)
...
...
circle/vm/models/instance.py
View file @
4c29a7bb
...
...
@@ -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.
"""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment