Shape => Drawing

pull/638/head
Julien Duponchelle 8 years ago
parent c847755f1b
commit 852d8e411e
No known key found for this signature in database
GPG Key ID: CE8B29639E07F5E8

@ -1,6 +1,6 @@
curl -i -X DELETE 'http://localhost:3080/v2/projects/6b74fe6e-f65a-46a9-84a9-1db0cd42a9d2/shapes/6e313846-5d8f-4dcc-a231-e2dd9406bb71'
curl -i -X DELETE 'http://localhost:3080/v2/projects/6b74fe6e-f65a-46a9-84a9-1db0cd42a9d2/drawings/6e313846-5d8f-4dcc-a231-e2dd9406bb71'
DELETE /v2/projects/6b74fe6e-f65a-46a9-84a9-1db0cd42a9d2/shapes/6e313846-5d8f-4dcc-a231-e2dd9406bb71 HTTP/1.1
DELETE /v2/projects/6b74fe6e-f65a-46a9-84a9-1db0cd42a9d2/drawings/6e313846-5d8f-4dcc-a231-e2dd9406bb71 HTTP/1.1
@ -8,5 +8,5 @@ HTTP/1.1 204
CONTENT-LENGTH: 0
DATE: Thu, 08 Jan 2015 16:09:15 GMT
SERVER: Python/3.5 GNS3/2.0.0dev1
X-ROUTE: /v2/projects/{project_id}/shapes/{shape_id}
X-ROUTE: /v2/projects/{project_id}/drawings/{drawing_id}

