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)
|
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)
|
- [Listing tags](#listing-tags)
|
||||||
- [Creating tag](#creating-tag)
|
- [Creating tag](#creating-tag)
|
||||||
- [Updating tag](#updating-tag)
|
- [Updating tag](#updating-tag)
|
||||||
- [Getting tag](#getting-tag)
|
- [Getting tag](#getting-tag)
|
||||||
- [Deleting tag](#deleting-tag)
|
- [Deleting tag](#deleting-tag)
|
||||||
|
- Users
|
||||||
- [Listing users](#listing-users)
|
- [Listing users](#listing-users)
|
||||||
- [Creating user](#creating-user)
|
- [Creating user](#creating-user)
|
||||||
- [Updating user](#updating-user)
|
- [Updating user](#updating-user)
|
||||||
- [Getting user](#getting-user)
|
- [Getting user](#getting-user)
|
||||||
- [Deleting user](#deleting-user)
|
- [Deleting user](#deleting-user)
|
||||||
|
- Password reset
|
||||||
- [Password reset - step 1: mail request](#password-reset---step-2-confirmation)
|
- [Password reset - step 1: mail request](#password-reset---step-2-confirmation)
|
||||||
- [Password reset - step 2: confirmation](#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.
|
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
|
## Listing tags
|
||||||
- **Request**
|
- **Request**
|
||||||
|
|
||||||
|
@ -178,8 +212,8 @@ data.
|
||||||
{
|
{
|
||||||
"names": [<name1>, <name2>, ...],
|
"names": [<name1>, <name2>, ...],
|
||||||
"category": <category>,
|
"category": <category>,
|
||||||
"implications": [<name1>, <name2>, ...],
|
"implications": [<name1>, <name2>, ...], // optional
|
||||||
"suggestions": [<name1>, <name2>, ...]
|
"suggestions": [<name1>, <name2>, ...] // optional
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -206,11 +240,12 @@ data.
|
||||||
|
|
||||||
Creates a new tag using specified parameters. Names, suggestions and
|
Creates a new tag using specified parameters. Names, suggestions and
|
||||||
implications must match `tag_name_regex` from server's configuration.
|
implications must match `tag_name_regex` from server's configuration.
|
||||||
Category must be one of `tag_categories` from server's configuration.
|
Category must exist and is the same as `name` field within
|
||||||
If specified implied tags or suggested tags do not exist yet, they will
|
[`<tag-category>` resource](#tag-category). Suggestions and implications
|
||||||
be automatically created. Tags created automatically have no implications,
|
are optional. If specified implied tags or suggested tags do not exist yet,
|
||||||
no suggestions, one name and their category is set to the first item of
|
they will be automatically created. Tags created automatically have no
|
||||||
`tag_categories` from server's configuration.
|
implications, no suggestions, one name and their category is set to the
|
||||||
|
first item of `tag_categories` from server's configuration.
|
||||||
|
|
||||||
|
|
||||||
## Updating tag
|
## 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
|
## Tag
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
|
|
|
@ -40,8 +40,10 @@ class TagListApi(BaseApi):
|
||||||
|
|
||||||
names = ctx.get_param_as_list('names', required=True)
|
names = ctx.get_param_as_list('names', required=True)
|
||||||
category = ctx.get_param_as_string('category', required=True)
|
category = ctx.get_param_as_string('category', required=True)
|
||||||
suggestions = ctx.get_param_as_list('suggestions', required=True)
|
suggestions = ctx.get_param_as_list(
|
||||||
implications = ctx.get_param_as_list('implications', required=True)
|
'suggestions', required=False, default=[])
|
||||||
|
implications = ctx.get_param_as_list(
|
||||||
|
'implications', required=False, default=[])
|
||||||
|
|
||||||
tag = tags.create_tag(names, category, suggestions, implications)
|
tag = tags.create_tag(names, category, suggestions, implications)
|
||||||
ctx.session.add(tag)
|
ctx.session.add(tag)
|
||||||
|
|
|
@ -70,6 +70,35 @@ def test_creating_simple_tags(test_ctx, fake_datetime):
|
||||||
assert_relations(tag.implications, [])
|
assert_relations(tag.implications, [])
|
||||||
assert os.path.exists(os.path.join(config.config['data_dir'], 'tags.json'))
|
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):
|
def test_duplicating_names(test_ctx):
|
||||||
result = test_ctx.api.post(
|
result = test_ctx.api.post(
|
||||||
test_ctx.context_factory(
|
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']
|
assert [tag_name.name for tag_name in tag.names] == ['tag1']
|
||||||
|
|
||||||
def test_trying_to_create_tag_without_names(test_ctx):
|
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.api.post(
|
||||||
test_ctx.context_factory(
|
test_ctx.context_factory(
|
||||||
input={
|
input={
|
||||||
|
@ -99,7 +128,7 @@ def test_trying_to_create_tag_without_names(test_ctx):
|
||||||
|
|
||||||
@pytest.mark.parametrize('names', [['!'], ['x' * 65]])
|
@pytest.mark.parametrize('names', [['!'], ['x' * 65]])
|
||||||
def test_trying_to_create_tag_with_invalid_name(test_ctx, names):
|
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.api.post(
|
||||||
test_ctx.context_factory(
|
test_ctx.context_factory(
|
||||||
input={
|
input={
|
||||||
|
@ -141,7 +170,7 @@ def test_trying_to_use_existing_name(test_ctx):
|
||||||
assert get_tag(test_ctx.session, 'unused') is None
|
assert get_tag(test_ctx.session, 'unused') is None
|
||||||
|
|
||||||
def test_trying_to_create_tag_with_invalid_category(test_ctx):
|
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.api.post(
|
||||||
test_ctx.context_factory(
|
test_ctx.context_factory(
|
||||||
input={
|
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):
|
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.api.post(
|
||||||
test_ctx.context_factory(
|
test_ctx.context_factory(
|
||||||
input=input, user=test_ctx.user_factory(rank='regular_user')))
|
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.session.add(
|
||||||
test_ctx.tag_factory(names=['tag1'], category_name='meta'))
|
test_ctx.tag_factory(names=['tag1'], category_name='meta'))
|
||||||
test_ctx.session.commit()
|
test_ctx.session.commit()
|
||||||
with pytest.raises(tags.InvalidNameError):
|
with pytest.raises(tags.InvalidTagNameError):
|
||||||
test_ctx.api.put(
|
test_ctx.api.put(
|
||||||
test_ctx.context_factory(
|
test_ctx.context_factory(
|
||||||
input=input,
|
input=input,
|
||||||
|
@ -151,7 +151,7 @@ def test_trying_to_update_tag_with_invalid_category(test_ctx):
|
||||||
test_ctx.session.add(
|
test_ctx.session.add(
|
||||||
test_ctx.tag_factory(names=['tag1'], category_name='meta'))
|
test_ctx.tag_factory(names=['tag1'], category_name='meta'))
|
||||||
test_ctx.session.commit()
|
test_ctx.session.commit()
|
||||||
with pytest.raises(tags.InvalidCategoryError):
|
with pytest.raises(tags.InvalidTagCategoryError):
|
||||||
test_ctx.api.put(
|
test_ctx.api.put(
|
||||||
test_ctx.context_factory(
|
test_ctx.context_factory(
|
||||||
input={
|
input={
|
||||||
|
@ -234,7 +234,7 @@ def test_trying_to_update_tag_with_invalid_relation(test_ctx, input):
|
||||||
test_ctx.session.add(
|
test_ctx.session.add(
|
||||||
test_ctx.tag_factory(names=['tag'], category_name='meta'))
|
test_ctx.tag_factory(names=['tag'], category_name='meta'))
|
||||||
test_ctx.session.commit()
|
test_ctx.session.commit()
|
||||||
with pytest.raises(tags.InvalidNameError):
|
with pytest.raises(tags.InvalidTagNameError):
|
||||||
test_ctx.api.put(
|
test_ctx.api.put(
|
||||||
test_ctx.context_factory(
|
test_ctx.context_factory(
|
||||||
input=input, user=test_ctx.user_factory(rank='regular_user')),
|
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 TagNotFoundError(errors.NotFoundError): pass
|
||||||
class TagAlreadyExistsError(errors.ValidationError): 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 TagIsInUseError(errors.ValidationError): pass
|
||||||
|
class InvalidTagNameError(errors.ValidationError): pass
|
||||||
|
class InvalidTagCategoryError(errors.ValidationError): pass
|
||||||
|
class RelationError(errors.ValidationError): pass
|
||||||
|
|
||||||
def _verify_name_validity(name):
|
def _verify_name_validity(name):
|
||||||
name_regex = config.config['tag_name_regex']
|
name_regex = config.config['tag_name_regex']
|
||||||
if not re.match(name_regex, name):
|
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):
|
def _get_plain_names(tag):
|
||||||
return [tag_name.name for tag_name in tag.names]
|
return [tag_name.name for tag_name in tag.names]
|
||||||
|
@ -105,7 +105,7 @@ def update_category_name(tag, category_name):
|
||||||
if not category:
|
if not category:
|
||||||
category_names = [
|
category_names = [
|
||||||
name[0] for name in session.query(db.TagCategory.name).all()]
|
name[0] for name in session.query(db.TagCategory.name).all()]
|
||||||
raise InvalidCategoryError(
|
raise InvalidTagCategoryError(
|
||||||
'Category %r is invalid. Valid categories: %r.' % (
|
'Category %r is invalid. Valid categories: %r.' % (
|
||||||
category_name, category_names))
|
category_name, category_names))
|
||||||
tag.category = category
|
tag.category = category
|
||||||
|
@ -113,13 +113,13 @@ def update_category_name(tag, category_name):
|
||||||
def update_names(tag, names):
|
def update_names(tag, names):
|
||||||
names = misc.icase_unique(names)
|
names = misc.icase_unique(names)
|
||||||
if not len(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:
|
for name in names:
|
||||||
_verify_name_validity(name)
|
_verify_name_validity(name)
|
||||||
expr = sqlalchemy.sql.false()
|
expr = sqlalchemy.sql.false()
|
||||||
for name in names:
|
for name in names:
|
||||||
if misc.value_exceeds_column_size(name, db.TagName.name):
|
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)
|
expr = expr | db.TagName.name.ilike(name)
|
||||||
if tag.tag_id:
|
if tag.tag_id:
|
||||||
expr = expr & (db.TagName.tag_id != tag.tag_id)
|
expr = expr & (db.TagName.tag_id != tag.tag_id)
|
||||||
|
|
Loading…
Reference in a new issue