server/tags: make creating tag relations optional

This commit is contained in:
rr- 2016-04-19 00:18:35 +02:00
parent 1597ae7c5c
commit 7263849fac
5 changed files with 111 additions and 36 deletions

58
API.md
View file

@ -13,16 +13,25 @@
2. [API reference](#api-reference)
- 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)
@ -82,6 +91,31 @@ as `/api/`. Values denoted with diamond braces (`<like this>`) 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": [<name1>, <name2>, ...],
"category": <category>,
"implications": [<name1>, <name2>, ...],
"suggestions": [<name1>, <name2>, ...]
"implications": [<name1>, <name2>, ...], // optional
"suggestions": [<name1>, <name2>, ...] // 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
[`<tag-category>` 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
@ -597,6 +632,15 @@ data.
}
```
## Tag category
```json5
{
"name": "character",
"color": "#FF0000", // used to colorize certain tag types in the web client
}
```
## Tag
```json5

View file

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

View file

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

View file

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

View file

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