server/tags: make creating tag relations optional
This commit is contained in:
parent
1597ae7c5c
commit
7263849fac
5 changed files with 111 additions and 36 deletions
58
API.md
58
API.md
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')))
|
||||
|
|
|
@ -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')),
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue