diff --git a/API.md b/API.md index 6ad5d9d..fe02c43 100644 --- a/API.md +++ b/API.md @@ -17,12 +17,12 @@ - [Creating tag](#creating-tag) - [Updating tag](#updating-tag) - [Getting tag](#getting-tag) - - [Removing tag](#removing-tag) + - [Deleting tag](#deleting-tag) - [Listing users](#listing-users) - [Creating user](#creating-user) - [Updating user](#updating-user) - [Getting user](#getting-user) - - [Removing user](#removing-user) + - [Deleting user](#deleting-user) - [Password reset - step 1: mail request](#password-reset---step-2-confirmation) - [Password reset - step 2: confirmation](#password-reset---step-2-confirmation) @@ -183,8 +183,26 @@ Not yet implemented. Not yet implemented. -## Removing tag -Not yet implemented. +## Deleting tag +- **Request** + + `DELETE /tag/` + +- **Output** + + ```json5 + {} + ``` + +- **Errors** + + - the tag does not exist + - the tag is used by some posts + - privileges are too low + +- **Description** + + Deletes existing tag. ## Listing users @@ -253,6 +271,7 @@ Not yet implemented. None. + ## Creating user - **Request** @@ -293,7 +312,6 @@ Not yet implemented. `default_rank` in the server's configuration. - ## Updating user - **Request** @@ -345,7 +363,6 @@ Not yet implemented. file - see [file uploads](#file-uploads) for details. - ## Getting user - **Request** @@ -370,8 +387,7 @@ Not yet implemented. Retrieves information about an existing user. - -## Removing user +## Deleting user - **Request** `DELETE /user/` @@ -392,7 +408,6 @@ Not yet implemented. Deletes existing user. - ## Password reset - step 1: mail request - **Request** @@ -418,7 +433,6 @@ Not yet implemented. account. - ## Password reset - step 2: confirmation - **Request** @@ -483,6 +497,8 @@ Not yet implemented. } ``` + + # Search Search queries are built of tokens that are separated by spaces. Each token can diff --git a/server/szurubooru/api/tag_api.py b/server/szurubooru/api/tag_api.py index 365c0ce..8d7353d 100644 --- a/server/szurubooru/api/tag_api.py +++ b/server/szurubooru/api/tag_api.py @@ -64,5 +64,16 @@ class TagDetailApi(BaseApi): ctx.session.commit() return {'tag': _serialize_tag(tag)} - def delete(self, ctx): - raise NotImplementedError() + def delete(self, ctx, tag_name): + tag = tags.get_by_name(ctx.session, tag_name) + if not tag: + raise tags.TagNotFoundError('Tag %r not found.' % tag_name) + if tag.post_count > 0: + raise tags.TagIsInUseError( + 'Tag has some usages and cannot be deleted. ' + + 'Please untag relevant posts first.') + + auth.verify_privilege(ctx.user, 'tags:delete') + ctx.session.delete(tag) + ctx.session.commit() + return {} diff --git a/server/szurubooru/tests/api/test_tag_deleting.py b/server/szurubooru/tests/api/test_tag_deleting.py new file mode 100644 index 0000000..7c7fcb6 --- /dev/null +++ b/server/szurubooru/tests/api/test_tag_deleting.py @@ -0,0 +1,59 @@ +import pytest +from datetime import datetime +from szurubooru import api, db, errors +from szurubooru.util import misc, tags + +@pytest.fixture +def test_ctx( + session, config_injector, context_factory, tag_factory, user_factory): + config_injector({ + 'privileges': { + 'tags:delete': 'regular_user', + }, + 'ranks': ['anonymous', 'regular_user'], + }) + ret = misc.dotdict() + ret.session = session + ret.context_factory = context_factory + ret.user_factory = user_factory + ret.tag_factory = tag_factory + ret.api = api.TagDetailApi() + return ret + +def test_removing_tags(test_ctx): + test_ctx.session.add(test_ctx.tag_factory(names=['tag'])) + test_ctx.session.commit() + result = test_ctx.api.delete( + test_ctx.context_factory( + user=test_ctx.user_factory(rank='regular_user')), + 'tag') + assert result == {} + assert test_ctx.session.query(db.Tag).count() == 0 + +def test_removing_tags_without_privileges(test_ctx): + test_ctx.session.add(test_ctx.tag_factory(names=['tag'])) + test_ctx.session.commit() + with pytest.raises(errors.AuthError): + test_ctx.api.delete( + test_ctx.context_factory( + user=test_ctx.user_factory(rank='anonymous')), + 'tag') + assert test_ctx.session.query(db.Tag).count() == 1 + +def test_removing_tags_with_usages(test_ctx): + tag = test_ctx.tag_factory(names=['tag']) + tag.post_count = 5 + test_ctx.session.add(tag) + test_ctx.session.commit() + with pytest.raises(tags.TagIsInUseError): + test_ctx.api.delete( + test_ctx.context_factory( + user=test_ctx.user_factory(rank='regular_user')), + 'tag') + assert test_ctx.session.query(db.Tag).count() == 1 + +def test_removing_non_existing(test_ctx): + with pytest.raises(tags.TagNotFoundError): + test_ctx.api.delete( + test_ctx.context_factory( + user=test_ctx.user_factory(rank='regular_user')), 'bad') diff --git a/server/szurubooru/tests/api/test_user_deletion.py b/server/szurubooru/tests/api/test_user_deleting.py similarity index 100% rename from server/szurubooru/tests/api/test_user_deletion.py rename to server/szurubooru/tests/api/test_user_deleting.py diff --git a/server/szurubooru/tests/api/test_user_retrieval.py b/server/szurubooru/tests/api/test_user_retrieving.py similarity index 100% rename from server/szurubooru/tests/api/test_user_retrieval.py rename to server/szurubooru/tests/api/test_user_retrieving.py diff --git a/server/szurubooru/tests/api/test_user_updating.py b/server/szurubooru/tests/api/test_user_updating.py index 1ed0a95..c3c0074 100644 --- a/server/szurubooru/tests/api/test_user_updating.py +++ b/server/szurubooru/tests/api/test_user_updating.py @@ -149,7 +149,6 @@ def test_mods_trying_to_become_admin(test_ctx): def test_uploading_avatar(test_ctx, tmpdir): config.config['data_dir'] = str(tmpdir.mkdir('data')) config.config['data_url'] = 'http://example.com/data/' - user = test_ctx.user_factory(name='u1', rank='mod') test_ctx.session.add(user) empty_pixel = \ diff --git a/server/szurubooru/util/tags.py b/server/szurubooru/util/tags.py index fe0fe66..9ae2c48 100644 --- a/server/szurubooru/util/tags.py +++ b/server/szurubooru/util/tags.py @@ -9,6 +9,7 @@ class TagAlreadyExistsError(errors.ValidationError): pass class InvalidNameError(errors.ValidationError): pass class InvalidCategoryError(errors.ValidationError): pass class RelationError(errors.ValidationError): pass +class TagIsInUseError(errors.ValidationError): pass def _verify_name_validity(name): name_regex = config.config['tag_name_regex']