server/posts: add post updating

This commit is contained in:
rr- 2016-05-02 21:58:13 +02:00
parent 067e438b8c
commit e632324f72
5 changed files with 210 additions and 7 deletions

58
API.md
View file

@ -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**

View file

@ -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)

View file

@ -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')))

View 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)

View file

@ -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')))