aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrr-2016-05-02 21:58:13 +0200
committerrr-2016-05-02 21:58:13 +0200
commite632324f727b6cbd7609e2d6ab16d6f103bcecf5 (patch)
treef95c50d703cf2b2f79563bc8a8d7651a74cf22cf
parent067e438b8cf0483c12ecca4c2d5da3279f477f94 (diff)
server/posts: add post updating
-rw-r--r--API.md58
-rw-r--r--server/szurubooru/api/post_api.py36
-rw-r--r--server/szurubooru/tests/api/test_post_creating.py6
-rw-r--r--server/szurubooru/tests/api/test_post_updating.py115
-rw-r--r--server/szurubooru/tests/api/test_tag_category_creating.py2
5 files changed, 210 insertions, 7 deletions
diff --git a/API.md b/API.md
index 4d52409..2835d3f 100644
--- a/API.md
+++ b/API.md
@@ -30,7 +30,7 @@
- Posts
- ~~Listing posts~~
- [Creating post](#creating-post)
- - ~~Updating post~~
+ - [Updating post](#updating-post)
- [Getting post](#getting-post)
- [Deleting post](#deleting-post)
- [Rating post](#rating-post)
@@ -539,7 +539,7 @@ data.
- **Errors**
- tags have invalid names
- - safety is invalid
+ - safety, notes or flags are invalid
- relations refer to non-existing posts
- privileges are too low
@@ -548,9 +548,57 @@ data.
Creates a new post. If specified tags do not exist yet, they will be
automatically created. Tags created automatically have no implications, no
suggestions, one name and their category is set to the first tag category
- found. Safety must be any of `"safe"`, `"sketchy"` or `"unsafe"`. `<flag>`
- currently can be only `"loop"` to enable looping for video posts. Sending
- empty `thumbnail` will cause the post to use default thumbnail.
+ found. Safety must be any of `"safe"`, `"sketchy"` or `"unsafe"`. Relations
+ must contain valid post IDs. `<flag>` currently can be only `"loop"` to
+ enable looping for video posts. Sending empty `thumbnail` will cause the
+ post to use default thumbnail. All fields are optional - update concerns
+ only provided fields. For details how to pass `content` and `thumbnail`,
+ see [file uploads](#file-uploads) for details.
+
+## Updating post
+- **Request**
+
+ `PUT /post/<id>`
+
+- **Input**
+
+ ```json5
+ {
+ "tags": [<tag1>, <tag2>, <tag3>], // optional
+ "safety": <safety>, // optional
+ "source": <source>, // optional
+ "relations": [<post1>, <post2>, <post3>], // optional
+ "notes": [<note1>, <note2>, <note3>], // optional
+ "flags": [<flag1>, <flag2>] // optional
+ }
+ ```
+
+- **Files**
+
+ - `content` - the content of the content (optional).
+ - `thumbnail` - the content of custom thumbnail (optional).
+
+- **Output**
+
+ A [detailed post resource](#detailed-post).
+
+- **Errors**
+
+ - tags have invalid names
+ - safety, notes or flags are invalid
+ - relations refer to non-existing posts
+ - privileges are too low
+
+- **Description**
+
+ Updates existing post. If specified tags do not exist yet, they will be
+ automatically created. Tags created automatically have no implications, no
+ suggestions, one name and their category is set to the first tag category
+ found. Safety must be any of `"safe"`, `"sketchy"` or `"unsafe"`. Relations
+ must contain valid post IDs. `<flag>` currently can be only `"loop"` to
+ enable looping for video posts. Sending empty `thumbnail` will reset the
+ post thumbnail to default. For details how to pass `content` and
+ `thumbnail`, see [file uploads](#file-uploads) for details.
## Getting post
- **Request**
diff --git a/server/szurubooru/api/post_api.py b/server/szurubooru/api/post_api.py
index a299dc4..6200d70 100644
--- a/server/szurubooru/api/post_api.py
+++ b/server/szurubooru/api/post_api.py
@@ -1,3 +1,4 @@
+import datetime
from szurubooru.api.base_api import BaseApi
from szurubooru.func import auth, tags, posts, snapshots, favorites, scores
@@ -18,6 +19,8 @@ class PostListApi(BaseApi):
posts.update_post_relations(post, relations)
posts.update_post_notes(post, notes)
posts.update_post_flags(post, flags)
+ if ctx.has_file('thumbnail'):
+ posts.update_post_thumbnail(post, ctx.get_file('thumbnail'))
ctx.session.add(post)
snapshots.save_entity_creation(post, ctx.user)
ctx.session.commit()
@@ -30,6 +33,39 @@ class PostDetailApi(BaseApi):
post = posts.get_post_by_id(post_id)
return posts.serialize_post_with_details(post, ctx.user)
+ def put(self, ctx, post_id):
+ post = posts.get_post_by_id(post_id)
+ if ctx.has_file('content'):
+ auth.verify_privilege(ctx.user, 'posts:edit:content')
+ posts.update_post_content(post, ctx.get_file('content'))
+ if ctx.has_param('tags'):
+ auth.verify_privilege(ctx.user, 'posts:edit:tags')
+ posts.update_post_tags(post, ctx.get_param_as_list('tags'))
+ if ctx.has_param('safety'):
+ auth.verify_privilege(ctx.user, 'posts:edit:safety')
+ posts.update_post_safety(post, ctx.get_param_as_string('safety'))
+ if ctx.has_param('source'):
+ auth.verify_privilege(ctx.user, 'posts:edit:source')
+ posts.update_post_source(post, ctx.get_param_as_string('source'))
+ if ctx.has_param('relations'):
+ auth.verify_privilege(ctx.user, 'posts:edit:relations')
+ posts.update_post_relations(post, ctx.get_param_as_list('relations'))
+ if ctx.has_param('notes'):
+ auth.verify_privilege(ctx.user, 'posts:edit:notes')
+ posts.update_post_notes(post, ctx.get_param_as_list('notes'))
+ if ctx.has_param('flags'):
+ auth.verify_privilege(ctx.user, 'posts:edit:flags')
+ posts.update_post_flags(post, ctx.get_param_as_list('flags'))
+ if ctx.has_file('thumbnail'):
+ auth.verify_privilege(ctx.user, 'posts:edit:thumbnail')
+ posts.update_post_thumbnail(post, ctx.get_file('thumbnail'))
+ post.last_edit_time = datetime.datetime.now()
+ ctx.session.flush()
+ snapshots.save_entity_modification(post, ctx.user)
+ ctx.session.commit()
+ tags.export_to_json()
+ return posts.serialize_post_with_details(post, ctx.user)
+
def delete(self, ctx, post_id):
auth.verify_privilege(ctx.user, 'posts:delete')
post = posts.get_post_by_id(post_id)
diff --git a/server/szurubooru/tests/api/test_post_creating.py b/server/szurubooru/tests/api/test_post_creating.py
index 9467506..54e9a49 100644
--- a/server/szurubooru/tests/api/test_post_creating.py
+++ b/server/szurubooru/tests/api/test_post_creating.py
@@ -25,6 +25,7 @@ def test_creating_minimal_posts(
unittest.mock.patch('szurubooru.func.posts.update_post_relations'), \
unittest.mock.patch('szurubooru.func.posts.update_post_notes'), \
unittest.mock.patch('szurubooru.func.posts.update_post_flags'), \
+ unittest.mock.patch('szurubooru.func.posts.update_post_thumbnail'), \
unittest.mock.patch('szurubooru.func.posts.serialize_post_with_details'), \
unittest.mock.patch('szurubooru.func.tags.export_to_json'), \
unittest.mock.patch('szurubooru.func.snapshots.save_entity_creation'):
@@ -40,17 +41,20 @@ def test_creating_minimal_posts(
},
files={
'content': 'post-content',
+ 'thumbnail': 'post-thumbnail',
},
user=auth_user))
assert result == 'serialized post'
posts.create_post.assert_called_once_with(
'post-content', ['tag1', 'tag2'], auth_user)
+ posts.update_post_thumbnail.assert_called_once_with(post, 'post-thumbnail')
posts.update_post_safety.assert_called_once_with(post, 'safe')
posts.update_post_source.assert_called_once_with(post, None)
posts.update_post_relations.assert_called_once_with(post, [])
posts.update_post_notes.assert_called_once_with(post, [])
posts.update_post_flags.assert_called_once_with(post, [])
+ posts.update_post_thumbnail.assert_called_once_with(post, 'post-thumbnail')
posts.serialize_post_with_details.assert_called_once_with(post, auth_user)
tags.export_to_json.assert_called_once_with()
snapshots.save_entity_creation.assert_called_once_with(post, auth_user)
@@ -129,5 +133,5 @@ def test_trying_to_create_without_privileges(context_factory, user_factory):
with pytest.raises(errors.AuthError):
api.PostListApi().post(
context_factory(
- input={'name': 'meta', 'colro': 'black'},
+ input='whatever',
user=user_factory(rank='anonymous')))
diff --git a/server/szurubooru/tests/api/test_post_updating.py b/server/szurubooru/tests/api/test_post_updating.py
new file mode 100644
index 0000000..7498f53
--- /dev/null
+++ b/server/szurubooru/tests/api/test_post_updating.py
@@ -0,0 +1,115 @@
+import datetime
+import os
+import unittest.mock
+import pytest
+from szurubooru import api, db, errors
+from szurubooru.func import posts, tags, snapshots
+
+def test_post_updating(
+ config_injector, context_factory, post_factory, user_factory, fake_datetime):
+ config_injector({
+ 'ranks': ['anonymous', 'regular_user'],
+ 'privileges': {
+ 'posts:edit:tags': 'regular_user',
+ 'posts:edit:content': 'regular_user',
+ 'posts:edit:safety': 'regular_user',
+ 'posts:edit:source': 'regular_user',
+ 'posts:edit:relations': 'regular_user',
+ 'posts:edit:notes': 'regular_user',
+ 'posts:edit:flags': 'regular_user',
+ 'posts:edit:thumbnail': 'regular_user',
+ },
+ })
+ auth_user = user_factory(rank='regular_user')
+ post = post_factory()
+ db.session.add(post)
+ db.session.flush()
+
+ with unittest.mock.patch('szurubooru.func.posts.create_post'), \
+ unittest.mock.patch('szurubooru.func.posts.update_post_tags'), \
+ unittest.mock.patch('szurubooru.func.posts.update_post_content'), \
+ unittest.mock.patch('szurubooru.func.posts.update_post_thumbnail'), \
+ unittest.mock.patch('szurubooru.func.posts.update_post_safety'), \
+ unittest.mock.patch('szurubooru.func.posts.update_post_source'), \
+ unittest.mock.patch('szurubooru.func.posts.update_post_relations'), \
+ unittest.mock.patch('szurubooru.func.posts.update_post_notes'), \
+ unittest.mock.patch('szurubooru.func.posts.update_post_flags'), \
+ unittest.mock.patch('szurubooru.func.posts.serialize_post_with_details'), \
+ unittest.mock.patch('szurubooru.func.tags.export_to_json'), \
+ unittest.mock.patch('szurubooru.func.snapshots.save_entity_modification'):
+
+ posts.serialize_post_with_details.return_value = 'serialized post'
+
+ with fake_datetime('1997-01-01'):
+ result = api.PostDetailApi().put(
+ context_factory(
+ input={
+ 'safety': 'safe',
+ 'tags': ['tag1', 'tag2'],
+ 'relations': [1, 2],
+ 'source': 'source',
+ 'notes': ['note1', 'note2'],
+ 'flags': ['flag1', 'flag2'],
+ },
+ files={
+ 'content': 'post-content',
+ 'thumbnail': 'post-thumbnail',
+ },
+ user=auth_user),
+ post.post_id)
+
+ assert result == 'serialized post'
+ posts.create_post.assert_not_called()
+ posts.update_post_tags.assert_called_once_with(post, ['tag1', 'tag2'])
+ posts.update_post_content.assert_called_once_with(post, 'post-content')
+ posts.update_post_thumbnail.assert_called_once_with(post, 'post-thumbnail')
+ posts.update_post_safety.assert_called_once_with(post, 'safe')
+ posts.update_post_source.assert_called_once_with(post, 'source')
+ posts.update_post_relations.assert_called_once_with(post, [1, 2])
+ posts.update_post_notes.assert_called_once_with(post, ['note1', 'note2'])
+ posts.update_post_flags.assert_called_once_with(post, ['flag1', 'flag2'])
+ posts.serialize_post_with_details.assert_called_once_with(post, auth_user)
+ tags.export_to_json.assert_called_once_with()
+ snapshots.save_entity_modification.assert_called_once_with(post, auth_user)
+ assert post.last_edit_time == datetime.datetime(1997, 1, 1)
+
+def test_trying_to_update_non_existing(context_factory, user_factory):
+ with pytest.raises(posts.PostNotFoundError):
+ api.PostDetailApi().put(
+ context_factory(
+ input='whatever',
+ user=user_factory(rank='regular_user')),
+ 1)
+
+@pytest.mark.parametrize('privilege,files,input', [
+ ('posts:edit:tags', {}, {'tags': '...'}),
+ ('posts:edit:safety', {}, {'safety': '...'}),
+ ('posts:edit:source', {}, {'source': '...'}),
+ ('posts:edit:relations', {}, {'relations': '...'}),
+ ('posts:edit:notes', {}, {'notes': '...'}),
+ ('posts:edit:flags', {}, {'flags': '...'}),
+ ('posts:edit:content', {'content': '...'}, {}),
+ ('posts:edit:thumbnail', {'thumbnail': '...'}, {}),
+])
+def test_trying_to_create_without_privileges(
+ config_injector,
+ context_factory,
+ post_factory,
+ user_factory,
+ files,
+ input,
+ privilege):
+ config_injector({
+ 'ranks': ['anonymous', 'regular_user'],
+ 'privileges': {privilege: 'regular_user'},
+ })
+ post = post_factory()
+ db.session.add(post)
+ db.session.flush()
+ with pytest.raises(errors.AuthError):
+ api.PostDetailApi().put(
+ context_factory(
+ input=input,
+ files=files,
+ user=user_factory(rank='anonymous')),
+ post.post_id)
diff --git a/server/szurubooru/tests/api/test_tag_category_creating.py b/server/szurubooru/tests/api/test_tag_category_creating.py
index dcc0a7a..ed4b944 100644
--- a/server/szurubooru/tests/api/test_tag_category_creating.py
+++ b/server/szurubooru/tests/api/test_tag_category_creating.py
@@ -84,5 +84,5 @@ def test_trying_to_create_without_privileges(test_ctx):
with pytest.raises(errors.AuthError):
test_ctx.api.post(
test_ctx.context_factory(
- input={'name': 'meta', 'colro': 'black'},
+ input={'name': 'meta', 'color': 'black'},
user=test_ctx.user_factory(rank='anonymous')))

© 2015 - 2026 Jakob L. Kreuze