Merge pull request #22 from pkgw/image-editing
Implement basic image editing
This commit is contained in:
Коммит
4a68c86e8b
|
@ -0,0 +1,25 @@
|
|||
ImageApiPermissions
|
||||
===================
|
||||
|
||||
.. currentmodule:: wwt_api_client.constellations.data
|
||||
|
||||
.. autoclass:: ImageApiPermissions
|
||||
:show-inheritance:
|
||||
|
||||
.. rubric:: Methods Summary
|
||||
|
||||
.. autosummary::
|
||||
|
||||
~ImageApiPermissions.from_dict
|
||||
~ImageApiPermissions.from_json
|
||||
~ImageApiPermissions.schema
|
||||
~ImageApiPermissions.to_dict
|
||||
~ImageApiPermissions.to_json
|
||||
|
||||
.. rubric:: Methods Documentation
|
||||
|
||||
.. automethod:: from_dict
|
||||
.. automethod:: from_json
|
||||
.. automethod:: schema
|
||||
.. automethod:: to_dict
|
||||
.. automethod:: to_json
|
|
@ -0,0 +1,25 @@
|
|||
ImageContentPermissions
|
||||
=======================
|
||||
|
||||
.. currentmodule:: wwt_api_client.constellations.data
|
||||
|
||||
.. autoclass:: ImageContentPermissions
|
||||
:show-inheritance:
|
||||
|
||||
.. rubric:: Methods Summary
|
||||
|
||||
.. autosummary::
|
||||
|
||||
~ImageContentPermissions.from_dict
|
||||
~ImageContentPermissions.from_json
|
||||
~ImageContentPermissions.schema
|
||||
~ImageContentPermissions.to_dict
|
||||
~ImageContentPermissions.to_json
|
||||
|
||||
.. rubric:: Methods Documentation
|
||||
|
||||
.. automethod:: from_dict
|
||||
.. automethod:: from_json
|
||||
.. automethod:: schema
|
||||
.. automethod:: to_dict
|
||||
.. automethod:: to_json
|
|
@ -1,20 +1,20 @@
|
|||
ImagePermissions
|
||||
================
|
||||
ImageUpdate
|
||||
===========
|
||||
|
||||
.. currentmodule:: wwt_api_client.constellations.data
|
||||
|
||||
.. autoclass:: ImagePermissions
|
||||
.. autoclass:: ImageUpdate
|
||||
:show-inheritance:
|
||||
|
||||
.. rubric:: Methods Summary
|
||||
|
||||
.. autosummary::
|
||||
|
||||
~ImagePermissions.from_dict
|
||||
~ImagePermissions.from_json
|
||||
~ImagePermissions.schema
|
||||
~ImagePermissions.to_dict
|
||||
~ImagePermissions.to_json
|
||||
~ImageUpdate.from_dict
|
||||
~ImageUpdate.from_json
|
||||
~ImageUpdate.schema
|
||||
~ImageUpdate.to_dict
|
||||
~ImageUpdate.to_json
|
||||
|
||||
.. rubric:: Methods Documentation
|
||||
|
|
@ -14,6 +14,8 @@ ImageClient
|
|||
~ImageClient.imageset_folder
|
||||
~ImageClient.imageset_object
|
||||
~ImageClient.imageset_wtml_url
|
||||
~ImageClient.permissions
|
||||
~ImageClient.update
|
||||
|
||||
.. rubric:: Methods Documentation
|
||||
|
||||
|
@ -21,3 +23,5 @@ ImageClient
|
|||
.. automethod:: imageset_folder
|
||||
.. automethod:: imageset_object
|
||||
.. automethod:: imageset_wtml_url
|
||||
.. automethod:: permissions
|
||||
.. automethod:: update
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
.. _endpoint-DELETE-scene-_id-likes:
|
||||
|
||||
=======================
|
||||
DELETE /scene/:id/likes
|
||||
=======================
|
||||
|
||||
This API attempts to remove a “like” for the specified scene.
|
||||
|
||||
In order to succeed, the request must include a valid session cookie from the
|
||||
WWT Constellations website, and various rate-limiting and self-consistency
|
||||
measures apply.
|
||||
|
||||
|
||||
Request Structure
|
||||
=================
|
||||
|
||||
The request takes no content. The ``:id`` URL parameter gives the ID of the
|
||||
scene to update.
|
||||
|
||||
|
||||
Response Structure
|
||||
==================
|
||||
|
||||
The response is as follows:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"error": $bool // Whether an error occurred
|
||||
"id": $string, // the ID of the scene that was updated
|
||||
"update": $boolean, // whether a like was actually removed
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
.. _endpoint-GET-image-_id-permissions:
|
||||
|
||||
==========================
|
||||
GET /image/:id/permissions
|
||||
==========================
|
||||
|
||||
This API returns JSON capturing the logged-in user's permissions with regards to
|
||||
the specified image.
|
||||
|
||||
The returned information is purely advisory. The final arbiters of what is
|
||||
actually allowed are the checks implemented for the actual APIs. Furthermore, it
|
||||
is always possible that the results of this API call end up being out-of-date by
|
||||
the time that you attempt to act on them. So in almost all circumstances, you
|
||||
should ignore this API and just attempt the operation that you want to attempt,
|
||||
and handle any errors that might happen. This API should only be used to
|
||||
construct UIs that may choose to hide or show certain elements based on the
|
||||
user's permissions.
|
||||
|
||||
|
||||
Request Structure
|
||||
=================
|
||||
|
||||
The request takes no content. The ``:id`` URL parameter gives the ID of the
|
||||
image to query.
|
||||
|
||||
|
||||
Response Structure
|
||||
==================
|
||||
|
||||
The response is as follows:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"error": $bool // Whether an error occurred
|
||||
"id": $string, // the ID of the image that was queried
|
||||
"edit": $boolean, // whether the user can edit the image
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
.. _endpoint-PATCH-image-_id:
|
||||
|
||||
================
|
||||
PATCH /image/:id
|
||||
================
|
||||
|
||||
This API updates various attributes of the specified image.
|
||||
|
||||
|
||||
Authorization
|
||||
=============
|
||||
|
||||
The request must be made by an account that has permissions to make *all* of the
|
||||
requested modifications. If any of the requested modifications are disallowed,
|
||||
the entire operation fails.
|
||||
|
||||
|
||||
Request Structure
|
||||
=================
|
||||
|
||||
The URL parameter ``:id`` is the ID of the image to modify.
|
||||
|
||||
The structure of the request is:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
// New textual note attached to the image
|
||||
"note": $string?,
|
||||
|
||||
// See docs for POST /handle/:handle/image for a description of
|
||||
// the permissions object.
|
||||
"permissions": $object?,
|
||||
}
|
||||
|
||||
For the definition of substructures such as ``permissions``, see
|
||||
:ref:`endpoint-POST-handle-_handle-image`.
|
||||
|
||||
|
||||
Response Structure
|
||||
==================
|
||||
|
||||
The structure of the response is:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"error": $bool // Whether an error occurred
|
||||
}
|
|
@ -41,7 +41,7 @@ The structure of the request is:
|
|||
"width_factor": $number(int),
|
||||
},
|
||||
"permissions": {
|
||||
// Free text giving the copyright statement for this image. Preferred form is
|
||||
// Free plain text giving the copyright statement for this image. Preferred form is
|
||||
// along the lines of "Copyright 2020 Henrietta Swan Leavitt" or "Public
|
||||
// domain". *Please* provide support in higher-level applications to allow
|
||||
// users to input valid information here — the correct information for this
|
||||
|
@ -52,11 +52,14 @@ The structure of the request is:
|
|||
// their duties.
|
||||
"copyright": $string,
|
||||
|
||||
// Free text giving credits to be shown when displaying this image. This is
|
||||
// HTML content giving credits to be shown when displaying this image. This is
|
||||
// different information than the copyright statement, which specifies who
|
||||
// "owns" the image. The credits have no legal significance, except that
|
||||
// some images are licensed in a way that requires that specific credit texts
|
||||
// are shown alongside them.
|
||||
//
|
||||
// Only a subset of HTML is allowed here. Allowed tags are `<a>`, `<b>`, `<br>`,
|
||||
// `<em>`, `<i>`, and `<strong>`.
|
||||
"credits": $string?,
|
||||
|
||||
// The SPDX License Identifier (https://spdx.org/licenses/) of the license
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
.. _endpoint-POST-scene-_id-impressions:
|
||||
|
||||
===========================
|
||||
POST /scene/:id/impressions
|
||||
===========================
|
||||
|
||||
This API attempts to record an impression for the specified scene.
|
||||
|
||||
In order to succeed, the request must include a valid session cookie from the
|
||||
WWT Constellations website, and various rate-limiting measures apply.
|
||||
|
||||
|
||||
Request Structure
|
||||
=================
|
||||
|
||||
The request takes no content. The ``:id`` URL parameter gives the ID of the
|
||||
scene to update.
|
||||
|
||||
|
||||
Response Structure
|
||||
==================
|
||||
|
||||
The response is as follows:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"error": $bool // Whether an error occurred
|
||||
"id": $string, // the ID of the scene that was updated
|
||||
"update": $boolean, // whether an impression was actually recorded
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
.. _endpoint-POST-scene-_id-likes:
|
||||
|
||||
=====================
|
||||
POST /scene/:id/likes
|
||||
=====================
|
||||
|
||||
This API attempts to record a “like” for the specified scene.
|
||||
|
||||
In order to succeed, the request must include a valid session cookie from the
|
||||
WWT Constellations website, and various rate-limiting and self-consistency
|
||||
measures apply.
|
||||
|
||||
|
||||
Request Structure
|
||||
=================
|
||||
|
||||
The request takes no content. The ``:id`` URL parameter gives the ID of the
|
||||
scene to update.
|
||||
|
||||
|
||||
Response Structure
|
||||
==================
|
||||
|
||||
The response is as follows:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"error": $bool // Whether an error occurred
|
||||
"id": $string, // the ID of the scene that was updated
|
||||
"update": $boolean, // whether a like was actually recorded
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
.. _endpoint-POST-session-init:
|
||||
|
||||
==================
|
||||
POST /session/init
|
||||
==================
|
||||
|
||||
This API initializes the WWT Constellations session cookie for session tracking.
|
||||
|
||||
|
||||
Request Structure
|
||||
=================
|
||||
|
||||
The request takes no content.
|
||||
|
||||
|
||||
Response Structure
|
||||
==================
|
||||
|
||||
The response content is vacuous JSON:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"error": false
|
||||
}
|
||||
|
||||
The more important aspect of the response is that it will include a
|
||||
``Set-Cookie`` header that sets a session cookie.
|
|
@ -55,13 +55,19 @@ Constellations API endpoints:
|
|||
endpoints/constellations/get-handle-_handle-stats
|
||||
endpoints/constellations/get-handle-_handle-timeline
|
||||
endpoints/constellations/get-image-_id
|
||||
endpoints/constellations/patch-image-_id
|
||||
endpoints/constellations/get-image-_id-img_wtml
|
||||
endpoints/constellations/get-image-_id-permissions
|
||||
endpoints/constellations/post-images-find-by-legacy-url
|
||||
endpoints/constellations/get-scene-_id
|
||||
endpoints/constellations/patch-scene-_id
|
||||
endpoints/constellations/post-scene-_id-impressions
|
||||
endpoints/constellations/post-scene-_id-likes
|
||||
endpoints/constellations/delete-scene-_id-likes
|
||||
endpoints/constellations/get-scene-_id-permissions
|
||||
endpoints/constellations/get-scene-_id-place_wtml
|
||||
endpoints/constellations/get-scenes-home-timeline
|
||||
endpoints/constellations/post-session-init
|
||||
|
||||
Legacy WWT APIs:
|
||||
|
||||
|
|
|
@ -20,11 +20,13 @@ HandlePermissions
|
|||
HandleSceneStats
|
||||
HandleStats
|
||||
HandleUpdate
|
||||
ImageApiPermissions
|
||||
ImageContentPermissions
|
||||
ImageInfo
|
||||
ImageDisplayInfo
|
||||
ImagePermissions
|
||||
ImageStorage
|
||||
ImageSummary
|
||||
ImageUpdate
|
||||
ImageWwt
|
||||
SceneContent
|
||||
SceneContentHydrated
|
||||
|
@ -156,7 +158,7 @@ class ImageSummary:
|
|||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class ImagePermissions:
|
||||
class ImageContentPermissions:
|
||||
copyright: str
|
||||
credits: Optional[str]
|
||||
license: str
|
||||
|
@ -180,16 +182,34 @@ class ImageDisplayInfo:
|
|||
@dataclass_json
|
||||
@dataclass
|
||||
class ImageInfo:
|
||||
"""Note that this class is *not* what is returned by the
|
||||
``/handle/:handle/imageinfo`` endpoint. That returns a
|
||||
:class:`ImageSummary`."""
|
||||
|
||||
id: str # 24 hex digits
|
||||
handle_id: str # 24 hex digits
|
||||
handle: HandleInfo
|
||||
creation_date: str # format: 2023-03-28T16:53:18.364Z
|
||||
wwt: ImageWwt
|
||||
permissions: ImagePermissions
|
||||
permissions: ImageContentPermissions
|
||||
storage: ImageStorage
|
||||
note: str
|
||||
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class ImageUpdate:
|
||||
note: Optional[str]
|
||||
permissions: Optional[ImageContentPermissions]
|
||||
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class ImageApiPermissions:
|
||||
id: str
|
||||
edit: bool
|
||||
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class ScenePlace:
|
||||
|
|
|
@ -24,7 +24,7 @@ from .data import (
|
|||
HandleStats,
|
||||
HandleUpdate,
|
||||
ImageWwt,
|
||||
ImagePermissions,
|
||||
ImageContentPermissions,
|
||||
ImageStorage,
|
||||
ImageSummary,
|
||||
SceneContent,
|
||||
|
@ -50,7 +50,7 @@ D2R = math.pi / 180
|
|||
@dataclass
|
||||
class AddImageRequest:
|
||||
wwt: ImageWwt
|
||||
permissions: ImagePermissions
|
||||
permissions: ImageContentPermissions
|
||||
storage: ImageStorage
|
||||
note: str
|
||||
|
||||
|
@ -351,7 +351,7 @@ class HandleClient:
|
|||
legacy_url_template=imageset.url,
|
||||
)
|
||||
|
||||
permissions = ImagePermissions(
|
||||
permissions = ImageContentPermissions(
|
||||
copyright=copyright,
|
||||
credits=imageset.credits,
|
||||
license=license_spdx_id,
|
||||
|
|
|
@ -11,7 +11,7 @@ from wwt_data_formats.folder import Folder
|
|||
from wwt_data_formats.imageset import ImageSet
|
||||
|
||||
from . import CxClient
|
||||
from .data import ImageInfo
|
||||
from .data import ImageApiPermissions, ImageInfo, ImageUpdate, _strip_nulls_in_place
|
||||
|
||||
__all__ = """
|
||||
ImageClient
|
||||
|
@ -53,6 +53,23 @@ class ImageClient:
|
|||
resp.pop("error")
|
||||
return ImageInfo.schema().load(resp)
|
||||
|
||||
def permissions(self) -> ImageApiPermissions:
|
||||
"""
|
||||
Get information about the logged-in user's permissions with regards to
|
||||
this image.
|
||||
|
||||
This method corresponds to the :ref:`endpoint-GET-image-_id-permissions`
|
||||
API endpoint. See that documentation for important guidance about when
|
||||
and how to use this API. In most cases you should not use it, and just
|
||||
go ahead and attempt whatever operation wish to perform.
|
||||
"""
|
||||
resp = self.client._send_and_check(
|
||||
self._url_base + "/permissions", http_method="GET"
|
||||
)
|
||||
resp = resp.json()
|
||||
resp.pop("error")
|
||||
return ImageApiPermissions.schema().load(resp)
|
||||
|
||||
def imageset_wtml_url(self) -> str:
|
||||
"""
|
||||
Get a URL that will yield a WTML folder containing this image as an imageset.
|
||||
|
@ -94,3 +111,20 @@ class ImageClient:
|
|||
imageset_wtml_url, imageset_folder
|
||||
"""
|
||||
return self.imageset_folder().children[0]
|
||||
|
||||
def update(self, updates: ImageUpdate):
|
||||
"""
|
||||
Update various attributes of this image.
|
||||
|
||||
This method corresponds to the :ref:`endpoint-PATCH-image-_id` API
|
||||
endpoint.
|
||||
"""
|
||||
resp = self.client._send_and_check(
|
||||
self._url_base,
|
||||
http_method="PATCH",
|
||||
json=_strip_nulls_in_place(updates.to_dict()),
|
||||
)
|
||||
resp = resp.json()
|
||||
resp.pop("error")
|
||||
# Might as well return the response, although it's currently vacuous
|
||||
return resp
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from license_expression import ExpressionError
|
||||
import pytest
|
||||
|
||||
from ..constellations.data import ImagePermissions
|
||||
from ..constellations.data import ImageContentPermissions
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -9,12 +9,12 @@ def valid_permissions_data():
|
|||
return {
|
||||
"copyright": "Some copyright information",
|
||||
"credits": "<strong>Image Credit:</strong> Someone",
|
||||
"license": "BSD-2-Clause"
|
||||
"license": "BSD-2-Clause",
|
||||
}
|
||||
|
||||
|
||||
def test_valid_image_permissions(valid_permissions_data):
|
||||
permissions = ImagePermissions(**valid_permissions_data)
|
||||
permissions = ImageContentPermissions(**valid_permissions_data)
|
||||
|
||||
assert permissions.copyright == valid_permissions_data["copyright"]
|
||||
assert permissions.credits == valid_permissions_data["credits"]
|
||||
|
@ -25,15 +25,14 @@ def test_invalid_permissions_license(valid_permissions_data):
|
|||
with pytest.raises(ExpressionError):
|
||||
permissions_data = valid_permissions_data.copy()
|
||||
permissions_data["license"] = "NO SUCH LICENSE"
|
||||
ImagePermissions(**permissions_data)
|
||||
ImageContentPermissions(**permissions_data)
|
||||
|
||||
|
||||
def test_sanitize_permissions_html(valid_permissions_data):
|
||||
permissions_data = valid_permissions_data.copy()
|
||||
permissions_data["credits"] = "<script>Some JS here</script><a>Credits Link</a>"
|
||||
permissions = ImagePermissions(**permissions_data)
|
||||
permissions = ImageContentPermissions(**permissions_data)
|
||||
|
||||
assert permissions.copyright == permissions_data["copyright"]
|
||||
assert permissions.license == permissions_data["license"]
|
||||
assert permissions.credits == "<a>Credits Link</a>"
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче