diff --git a/API.md b/API.md index 4d92239..b230d75 100644 --- a/API.md +++ b/API.md @@ -379,6 +379,7 @@ data. { "names": [, , ...], "category": , + "description": , // optional "implications": [, , ...], // optional "suggestions": [, , ...] // optional } @@ -421,6 +422,7 @@ data. { "names": [, , ...], // optional "category": , // optional + "description": , // optional "implications": [, , ...], // optional "suggestions": [, , ...] // optional } @@ -1514,6 +1516,7 @@ A single tag. Tags are used to let users search for posts. "creationTime": , "lastEditTime": , "usages": , + "description": , "snapshots": [ , , @@ -1534,6 +1537,8 @@ A single tag. Tags are used to let users search for posts. - ``: time the tag was created, formatted as per RFC 3339. - ``: time the tag was edited, formatted as per RFC 3339. - ``: the number of posts the tag was used in. +- ``: the tag description (instructions how to use, history etc.) + The client should render is as Markdown. - ``: a [snapshot resource](#snapshot) that contains the tag's earlier versions. diff --git a/config.yaml.dist b/config.yaml.dist index a623e34..f6a33d8 100644 --- a/config.yaml.dist +++ b/config.yaml.dist @@ -84,6 +84,7 @@ privileges: 'tags:create': regular 'tags:edit:names': power 'tags:edit:category': power + 'tags:edit:description': power 'tags:edit:implications': power 'tags:edit:suggestions': power 'tags:list': regular # note: will be available as data_url/tags.json anyway diff --git a/server/szurubooru/api/tag_api.py b/server/szurubooru/api/tag_api.py index c49ec1b..d3f8c7a 100644 --- a/server/szurubooru/api/tag_api.py +++ b/server/szurubooru/api/tag_api.py @@ -32,6 +32,8 @@ class TagListApi(BaseApi): names = ctx.get_param_as_list('names', required=True) category = ctx.get_param_as_string('category', required=True) + description = ctx.get_param_as_string( + 'description', required=False, default=None) suggestions = ctx.get_param_as_list( 'suggestions', required=False, default=[]) implications = ctx.get_param_as_list( @@ -41,6 +43,7 @@ class TagListApi(BaseApi): _create_if_needed(implications, ctx.user) tag = tags.create_tag(names, category, suggestions, implications) + tags.update_tag_description(tag, description) ctx.session.add(tag) ctx.session.flush() snapshots.save_entity_creation(tag, ctx.user) @@ -63,6 +66,10 @@ class TagDetailApi(BaseApi): auth.verify_privilege(ctx.user, 'tags:edit:category') tags.update_tag_category_name( tag, ctx.get_param_as_string('category')) + if ctx.has_param('description'): + auth.verify_privilege(ctx.user, 'tags:edit:description') + tags.update_tag_description( + tag, ctx.get_param_as_string('description', default=None)) if ctx.has_param('suggestions'): auth.verify_privilege(ctx.user, 'tags:edit:suggestions') suggestions = ctx.get_param_as_list('suggestions') diff --git a/server/szurubooru/db/tag.py b/server/szurubooru/db/tag.py index 3188739..8ba7dbe 100644 --- a/server/szurubooru/db/tag.py +++ b/server/szurubooru/db/tag.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, DateTime, Unicode, ForeignKey +from sqlalchemy import Column, Integer, DateTime, Unicode, UnicodeText, ForeignKey from sqlalchemy.orm import relationship, column_property from sqlalchemy.sql.expression import func, select from szurubooru.db.base import Base @@ -46,6 +46,7 @@ class Tag(Base): 'category_id', Integer, ForeignKey('tag_category.id'), nullable=False, index=True) creation_time = Column('creation_time', DateTime, nullable=False) last_edit_time = Column('last_edit_time', DateTime) + description = Column('description', UnicodeText, default=None) category = relationship('TagCategory', lazy='joined') names = relationship( diff --git a/server/szurubooru/func/tags.py b/server/szurubooru/func/tags.py index 34d63c1..9532f70 100644 --- a/server/szurubooru/func/tags.py +++ b/server/szurubooru/func/tags.py @@ -42,6 +42,7 @@ def serialize_tag(tag, options=None): { 'names': lambda: [tag_name.name for tag_name in tag.names], 'category': lambda: tag.category.name, + 'description': lambda: tag.description, 'creationTime': lambda: tag.creation_time, 'lastEditTime': lambda: tag.last_edit_time, 'usages': lambda: tag.post_count, @@ -218,3 +219,6 @@ def update_tag_suggestions(tag, relations): if _check_name_intersection(_get_plain_names(tag), relations): raise InvalidTagRelationError('Tag cannot suggest itself.') tag.suggestions = get_tags_by_names(relations) + +def update_tag_description(tag, description): + tag.description = description diff --git a/server/szurubooru/migrations/versions/4c526f869323_add_description_to_tags.py b/server/szurubooru/migrations/versions/4c526f869323_add_description_to_tags.py new file mode 100644 index 0000000..3aff3ad --- /dev/null +++ b/server/szurubooru/migrations/versions/4c526f869323_add_description_to_tags.py @@ -0,0 +1,20 @@ +''' +Add description to tags + +Revision ID: 4c526f869323 +Created at: 2016-06-21 17:56:34.979741 +''' + +import sqlalchemy as sa +from alembic import op + +revision = '4c526f869323' +down_revision = '055d0e048fb3' +branch_labels = None +depends_on = None + +def upgrade(): + op.add_column('tag', sa.Column('description', sa.UnicodeText(), nullable=True)) + +def downgrade(): + op.drop_column('tag', 'description') diff --git a/server/szurubooru/tests/api/test_tag_creating.py b/server/szurubooru/tests/api/test_tag_creating.py index 14269f5..666b940 100644 --- a/server/szurubooru/tests/api/test_tag_creating.py +++ b/server/szurubooru/tests/api/test_tag_creating.py @@ -34,6 +34,7 @@ def test_creating_simple_tags(test_ctx, fake_datetime): input={ 'names': ['tag1', 'tag2'], 'category': 'meta', + 'description': 'desc', 'suggestions': [], 'implications': [], }, @@ -43,6 +44,7 @@ def test_creating_simple_tags(test_ctx, fake_datetime): assert result == { 'names': ['tag1', 'tag2'], 'category': 'meta', + 'description': 'desc', 'suggestions': [], 'implications': [], 'creationTime': datetime.datetime(1997, 12, 1), diff --git a/server/szurubooru/tests/api/test_tag_merging.py b/server/szurubooru/tests/api/test_tag_merging.py index 54a4243..e213882 100644 --- a/server/szurubooru/tests/api/test_tag_merging.py +++ b/server/szurubooru/tests/api/test_tag_merging.py @@ -38,6 +38,7 @@ def test_merging_without_usages(test_ctx, fake_datetime): assert result == { 'names': ['target'], 'category': 'meta', + 'description': None, 'suggestions': [], 'implications': [], 'creationTime': datetime.datetime(1996, 1, 1), diff --git a/server/szurubooru/tests/api/test_tag_retrieving.py b/server/szurubooru/tests/api/test_tag_retrieving.py index 20c3003..5c0dc3d 100644 --- a/server/szurubooru/tests/api/test_tag_retrieving.py +++ b/server/szurubooru/tests/api/test_tag_retrieving.py @@ -50,6 +50,7 @@ def test_retrieving_single(test_ctx): assert result == { 'names': ['tag'], 'category': 'dummy', + 'description': None, 'creationTime': datetime.datetime(1996, 1, 1), 'lastEditTime': None, 'suggestions': [], diff --git a/server/szurubooru/tests/api/test_tag_updating.py b/server/szurubooru/tests/api/test_tag_updating.py index 9400fb9..778dcc5 100644 --- a/server/szurubooru/tests/api/test_tag_updating.py +++ b/server/szurubooru/tests/api/test_tag_updating.py @@ -19,6 +19,7 @@ def test_ctx( 'tags:create': db.User.RANK_REGULAR, 'tags:edit:names': db.User.RANK_REGULAR, 'tags:edit:category': db.User.RANK_REGULAR, + 'tags:edit:description': db.User.RANK_REGULAR, 'tags:edit:suggestions': db.User.RANK_REGULAR, 'tags:edit:implications': db.User.RANK_REGULAR, }, @@ -43,6 +44,7 @@ def test_simple_updating(test_ctx, fake_datetime): input={ 'names': ['tag3'], 'category': 'character', + 'description': 'desc', }, user=test_ctx.user_factory(rank=db.User.RANK_REGULAR)), 'tag1') @@ -51,6 +53,7 @@ def test_simple_updating(test_ctx, fake_datetime): assert result == { 'names': ['tag3'], 'category': 'character', + 'description': 'desc', 'suggestions': [], 'implications': [], 'creationTime': datetime.datetime(1996, 1, 1), @@ -91,13 +94,14 @@ def test_trying_to_pass_invalid_input(test_ctx, input, expected_exception): 'tag1') @pytest.mark.parametrize( - 'field', ['names', 'category', 'implications', 'suggestions']) + 'field', ['names', 'category', 'description', 'implications', 'suggestions']) def test_omitting_optional_field(test_ctx, field): db.session.add(test_ctx.tag_factory(names=['tag'], category_name='meta')) db.session.commit() input = { 'names': ['tag1', 'tag2'], 'category': 'meta', + 'description': 'desc', 'suggestions': [], 'implications': [], }