Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
CIRCLE
/
cloud
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
94
Merge Requests
10
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
5912d4e8
authored
Apr 30, 2014
by
Kálmán Viktor
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into feature-template-wizard
Conflicts: circle/dashboard/urls.py requirements/local.txt
parents
0a56f429
c7b205a4
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
493 additions
and
155 deletions
+493
-155
circle/__init__.py
+0
-0
circle/acl/models.py
+4
-3
circle/acl/tests/test_acl.py
+14
-0
circle/circle/settings/test.py
+1
-7
circle/common/operations.py
+6
-4
circle/dashboard/static/dashboard/dashboard.css
+26
-2
circle/dashboard/static/dashboard/dashboard.js
+2
-1
circle/dashboard/static/dashboard/vm-details.js
+121
-29
circle/dashboard/templates/dashboard/vm-detail.html
+8
-4
circle/dashboard/templates/dashboard/vm-detail/home.html
+40
-2
circle/dashboard/templates/dashboard/vm-detail/network.html
+41
-30
circle/dashboard/tests/test_views.py
+79
-3
circle/dashboard/urls.py
+4
-1
circle/dashboard/views.py
+82
-8
circle/firewall/fw.py
+3
-2
circle/network/templates/network/base.html
+3
-1
circle/storage/tasks/local_tasks.py
+7
-12
circle/vm/models/network.py
+1
-0
circle/vm/operations.py
+0
-10
circle/vm/tests/test_models.py
+16
-11
requirements.txt
+2
-2
requirements/base.txt
+26
-11
requirements/local.txt
+3
-4
requirements/production.txt
+2
-4
requirements/test.txt
+2
-4
No files found.
circle/__init__.py
deleted
100644 → 0
View file @
0a56f429
circle/acl/models.py
View file @
5912d4e8
...
...
@@ -177,14 +177,15 @@ class AclBase(Model):
@classmethod
def
get_objects_with_level
(
cls
,
level
,
user
,
group_also
=
True
,
owner_also
=
False
):
group_also
=
True
,
owner_also
=
False
,
disregard_superuser
=
False
):
logger
.
debug
(
'
%
s.get_objects_with_level(
%
s,
%
s) called'
,
unicode
(
cls
),
unicode
(
level
),
unicode
(
user
))
if
user
is
None
or
not
user
.
is_authenticated
():
return
cls
.
objects
.
none
()
if
getattr
(
user
,
'is_superuser'
,
False
):
if
getattr
(
user
,
'is_superuser'
,
False
)
and
not
disregard_superuser
:
logger
.
debug
(
'- superuser granted'
)
return
cls
.
objects
return
cls
.
objects
.
all
()
if
isinstance
(
level
,
basestring
):
level
=
cls
.
get_level_object
(
level
)
logger
.
debug
(
"- level set by str:
%
s"
,
unicode
(
level
))
...
...
circle/acl/tests/test_acl.py
View file @
5912d4e8
...
...
@@ -161,6 +161,20 @@ class AclUserTest(TestCase):
self
.
assertItemsEqual
(
TestModel
.
get_objects_with_level
(
'alfa'
,
self
.
u2
),
[
i2
])
def
test_get_objects_with_level_for_superuser
(
self
):
i1
=
TestModel
.
objects
.
create
(
normal_field
=
'Hello1'
)
i2
=
TestModel
.
objects
.
create
(
normal_field
=
'Hello2'
)
i1
.
set_level
(
self
.
u1
,
'alfa'
)
i2
.
set_level
(
self
.
us
,
'alfa'
)
self
.
assertItemsEqual
(
TestModel
.
get_objects_with_level
(
'alfa'
,
self
.
u1
),
[
i1
])
self
.
assertItemsEqual
(
TestModel
.
get_objects_with_level
(
'alfa'
,
self
.
us
),
[
i1
,
i2
])
self
.
assertItemsEqual
(
TestModel
.
get_objects_with_level
(
'alfa'
,
self
.
us
,
disregard_superuser
=
True
),
[
i2
])
def
test_get_objects_with_level_for_group
(
self
):
i1
=
TestModel
.
objects
.
create
(
normal_field
=
'Hello1'
)
i2
=
TestModel
.
objects
.
create
(
normal_field
=
'Hello2'
)
...
...
circle/circle/settings/test.py
View file @
5912d4e8
from
base
import
*
# noqa
from
.
base
import
*
# noqa
########## TEST SETTINGS
TEST_RUNNER
=
'discover_runner.DiscoverRunner'
TEST_DISCOVER_TOP_LEVEL
=
SITE_ROOT
TEST_DISCOVER_ROOT
=
SITE_ROOT
TEST_DISCOVER_PATTERN
=
"test_*.py"
########## IN-MEMORY TEST DATABASE
DATABASES
=
{
"default"
:
{
...
...
@@ -21,7 +16,6 @@ SOUTH_TESTS_MIGRATE = False
INSTALLED_APPS
+=
(
'acl.tests'
,
'django_nose'
,
)
CACHES
=
{
...
...
circle/common/operations.py
View file @
5912d4e8
...
...
@@ -62,8 +62,9 @@ class Operation(object):
For more information, check the synchronous call's documentation.
"""
logger
.
info
(
"
%
s called asynchronously with the following parameters: "
"
%
r"
,
self
.
__class__
.
__name__
,
kwargs
)
logger
.
info
(
"
%
s called asynchronously on
%
s with the following "
"parameters:
%
r"
,
self
.
__class__
.
__name__
,
self
.
subject
,
kwargs
)
activity
=
self
.
__prelude
(
kwargs
)
return
self
.
async_operation
.
apply_async
(
args
=
(
self
.
id
,
self
.
subject
.
pk
,
...
...
@@ -84,8 +85,9 @@ class Operation(object):
* user: The User invoking the operation. If this argument is not
present, it'll be provided with a default value of None.
"""
logger
.
info
(
"
%
s called (synchronously) with the following parameters: "
"
%
r"
,
self
.
__class__
.
__name__
,
kwargs
)
logger
.
info
(
"
%
s called (synchronously) on
%
s with the following "
"parameters:
%
r"
,
self
.
__class__
.
__name__
,
self
.
subject
,
kwargs
)
activity
=
self
.
__prelude
(
kwargs
)
return
self
.
_exec_op
(
activity
=
activity
,
**
kwargs
)
...
...
circle/dashboard/static/dashboard/dashboard.css
View file @
5912d4e8
...
...
@@ -187,7 +187,7 @@ html {
height
:
300px
;
}
#vm-details-rename
,
#vm-details-
rename
*,
#vm-details-h1-name
,
#vm-list-rename
,
#vm-list-rename
*
,
#vm-details-rename
,
#vm-details-
h1-name
,
#vm-details-rename
,
#node-details-rename
,
#node-details-rename
*,
#node-details-h1-name
,
#node-list-rename
,
#node-list-rename
*
#group-details-rename
,
#group-details-rename
*,
#group-details-h1-name
,
#group-list-rename
,
#group-list-rename
*
{
display
:
inline
;
...
...
@@ -197,7 +197,11 @@ html {
display
:
none
;
}
#vm-details-rename-name
,
#node-details-rename-name
,
#group-details-rename-name
{
.vm-details-home-name
{
max-width
:
401px
;
}
#node-details-rename-name
,
#group-details-rename-name
{
max-width
:
160px
;
}
...
...
@@ -516,3 +520,23 @@ footer a, footer a:hover, footer a:visited {
overflow
:
hidden
;
padding-left
:
10px
;
}
#vm-details-home-description
{
display
:
inline-block
;
position
:
relative
;
}
#vm-details-home-description
textarea
{
min-width
:
240px
;
min-height
:
250px
;
}
.vm-details-home-edit-description-click
,
.vm-details-home-edit-name-click
{
cursor
:
pointer
;
}
.vm-details-description-submit
{
position
:
absolute
;
bottom
:
10px
;
right
:
20px
;
}
circle/dashboard/static/dashboard/dashboard.js
View file @
5912d4e8
...
...
@@ -321,7 +321,8 @@ function deleteObject(data) {
// no need to remove them from DOM
$
(
'a[data-disk-pk="'
+
data
.
pk
+
'"]'
).
parent
(
"li"
).
fadeOut
();
$
(
'a[data-disk-pk="'
+
data
.
pk
+
'"]'
).
parent
(
"h4"
).
fadeOut
();
}
else
{
}
else
{
$
(
'a[data-'
+
data
[
'type'
]
+
'-pk="'
+
data
[
'pk'
]
+
'"]'
).
closest
(
'tr'
).
fadeOut
(
function
()
{
$
(
this
).
remove
();
});
...
...
circle/dashboard/static/dashboard/vm-details.js
View file @
5912d4e8
...
...
@@ -31,33 +31,6 @@ $(function() {
return
false
;
});
/* rename */
$
(
"#vm-details-h1-name, .vm-details-rename-button"
).
click
(
function
()
{
$
(
"#vm-details-h1-name"
).
hide
();
$
(
"#vm-details-rename"
).
css
(
'display'
,
'inline'
);
$
(
"#vm-details-rename-name"
).
focus
();
});
/* rename ajax */
$
(
'#vm-details-rename-submit'
).
click
(
function
()
{
var
name
=
$
(
'#vm-details-rename-name'
).
val
();
$
.
ajax
({
method
:
'POST'
,
url
:
location
.
href
,
data
:
{
'new_name'
:
name
},
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)},
success
:
function
(
data
,
textStatus
,
xhr
)
{
$
(
"#vm-details-h1-name"
).
html
(
data
[
'new_name'
]).
show
();
$
(
'#vm-details-rename'
).
hide
();
// addMessage(data['message'], "success");
},
error
:
function
(
xhr
,
textStatus
,
error
)
{
addMessage
(
"Error during renaming!"
,
"danger"
);
}
});
return
false
;
});
/* remove tag */
$
(
'.vm-details-remove-tag'
).
click
(
function
()
{
var
to_remove
=
$
.
trim
(
$
(
this
).
parent
(
'div'
).
text
());
...
...
@@ -150,8 +123,7 @@ $(function() {
/* add network button */
$
(
"#vm-details-network-add"
).
click
(
function
()
{
$
(
"#vm-details-network-add-for-form"
).
html
(
$
(
"#vm-details-network-add-form"
).
html
());
$
(
'input[name="new_network_managed"]'
).
tooltip
();
$
(
"#vm-details-network-add-form"
).
toggle
();
return
false
;
});
...
...
@@ -165,6 +137,126 @@ $(function() {
$
(
".vm-details-help-button"
).
click
(
function
()
{
$
(
".vm-details-help"
).
stop
().
slideToggle
();
});
/* for interface remove buttons */
$
(
'.interface-remove'
).
click
(
function
()
{
var
interface_pk
=
$
(
this
).
data
(
'interface-pk'
);
addModalConfirmation
(
removeInterface
,
{
'url'
:
'/dashboard/interface/'
+
interface_pk
+
'/delete/'
,
'data'
:
[],
'pk'
:
interface_pk
,
'type'
:
"interface"
,
});
return
false
;
});
/* removing interface post */
function
removeInterface
(
data
)
{
$
.
ajax
({
type
:
'POST'
,
url
:
data
[
'url'
],
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)},
success
:
function
(
re
,
textStatus
,
xhr
)
{
/* remove the html element */
$
(
'a[data-interface-pk="'
+
data
.
pk
+
'"]'
).
closest
(
"div"
).
fadeOut
();
/* add the removed element to the list */
network_select
=
$
(
'select[name="new_network_vlan"]'
);
name_html
=
(
re
.
removed_network
.
managed
?
""
:
""
)
+
" "
+
re
.
removed_network
.
vlan
;
option_html
=
'<option value="'
+
re
.
removed_network
.
vlan_pk
+
'">'
+
name_html
+
'</option>'
;
// if it's -1 then it's a dummy placeholder so we can use .html
if
(
$
(
"option"
,
network_select
)[
0
].
value
===
"-1"
)
{
network_select
.
html
(
option_html
);
network_select
.
next
(
"div"
).
children
(
"button"
).
prop
(
"disabled"
,
false
);
}
else
{
network_select
.
append
(
option_html
);
}
},
error
:
function
(
xhr
,
textStatus
,
error
)
{
addMessage
(
'Uh oh :('
,
'danger'
)
}
});
}
/* rename */
$
(
"#vm-details-h1-name, .vm-details-rename-button"
).
click
(
function
()
{
$
(
"#vm-details-h1-name"
).
hide
();
$
(
"#vm-details-rename"
).
css
(
'display'
,
'inline'
);
$
(
"#vm-details-rename-name"
).
focus
();
});
/* rename in home tab */
$
(
".vm-details-home-edit-name-click"
).
click
(
function
()
{
$
(
".vm-details-home-edit-name-click"
).
hide
();
$
(
"#vm-details-home-rename"
).
show
();
$
(
"input"
,
$
(
"#vm-details-home-rename"
)).
focus
();
});
/* rename ajax */
$
(
'.vm-details-rename-submit'
).
click
(
function
()
{
var
name
=
$
(
this
).
parent
(
"span"
).
prev
(
"input"
).
val
();
$
.
ajax
({
method
:
'POST'
,
url
:
location
.
href
,
data
:
{
'new_name'
:
name
},
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)},
success
:
function
(
data
,
textStatus
,
xhr
)
{
$
(
".vm-details-home-edit-name"
).
text
(
data
[
'new_name'
]).
show
();
$
(
".vm-details-home-edit-name"
).
parent
(
"div"
).
show
();
$
(
".vm-details-home-edit-name-click"
).
show
();
$
(
".vm-details-home-rename-form-div"
).
hide
();
// update the inputs too
$
(
".vm-details-rename-submit"
).
parent
(
"span"
).
prev
(
"input"
).
val
(
data
[
'new_name'
]);
},
error
:
function
(
xhr
,
textStatus
,
error
)
{
addMessage
(
"Error during renaming!"
,
"danger"
);
}
});
return
false
;
});
/* update description click */
$
(
".vm-details-home-edit-description-click"
).
click
(
function
()
{
$
(
".vm-details-home-edit-description-click"
).
hide
();
$
(
"#vm-details-home-description"
).
show
();
return
false
;
});
/* description update ajax */
$
(
'.vm-details-description-submit'
).
click
(
function
()
{
var
description
=
$
(
this
).
prev
(
"textarea"
).
val
();
$
.
ajax
({
method
:
'POST'
,
url
:
location
.
href
,
data
:
{
'new_description'
:
description
},
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)},
success
:
function
(
data
,
textStatus
,
xhr
)
{
var
new_desc
=
data
[
'new_description'
];
/* we can't simply use $.text, because we need new lines */
var
tagsToReplace
=
{
'&'
:
"&"
,
'<'
:
"<"
,
'>'
:
">"
,
};
new_desc
=
new_desc
.
replace
(
/
[
&<>
]
/g
,
function
(
tag
)
{
return
tagsToReplace
[
tag
]
||
tag
;
});
$
(
".vm-details-home-edit-description"
)
.
html
(
new_desc
.
replace
(
/
\n
/g
,
"<br />"
));
$
(
".vm-details-home-edit-description-click"
).
show
();
$
(
"#vm-details-home-description"
).
hide
();
// update the textareia
$
(
"vm-details-home-description textarea"
).
text
(
data
[
'new_description'
]);
},
error
:
function
(
xhr
,
textStatus
,
error
)
{
addMessage
(
"Error during renaming!"
,
"danger"
);
}
});
return
false
;
});
});
...
...
circle/dashboard/templates/dashboard/vm-detail.html
View file @
5912d4e8
...
...
@@ -36,14 +36,18 @@
{% include "dashboard/vm-detail/_operations.html" %}
</div>
<h1>
<div
id=
"vm-details-rename"
>
<div
id=
"vm-details-rename"
class=
"vm-details-home-rename-form-div"
>
<form
action=
""
method=
"POST"
id=
"vm-details-rename-form"
>
{% csrf_token %}
<input
id=
"vm-details-rename-name"
class=
"form-control"
name=
"new_name"
type=
"text"
value=
"{{ instance.name }}"
/>
<button
type=
"submit"
id=
"vm-details-rename-submit"
class=
"btn"
>
{% trans "Rename" %}
</button>
<div
class=
"input-group vm-details-home-name"
>
<input
id=
"vm-details-rename-name"
class=
"form-control input-sm"
name=
"new_name"
type=
"text"
value=
"{{ instance.name }}"
/>
<span
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"btn btn-sm vm-details-rename-submit"
>
{% trans "Rename" %}
</button>
</span>
</div>
</form>
</div>
<div
id=
"vm-details-h1-name"
>
<div
id=
"vm-details-h1-name"
class=
"vm-details-home-edit-name"
>
{{ instance.name }}
</div>
<small>
{{ instance.primary_host.get_fqdn }}
</small>
...
...
circle/dashboard/templates/dashboard/vm-detail/home.html
View file @
5912d4e8
...
...
@@ -4,8 +4,46 @@
<dl>
<dt>
{% trans "System" %}:
</dt>
<dd><i
class=
"icon-{{ os_type_icon }}"
></i>
{{ instance.system }}
</dd>
<dt
style=
"margin-top: 5px;"
>
{% trans "Description" %}:
</dt>
<dd><small>
{{ instance.description }}
</small></dd>
<dt
style=
"margin-top: 5px;"
>
{% trans "Name" %}:
<a
href=
"#"
class=
"vm-details-home-edit-name-click"
><i
class=
"icon-pencil"
></i></a>
</dt>
<dd>
<div
class=
"vm-details-home-edit-name-click"
>
<small
class=
"vm-details-home-edit-name"
>
{{ instance.name }}
</small>
</div>
<div
class=
"js-hidden vm-details-home-rename-form-div"
id=
"vm-details-home-rename"
>
<form
method=
"POST"
>
{% csrf_token %}
<div
class=
"input-group"
>
<input
type=
"text"
name=
"new_name"
value=
"{{ instance.name }}"
class=
"form-control input-sm"
/>
<span
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"btn btn-success btn-sm vm-details-rename-submit"
>
<i
class=
"icon-pencil"
></i>
{% trans "Rename" %}
</button>
</span>
</div>
</form>
</div>
</dd>
<dt
style=
"margin-top: 5px;"
>
{% trans "Description" %}:
<a
href=
"#"
class=
"vm-details-home-edit-description-click"
><i
class=
"icon-pencil"
></i></a>
</dt>
<dd>
{% csrf_token %}
<div
class=
"vm-details-home-edit-description-click"
>
<small
class=
"vm-details-home-edit-description"
>
{{ instance.description|linebreaks }}
</small>
</div>
<div
id=
"vm-details-home-description"
class=
"js-hidden"
>
<form
method=
"POST"
>
<textarea
name=
"new_description"
class=
"form-control"
>
{{ instance.description }}
</textarea>
<button
type=
"submit"
class=
"btn btn-xs btn-success vm-details-description-submit"
>
<i
class=
"icon-pencil"
></i>
{% trans "Update" %}
</button>
</form>
</div>
</dd>
</dl>
<h4>
{% trans "Expiration" %} {% if instance.is_expiring %}
<i
class=
"icon-warning-sign text-danger"
></i>
{% endif %}
...
...
circle/dashboard/templates/dashboard/vm-detail/network.html
View file @
5912d4e8
...
...
@@ -6,13 +6,50 @@
{% trans "Interfaces" %}
</h2>
<div
class=
"row"
id=
"vm-details-network-add-for-form"
>
<div
class=
"js-hidden row"
id=
"vm-details-network-add-form"
>
<div
class=
"col-md-12"
>
<div>
<hr
/>
<h3>
{% trans "Add new network interface!" %}
</h3>
<form
method=
"POST"
action=
""
>
{% csrf_token %}
<div
class=
"input-group"
style=
"max-width: 330px;"
>
<select
name=
"new_network_vlan"
class=
"form-control font-awesome-font"
>
{% for v in vlans %}
<option
value=
"{{ v.pk }}"
>
{% if v.managed %}

{% else %}

{% endif %}
{{ v.name }}
</option>
{% empty %}
<option
value=
"-1"
>
No more networks!
</option>
{% endfor %}
</select>
<div
class=
"input-group-btn"
>
<button
{%
if
vlans
|
length =
=
0
%}
disabled
{%
endif
%}
type=
"submit"
class=
"btn btn-success"
><i
class=
"icon-plus-sign"
></i></button>
</div>
</div>
</form>
<hr
/>
</div>
</div>
</div>
{% for i in instance.interface_set.all %}
<div>
<h3
class=
"list-group-item-heading dashboard-vm-details-network-h3"
>
<i
class=
"icon-{% if i.host %}globe{% else %}link{% endif %}"
></i>
{{ i.vlan.name }}
{% if not i.host %}(unmanaged)
<a
href=
"#"
class=
"btn btn-danger btn-xs"
>
{% trans "remove" %}
</a>
{% endif %}
{% if not i.host%}({% trans "unmanaged" %}){% endif %}
<a
href=
"{% url "
dashboard
.
views
.
interface-delete
"
pk=
i.pk
%}?
next=
{{
request
.
path
}}"
class=
"btn btn-danger btn-xs interface-remove"
data-interface-pk=
"{{ i.pk }}"
>
{% trans "remove" %}
</a>
</h3>
{% if i.host %}
<div
class=
"row"
>
...
...
@@ -109,31 +146,5 @@
</div>
</div>
{% endif %}
{% endfor %}
<div
class=
"js-hidden row"
id=
"vm-details-network-add-form"
>
<div
class=
"col-md-12"
>
<div>
<hr
/>
<h3>
{% trans "Add new network interface!" %}
</h3>
<form
method=
"POST"
action=
""
>
{% csrf_token %}
<div
class=
"input-group"
style=
"max-width: 330px;"
>
<select
name=
"new_network_vlan"
class=
"form-control"
>
{% if vlans|length == 0 %}
<option
value=
"-1"
>
No more networks!
</option>
{% endif %}
{% for v in vlans %}
<option
value=
"{{ v.pk }}"
>
{{ v.name }}
</option>
{% endfor %}
</select>
<div
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"btn btn-success"
><i
class=
"icon-plus-sign"
></i></button>
</div>
</div>
</form>
<hr
/>
</div>
</div>
</div>
{% endfor %}
circle/dashboard/tests/test_views.py
View file @
5912d4e8
import
json
from
unittest
import
skip
from
django.test
import
TestCase
from
django.test.client
import
Client
from
django.contrib.auth.models
import
User
,
Group
from
django.core.exceptions
import
SuspiciousOperation
from
django.core.urlresolvers
import
reverse
from
django.contrib.auth.models
import
Permission
from
django.contrib.auth
import
authenticate
...
...
@@ -174,6 +175,46 @@ class VmDetailTest(LoginMixin, TestCase):
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
inst
.
interface_set
.
count
(),
interface_count
+
1
)
def
test_permitted_network_delete
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'owner'
)
inst
.
add_interface
(
vlan
=
Vlan
.
objects
.
get
(
pk
=
1
),
user
=
self
.
us
)
iface_count
=
inst
.
interface_set
.
count
()
c
.
post
(
"/dashboard/interface/1/delete/"
)
self
.
assertEqual
(
inst
.
interface_set
.
count
(),
iface_count
-
1
)
def
test_permitted_network_delete_w_ajax
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'owner'
)
vlan
=
Vlan
.
objects
.
get
(
pk
=
1
)
inst
.
add_interface
(
vlan
=
vlan
,
user
=
self
.
us
)
iface_count
=
inst
.
interface_set
.
count
()
response
=
c
.
post
(
"/dashboard/interface/1/delete/"
,
HTTP_X_REQUESTED_WITH
=
'XMLHttpRequest'
)
removed_network
=
json
.
loads
(
response
.
content
)[
'removed_network'
]
self
.
assertEqual
(
removed_network
[
'vlan'
],
vlan
.
name
)
self
.
assertEqual
(
removed_network
[
'vlan_pk'
],
vlan
.
pk
)
self
.
assertEqual
(
removed_network
[
'managed'
],
vlan
.
managed
)
self
.
assertEqual
(
inst
.
interface_set
.
count
(),
iface_count
-
1
)
def
test_unpermitted_network_delete
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'user'
)
inst
.
add_interface
(
vlan
=
Vlan
.
objects
.
get
(
pk
=
1
),
user
=
self
.
us
)
iface_count
=
inst
.
interface_set
.
count
()
response
=
c
.
post
(
"/dashboard/interface/1/delete/"
)
self
.
assertEqual
(
iface_count
,
inst
.
interface_set
.
count
())
self
.
assertEqual
(
response
.
status_code
,
403
)
def
test_create_vm_w_unpermitted_network
(
self
):
c
=
Client
()
self
.
login
(
c
,
'user2'
)
...
...
@@ -559,6 +600,41 @@ class VmDetailTest(LoginMixin, TestCase):
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
instance_count
+
2
,
Instance
.
objects
.
all
()
.
count
())
def
test_unpermitted_description_update
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u2
,
'owner'
)
inst
.
set_level
(
self
.
u1
,
'user'
)
old_desc
=
inst
.
description
response
=
c
.
post
(
"/dashboard/vm/1/"
,
{
'new_description'
:
'test1234'
})
self
.
assertEqual
(
response
.
status_code
,
403
)
self
.
assertEqual
(
Instance
.
objects
.
get
(
pk
=
1
)
.
description
,
old_desc
)
def
test_permitted_description_update_w_ajax
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'owner'
)
response
=
c
.
post
(
"/dashboard/vm/1/"
,
{
'new_description'
:
"naonyo"
},
HTTP_X_REQUESTED_WITH
=
'XMLHttpRequest'
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
json
.
loads
(
response
.
content
)[
'new_description'
],
"naonyo"
)
self
.
assertEqual
(
Instance
.
objects
.
get
(
pk
=
1
)
.
description
,
"naonyo"
)
def
test_permitted_description_update
(
self
):
c
=
Client
()
self
.
login
(
c
,
"user1"
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
set_level
(
self
.
u1
,
'owner'
)
response
=
c
.
post
(
"/dashboard/vm/1/"
,
{
'new_description'
:
"naonyo"
})
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertEqual
(
Instance
.
objects
.
get
(
pk
=
1
)
.
description
,
"naonyo"
)
class
NodeDetailTest
(
LoginMixin
,
TestCase
):
fixtures
=
[
'test-vm-fixture.json'
,
'node.json'
]
...
...
@@ -820,8 +896,8 @@ class TransferOwnershipViewTest(LoginMixin, TestCase):
c2
=
self
.
u2
.
notification_set
.
count
()
c
=
Client
()
self
.
login
(
c
,
'user2'
)
with
self
.
assertRaises
(
SuspiciousOperation
):
c
.
post
(
'/dashboard/vm/1/tx/'
)
response
=
c
.
post
(
'/dashboard/vm/1/tx/'
)
assert
response
.
status_code
==
400
self
.
assertEqual
(
self
.
u2
.
notification_set
.
count
(),
c2
)
def
test_owned_offer
(
self
):
...
...
circle/dashboard/urls.py
View file @
5912d4e8
...
...
@@ -12,7 +12,7 @@ from .views import (
TemplateDelete
,
TemplateDetail
,
TemplateList
,
TransferOwnershipConfirmView
,
TransferOwnershipView
,
vm_activity
,
VmCreate
,
VmDelete
,
VmDetailView
,
VmDetailVncTokenView
,
VmGraphView
,
VmList
,
VmMassDelete
,
VmMigrateView
,
VmRenewView
,
DiskRemoveView
,
get_disk_download_status
,
VmRenewView
,
DiskRemoveView
,
get_disk_download_status
,
InterfaceDeleteView
,
TemplateChoose
,
TemplateClone
,
)
...
...
@@ -114,6 +114,9 @@ urlpatterns = patterns(
url
(
r'^disk/(?P<pk>\d+)/status/$'
,
get_disk_download_status
,
name
=
"dashboard.views.disk-status"
),
url
(
r'^interface/(?P<pk>\d+)/delete/$'
,
InterfaceDeleteView
.
as_view
(),
name
=
"dashboard.views.interface-delete"
),
url
(
r'^profile/$'
,
MyPreferencesView
.
as_view
(),
name
=
"dashboard.views.profile"
),
)
circle/dashboard/views.py
View file @
5912d4e8
...
...
@@ -30,9 +30,8 @@ from django.template import RequestContext
from
django.forms.models
import
inlineformset_factory
from
django_tables2
import
SingleTableView
from
braces.views
import
(
LoginRequiredMixin
,
SuperuserRequiredMixin
,
AccessMixin
)
from
braces.views
import
LoginRequiredMixin
,
SuperuserRequiredMixin
from
braces.views._access
import
AccessMixin
from
.forms
import
(
CircleAuthenticationForm
,
DiskAddForm
,
HostForm
,
LeaseForm
,
MyProfileForm
,
...
...
@@ -90,7 +89,7 @@ class IndexView(LoginRequiredMixin, TemplateView):
# instances
favs
=
Instance
.
objects
.
filter
(
favourite__user
=
self
.
request
.
user
)
instances
=
Instance
.
get_objects_with_level
(
'user'
,
user
)
.
filter
(
destroyed_at
=
None
)
'user'
,
user
,
disregard_superuser
=
True
)
.
filter
(
destroyed_at
=
None
)
display
=
list
(
favs
)
+
list
(
set
(
instances
)
-
set
(
favs
))
for
d
in
display
:
d
.
fav
=
True
if
d
in
favs
else
False
...
...
@@ -216,7 +215,7 @@ class VmDetailView(CheckedDetailView):
context
[
'vlans'
]
=
Vlan
.
get_objects_with_level
(
'user'
,
self
.
request
.
user
)
.
exclude
(
)
.
exclude
(
# exclude already added interfaces
pk__in
=
Interface
.
objects
.
filter
(
instance
=
self
.
get_object
())
.
values_list
(
"vlan"
,
flat
=
True
)
)
.
all
()
...
...
@@ -239,6 +238,7 @@ class VmDetailView(CheckedDetailView):
options
=
{
'change_password'
:
self
.
__change_password
,
'new_name'
:
self
.
__set_name
,
'new_description'
:
self
.
__set_description
,
'new_tag'
:
self
.
__add_tag
,
'to_remove'
:
self
.
__remove_tag
,
'port'
:
self
.
__add_port
,
...
...
@@ -309,8 +309,30 @@ class VmDetailView(CheckedDetailView):
)
else
:
messages
.
success
(
request
,
success_message
)
return
redirect
(
reverse_lazy
(
"dashboard.views.detail"
,
kwargs
=
{
'pk'
:
self
.
object
.
pk
}))
return
redirect
(
self
.
object
.
get_absolute_url
())
def
__set_description
(
self
,
request
):
self
.
object
=
self
.
get_object
()
if
not
self
.
object
.
has_level
(
request
.
user
,
'owner'
):
raise
PermissionDenied
()
new_description
=
request
.
POST
.
get
(
"new_description"
)
Instance
.
objects
.
filter
(
pk
=
self
.
object
.
pk
)
.
update
(
**
{
'description'
:
new_description
})
success_message
=
_
(
"VM description successfully updated!"
)
if
request
.
is_ajax
():
response
=
{
'message'
:
success_message
,
'new_description'
:
new_description
,
}
return
HttpResponse
(
json
.
dumps
(
response
),
content_type
=
"application/json"
)
else
:
messages
.
success
(
request
,
success_message
)
return
redirect
(
self
.
object
.
get_absolute_url
())
def
__add_tag
(
self
,
request
):
new_tag
=
request
.
POST
.
get
(
'new_tag'
)
...
...
@@ -393,7 +415,7 @@ class VmDetailView(CheckedDetailView):
if
not
self
.
object
.
has_level
(
request
.
user
,
'owner'
):
raise
PermissionDenied
()
vlan
=
Vlan
.
objects
.
get
(
pk
=
request
.
POST
.
get
(
"new_network_vlan"
))
vlan
=
get_object_or_404
(
Vlan
,
pk
=
request
.
POST
.
get
(
"new_network_vlan"
))
if
not
vlan
.
has_level
(
request
.
user
,
'user'
):
raise
PermissionDenied
()
try
:
...
...
@@ -481,6 +503,7 @@ class OperationView(DetailView):
class
VmOperationView
(
OperationView
):
model
=
Instance
context_object_name
=
'instance'
# much simpler to mock object
class
VmMigrateView
(
VmOperationView
):
...
...
@@ -2353,6 +2376,7 @@ def get_disk_download_status(request, pk):
class
InstanceActivityDetail
(
SuperuserRequiredMixin
,
DetailView
):
model
=
InstanceActivity
context_object_name
=
'instanceactivity'
# much simpler to mock object
template_name
=
'dashboard/instanceactivity_detail.html'
def
get_context_data
(
self
,
**
kwargs
):
...
...
@@ -2362,3 +2386,53 @@ class InstanceActivityDetail(SuperuserRequiredMixin, DetailView):
order_by
(
'-started'
)
.
select_related
(
'user'
)
.
prefetch_related
(
'children'
))
return
ctx
class
InterfaceDeleteView
(
DeleteView
):
model
=
Interface
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/confirm/ajax-delete.html'
]
else
:
return
[
'dashboard/confirm/base-delete.html'
]
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
InterfaceDeleteView
,
self
)
.
get_context_data
(
**
kwargs
)
interface
=
self
.
get_object
()
context
[
'text'
]
=
_
(
"Are you sure you want to remove this interface "
"from <strong>
%(vm)
s</strong>?"
%
{
'vm'
:
interface
.
instance
.
name
})
return
context
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
object
=
self
.
get_object
()
instance
=
self
.
object
.
instance