diff --git a/API.md b/API.md index 71fd66b0..16d3dd54 100644 --- a/API.md +++ b/API.md @@ -13,18 +13,27 @@ 2. [API reference](#api-reference) - - [Listing tags](#listing-tags) - - [Creating tag](#creating-tag) - - [Updating tag](#updating-tag) - - [Getting tag](#getting-tag) - - [Deleting tag](#deleting-tag) - - [Listing users](#listing-users) - - [Creating user](#creating-user) - - [Updating user](#updating-user) - - [Getting user](#getting-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) + - Tag categories + - [Listing tag categories](#listing-tags-category) + - [Creating tag category](#creating-tag-category) + - [Updating tag category](#updating-tag-category) + - [Getting tag category](#getting-tag-category) + - [Deleting tag category](#deleting-tag-category) + - Tags + - [Listing tags](#listing-tags) + - [Creating tag](#creating-tag) + - [Updating tag](#updating-tag) + - [Getting tag](#getting-tag) + - [Deleting tag](#deleting-tag) + - Users + - [Listing users](#listing-users) + - [Creating user](#creating-user) + - [Updating user](#updating-user) + - [Getting user](#getting-user) + - [Deleting user](#deleting-user) + - Password reset + - [Password reset - step 1: mail request](#password-reset---step-2-confirmation) + - [Password reset - step 2: confirmation](#password-reset---step-2-confirmation) 3. [Resources](#resources) @@ -82,6 +91,31 @@ as `/api/`. Values denoted with diamond braces (``) signify variable data. +## Listing tag categories + +Not implemented yet. + + +## Creating tag category + +Not implemented yet. + + +## Updating tag category + +Not implemented yet. + + +## Getting tag category + +Not implemented yet. + + +## Deleting tag category + +Not implemented yet. + + ## Listing tags - **Request** @@ -178,8 +212,8 @@ data. { "names": [, , ...], "category": , - "implications": [, , ...], - "suggestions": [, , ...] + "implications": [, , ...], // optional + "suggestions": [, , ...] // optional } ``` @@ -206,11 +240,12 @@ data. Creates a new tag using specified parameters. Names, suggestions and implications must match `tag_name_regex` from server's configuration. - Category must be one of `tag_categories` from server's configuration. - If specified implied tags or suggested 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 item of - `tag_categories` from server's configuration. + Category must exist and is the same as `name` field within + [`` resource](#tag-category). Suggestions and implications + are optional. If specified implied tags or suggested 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 item of `tag_categories` from server's configuration. ## Updating tag @@ -592,11 +627,20 @@ data. "rankName": "Administrator", // controlled by server's configuration "lastLoginTime": "2016-04-08T20:20:16.570517", "creationTime": "2016-03-28T13:37:01.755461", - "avatarStyle": "gravatar", // "gravatar" or "manual" + "avatarStyle": "gravatar", // "gravatar" or "manual" "avatarUrl": "http://gravatar.com/(...)" } ``` +## Tag category + +```json5 +{ + "name": "character", + "color": "#FF0000", // used to colorize certain tag types in the web client +} +``` + ## Tag ```json5 diff --git a/server/szurubooru/api/tag_api.py b/server/szurubooru/api/tag_api.py index 7287aedd..6fcf1709 100644 --- a/server/szurubooru/api/tag_api.py +++ b/server/szurubooru/api/tag_api.py @@ -40,8 +40,10 @@ class TagListApi(BaseApi): names = ctx.get_param_as_list('names', required=True) category = ctx.get_param_as_string('category', required=True) - suggestions = ctx.get_param_as_list('suggestions', required=True) - implications = ctx.get_param_as_list('implications', required=True) + suggestions = ctx.get_param_as_list( + 'suggestions', required=False, default=[]) + implications = ctx.get_param_as_list( + 'implications', required=False, default=[]) tag = tags.create_tag(names, category, suggestions, implications) ctx.session.add(tag) diff --git a/server/szurubooru/tests/api/test_tag_creating.py b/server/szurubooru/tests/api/test_tag_creating.py index ffc8ccdf..fb36f7a7 100644 --- a/server/szurubooru/tests/api/test_tag_creating.py +++ b/server/szurubooru/tests/api/test_tag_creating.py @@ -70,6 +70,35 @@ def test_creating_simple_tags(test_ctx, fake_datetime): assert_relations(tag.implications, []) assert os.path.exists(os.path.join(config.config['data_dir'], 'tags.json')) +@pytest.mark.parametrize('field', ['names', 'category']) +def test_missing_mandatory_field(test_ctx, field): + input = { + 'names': ['tag1', 'tag2'], + 'category': 'meta', + 'suggestions': [], + 'implications': [], + } + del input[field] + with pytest.raises(errors.ValidationError): + test_ctx.api.post( + test_ctx.context_factory( + input=input, + user=test_ctx.user_factory(rank='regular_user'))) + +@pytest.mark.parametrize('field', ['implications', 'suggestions']) +def test_missing_optional_field(test_ctx, tmpdir, field): + input = { + 'names': ['tag1', 'tag2'], + 'category': 'meta', + 'suggestions': [], + 'implications': [], + } + del input[field] + test_ctx.api.post( + test_ctx.context_factory( + input=input, + user=test_ctx.user_factory(rank='regular_user'))) + def test_duplicating_names(test_ctx): result = test_ctx.api.post( test_ctx.context_factory( @@ -86,7 +115,7 @@ def test_duplicating_names(test_ctx): assert [tag_name.name for tag_name in tag.names] == ['tag1'] def test_trying_to_create_tag_without_names(test_ctx): - with pytest.raises(tags.InvalidNameError): + with pytest.raises(tags.InvalidTagNameError): test_ctx.api.post( test_ctx.context_factory( input={ @@ -99,7 +128,7 @@ def test_trying_to_create_tag_without_names(test_ctx): @pytest.mark.parametrize('names', [['!'], ['x' * 65]]) def test_trying_to_create_tag_with_invalid_name(test_ctx, names): - with pytest.raises(tags.InvalidNameError): + with pytest.raises(tags.InvalidTagNameError): test_ctx.api.post( test_ctx.context_factory( input={ @@ -141,7 +170,7 @@ def test_trying_to_use_existing_name(test_ctx): assert get_tag(test_ctx.session, 'unused') is None def test_trying_to_create_tag_with_invalid_category(test_ctx): - with pytest.raises(tags.InvalidCategoryError): + with pytest.raises(tags.InvalidTagCategoryError): test_ctx.api.post( test_ctx.context_factory( input={ @@ -233,7 +262,7 @@ def test_reusing_suggestions_and_implications(test_ctx): } ]) def test_trying_to_create_tag_with_invalid_relation(test_ctx, input): - with pytest.raises(tags.InvalidNameError): + with pytest.raises(tags.InvalidTagNameError): test_ctx.api.post( test_ctx.context_factory( input=input, user=test_ctx.user_factory(rank='regular_user'))) diff --git a/server/szurubooru/tests/api/test_tag_updating.py b/server/szurubooru/tests/api/test_tag_updating.py index 7c41290a..40b3ee23 100644 --- a/server/szurubooru/tests/api/test_tag_updating.py +++ b/server/szurubooru/tests/api/test_tag_updating.py @@ -127,7 +127,7 @@ def test_trying_to_set_invalid_name(test_ctx, input): test_ctx.session.add( test_ctx.tag_factory(names=['tag1'], category_name='meta')) test_ctx.session.commit() - with pytest.raises(tags.InvalidNameError): + with pytest.raises(tags.InvalidTagNameError): test_ctx.api.put( test_ctx.context_factory( input=input, @@ -151,7 +151,7 @@ def test_trying_to_update_tag_with_invalid_category(test_ctx): test_ctx.session.add( test_ctx.tag_factory(names=['tag1'], category_name='meta')) test_ctx.session.commit() - with pytest.raises(tags.InvalidCategoryError): + with pytest.raises(tags.InvalidTagCategoryError): test_ctx.api.put( test_ctx.context_factory( input={ @@ -234,7 +234,7 @@ def test_trying_to_update_tag_with_invalid_relation(test_ctx, input): test_ctx.session.add( test_ctx.tag_factory(names=['tag'], category_name='meta')) test_ctx.session.commit() - with pytest.raises(tags.InvalidNameError): + with pytest.raises(tags.InvalidTagNameError): test_ctx.api.put( test_ctx.context_factory( input=input, user=test_ctx.user_factory(rank='regular_user')), diff --git a/server/szurubooru/util/tags.py b/server/szurubooru/util/tags.py index 141eb321..3fc73b5a 100644 --- a/server/szurubooru/util/tags.py +++ b/server/szurubooru/util/tags.py @@ -8,15 +8,15 @@ from szurubooru.util import misc class TagNotFoundError(errors.NotFoundError): pass 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 +class InvalidTagNameError(errors.ValidationError): pass +class InvalidTagCategoryError(errors.ValidationError): pass +class RelationError(errors.ValidationError): pass def _verify_name_validity(name): name_regex = config.config['tag_name_regex'] if not re.match(name_regex, name): - raise InvalidNameError('Name must satisfy regex %r.' % name_regex) + raise InvalidTagNameError('Name must satisfy regex %r.' % name_regex) def _get_plain_names(tag): return [tag_name.name for tag_name in tag.names] @@ -105,7 +105,7 @@ def update_category_name(tag, category_name): if not category: category_names = [ name[0] for name in session.query(db.TagCategory.name).all()] - raise InvalidCategoryError( + raise InvalidTagCategoryError( 'Category %r is invalid. Valid categories: %r.' % ( category_name, category_names)) tag.category = category @@ -113,13 +113,13 @@ def update_category_name(tag, category_name): def update_names(tag, names): names = misc.icase_unique(names) if not len(names): - raise InvalidNameError('At least one name must be specified.') + raise InvalidTagNameError('At least one name must be specified.') for name in names: _verify_name_validity(name) expr = sqlalchemy.sql.false() for name in names: if misc.value_exceeds_column_size(name, db.TagName.name): - raise InvalidNameError('Name is too long.') + raise InvalidTagNameError('Name is too long.') expr = expr | db.TagName.name.ilike(name) if tag.tag_id: expr = expr & (db.TagName.tag_id != tag.tag_id)