@ -1,6 +1,6 @@
curl -i -X GET 'http://localhost:3080/v2/projects/0bb34c74-e696-46b5-9c94-d00e3f55da18/shapes'
curl -i -X GET 'http://localhost:3080/v2/projects/0bb34c74-e696-46b5-9c94-d00e3f55da18/drawings'
GET /v2/projects/0bb34c74-e696-46b5-9c94-d00e3f55da18/shapes HTTP/1.1
GET /v2/projects/0bb34c74-e696-46b5-9c94-d00e3f55da18/drawings HTTP/1.1
@ -9,13 +9,13 @@ CONTENT-LENGTH: 361
CONTENT-TYPE: application/json
DATE: Thu, 08 Jan 2015 16:09:15 GMT
SERVER: Python/3.5 GNS3/2.0.0dev1
X-ROUTE: /v2/projects/{project_id}/shapes
X-ROUTE: /v2/projects/{project_id}/drawings
[
{
"project_id": "0bb34c74-e696-46b5-9c94-d00e3f55da18",
"rotation": 0,
"shape_id": "a0690cd5-6460-464e-b599-7980f5c08e67",
"drawing_id": "a0690cd5-6460-464e-b599-7980f5c08e67",
"svg": "<svg height=\"210\" width=\"500\"><line x1=\"0\" y1=\"0\" x2=\"200\" y2=\"200\" style=\"stroke:rgb(255,0,0);stroke-width:2\" /></svg>",
"x": 10,
"y": 20,

@ -1,6 +1,6 @@
curl -i -X POST 'http://localhost:3080/v2/projects/e82d1e06-4235-41dc-9a57-0cd0672692f0/shapes' -d '{"svg": "<svg height=\"210\" width=\"500\"><line x1=\"0\" y1=\"0\" x2=\"200\" y2=\"200\" style=\"stroke:rgb(255,0,0);stroke-width:2\" /></svg>", "x": 10, "y": 20, "z": 0}'
curl -i -X POST 'http://localhost:3080/v2/projects/e82d1e06-4235-41dc-9a57-0cd0672692f0/drawings' -d '{"svg": "<svg height=\"210\" width=\"500\"><line x1=\"0\" y1=\"0\" x2=\"200\" y2=\"200\" style=\"stroke:rgb(255,0,0);stroke-width:2\" /></svg>", "x": 10, "y": 20, "z": 0}'
POST /v2/projects/e82d1e06-4235-41dc-9a57-0cd0672692f0/shapes HTTP/1.1
POST /v2/projects/e82d1e06-4235-41dc-9a57-0cd0672692f0/drawings HTTP/1.1
{
"svg": "<svg height=\"210\" width=\"500\"><line x1=\"0\" y1=\"0\" x2=\"200\" y2=\"200\" style=\"stroke:rgb(255,0,0);stroke-width:2\" /></svg>",
"x": 10,
@ -14,12 +14,12 @@ CONTENT-LENGTH: 321
CONTENT-TYPE: application/json
DATE: Thu, 08 Jan 2015 16:09:15 GMT
SERVER: Python/3.5 GNS3/2.0.0dev1
X-ROUTE: /v2/projects/{project_id}/shapes
X-ROUTE: /v2/projects/{project_id}/drawings
{
"project_id": "e82d1e06-4235-41dc-9a57-0cd0672692f0",
"rotation": 0,
"shape_id": "66594d41-524e-4dfd-a07b-3186ed392c86",
"drawing_id": "66594d41-524e-4dfd-a07b-3186ed392c86",
"svg": "<svg height=\"210\" width=\"500\"><line x1=\"0\" y1=\"0\" x2=\"200\" y2=\"200\" style=\"stroke:rgb(255,0,0);stroke-width:2\" /></svg>",
"x": 10,
"y": 20,

@ -1,6 +1,6 @@
curl -i -X PUT 'http://localhost:3080/v2/projects/2312697c-8e49-40e0-9d6d-5bd6dd0679ed/shapes/dbefe2d5-79d7-4f18-b898-cd073224563c' -d '{"x": 42}'
curl -i -X PUT 'http://localhost:3080/v2/projects/2312697c-8e49-40e0-9d6d-5bd6dd0679ed/drawings/dbefe2d5-79d7-4f18-b898-cd073224563c' -d '{"x": 42}'
PUT /v2/projects/2312697c-8e49-40e0-9d6d-5bd6dd0679ed/shapes/dbefe2d5-79d7-4f18-b898-cd073224563c HTTP/1.1
PUT /v2/projects/2312697c-8e49-40e0-9d6d-5bd6dd0679ed/drawings/dbefe2d5-79d7-4f18-b898-cd073224563c HTTP/1.1
{
"x": 42
}
@ -11,12 +11,12 @@ CONTENT-LENGTH: 321
CONTENT-TYPE: application/json
DATE: Thu, 08 Jan 2015 16:09:15 GMT
SERVER: Python/3.5 GNS3/2.0.0dev1
X-ROUTE: /v2/projects/{project_id}/shapes/{shape_id}
X-ROUTE: /v2/projects/{project_id}/drawings/{drawing_id}
{
"project_id": "2312697c-8e49-40e0-9d6d-5bd6dd0679ed",
"rotation": 0,
"shape_id": "dbefe2d5-79d7-4f18-b898-cd073224563c",
"drawing_id": "dbefe2d5-79d7-4f18-b898-cd073224563c",
"svg": "<svg height=\"210\" width=\"500\"><line x1=\"0\" y1=\"0\" x2=\"200\" y2=\"200\" style=\"stroke:rgb(255,0,0);stroke-width:2\" /></svg>",
"x": 42,
"y": 20,

@ -1,8 +1,8 @@
Shape
Drawing
-----------------------------
.. toctree::
:glob:
:maxdepth: 2
shape/*
drawing/*

@ -1,11 +1,11 @@
/v2/projects/{project_id}/shapes
/v2/projects/{project_id}/drawings
------------------------------------------------------------------------------------------------------------------------------------------
.. contents::
GET /v2/projects/**{project_id}**/shapes
GET /v2/projects/**{project_id}**/drawings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
List shapes of a project
List drawings of a project
Parameters
**********
@ -13,18 +13,18 @@ Parameters
Response status codes
**********************
- **200**: List of shapes returned
- **200**: List of drawings returned
Sample session
***************
.. literalinclude:: ../../../examples/controller_get_projectsprojectidshapes.txt
.. literalinclude:: ../../../examples/controller_get_projectsprojectiddrawings.txt
POST /v2/projects/**{project_id}**/shapes
POST /v2/projects/**{project_id}**/drawings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Create a new shape instance
Create a new drawing instance
Parameters
**********
@ -33,7 +33,7 @@ Parameters
Response status codes
**********************
- **400**: Invalid request
- **201**: Shape created
- **201**: Drawing created
Input
*******
@ -43,8 +43,8 @@ Input
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>project_id</td> <td> </td> <td>string</td> <td>Project UUID</td> </tr>
<tr><td>rotation</td> <td> </td> <td>integer</td> <td>Rotation of the element</td> </tr>
<tr><td>shape_id</td> <td> </td> <td>string</td> <td>Shape UUID</td> </tr>
<tr><td>svg</td> <td> </td> <td>string</td> <td>SVG content of the shape</td> </tr>
<tr><td>drawing_id</td> <td> </td> <td>string</td> <td>Drawing UUID</td> </tr>
<tr><td>svg</td> <td> </td> <td>string</td> <td>SVG content of the drawing</td> </tr>
<tr><td>x</td> <td> </td> <td>integer</td> <td>X property</td> </tr>
<tr><td>y</td> <td> </td> <td>integer</td> <td>Y property</td> </tr>
<tr><td>z</td> <td> </td> <td>integer</td> <td>Z property</td> </tr>
@ -58,8 +58,8 @@ Output
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>project_id</td> <td> </td> <td>string</td> <td>Project UUID</td> </tr>
<tr><td>rotation</td> <td> </td> <td>integer</td> <td>Rotation of the element</td> </tr>
<tr><td>shape_id</td> <td> </td> <td>string</td> <td>Shape UUID</td> </tr>
<tr><td>svg</td> <td> </td> <td>string</td> <td>SVG content of the shape</td> </tr>
<tr><td>drawing_id</td> <td> </td> <td>string</td> <td>Drawing UUID</td> </tr>
<tr><td>svg</td> <td> </td> <td>string</td> <td>SVG content of the drawing</td> </tr>
<tr><td>x</td> <td> </td> <td>integer</td> <td>X property</td> </tr>
<tr><td>y</td> <td> </td> <td>integer</td> <td>Y property</td> </tr>
<tr><td>z</td> <td> </td> <td>integer</td> <td>Z property</td> </tr>
@ -69,5 +69,5 @@ Sample session
***************
.. literalinclude:: ../../../examples/controller_post_projectsprojectidshapes.txt
.. literalinclude:: ../../../examples/controller_post_projectsprojectiddrawings.txt

@ -1,21 +1,21 @@
/v2/projects/{project_id}/shapes/{shape_id}
/v2/projects/{project_id}/drawings/{drawing_id}
------------------------------------------------------------------------------------------------------------------------------------------
.. contents::
PUT /v2/projects/**{project_id}**/shapes/**{shape_id}**
PUT /v2/projects/**{project_id}**/drawings/**{drawing_id}**
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Create a new shape instance
Create a new drawing instance
Parameters
**********
- **project_id**: Project UUID
- **shape_id**: Shape UUID
- **drawing_id**: Drawing UUID
Response status codes
**********************
- **400**: Invalid request
- **201**: Shape updated
- **201**: Drawing updated
Input
*******
@ -25,8 +25,8 @@ Input
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>project_id</td> <td> </td> <td>string</td> <td>Project UUID</td> </tr>
<tr><td>rotation</td> <td> </td> <td>integer</td> <td>Rotation of the element</td> </tr>
<tr><td>shape_id</td> <td> </td> <td>string</td> <td>Shape UUID</td> </tr>
<tr><td>svg</td> <td> </td> <td>string</td> <td>SVG content of the shape</td> </tr>
<tr><td>drawing_id</td> <td> </td> <td>string</td> <td>Drawing UUID</td> </tr>
<tr><td>svg</td> <td> </td> <td>string</td> <td>SVG content of the drawing</td> </tr>
<tr><td>x</td> <td> </td> <td>integer</td> <td>X property</td> </tr>
<tr><td>y</td> <td> </td> <td>integer</td> <td>Y property</td> </tr>
<tr><td>z</td> <td> </td> <td>integer</td> <td>Z property</td> </tr>
@ -40,8 +40,8 @@ Output
<tr> <th>Name</th> <th>Mandatory</th> <th>Type</th> <th>Description</th> </tr>
<tr><td>project_id</td> <td> </td> <td>string</td> <td>Project UUID</td> </tr>
<tr><td>rotation</td> <td> </td> <td>integer</td> <td>Rotation of the element</td> </tr>
<tr><td>shape_id</td> <td> </td> <td>string</td> <td>Shape UUID</td> </tr>
<tr><td>svg</td> <td> </td> <td>string</td> <td>SVG content of the shape</td> </tr>
<tr><td>drawing_id</td> <td> </td> <td>string</td> <td>Drawing UUID</td> </tr>
<tr><td>svg</td> <td> </td> <td>string</td> <td>SVG content of the drawing</td> </tr>
<tr><td>x</td> <td> </td> <td>integer</td> <td>X property</td> </tr>
<tr><td>y</td> <td> </td> <td>integer</td> <td>Y property</td> </tr>
<tr><td>z</td> <td> </td> <td>integer</td> <td>Z property</td> </tr>
@ -51,26 +51,26 @@ Sample session
***************
.. literalinclude:: ../../../examples/controller_put_projectsprojectidshapesshapeid.txt
.. literalinclude:: ../../../examples/controller_put_projectsprojectiddrawingsdrawingid.txt
DELETE /v2/projects/**{project_id}**/shapes/**{shape_id}**
DELETE /v2/projects/**{project_id}**/drawings/**{drawing_id}**
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Delete a shape instance
Delete a drawing instance
Parameters
**********
- **project_id**: Project UUID
- **shape_id**: Shape UUID
- **drawing_id**: Drawing UUID
Response status codes
**********************
- **400**: Invalid request
- **204**: Shape deleted
- **204**: Drawing deleted
Sample session
***************
.. literalinclude:: ../../../examples/controller_delete_projectsprojectidshapesshapeid.txt
.. literalinclude:: ../../../examples/controller_delete_projectsprojectiddrawingsdrawingid.txt

@ -225,7 +225,7 @@ This will display a red square in the middle of your topologies:
.. code-block:: shell-session
# curl -X POST "http://localhost:3080/v2/projects/b8c070f7-f34c-4b7b-ba6f-be3d26ed073f/shapes" -d '{"x":0, "y": 12, "svg": "<svg width=\"50\" height=\"50\"><rect width=\"50\" height=\"50\" style=\"fill: #ff0000\"></rect></svg>"}'
# curl -X POST "http://localhost:3080/v2/projects/b8c070f7-f34c-4b7b-ba6f-be3d26ed073f/drawings" -d '{"x":0, "y": 12, "svg": "<svg width=\"50\" height=\"50\"><rect width=\"50\" height=\"50\" style=\"fill: #ff0000\"></rect></svg>"}'
Tips: you can embed png/jpg... by using a base64 encoding in the SVG.

@ -108,9 +108,9 @@ The available notification are:
* link.created
* link.updated
* link.deleted
* shape.created
* shape.updated
* shape.deleted
* drawing.created
* drawing.updated
* drawing.deleted
* log.error
* log.warning
* log.info

@ -6,10 +6,10 @@ Node
A Virtual Machine (Dynamips, IOU, Qemu, VPCS...), a cloud, a builtin device (switch, hub...)
Shape
Drawing
-----
Shape are visual element not used by the network emulation. Like
Drawing are visual element not used by the network emulation. Like
text, images, rectangle... They are pure SVG elements.
Adapter

@ -19,19 +19,19 @@ import asyncio
import uuid
class Shape:
class Drawing:
"""
Shape are visual element not used by the network emulation. Like
Drawing are visual element not used by the network emulation. Like
text, images, rectangle... They are pure SVG elements.
"""
def __init__(self, project, shape_id=None, svg="<svg></svg>", x=0, y=0, z=0, rotation=0):
def __init__(self, project, drawing_id=None, svg="<svg></svg>", x=0, y=0, z=0, rotation=0):
self.svg = svg
self._project = project
if shape_id is None:
if drawing_id is None:
self._id = str(uuid.uuid4())
else:
self._id = shape_id
self._id = drawing_id
self._x = x
self._y = y
self._z = z
@ -84,9 +84,9 @@ class Shape:
@asyncio.coroutine
def update(self, **kwargs):
"""
Update the shape
Update the drawing
:param kwargs: Shape properties
:param kwargs: Drawing properties
"""
# Update node properties with additional elements
@ -100,7 +100,7 @@ class Shape:
data = self.__json__()
if not svg_changed:
del data["svg"]
self._project.controller.notification.emit("shape.updated", data)
self._project.controller.notification.emit("drawing.updated", data)
self._project.dump()
def __json__(self, topology_dump=False):
@ -109,7 +109,7 @@ class Shape:
"""
if topology_dump:
return {
"shape_id": self._id,
"drawing_id": self._id,
"x": self._x,
"y": self._y,
"z": self._z,
@ -118,7 +118,7 @@ class Shape:
}
return {
"project_id": self._project.id,
"shape_id": self._id,
"drawing_id": self._id,
"x": self._x,
"y": self._y,
"z": self._z,
@ -127,4 +127,4 @@ class Shape:
}
def __repr__(self):
return "<gns3server.controller.Shape {}>".format(self._id)
return "<gns3server.controller.Drawing {}>".format(self._id)

@ -24,7 +24,7 @@ import shutil
from uuid import UUID, uuid4
from .node import Node
from .shape import Shape
from .drawing import Drawing
from .topology import project_to_topology, load_topology
from .udp_link import UDPLink
from ..config import Config
@ -77,7 +77,7 @@ class Project:
self._allocated_node_names = set()
self._nodes = {}
self._links = {}
self._shapes = {}
self._drawings = {}
# Create the project on demand on the compute node
self._project_created_on_compute = set()
@ -266,42 +266,42 @@ class Project:
return self._nodes
@property
def shapes(self):
def drawings(self):
"""
:returns: Dictionary of the shapes
:returns: Dictionary of the drawings
"""
return self._shapes
return self._drawings
@asyncio.coroutine
def add_shape(self, shape_id=None, **kwargs):
def add_drawing(self, drawing_id=None, **kwargs):
"""
Create an shape or return an existing shape
Create an drawing or return an existing drawing
:param kwargs: See the documentation of shape
:param kwargs: See the documentation of drawing
"""
if shape_id not in self._shapes:
shape = Shape(self, shape_id=shape_id, **kwargs)
self._shapes[shape.id] = shape
self.controller.notification.emit("shape.created", shape.__json__())
if drawing_id not in self._drawings:
drawing = Drawing(self, drawing_id=drawing_id, **kwargs)
self._drawings[drawing.id] = drawing
self.controller.notification.emit("drawing.created", drawing.__json__())
self.dump()
return shape
return self._shapes[shape_id]
return drawing
return self._drawings[drawing_id]
def get_shape(self, shape_id):
def get_drawing(self, drawing_id):
"""
Return the Shape or raise a 404 if the shape is unknown
Return the Drawing or raise a 404 if the drawing is unknown
"""
try:
return self._shapes[shape_id]
return self._drawings[drawing_id]
except KeyError:
raise aiohttp.web.HTTPNotFound(text="Shape ID {} doesn't exist".format(shape_id))
raise aiohttp.web.HTTPNotFound(text="Drawing ID {} doesn't exist".format(drawing_id))
@asyncio.coroutine
def delete_shape(self, shape_id):
shape = self.get_shape(shape_id)
del self._shapes[shape.id]
def delete_drawing(self, drawing_id):
drawing = self.get_drawing(drawing_id)
del self._drawings[drawing.id]
self.dump()
self.controller.notification.emit("shape.deleted", shape.__json__())
self.controller.notification.emit("drawing.deleted", drawing.__json__())
@asyncio.coroutine
def add_link(self, link_id=None):
@ -397,8 +397,8 @@ class Project:
node = self.get_node(node_link["node_id"])
yield from link.add_node(node, node_link["adapter_number"], node_link["port_number"])
for shape_data in topology.get("shapes", []):
shape = yield from self.add_shape(**shape_data)
for drawing_data in topology.get("drawings", []):
drawing = yield from self.add_drawing(**drawing_data)
self._status = "opened"
def dump(self):

@ -34,7 +34,7 @@ def project_to_topology(project):
"nodes": [],
"links": [],
"computes": [],
"shapes": []
"drawings": []
},
"type": "topology",
"revision": GNS3_FILE_FORMAT_REVISION,
@ -47,8 +47,8 @@ def project_to_topology(project):
data["topology"]["nodes"].append(node.__json__(topology_dump=True))
for link in project.links.values():
data["topology"]["links"].append(link.__json__(topology_dump=True))
for shape in project.shapes.values():
data["topology"]["shapes"].append(shape.__json__(topology_dump=True))
for drawing in project.drawings.values():
data["topology"]["drawings"].append(drawing.__json__(topology_dump=True))
for compute in computes:
if hasattr(compute, "__json__"):
data["topology"]["computes"].append(compute.__json__(topology_dump=True))

@ -20,4 +20,4 @@ from .project_handler import ProjectHandler
from .node_handler import NodeHandler
from .link_handler import LinkHandler
from .server_handler import ServerHandler
from .shape_handler import ShapeHandler
from .drawing_handler import DrawingHandler

@ -20,88 +20,88 @@ import aiohttp
from gns3server.web.route import Route
from gns3server.controller import Controller
from gns3server.schemas.shape import (
SHAPE_OBJECT_SCHEMA,
from gns3server.schemas.drawing import (
DRAWING_OBJECT_SCHEMA,
)
class ShapeHandler:
class DrawingHandler:
"""
API entry point for Shape
API entry point for Drawing
"""
@Route.get(
r"/projects/{project_id}/shapes",
r"/projects/{project_id}/drawings",
parameters={
"project_id": "Project UUID"
},
status_codes={
200: "List of shapes returned",
200: "List of drawings returned",
},
description="List shapes of a project")
def list_shapes(request, response):
description="List drawings of a project")
def list_drawings(request, response):
controller = Controller.instance()
project = controller.get_project(request.match_info["project_id"])
response.json([v for v in project.shapes.values()])
response.json([v for v in project.drawings.values()])
@Route.post(
r"/projects/{project_id}/shapes",
r"/projects/{project_id}/drawings",
parameters={
"project_id": "Project UUID"
},
status_codes={
201: "Shape created",
201: "Drawing created",
400: "Invalid request"
},
description="Create a new shape instance",
input=SHAPE_OBJECT_SCHEMA,
output=SHAPE_OBJECT_SCHEMA)
description="Create a new drawing instance",
input=DRAWING_OBJECT_SCHEMA,
output=DRAWING_OBJECT_SCHEMA)
def create(request, response):
controller = Controller.instance()
project = controller.get_project(request.match_info["project_id"])
shape = yield from project.add_shape(**request.json)
drawing = yield from project.add_drawing(**request.json)
response.set_status(201)
response.json(shape)
response.json(drawing)
@Route.put(
r"/projects/{project_id}/shapes/{shape_id}",
r"/projects/{project_id}/drawings/{drawing_id}",
parameters={
"project_id": "Project UUID",
"shape_id": "Shape UUID"
"drawing_id": "Drawing UUID"
},
status_codes={
201: "Shape updated",
201: "Drawing updated",
400: "Invalid request"
},
description="Create a new shape instance",
input=SHAPE_OBJECT_SCHEMA,
output=SHAPE_OBJECT_SCHEMA)
description="Create a new drawing instance",
input=DRAWING_OBJECT_SCHEMA,
output=DRAWING_OBJECT_SCHEMA)
def update(request, response):
controller = Controller.instance()
project = controller.get_project(request.match_info["project_id"])
shape = project.get_shape(request.match_info["shape_id"])
yield from shape.update(**request.json)
drawing = project.get_drawing(request.match_info["drawing_id"])
yield from drawing.update(**request.json)
response.set_status(201)
response.json(shape)
response.json(drawing)
@Route.delete(
r"/projects/{project_id}/shapes/{shape_id}",
r"/projects/{project_id}/drawings/{drawing_id}",
parameters={
"project_id": "Project UUID",
"shape_id": "Shape UUID"
"drawing_id": "Drawing UUID"
},
status_codes={
204: "Shape deleted",
204: "Drawing deleted",
400: "Invalid request"
},
description="Delete a shape instance")
description="Delete a drawing instance")
def delete(request, response):
controller = Controller.instance()
project = controller.get_project(request.match_info["project_id"])
yield from project.delete_shape(request.match_info["shape_id"])
yield from project.delete_drawing(request.match_info["drawing_id"])
response.set_status(204)

@ -16,13 +16,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
SHAPE_OBJECT_SCHEMA = {
DRAWING_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "An shape object",
"description": "An drawing object",
"type": "object",
"properties": {
"shape_id": {
"description": "Shape UUID",
"drawing_id": {
"description": "Drawing UUID",
"type": "string",
"minLength": 36,
"maxLength": 36,
@ -54,7 +54,7 @@ SHAPE_OBJECT_SCHEMA = {
"maximum": 360
},
"svg": {
"description": "SVG content of the shape",
"description": "SVG content of the drawing",
"type": "string",
"pattern": "^<(.|[\r\n])+>$"
}

@ -57,18 +57,18 @@ in futur GNS3 versions.
</table>
<h2>Shapes</h2>
<h2>Drawings</h2>
<table border="1">
<tr>
<th>ID</th>
<th>Position</th>
<th>Content</th>
</tr>
{% for shape in project.shapes.values() %}
{% for drawing in project.drawings.values() %}
<tr>
<td>{{shape.id}}</td>
<td>{{shape.x}}, {{shape.y}}, {{shape.z}}</td>
<td>{{shape.svg}}</td>
<td>{{drawing.id}}</td>
<td>{{drawing.x}}, {{drawing.y}}, {{drawing.z}}</td>
<td>{{drawing.svg}}</td>
</tr>
{% endfor %}
</table>

@ -213,30 +213,30 @@ def test_deleteLink(async_run, project, controller):
assert len(project._links) == 0
def test_addShape(async_run, project, controller):
def test_addDrawing(async_run, project, controller):
controller.notification.emit = MagicMock()
shape = async_run(project.add_shape(None, svg="<svg></svg>"))
assert len(project._shapes) == 1
controller.notification.emit.assert_any_call("shape.created", shape.__json__())
drawing = async_run(project.add_drawing(None, svg="<svg></svg>"))
assert len(project._drawings) == 1
controller.notification.emit.assert_any_call("drawing.created", drawing.__json__())
def test_getShape(async_run, project):
shape = async_run(project.add_shape(None))
assert project.get_shape(shape.id) == shape
def test_getDrawing(async_run, project):
drawing = async_run(project.add_drawing(None))
assert project.get_drawing(drawing.id) == drawing
with pytest.raises(aiohttp.web_exceptions.HTTPNotFound):
project.get_shape("test")
project.get_drawing("test")
def test_deleteShape(async_run, project, controller):
assert len(project._shapes) == 0
shape = async_run(project.add_shape())
assert len(project._shapes) == 1
def test_deleteDrawing(async_run, project, controller):
assert len(project._drawings) == 0
drawing = async_run(project.add_drawing())
assert len(project._drawings) == 1
controller._notification = MagicMock()
async_run(project.delete_shape(shape.id))
controller.notification.emit.assert_any_call("shape.deleted", shape.__json__())
assert len(project._shapes) == 0
async_run(project.delete_drawing(drawing.id))
controller.notification.emit.assert_any_call("drawing.deleted", drawing.__json__())
assert len(project._drawings) == 0
def test_delete(async_run, project, controller):

@ -22,7 +22,7 @@ import uuid
from tests.utils import AsyncioMagicMock
from gns3server.controller.shape import Shape
from gns3server.controller.drawing import Drawing
from gns3server.controller.project import Project
@ -32,25 +32,25 @@ def project(controller, async_run):
@pytest.fixture
def shape(project):
return Shape(project, None, svg="<svg></svg>")
def drawing(project):
return Drawing(project, None, svg="<svg></svg>")
def test_init_without_uuid(project):
shape = Shape(project, None, svg="<svg></svg>")
assert shape.id is not None
drawing = Drawing(project, None, svg="<svg></svg>")
assert drawing.id is not None
def test_init_with_uuid(project):
id = str(uuid.uuid4())
shape = Shape(project, id, svg="<svg></svg>")
assert shape.id == id
drawing = Drawing(project, id, svg="<svg></svg>")
assert drawing.id == id
def test_json(project):
i = Shape(project, None, svg="<svg></svg>")
i = Drawing(project, None, svg="<svg></svg>")
assert i.__json__() == {
"shape_id": i.id,
"drawing_id": i.id,
"project_id": project.id,
"x": i.x,
"y": i.y,
@ -59,7 +59,7 @@ def test_json(project):
"rotation": i.rotation
}
assert i.__json__(topology_dump=True) == {
"shape_id": i.id,
"drawing_id": i.id,
"x": i.x,
"y": i.y,
"z": i.z,
@ -68,22 +68,22 @@ def test_json(project):
}
def test_update(shape, project, async_run, controller):
def test_update(drawing, project, async_run, controller):
controller._notification = AsyncioMagicMock()
project.dump = MagicMock()
async_run(shape.update(x=42, svg="<svg><rect></rect></svg>"))
assert shape.x == 42
async_run(drawing.update(x=42, svg="<svg><rect></rect></svg>"))
assert drawing.x == 42
args, kwargs = controller._notification.emit.call_args
assert args[0] == "shape.updated"
assert args[0] == "drawing.updated"
# JSON
assert args[1]["x"] == 42
assert args[1]["svg"] == "<svg><rect></rect></svg>"
async_run(shape.update(x=12, svg="<svg><rect></rect></svg>"))
assert shape.x == 12
async_run(drawing.update(x=12, svg="<svg><rect></rect></svg>"))
assert drawing.x == 12
args, kwargs = controller._notification.emit.call_args
assert args[0] == "shape.updated"
assert args[0] == "drawing.updated"
# JSON
assert args[1]["x"] == 12
# To avoid spamming client with large data we don't send the svg if the SVG didn't change

@ -38,7 +38,7 @@ def test_project_to_topology_empty(tmpdir):
"nodes": [],
"links": [],
"computes": [],
"shapes": []
"drawings": []
},
"type": "topology",
"version": __version__
@ -58,14 +58,14 @@ def test_basic_topology(tmpdir, async_run, controller):
async_run(link.add_node(node1, 0, 0))
async_run(link.add_node(node2, 0, 0))
shape = async_run(project.add_shape(svg="<svg></svg>"))
drawing = async_run(project.add_drawing(svg="<svg></svg>"))
topo = project_to_topology(project)
assert len(topo["topology"]["nodes"]) == 2
assert node1.__json__(topology_dump=True) in topo["topology"]["nodes"]
assert topo["topology"]["links"][0] == link.__json__(topology_dump=True)
assert topo["topology"]["computes"][0] == compute.__json__(topology_dump=True)
assert topo["topology"]["shapes"][0] == shape.__json__(topology_dump=True)
assert topo["topology"]["drawings"][0] == drawing.__json__(topology_dump=True)
def test_load_topology(tmpdir):
@ -77,7 +77,7 @@ def test_load_topology(tmpdir):
"nodes": [],
"links": [],
"computes": [],
"shapes": []
"drawings": []
},
"type": "topology",
"version": __version__}

@ -30,7 +30,7 @@ from tests.utils import asyncio_patch
from gns3server.handlers.api.controller.project_handler import ProjectHandler
from gns3server.controller import Controller
from gns3server.controller.shape import Shape
from gns3server.controller.drawing import Drawing
@ -39,49 +39,49 @@ def project(http_controller, async_run):
return async_run(Controller.instance().add_project())
def test_create_shape(http_controller, tmpdir, project, async_run):
def test_create_drawing(http_controller, tmpdir, project, async_run):
response = http_controller.post("/projects/{}/shapes".format(project.id), {
response = http_controller.post("/projects/{}/drawings".format(project.id), {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
"x": 10,
"y": 20,
"z": 0
}, example=True)
assert response.status == 201
assert response.json["shape_id"] is not None
assert response.json["drawing_id"] is not None
def test_update_shape(http_controller, tmpdir, project, async_run):
def test_update_drawing(http_controller, tmpdir, project, async_run):
response = http_controller.post("/projects/{}/shapes".format(project.id), {
response = http_controller.post("/projects/{}/drawings".format(project.id), {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
"x": 10,
"y": 20,
"z": 0
},)
response = http_controller.put("/projects/{}/shapes/{}".format(project.id, response.json["shape_id"]), {
response = http_controller.put("/projects/{}/drawings/{}".format(project.id, response.json["drawing_id"]), {
"x": 42,
}, example=True)
assert response.status == 201
assert response.json["x"] == 42
def test_list_shape(http_controller, tmpdir, project, async_run):
response = http_controller.post("/projects/{}/shapes".format(project.id), {
def test_list_drawing(http_controller, tmpdir, project, async_run):
response = http_controller.post("/projects/{}/drawings".format(project.id), {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
"x": 10,
"y": 20,
"z": 0
}, example=False)
response = http_controller.get("/projects/{}/shapes".format(project.id), example=True)
response = http_controller.get("/projects/{}/drawings".format(project.id), example=True)
assert response.status == 200
assert len(response.json) == 1
def test_delete_shape(http_controller, tmpdir, project, async_run):
def test_delete_drawing(http_controller, tmpdir, project, async_run):
shape = Shape(project)
project._shapes = {shape.id: shape}
response = http_controller.delete("/projects/{}/shapes/{}".format(project.id, shape.id), example=True)
drawing = Drawing(project)
project._drawings = {drawing.id: drawing}
response = http_controller.delete("/projects/{}/drawings/{}".format(project.id, drawing.id), example=True)
assert response.status == 204
assert shape.id not in project._shapes
assert drawing.id not in project._drawings

Loading…
Cancel
Save