server/posts: add post updating
This commit is contained in:
parent
067e438b8c
commit
e632324f72
5 changed files with 210 additions and 7 deletions
58
API.md
58
API.md
|
@ -30,7 +30,7 @@
|
||||||
- Posts
|
- Posts
|
||||||
- ~~Listing posts~~
|
- ~~Listing posts~~
|
||||||
- [Creating post](#creating-post)
|
- [Creating post](#creating-post)
|
||||||
- ~~Updating post~~
|
- [Updating post](#updating-post)
|
||||||
- [Getting post](#getting-post)
|
- [Getting post](#getting-post)
|
||||||
- [Deleting post](#deleting-post)
|
- [Deleting post](#deleting-post)
|
||||||
- [Rating post](#rating-post)
|
- [Rating post](#rating-post)
|
||||||
|
@ -539,7 +539,7 @@ data.
|
||||||
- **Errors**
|
- **Errors**
|
||||||
|
|
||||||
- tags have invalid names
|
- tags have invalid names
|
||||||
- safety is invalid
|
- safety, notes or flags are invalid
|
||||||
- relations refer to non-existing posts
|
- relations refer to non-existing posts
|
||||||
- privileges are too low
|
- privileges are too low
|
||||||
|
|
||||||
|
@ -548,9 +548,57 @@ data.
|
||||||
Creates a new post. If specified tags do not exist yet, they will be
|
Creates a new post. If specified tags do not exist yet, they will be
|
||||||
automatically created. Tags created automatically have no implications, no
|
automatically created. Tags created automatically have no implications, no
|
||||||
suggestions, one name and their category is set to the first tag category
|
suggestions, one name and their category is set to the first tag category
|
||||||
found. Safety must be any of `"safe"`, `"sketchy"` or `"unsafe"`. `<flag>`
|
found. Safety must be any of `"safe"`, `"sketchy"` or `"unsafe"`. Relations
|
||||||
currently can be only `"loop"` to enable looping for video posts. Sending
|
must contain valid post IDs. `<flag>` currently can be only `"loop"` to
|
||||||
empty `thumbnail` will cause the post to use default thumbnail.
|
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
|
## Getting post
|
||||||
- **Request**
|
- **Request**
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import datetime
|
||||||
from szurubooru.api.base_api import BaseApi
|
from szurubooru.api.base_api import BaseApi
|
||||||
from szurubooru.func import auth, tags, posts, snapshots, favorites, scores
|
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_relations(post, relations)
|
||||||
posts.update_post_notes(post, notes)
|
posts.update_post_notes(post, notes)
|
||||||
posts.update_post_flags(post, flags)
|
posts.update_post_flags(post, flags)
|
||||||
|
if ctx.has_file('thumbnail'):
|
||||||
|
posts.update_post_thumbnail(post, ctx.get_file('thumbnail'))
|
||||||
ctx.session.add(post)
|
ctx.session.add(post)
|
||||||
snapshots.save_entity_creation(post, ctx.user)
|
snapshots.save_entity_creation(post, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
|
@ -30,6 +33,39 @@ class PostDetailApi(BaseApi):
|
||||||
post = posts.get_post_by_id(post_id)
|
post = posts.get_post_by_id(post_id)
|
||||||
return posts.serialize_post_with_details(post, ctx.user)
|
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):
|
def delete(self, ctx, post_id):
|
||||||
auth.verify_privilege(ctx.user, 'posts:delete')
|
auth.verify_privilege(ctx.user, 'posts:delete')
|
||||||
post = posts.get_post_by_id(post_id)
|
post = posts.get_post_by_id(post_id)
|
||||||
|
|
|
@ -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_relations'), \
|
||||||
unittest.mock.patch('szurubooru.func.posts.update_post_notes'), \
|
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_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.posts.serialize_post_with_details'), \
|
||||||
unittest.mock.patch('szurubooru.func.tags.export_to_json'), \
|
unittest.mock.patch('szurubooru.func.tags.export_to_json'), \
|
||||||
unittest.mock.patch('szurubooru.func.snapshots.save_entity_creation'):
|
unittest.mock.patch('szurubooru.func.snapshots.save_entity_creation'):
|
||||||
|
@ -40,17 +41,20 @@ def test_creating_minimal_posts(
|
||||||
},
|
},
|
||||||
files={
|
files={
|
||||||
'content': 'post-content',
|
'content': 'post-content',
|
||||||
|
'thumbnail': 'post-thumbnail',
|
||||||
},
|
},
|
||||||
user=auth_user))
|
user=auth_user))
|
||||||
|
|
||||||
assert result == 'serialized post'
|
assert result == 'serialized post'
|
||||||
posts.create_post.assert_called_once_with(
|
posts.create_post.assert_called_once_with(
|
||||||
'post-content', ['tag1', 'tag2'], auth_user)
|
'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_safety.assert_called_once_with(post, 'safe')
|
||||||
posts.update_post_source.assert_called_once_with(post, None)
|
posts.update_post_source.assert_called_once_with(post, None)
|
||||||
posts.update_post_relations.assert_called_once_with(post, [])
|
posts.update_post_relations.assert_called_once_with(post, [])
|
||||||
posts.update_post_notes.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_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)
|
posts.serialize_post_with_details.assert_called_once_with(post, auth_user)
|
||||||
tags.export_to_json.assert_called_once_with()
|
tags.export_to_json.assert_called_once_with()
|
||||||
snapshots.save_entity_creation.assert_called_once_with(post, auth_user)
|
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):
|
with pytest.raises(errors.AuthError):
|
||||||
api.PostListApi().post(
|
api.PostListApi().post(
|
||||||
context_factory(
|
context_factory(
|
||||||
input={'name': 'meta', 'colro': 'black'},
|
input='whatever',
|
||||||
user=user_factory(rank='anonymous')))
|
user=user_factory(rank='anonymous')))
|
||||||
|
|
115
server/szurubooru/tests/api/test_post_updating.py
Normal file
115
server/szurubooru/tests/api/test_post_updating.py
Normal file
|
@ -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)
|
|
@ -84,5 +84,5 @@ def test_trying_to_create_without_privileges(test_ctx):
|
||||||
with pytest.raises(errors.AuthError):
|
with pytest.raises(errors.AuthError):
|
||||||
test_ctx.api.post(
|
test_ctx.api.post(
|
||||||
test_ctx.context_factory(
|
test_ctx.context_factory(
|
||||||
input={'name': 'meta', 'colro': 'black'},
|
input={'name': 'meta', 'color': 'black'},
|
||||||
user=test_ctx.user_factory(rank='anonymous')))
|
user=test_ctx.user_factory(rank='anonymous')))
|
||||||
|
|
Loading…
Reference in a new issue