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
|
||||
- ~~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**
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')))
|
||||
|
|
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):
|
||||
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')))
|
||||
|
|
Loading…
Reference in a new issue