aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--API.md129
-rw-r--r--server/szurubooru/api/__init__.py11
-rw-r--r--server/szurubooru/api/post_api.py58
-rw-r--r--server/szurubooru/api/user_api.py13
-rw-r--r--server/szurubooru/app.py2
-rw-r--r--server/szurubooru/db/post.py20
-rw-r--r--server/szurubooru/func/posts.py25
-rw-r--r--server/szurubooru/func/snapshots.py21
-rw-r--r--server/szurubooru/migrations/versions/84bd402f15f0_change_flags_column_type.py22
-rw-r--r--server/szurubooru/tests/api/test_post_featuring.py82
-rw-r--r--server/szurubooru/tests/conftest.py4
-rw-r--r--server/szurubooru/tests/db/test_post.py2
-rw-r--r--server/szurubooru/tests/func/test_snapshots.py59
13 files changed, 429 insertions, 19 deletions
diff --git a/API.md b/API.md
index 94b3f26..b301016 100644
--- a/API.md
+++ b/API.md
@@ -27,6 +27,16 @@
- [Deleting tag](#deleting-tag)
- [Merging tags](#merging-tags)
- [Listing tag siblings](#listing-tag-siblings)
+ - Posts
+ - ~~Listing posts~~
+ - ~~Creating post~~
+ - ~~Updating post~~
+ - ~~Getting post~~
+ - ~~Deleting post~~
+ - ~~Scoring posts~~
+ - ~~Adding posts to favorites~~
+ - ~~Removing posts from favorites~~
+ - [Featuring post](#featuring-post)
- Users
- [Listing users](#listing-users)
- [Creating user](#creating-user)
@@ -44,6 +54,7 @@
- [User](#user)
- [Tag category](#tag-category)
- [Tag](#tag)
+ - [Post](#post)
- [Snapshot](#snapshot)
4. [Search](#search)
@@ -590,6 +601,36 @@ data.
list is truncated to the first 50 elements. Doesn't use paging.
+## Featuring post
+- **Request**
+
+ `POST /featured-post`
+
+- **Output**
+
+ ```json5
+ {
+ "post": <post>,
+ "snapshots": [
+ <snapshot>,
+ <snapshot>,
+ <snapshot>
+ ]
+ }
+ ```
+ ...where `<post>` is a [post resource](#post), and `snapshots` contain its
+ earlier versions.
+
+- **Errors**
+
+ - privileges are too low
+ - trying to feature a post that is currently featured
+
+- **Description**
+
+ Features a post on the main page.
+
+
## Listing users
- **Request**
@@ -1007,7 +1048,76 @@ A single tag. Tags are used to let users search for posts.
- `<suggestions>`: a list of suggested tag names. Suggested tags are shown to
the user by the web client on usage.
- `<creation-time>`: time the tag was created, formatted as per RFC 3339.
-- `<creation-time>`: time the tag was edited, formatted as per RFC 3339.
+- `<last-edit-time>`: time the tag was edited, formatted as per RFC 3339.
+
+## Post
+**Description**
+
+One file together with its metadata posted to the site.
+
+**Structure**
+
+```json5
+{
+ "id": <id>,
+ "safety": <safety>,
+ "type": <type>,
+ "checksum": <checksum>,
+ "source": <source>,
+ "canvasWidth": <canvas-width>,
+ "canvasHeight": <canvas-height>,
+ "flags": <flags>,
+ "tags": <tags>,
+ "relations": <relations>,
+ "creationTime": <creation-time>,
+ "lastEditTime": <last-edit-time>,
+ "user": <user>,
+ "score": <score>,
+ "favoritedBy": <favorited-by>,
+ "featureCount": <feature-count>,
+ "lastFeatureTime": <last-feature-time>,
+}
+```
+
+**Field meaning**
+
+- `<id>`: the post identifier.
+- `<safety>`: whether the post is safe for work.
+
+ Available values:
+
+ - `"safe"`
+ - `"sketchy"`
+ - `"unsafe"`
+
+- `<type>`: the type of the post.
+
+ Available values:
+
+ - `"image"` - plain image.
+ - `"animation"` - animated image (GIF).
+ - `"video"` - WEBM video.
+ - `"flash"` - Flash animation / game.
+ - `"youtube"` - Youtube embed.
+
+- `<checksum>`: the file checksum. Used in snapshots to signify changes of the
+ post content.
+- `<source>`: where the post was grabbed form, supplied by the user.
+- `<canvas-width>` and `<canvas-height>`: the original width and height of the
+ post content.
+- `<flags>`: various flags such as whether the post is looped, represented as
+ array of plain strings.
+- `<tags>`: list of tag names the post is tagged with.
+- `<relations>`: a list of related post IDs. Links to related posts are shown
+ to the user by the web client.
+- `<creation-time>`: time the tag was created, formatted as per RFC 3339.
+- `<last-edit-time>`: time the tag was edited, formatted as per RFC 3339.
+- `<user>`: who created the post, serialized as [user resource](#user).
+- `<score>`: the score (+1/-1 rating) of the given post.
+- `<favorited-by>`: list of users, serialized as [user resources](#user).
+- `<feature-count>`: how many times has the post been featured.
+- `<last-feature-time>`: the last time the post was featured, formatted as per
+ RFC 3339.
## Snapshot
**Description**
@@ -1078,6 +1188,23 @@ A snapshot is a version of a database resource.
}
```
+ - Post snapshot data (`<resource-type> = "post"`)
+
+ *Example*
+
+ ```json5
+ {
+ "source": "http://example.com/",
+ "safety": "safe",
+ "checksum": "deadbeef",
+ "tags": ["tag1", "tag2"],
+ "relations": [1, 2],
+ "notes": [{"polygon": [[1,1],[200,1],[200,200],[1,200]], "text": "..."}],
+ "flags": ["loop"],
+ "featured": false
+ }
+ ```
+
- `<earlier-data>`: `<data>` field from the last snapshot of the same resource.
This allows the client to create visual diffs for any given snapshot without
the need to know any other snapshots for a given resource.
diff --git a/server/szurubooru/api/__init__.py b/server/szurubooru/api/__init__.py
index fd68915..3548ecd 100644
--- a/server/szurubooru/api/__init__.py
+++ b/server/szurubooru/api/__init__.py
@@ -2,8 +2,15 @@
from szurubooru.api.password_reset_api import PasswordResetApi
from szurubooru.api.user_api import UserListApi, UserDetailApi
-from szurubooru.api.tag_api import TagListApi, TagDetailApi, TagMergeApi, TagSiblingsApi
-from szurubooru.api.tag_category_api import TagCategoryListApi, TagCategoryDetailApi
+from szurubooru.api.tag_api import (
+ TagListApi,
+ TagDetailApi,
+ TagMergeApi,
+ TagSiblingsApi)
+from szurubooru.api.tag_category_api import (
+ TagCategoryListApi,
+ TagCategoryDetailApi)
+from szurubooru.api.post_api import PostFeatureApi
from szurubooru.api.snapshot_api import SnapshotListApi
from szurubooru.api.info_api import InfoApi
from szurubooru.api.context import Context, Request
diff --git a/server/szurubooru/api/post_api.py b/server/szurubooru/api/post_api.py
new file mode 100644
index 0000000..e38c84f
--- /dev/null
+++ b/server/szurubooru/api/post_api.py
@@ -0,0 +1,58 @@
+from szurubooru.api.base_api import BaseApi
+from szurubooru.api.user_api import serialize_user
+from szurubooru.func import auth, posts, snapshots
+
+def serialize_post(post, authenticated_user):
+ ret = {
+ 'id': post.post_id,
+ 'creationTime': post.creation_time,
+ 'lastEditTime': post.last_edit_time,
+ 'safety': post.safety,
+ 'type': post.type,
+ 'checksum': post.checksum,
+ 'source': post.source,
+ 'fileSize': post.file_size,
+ 'canvasWidth': post.canvas_width,
+ 'canvasHeight': post.canvas_height,
+ 'flags': post.flags,
+ 'tags': [tag.first_name for tag in post.tags],
+ 'relations': [rel.post_id for rel in post.relations],
+ 'notes': sorted([{
+ 'path': note.path,
+ 'text': note.text,
+ } for note in post.notes]),
+ 'user': serialize_user(post.user, authenticated_user),
+ 'score': post.score,
+ 'featureCount': post.feature_count,
+ 'lastFeatureTime': post.last_feature_time,
+ 'favoritedBy': [serialize_user(rel, authenticated_user) \
+ for rel in post.favorited_by],
+ }
+
+ # TODO: fetch own score if needed
+
+ return ret
+
+def serialize_post_with_details(post, authenticated_user):
+ return {
+ 'post': serialize_post(post, authenticated_user),
+ 'snapshots': snapshots.get_serialized_history(post),
+ }
+
+class PostFeatureApi(BaseApi):
+ def post(self, ctx):
+ auth.verify_privilege(ctx.user, 'posts:feature')
+ post_id = ctx.get_param_as_int('id', required=True)
+ post = posts.get_post_by_id(post_id)
+ if not post:
+ raise posts.PostNotFoundError('Post %r not found.' % post_id)
+ featured_post = posts.get_featured_post()
+ if featured_post and featured_post.post_id == post.post_id:
+ raise posts.PostAlreadyFeaturedError(
+ 'Post %r is already featured.' % post_id)
+ posts.feature_post(post, ctx.user)
+ if featured_post:
+ snapshots.modify(featured_post, ctx.user)
+ snapshots.modify(post, ctx.user)
+ ctx.session.commit()
+ return serialize_post_with_details(post, ctx.user)
diff --git a/server/szurubooru/api/user_api.py b/server/szurubooru/api/user_api.py
index d1ee389..2164eaa 100644
--- a/server/szurubooru/api/user_api.py
+++ b/server/szurubooru/api/user_api.py
@@ -3,7 +3,10 @@ from szurubooru import config, search
from szurubooru.api.base_api import BaseApi
from szurubooru.func import auth, users
-def _serialize_user(authenticated_user, user):
+def serialize_user(user, authenticated_user):
+ if not user:
+ return {}
+
ret = {
'name': user.name,
'rank': user.rank,
@@ -36,7 +39,7 @@ class UserListApi(BaseApi):
def get(self, ctx):
auth.verify_privilege(ctx.user, 'users:list')
return self._search_executor.execute_and_serialize(
- ctx, lambda user: _serialize_user(ctx.user, user), 'users')
+ ctx, lambda user: serialize_user(user, ctx.user), 'users')
def post(self, ctx):
auth.verify_privilege(ctx.user, 'users:create')
@@ -58,7 +61,7 @@ class UserListApi(BaseApi):
ctx.session.add(user)
ctx.session.commit()
- return {'user': _serialize_user(ctx.user, user)}
+ return {'user': serialize_user(user, ctx.user)}
class UserDetailApi(BaseApi):
def get(self, ctx, user_name):
@@ -66,7 +69,7 @@ class UserDetailApi(BaseApi):
user = users.get_user_by_name(user_name)
if not user:
raise users.UserNotFoundError('User %r not found.' % user_name)
- return {'user': _serialize_user(ctx.user, user)}
+ return {'user': serialize_user(user, ctx.user)}
def put(self, ctx, user_name):
user = users.get_user_by_name(user_name)
@@ -102,7 +105,7 @@ class UserDetailApi(BaseApi):
ctx.get_file('avatar'))
ctx.session.commit()
- return {'user': _serialize_user(ctx.user, user)}
+ return {'user': serialize_user(user, ctx.user)}
def delete(self, ctx, user_name):
user = users.get_user_by_name(user_name)
diff --git a/server/szurubooru/app.py b/server/szurubooru/app.py
index b6c285c..729351e 100644
--- a/server/szurubooru/app.py
+++ b/server/szurubooru/app.py
@@ -55,6 +55,7 @@ def create_app():
tag_detail_api = api.TagDetailApi()
tag_merge_api = api.TagMergeApi()
tag_siblings_api = api.TagSiblingsApi()
+ post_feature_api = api.PostFeatureApi()
password_reset_api = api.PasswordResetApi()
snapshot_list_api = api.SnapshotListApi()
info_api = api.InfoApi()
@@ -77,5 +78,6 @@ def create_app():
app.add_route('/password-reset/{user_name}', password_reset_api)
app.add_route('/snapshots/', snapshot_list_api)
app.add_route('/info/', info_api)
+ app.add_route('/featured-post/', post_feature_api)
return app
diff --git a/server/szurubooru/db/post.py b/server/szurubooru/db/post.py
index 85e4476..3c1972a 100644
--- a/server/szurubooru/db/post.py
+++ b/server/szurubooru/db/post.py
@@ -1,5 +1,5 @@
from sqlalchemy import Column, Integer, DateTime, String, Text, PickleType, ForeignKey
-from sqlalchemy.orm import relationship, column_property
+from sqlalchemy.orm import relationship, column_property, object_session
from sqlalchemy.sql.expression import func, select
from szurubooru.db.base import Base
@@ -87,9 +87,9 @@ class Post(Base):
checksum = Column('checksum', String(64), nullable=False)
source = Column('source', String(200))
file_size = Column('file_size', Integer)
- image_width = Column('image_width', Integer)
- image_height = Column('image_height', Integer)
- flags = Column('flags', Integer, nullable=False, default=0)
+ canvas_width = Column('image_width', Integer)
+ canvas_height = Column('image_height', Integer)
+ flags = Column('flags', PickleType, default=None)
user = relationship('User')
tags = relationship('Tag', backref='posts', secondary='post_tag')
@@ -102,7 +102,7 @@ class Post(Base):
'PostFeature', cascade='all, delete-orphan', lazy='joined')
scores = relationship(
'PostScore', cascade='all, delete-orphan', lazy='joined')
- favorites = relationship(
+ favorited_by = relationship(
'PostFavorite', cascade='all, delete-orphan', lazy='joined')
notes = relationship(
'PostNote', cascade='all, delete-orphan', lazy='joined')
@@ -112,8 +112,16 @@ class Post(Base):
.where(PostTag.post_id == post_id) \
.correlate_except(PostTag))
+ @property
+ def is_featured(self):
+ featured_post = object_session(self) \
+ .query(PostFeature) \
+ .order_by(PostFeature.time.desc()) \
+ .first()
+ return featured_post and featured_post.post_id == self.post_id
+
# TODO: wire these
- fav_count = Column('auto_fav_count', Integer, nullable=False, default=0)
+ favorite_count = Column('auto_fav_count', Integer, nullable=False, default=0)
score = Column('auto_score', Integer, nullable=False, default=0)
feature_count = Column('auto_feature_count', Integer, nullable=False, default=0)
comment_count = Column('auto_comment_count', Integer, nullable=False, default=0)
diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py
index 027cf5e..f84cd26 100644
--- a/server/szurubooru/func/posts.py
+++ b/server/szurubooru/func/posts.py
@@ -1,5 +1,28 @@
+import datetime
import sqlalchemy
-from szurubooru import db
+from szurubooru import db, errors
+
+class PostNotFoundError(errors.NotFoundError): pass
+class PostAlreadyFeaturedError(errors.ValidationError): pass
def get_post_count():
return db.session.query(sqlalchemy.func.count(db.Post.post_id)).one()[0]
+
+def get_post_by_id(post_id):
+ return db.session.query(db.Post) \
+ .filter(db.Post.post_id == post_id) \
+ .one_or_none()
+
+def get_featured_post():
+ post_feature = db.session \
+ .query(db.PostFeature) \
+ .order_by(db.PostFeature.time.desc()) \
+ .first()
+ return post_feature.post if post_feature else None
+
+def feature_post(post, user):
+ post_feature = db.PostFeature()
+ post_feature.time = datetime.datetime.now()
+ post_feature.post = post
+ post_feature.user = user
+ db.session.add(post_feature)
diff --git a/server/szurubooru/func/snapshots.py b/server/szurubooru/func/snapshots.py
index 3caf8cd..bbb13d8 100644
--- a/server/szurubooru/func/snapshots.py
+++ b/server/szurubooru/func/snapshots.py
@@ -3,13 +3,27 @@ from sqlalchemy.inspection import inspect
from szurubooru import db
def get_tag_snapshot(tag):
- ret = {
+ return {
'names': [tag_name.name for tag_name in tag.names],
'category': tag.category.name,
'suggestions': sorted(rel.first_name for rel in tag.suggestions),
'implications': sorted(rel.first_name for rel in tag.implications),
}
- return ret
+
+def get_post_snapshot(post):
+ return {
+ 'source': post.source,
+ 'safety': post.safety,
+ 'checksum': post.checksum,
+ 'tags': sorted([tag.first_name for tag in post.tags]),
+ 'relations': sorted([rel.post_id for rel in post.relations]),
+ 'notes': sorted([{
+ 'polygon': note.polygon,
+ 'text': note.text,
+ } for note in post.notes]),
+ 'flags': post.flags,
+ 'featured': post.is_featured,
+ }
def get_tag_category_snapshot(category):
return {
@@ -25,6 +39,9 @@ serializers = {
'tag_category': (
get_tag_category_snapshot,
lambda category: category.name),
+ 'post': (
+ get_post_snapshot,
+ lambda post: post.post_id),
}
def get_resource_info(entity):
diff --git a/server/szurubooru/migrations/versions/84bd402f15f0_change_flags_column_type.py b/server/szurubooru/migrations/versions/84bd402f15f0_change_flags_column_type.py
new file mode 100644
index 0000000..adc040e
--- /dev/null
+++ b/server/szurubooru/migrations/versions/84bd402f15f0_change_flags_column_type.py
@@ -0,0 +1,22 @@
+'''
+Change flags column type
+
+Revision ID: 84bd402f15f0
+Created at: 2016-04-22 20:48:32.386159
+'''
+
+import sqlalchemy as sa
+from alembic import op
+
+revision = '84bd402f15f0'
+down_revision = '9587de88a84b'
+branch_labels = None
+depends_on = None
+
+def upgrade():
+ op.drop_column('post', 'flags')
+ op.add_column('post', sa.Column('flags', sa.PickleType(), nullable=True))
+
+def downgrade():
+ op.drop_column('post', 'flags')
+ op.add_column('post', sa.Column('flags', sa.Integer(), autoincrement=False, nullable=False))
diff --git a/server/szurubooru/tests/api/test_post_featuring.py b/server/szurubooru/tests/api/test_post_featuring.py
new file mode 100644
index 0000000..1172fc2
--- /dev/null
+++ b/server/szurubooru/tests/api/test_post_featuring.py
@@ -0,0 +1,82 @@
+import datetime
+import pytest
+from szurubooru import api, db, errors
+from szurubooru.func import util, posts
+
+@pytest.fixture
+def test_ctx(context_factory, config_injector, user_factory, post_factory):
+ config_injector({
+ 'privileges': {'posts:feature': 'regular_user'},
+ 'ranks': ['anonymous', 'regular_user'],
+ })
+ ret = util.dotdict()
+ ret.context_factory = context_factory
+ ret.user_factory = user_factory
+ ret.post_factory = post_factory
+ ret.api = api.PostFeatureApi()
+ return ret
+
+def test_featuring(test_ctx):
+ db.session.add(test_ctx.post_factory(id=1))
+ db.session.commit()
+ assert posts.get_featured_post() is None
+ assert not posts.get_post_by_id(1).is_featured
+ result = test_ctx.api.post(
+ test_ctx.context_factory(
+ input={'id': 1},
+ user=test_ctx.user_factory(rank='regular_user')))
+ assert posts.get_featured_post() is not None
+ assert posts.get_featured_post().post_id == 1
+ assert posts.get_post_by_id(1).is_featured
+ assert 'post' in result
+ assert 'snapshots' in result
+ assert 'id' in result['post']
+
+def test_trying_to_feature_the_same_post_twice(test_ctx):
+ db.session.add(test_ctx.post_factory(id=1))
+ db.session.commit()
+ test_ctx.api.post(
+ test_ctx.context_factory(
+ input={'id': 1},
+ user=test_ctx.user_factory(rank='regular_user')))
+ with pytest.raises(posts.PostAlreadyFeaturedError):
+ test_ctx.api.post(
+ test_ctx.context_factory(
+ input={'id': 1},
+ user=test_ctx.user_factory(rank='regular_user')))
+
+def test_featuring_one_post_after_another(test_ctx, fake_datetime):
+ db.session.add(test_ctx.post_factory(id=1))
+ db.session.add(test_ctx.post_factory(id=2))
+ db.session.commit()
+ assert posts.get_featured_post() is None
+ assert not posts.get_post_by_id(1).is_featured
+ assert not posts.get_post_by_id(2).is_featured
+ with fake_datetime('1997'):
+ result = test_ctx.api.post(
+ test_ctx.context_factory(
+ input={'id': 1},
+ user=test_ctx.user_factory(rank='regular_user')))
+ with fake_datetime('1998'):
+ result = test_ctx.api.post(
+ test_ctx.context_factory(
+ input={'id': 2},
+ user=test_ctx.user_factory(rank='regular_user')))
+ assert posts.get_featured_post() is not None
+ assert posts.get_featured_post().post_id == 2
+ assert not posts.get_post_by_id(1).is_featured
+ assert posts.get_post_by_id(2).is_featured
+
+def test_trying_to_feature_non_existing(test_ctx):
+ with pytest.raises(posts.PostNotFoundError):
+ test_ctx.api.post(
+ test_ctx.context_factory(
+ input={'id': 1},
+ user=test_ctx.user_factory(rank='regular_user')))
+
+def test_trying_to_feature_without_privileges(test_ctx):
+ with pytest.raises(errors.AuthError):
+ test_ctx.api.post(
+ test_ctx.context_factory(
+ input={'id': 1},
+ user=test_ctx.user_factory(rank='anonymous')))
diff --git a/server/szurubooru/tests/conftest.py b/server/szurubooru/tests/conftest.py
index 57841c1..373e5d9 100644
--- a/server/szurubooru/tests/conftest.py
+++ b/server/szurubooru/tests/conftest.py
@@ -125,14 +125,16 @@ def tag_factory(session):
@pytest.fixture
def post_factory():
def factory(
+ id=None,
safety=db.Post.SAFETY_SAFE,
type=db.Post.TYPE_IMAGE,
checksum='...'):
post = db.Post()
+ post.post_id = id
post.safety = safety
post.type = type
post.checksum = checksum
- post.flags = 0
+ post.flags = []
post.creation_time = datetime.datetime(1996, 1, 1)
return post
return factory
diff --git a/server/szurubooru/tests/db/test_post.py b/server/szurubooru/tests/db/test_post.py
index acc444f..90125d6 100644
--- a/server/szurubooru/tests/db/test_post.py
+++ b/server/szurubooru/tests/db/test_post.py
@@ -68,7 +68,7 @@ def test_cascade_deletions(post_factory, user_factory, tag_factory):
post.relations.append(related_post1)
post.relations.append(related_post2)
post.scores.append(score)
- post.favorites.append(favorite)
+ post.favorited_by.append(favorite)
post.features.append(feature)
post.notes.append(note)
db.session.flush()
diff --git a/server/szurubooru/tests/func/test_snapshots.py b/server/szurubooru/tests/func/test_snapshots.py
index 4e421b5..cad9115 100644
--- a/server/szurubooru/tests/func/test_snapshots.py
+++ b/server/szurubooru/tests/func/test_snapshots.py
@@ -3,6 +3,65 @@ import pytest
from szurubooru import db
from szurubooru.func import snapshots
+def test_serializing_post(post_factory, user_factory, tag_factory):
+ user = user_factory(name='dummy-user')
+ tag1 = tag_factory(names=['dummy-tag1'])
+ tag2 = tag_factory(names=['dummy-tag2'])
+ post = post_factory(id=1)
+ related_post1 = post_factory(id=2)
+ related_post2 = post_factory(id=3)
+ db.session.add_all([user, tag1, tag2, post, related_post1, related_post2])
+ db.session.flush()
+
+ score = db.PostScore()
+ score.post = post
+ score.user = user
+ score.time = datetime.datetime(1997, 1, 1)
+ score.score = 1
+ favorite = db.PostFavorite()
+ favorite.post = post
+ favorite.user = user
+ favorite.time = datetime.datetime(1997, 1, 1)
+ feature = db.PostFeature()
+ feature.post = post
+ feature.user = user
+ feature.time = datetime.datetime(1997, 1, 1)
+ note = db.PostNote()
+ note.post = post
+ note.polygon = [(1, 1), (200, 1), (200, 200), (1, 200)]
+ note.text = 'some text'
+ db.session.add_all([score])
+ db.session.flush()
+
+ post.user = user
+ post.checksum = 'deadbeef'
+ post.source = 'example.com'
+ post.tags.append(tag1)
+ post.tags.append(tag2)
+ post.relations.append(related_post1)
+ post.relations.append(related_post2)
+ post.scores.append(score)
+ post.favorited_by.append(favorite)
+ post.features.append(feature)
+ post.notes.append(note)
+
+ assert snapshots.get_post_snapshot(post) == {
+ 'checksum': 'deadbeef',
+ 'featured': True,
+ 'flags': [],
+ 'notes': [
+ {
+ 'polygon': [(1, 1), (200, 1), (200, 200), (1, 200)],
+ 'text': 'some text',
+ }
+ ],
+ 'relations': [2, 3],
+ 'safety': 'safe',
+ 'source': 'example.com',
+ 'tags': ['dummy-tag1', 'dummy-tag2'],
+ }
+
+
def test_serializing_tag(tag_factory):
tag = tag_factory(names=['main_name', 'alias'], category_name='dummy')
assert snapshots.get_tag_snapshot(tag) == {

© 2015 - 2026 Jakob L. Kreuze