server/snapshots: rewrite
This commit is contained in:
parent
03a7bd0d5c
commit
80af79779d
36 changed files with 931 additions and 629 deletions
249
API.md
249
API.md
|
@ -943,10 +943,9 @@ data.
|
||||||
- **Description**
|
- **Description**
|
||||||
|
|
||||||
Retrieves the post that is currently featured on the main page in web
|
Retrieves the post that is currently featured on the main page in web
|
||||||
client. If no post is featured, `<post>` is null and `snapshots` array is
|
client. If no post is featured, `<post>` is null. Note that this method
|
||||||
empty. Note that this method exists mostly for compatibility with setting
|
exists mostly for compatibility with setting featured post - most of times,
|
||||||
featured post - most of times, you'd want to use query global info which
|
you'd want to use query global info which contains more information.
|
||||||
contains more information.
|
|
||||||
|
|
||||||
## Featuring post
|
## Featuring post
|
||||||
- **Request**
|
- **Request**
|
||||||
|
@ -1441,7 +1440,7 @@ data.
|
||||||
| `id` | involving given resource id |
|
| `id` | involving given resource id |
|
||||||
| `date` | created at given date |
|
| `date` | created at given date |
|
||||||
| `time` | alias of `date` |
|
| `time` | alias of `date` |
|
||||||
| `operation` | `changed`, `created` or `deleted` |
|
| `operation` | `modified`, `created`, `deleted` or `merged` |
|
||||||
| `user` | name of the user that created given snapshot |
|
| `user` | name of the user that created given snapshot |
|
||||||
|
|
||||||
**Sort style tokens**
|
**Sort style tokens**
|
||||||
|
@ -1573,12 +1572,7 @@ experience.
|
||||||
"name": <name>,
|
"name": <name>,
|
||||||
"color": <color>,
|
"color": <color>,
|
||||||
"usages": <usages>
|
"usages": <usages>
|
||||||
"default": <is-default>,
|
"default": <is-default>
|
||||||
"snapshots": [
|
|
||||||
<snapshot>,
|
|
||||||
<snapshot>,
|
|
||||||
<snapshot>
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1589,8 +1583,6 @@ experience.
|
||||||
- `<color>`: the category color.
|
- `<color>`: the category color.
|
||||||
- `<usages>`: how many tags is the given category used with.
|
- `<usages>`: how many tags is the given category used with.
|
||||||
- `<is-default>`: whether the tag category is the default one.
|
- `<is-default>`: whether the tag category is the default one.
|
||||||
- `<snapshot>`: a [snapshot resource](#snapshot) that contains the tag
|
|
||||||
category's earlier versions.
|
|
||||||
|
|
||||||
## Tag
|
## Tag
|
||||||
**Description**
|
**Description**
|
||||||
|
@ -1609,12 +1601,7 @@ A single tag. Tags are used to let users search for posts.
|
||||||
"creationTime": <creation-time>,
|
"creationTime": <creation-time>,
|
||||||
"lastEditTime": <last-edit-time>,
|
"lastEditTime": <last-edit-time>,
|
||||||
"usages": <usage-count>,
|
"usages": <usage-count>,
|
||||||
"description": <description>,
|
"description": <description>
|
||||||
"snapshots": [
|
|
||||||
<snapshot>,
|
|
||||||
<snapshot>,
|
|
||||||
<snapshot>
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1633,8 +1620,6 @@ A single tag. Tags are used to let users search for posts.
|
||||||
- `<usage-count>`: the number of posts the tag was used in.
|
- `<usage-count>`: the number of posts the tag was used in.
|
||||||
- `<description>`: the tag description (instructions how to use, history etc.)
|
- `<description>`: the tag description (instructions how to use, history etc.)
|
||||||
The client should render is as Markdown.
|
The client should render is as Markdown.
|
||||||
- `<snapshot>`: a [snapshot resource](#snapshot) that contains the tag's
|
|
||||||
earlier versions.
|
|
||||||
|
|
||||||
## Post
|
## Post
|
||||||
**Description**
|
**Description**
|
||||||
|
@ -1675,11 +1660,6 @@ One file together with its metadata posted to the site.
|
||||||
"favoritedBy": <favorited-by>,
|
"favoritedBy": <favorited-by>,
|
||||||
"hasCustomThumbnail": <has-custom-thumbnail>,
|
"hasCustomThumbnail": <has-custom-thumbnail>,
|
||||||
"mimeType": <mime-type>
|
"mimeType": <mime-type>
|
||||||
"snapshots": [
|
|
||||||
<snapshot>,
|
|
||||||
<snapshot>,
|
|
||||||
<snapshot>
|
|
||||||
],
|
|
||||||
"comments": {
|
"comments": {
|
||||||
<comment>,
|
<comment>,
|
||||||
<comment>,
|
<comment>,
|
||||||
|
@ -1745,8 +1725,6 @@ One file together with its metadata posted to the site.
|
||||||
- `<has-custom-thumbnail>`: whether the post uses custom thumbnail.
|
- `<has-custom-thumbnail>`: whether the post uses custom thumbnail.
|
||||||
- `<mime-type>`: subsidiary to `<type>`, used to tell exact content format;
|
- `<mime-type>`: subsidiary to `<type>`, used to tell exact content format;
|
||||||
useful for `<video>` tags for instance.
|
useful for `<video>` tags for instance.
|
||||||
- `<snapshot>`: a [snapshot resource](#snapshot) that contains the post's
|
|
||||||
earlier versions.
|
|
||||||
- `<comment>`: a [comment resource](#comment) for given post.
|
- `<comment>`: a [comment resource](#comment) for given post.
|
||||||
|
|
||||||
## Micro post
|
## Micro post
|
||||||
|
@ -1817,13 +1795,12 @@ A snapshot is a version of a database resource.
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"operation": <operation>,
|
"operation": <operation>,
|
||||||
"type": <resource-type>
|
"type": <resource-type>,
|
||||||
"id": <resource-id>,
|
"id": <resource-id>,
|
||||||
"user": <user-name>,
|
"user": <issuer>,
|
||||||
"data": <data>,
|
"data": <data>,
|
||||||
"earlier-data": <earlier-data>,
|
"time": <time>
|
||||||
"time": <time>
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1836,6 +1813,7 @@ A snapshot is a version of a database resource.
|
||||||
- `"created"` - the resource has been created
|
- `"created"` - the resource has been created
|
||||||
- `"modified"` - the resource has been modified
|
- `"modified"` - the resource has been modified
|
||||||
- `"deleted"` - the resource has been deleted
|
- `"deleted"` - the resource has been deleted
|
||||||
|
- `"merged"` - the resource has been merged to another resource
|
||||||
|
|
||||||
- `<resource-type>` and `<resource-id>`: the resource that was changed.
|
- `<resource-type>` and `<resource-id>`: the resource that was changed.
|
||||||
|
|
||||||
|
@ -1847,61 +1825,162 @@ A snapshot is a version of a database resource.
|
||||||
| `"tag_category"` | tag category name at given time |
|
| `"tag_category"` | tag category name at given time |
|
||||||
| `"post"` | post ID |
|
| `"post"` | post ID |
|
||||||
|
|
||||||
- `<user-name>`: name of the user who has made the change.
|
- `<issuer>`: a [micro user resource](#micro-user) representing the user who
|
||||||
|
has made the change.
|
||||||
|
|
||||||
- `<data>`: the snapshot data.
|
- `<data>`: the snapshot data, of which content depends on the `<operation>`.
|
||||||
|
More explained later.
|
||||||
The value can be either of structures below:
|
|
||||||
|
|
||||||
- Tag category snapshot data (`<resource-type> = "tag"`)
|
|
||||||
|
|
||||||
*Example*
|
|
||||||
|
|
||||||
```json5
|
|
||||||
{
|
|
||||||
"name": "character",
|
|
||||||
"color": "#FF0000",
|
|
||||||
"default": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Tag snapshot data (`<resource-type> = "tag"`)
|
|
||||||
|
|
||||||
*Example*
|
|
||||||
|
|
||||||
```json5
|
|
||||||
{
|
|
||||||
"names": ["tag1", "tag2", "tag3"],
|
|
||||||
"category": "plain",
|
|
||||||
"implications": ["imp1", "imp2", "imp3"],
|
|
||||||
"suggestions": ["sug1", "sug2", "sug3"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Post snapshot data (`<resource-type> = "post"`)
|
|
||||||
|
|
||||||
*Example*
|
|
||||||
|
|
||||||
```json5
|
|
||||||
{
|
|
||||||
"source": "http://example.com/",
|
|
||||||
"safety": "safe",
|
|
||||||
"checksum": "deadbeef",
|
|
||||||
"tags": ["tag1", "tag2"],
|
|
||||||
"relations": [1, 2],
|
|
||||||
"notes": [<note1>, <note2>, <note3>],
|
|
||||||
"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.
|
|
||||||
|
|
||||||
- `<time>`: when the snapshot was created (i.e. when the resource was changed),
|
- `<time>`: when the snapshot was created (i.e. when the resource was changed),
|
||||||
formatted as per RFC 3339.
|
formatted as per RFC 3339.
|
||||||
|
|
||||||
|
**`<data>` field for creation snapshots**
|
||||||
|
|
||||||
|
The value can be either of structures below, depending on
|
||||||
|
`<resource-type>`:
|
||||||
|
|
||||||
|
- Tag category snapshot data (`<resource-type> = "tag_category"`)
|
||||||
|
|
||||||
|
*Example*
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
"name": "character",
|
||||||
|
"color": "#FF0000",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Tag snapshot data (`<resource-type> = "tag"`)
|
||||||
|
|
||||||
|
*Example*
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
"names": ["tag1", "tag2", "tag3"],
|
||||||
|
"category": "plain",
|
||||||
|
"implications": ["imp1", "imp2", "imp3"],
|
||||||
|
"suggestions": ["sug1", "sug2", "sug3"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Post snapshot data (`<resource-type> = "post"`)
|
||||||
|
|
||||||
|
*Example*
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
"source": "http://example.com/",
|
||||||
|
"safety": "safe",
|
||||||
|
"checksum": "deadbeef",
|
||||||
|
"tags": ["tag1", "tag2"],
|
||||||
|
"relations": [1, 2],
|
||||||
|
"notes": [<note1>, <note2>, <note3>],
|
||||||
|
"flags": ["loop"],
|
||||||
|
"featured": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`<note>`s are serialized the same way as [note resources](#note).
|
||||||
|
|
||||||
|
**`<data>` field for modification snapshots**
|
||||||
|
|
||||||
|
The value is a property-wise recursive diff between previous version of the
|
||||||
|
resource and its current version. Its structure is a `<dictionary-diff>` of
|
||||||
|
dictionaries as created by creation snapshots, which is described below.
|
||||||
|
|
||||||
|
`<primitive>`: any primitive (number or a string)
|
||||||
|
|
||||||
|
`<anything>`: any dictionary, list or primitive
|
||||||
|
|
||||||
|
`<dictionary-diff>`:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
"type": "object change",
|
||||||
|
"value":
|
||||||
|
{
|
||||||
|
"property-of-any-type-1":
|
||||||
|
{
|
||||||
|
"type": "deleted property",
|
||||||
|
"value": <anything>
|
||||||
|
},
|
||||||
|
"property-of-any-type-2":
|
||||||
|
{
|
||||||
|
"type": "added property",
|
||||||
|
"value": <anything>
|
||||||
|
},
|
||||||
|
"primitive-property":
|
||||||
|
{
|
||||||
|
"type": "primitive change":
|
||||||
|
"old-value": "<primitive>",
|
||||||
|
"new-value": "<primitive>"
|
||||||
|
},
|
||||||
|
"list-property": <list-diff>,
|
||||||
|
"dictionary-property": <dictionary-diff>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`<list-diff>`:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
"type": "list change",
|
||||||
|
"removed": [<anything>, <anything>],
|
||||||
|
"added": [<anything>, <anything>]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example - a diff for a post that has changed source and has one note added.
|
||||||
|
Note the similarities with the structure of post creation snapshots.
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
"type": "object change",
|
||||||
|
"value":
|
||||||
|
{
|
||||||
|
"source":
|
||||||
|
{
|
||||||
|
"type": "primitive change",
|
||||||
|
"old-value": None,
|
||||||
|
"new-value": "new source"
|
||||||
|
},
|
||||||
|
"notes":
|
||||||
|
{
|
||||||
|
"type": "list change",
|
||||||
|
"removed": [],
|
||||||
|
"added":
|
||||||
|
[
|
||||||
|
{"polygon": [[0, 0], [0, 1], [1, 1]], "text": "new note"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Since the snapshot dictionaries structure is pretty immutable, you probably
|
||||||
|
won't see `added property` or `deleted property` around. This observation holds
|
||||||
|
true even if the way the snapshots are generated changes - szurubooru stores
|
||||||
|
just the diffs rather than original snapshots, so it wouldn't be able to
|
||||||
|
generate a diff against an old version.
|
||||||
|
|
||||||
|
**`<data>` field for deletion snapshots**
|
||||||
|
|
||||||
|
Same as creation snapshot. In emergencies, it can be used to reconstruct
|
||||||
|
deleted entities. Please note that this does not constitute as means against
|
||||||
|
vandalism (it's still possible to cause chaos by mass editing - this should be
|
||||||
|
dealt with by configuring role privileges in the config) or replace database
|
||||||
|
backups.
|
||||||
|
|
||||||
|
**`<data>` field for merge snapshots**
|
||||||
|
|
||||||
|
A tuple containing 2 elements:
|
||||||
|
|
||||||
|
- resource type equivalent to `<resource-type>` of the target entity.
|
||||||
|
- resource ID euivalen to `<resource-id>` of the target entity.
|
||||||
|
|
||||||
|
|
||||||
## Unpaged search result
|
## Unpaged search result
|
||||||
**Description**
|
**Description**
|
||||||
|
|
||||||
|
|
|
@ -355,50 +355,6 @@ def import_scores(v1_session, v2_session):
|
||||||
v2_session.add(score)
|
v2_session.add(score)
|
||||||
v2_session.commit()
|
v2_session.commit()
|
||||||
|
|
||||||
def import_snapshots(v1_session, v2_session):
|
|
||||||
logger.info('Importing snapshots...')
|
|
||||||
for row in exec_query(v1_session, 'SELECT * FROM snapshots ORDER BY time ASC'):
|
|
||||||
snapshot = db.Snapshot()
|
|
||||||
snapshot.creation_time = row['time']
|
|
||||||
snapshot.user_id = row['userId']
|
|
||||||
snapshot.operation = {
|
|
||||||
0: db.Snapshot.OPERATION_CREATED,
|
|
||||||
1: db.Snapshot.OPERATION_MODIFIED,
|
|
||||||
2: db.Snapshot.OPERATION_DELETED,
|
|
||||||
}[row['operation']]
|
|
||||||
snapshot.resource_type = {
|
|
||||||
0: 'post',
|
|
||||||
1: 'tag',
|
|
||||||
}[row['type']]
|
|
||||||
snapshot.resource_id = row['primaryKey']
|
|
||||||
|
|
||||||
data = json.loads(zlib.decompress(row['data'], -15).decode('utf-8'))
|
|
||||||
if snapshot.resource_type == 'post':
|
|
||||||
if 'contentChecksum' in data:
|
|
||||||
data['checksum'] = data['contentChecksum']
|
|
||||||
del data['contentChecksum']
|
|
||||||
if 'tags' in data and isinstance(data['tags'], dict):
|
|
||||||
data['tags'] = list(data['tags'].values())
|
|
||||||
if 'notes' in data:
|
|
||||||
notes = []
|
|
||||||
for note in data['notes']:
|
|
||||||
notes.append({
|
|
||||||
'polygon': translate_note_polygon(note),
|
|
||||||
'text': note['text'],
|
|
||||||
})
|
|
||||||
data['notes'] = notes
|
|
||||||
snapshot.resource_repr = row['primaryKey']
|
|
||||||
elif snapshot.resource_type == 'tag':
|
|
||||||
if 'banned' in data:
|
|
||||||
del data['banned']
|
|
||||||
if 'name' in data:
|
|
||||||
data['names'] = [data['name']]
|
|
||||||
del data['name']
|
|
||||||
snapshot.resource_repr = data['names'][0]
|
|
||||||
snapshot.data = data
|
|
||||||
v2_session.add(snapshot)
|
|
||||||
v2_session.commit()
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
||||||
|
@ -426,7 +382,6 @@ def main():
|
||||||
import_post_favorites(unused_post_ids, v1_session, v2_session)
|
import_post_favorites(unused_post_ids, v1_session, v2_session)
|
||||||
import_comments(unused_post_ids, v1_session, v2_session)
|
import_comments(unused_post_ids, v1_session, v2_session)
|
||||||
import_scores(v1_session, v2_session)
|
import_scores(v1_session, v2_session)
|
||||||
import_snapshots(v1_session, v2_session)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
from szurubooru import search
|
from szurubooru import search, db
|
||||||
from szurubooru.rest import routes
|
from szurubooru.rest import routes
|
||||||
from szurubooru.func import (
|
from szurubooru.func import (
|
||||||
auth, tags, posts, snapshots, favorites, scores, util, versions)
|
auth, tags, posts, snapshots, favorites, scores, util, versions)
|
||||||
|
@ -44,6 +44,9 @@ def create_post(ctx, _params=None):
|
||||||
content, tag_names, None if anonymous else ctx.user)
|
content, tag_names, None if anonymous else ctx.user)
|
||||||
if len(new_tags):
|
if len(new_tags):
|
||||||
auth.verify_privilege(ctx.user, 'tags:create')
|
auth.verify_privilege(ctx.user, 'tags:create')
|
||||||
|
db.session.flush()
|
||||||
|
for tag in new_tags:
|
||||||
|
snapshots.create(tag, ctx.user)
|
||||||
posts.update_post_safety(post, safety)
|
posts.update_post_safety(post, safety)
|
||||||
posts.update_post_source(post, source)
|
posts.update_post_source(post, source)
|
||||||
posts.update_post_relations(post, relations)
|
posts.update_post_relations(post, relations)
|
||||||
|
@ -52,7 +55,7 @@ def create_post(ctx, _params=None):
|
||||||
if ctx.has_file('thumbnail'):
|
if ctx.has_file('thumbnail'):
|
||||||
posts.update_post_thumbnail(post, ctx.get_file('thumbnail'))
|
posts.update_post_thumbnail(post, ctx.get_file('thumbnail'))
|
||||||
ctx.session.add(post)
|
ctx.session.add(post)
|
||||||
snapshots.save_entity_creation(post, ctx.user)
|
snapshots.create(post, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
return _serialize_post(ctx, post)
|
return _serialize_post(ctx, post)
|
||||||
|
@ -78,6 +81,9 @@ def update_post(ctx, params):
|
||||||
new_tags = posts.update_post_tags(post, ctx.get_param_as_list('tags'))
|
new_tags = posts.update_post_tags(post, ctx.get_param_as_list('tags'))
|
||||||
if len(new_tags):
|
if len(new_tags):
|
||||||
auth.verify_privilege(ctx.user, 'tags:create')
|
auth.verify_privilege(ctx.user, 'tags:create')
|
||||||
|
db.session.flush()
|
||||||
|
for tag in new_tags:
|
||||||
|
snapshots.create(tag, ctx.user)
|
||||||
if ctx.has_param('safety'):
|
if ctx.has_param('safety'):
|
||||||
auth.verify_privilege(ctx.user, 'posts:edit:safety')
|
auth.verify_privilege(ctx.user, 'posts:edit:safety')
|
||||||
posts.update_post_safety(post, ctx.get_param_as_string('safety'))
|
posts.update_post_safety(post, ctx.get_param_as_string('safety'))
|
||||||
|
@ -100,7 +106,7 @@ def update_post(ctx, params):
|
||||||
posts.update_post_thumbnail(post, ctx.get_file('thumbnail'))
|
posts.update_post_thumbnail(post, ctx.get_file('thumbnail'))
|
||||||
post.last_edit_time = datetime.datetime.utcnow()
|
post.last_edit_time = datetime.datetime.utcnow()
|
||||||
ctx.session.flush()
|
ctx.session.flush()
|
||||||
snapshots.save_entity_modification(post, ctx.user)
|
snapshots.modify(post, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
return _serialize_post(ctx, post)
|
return _serialize_post(ctx, post)
|
||||||
|
@ -111,7 +117,7 @@ def delete_post(ctx, params):
|
||||||
auth.verify_privilege(ctx.user, 'posts:delete')
|
auth.verify_privilege(ctx.user, 'posts:delete')
|
||||||
post = posts.get_post_by_id(params['post_id'])
|
post = posts.get_post_by_id(params['post_id'])
|
||||||
versions.verify_version(post, ctx)
|
versions.verify_version(post, ctx)
|
||||||
snapshots.save_entity_deletion(post, ctx.user)
|
snapshots.delete(post, ctx.user)
|
||||||
posts.delete(post)
|
posts.delete(post)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
|
@ -134,9 +140,7 @@ def set_featured_post(ctx, _params=None):
|
||||||
raise posts.PostAlreadyFeaturedError(
|
raise posts.PostAlreadyFeaturedError(
|
||||||
'Post %r is already featured.' % post_id)
|
'Post %r is already featured.' % post_id)
|
||||||
posts.feature_post(post, ctx.user)
|
posts.feature_post(post, ctx.user)
|
||||||
if featured_post:
|
snapshots.modify(post, ctx.user)
|
||||||
snapshots.save_entity_modification(featured_post, ctx.user)
|
|
||||||
snapshots.save_entity_modification(post, ctx.user)
|
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
return _serialize_post(ctx, post)
|
return _serialize_post(ctx, post)
|
||||||
|
|
||||||
|
|
|
@ -10,4 +10,4 @@ _search_executor = search.Executor(search.configs.SnapshotSearchConfig())
|
||||||
def get_snapshots(ctx, _params=None):
|
def get_snapshots(ctx, _params=None):
|
||||||
auth.verify_privilege(ctx.user, 'snapshots:list')
|
auth.verify_privilege(ctx.user, 'snapshots:list')
|
||||||
return _search_executor.execute_and_serialize(
|
return _search_executor.execute_and_serialize(
|
||||||
ctx, snapshots.serialize_snapshot)
|
ctx, lambda snapshot: snapshots.serialize_snapshot(snapshot, ctx.user))
|
||||||
|
|
|
@ -20,7 +20,7 @@ def _create_if_needed(tag_names, user):
|
||||||
auth.verify_privilege(user, 'tags:create')
|
auth.verify_privilege(user, 'tags:create')
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
for tag in new_tags:
|
for tag in new_tags:
|
||||||
snapshots.save_entity_creation(tag, user)
|
snapshots.create(tag, user)
|
||||||
|
|
||||||
|
|
||||||
@routes.get('/tags/?')
|
@routes.get('/tags/?')
|
||||||
|
@ -50,7 +50,7 @@ def create_tag(ctx, _params=None):
|
||||||
tags.update_tag_description(tag, description)
|
tags.update_tag_description(tag, description)
|
||||||
ctx.session.add(tag)
|
ctx.session.add(tag)
|
||||||
ctx.session.flush()
|
ctx.session.flush()
|
||||||
snapshots.save_entity_creation(tag, ctx.user)
|
snapshots.create(tag, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
return _serialize(ctx, tag)
|
return _serialize(ctx, tag)
|
||||||
|
@ -91,7 +91,7 @@ def update_tag(ctx, params):
|
||||||
tags.update_tag_implications(tag, implications)
|
tags.update_tag_implications(tag, implications)
|
||||||
tag.last_edit_time = datetime.datetime.utcnow()
|
tag.last_edit_time = datetime.datetime.utcnow()
|
||||||
ctx.session.flush()
|
ctx.session.flush()
|
||||||
snapshots.save_entity_modification(tag, ctx.user)
|
snapshots.modify(tag, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
return _serialize(ctx, tag)
|
return _serialize(ctx, tag)
|
||||||
|
@ -102,7 +102,7 @@ def delete_tag(ctx, params):
|
||||||
tag = tags.get_tag_by_name(params['tag_name'])
|
tag = tags.get_tag_by_name(params['tag_name'])
|
||||||
versions.verify_version(tag, ctx)
|
versions.verify_version(tag, ctx)
|
||||||
auth.verify_privilege(ctx.user, 'tags:delete')
|
auth.verify_privilege(ctx.user, 'tags:delete')
|
||||||
snapshots.save_entity_deletion(tag, ctx.user)
|
snapshots.delete(tag, ctx.user)
|
||||||
tags.delete(tag)
|
tags.delete(tag)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
|
@ -120,7 +120,7 @@ def merge_tags(ctx, _params=None):
|
||||||
versions.bump_version(target_tag)
|
versions.bump_version(target_tag)
|
||||||
auth.verify_privilege(ctx.user, 'tags:merge')
|
auth.verify_privilege(ctx.user, 'tags:merge')
|
||||||
tags.merge_tags(source_tag, target_tag)
|
tags.merge_tags(source_tag, target_tag)
|
||||||
snapshots.save_entity_deletion(source_tag, ctx.user)
|
snapshots.merge(source_tag, target_tag, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
return _serialize(ctx, target_tag)
|
return _serialize(ctx, target_tag)
|
||||||
|
|
|
@ -25,7 +25,7 @@ def create_tag_category(ctx, _params=None):
|
||||||
category = tag_categories.create_category(name, color)
|
category = tag_categories.create_category(name, color)
|
||||||
ctx.session.add(category)
|
ctx.session.add(category)
|
||||||
ctx.session.flush()
|
ctx.session.flush()
|
||||||
snapshots.save_entity_creation(category, ctx.user)
|
snapshots.create(category, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
return _serialize(ctx, category)
|
return _serialize(ctx, category)
|
||||||
|
@ -52,7 +52,7 @@ def update_tag_category(ctx, params):
|
||||||
tag_categories.update_category_color(
|
tag_categories.update_category_color(
|
||||||
category, ctx.get_param_as_string('color'))
|
category, ctx.get_param_as_string('color'))
|
||||||
ctx.session.flush()
|
ctx.session.flush()
|
||||||
snapshots.save_entity_modification(category, ctx.user)
|
snapshots.modify(category, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
return _serialize(ctx, category)
|
return _serialize(ctx, category)
|
||||||
|
@ -64,7 +64,7 @@ def delete_tag_category(ctx, params):
|
||||||
versions.verify_version(category, ctx)
|
versions.verify_version(category, ctx)
|
||||||
auth.verify_privilege(ctx.user, 'tag_categories:delete')
|
auth.verify_privilege(ctx.user, 'tag_categories:delete')
|
||||||
tag_categories.delete_category(category)
|
tag_categories.delete_category(category)
|
||||||
snapshots.save_entity_deletion(category, ctx.user)
|
snapshots.delete(category, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
return {}
|
return {}
|
||||||
|
@ -75,7 +75,7 @@ def set_tag_category_as_default(ctx, params):
|
||||||
auth.verify_privilege(ctx.user, 'tag_categories:set_default')
|
auth.verify_privilege(ctx.user, 'tag_categories:set_default')
|
||||||
category = tag_categories.get_category_by_name(params['category_name'])
|
category = tag_categories.get_category_by_name(params['category_name'])
|
||||||
tag_categories.set_default_category(category)
|
tag_categories.set_default_category(category)
|
||||||
snapshots.save_entity_modification(category, ctx.user)
|
snapshots.modify(category, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
return _serialize(ctx, category)
|
return _serialize(ctx, category)
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
from szurubooru.db.base import Base
|
from szurubooru.db.base import Base
|
||||||
from szurubooru.db.user import User
|
from szurubooru.db.user import User
|
||||||
from szurubooru.db.tag_category import TagCategory
|
from szurubooru.db.tag_category import TagCategory
|
||||||
from szurubooru.db.tag import (
|
from szurubooru.db.tag import (Tag, TagName, TagSuggestion, TagImplication)
|
||||||
Tag,
|
|
||||||
TagName,
|
|
||||||
TagSuggestion,
|
|
||||||
TagImplication)
|
|
||||||
from szurubooru.db.post import (
|
from szurubooru.db.post import (
|
||||||
Post,
|
Post,
|
||||||
PostTag,
|
PostTag,
|
||||||
|
@ -14,12 +10,8 @@ from szurubooru.db.post import (
|
||||||
PostScore,
|
PostScore,
|
||||||
PostNote,
|
PostNote,
|
||||||
PostFeature)
|
PostFeature)
|
||||||
from szurubooru.db.comment import (
|
from szurubooru.db.comment import (Comment, CommentScore)
|
||||||
Comment,
|
|
||||||
CommentScore)
|
|
||||||
from szurubooru.db.snapshot import Snapshot
|
from szurubooru.db.snapshot import Snapshot
|
||||||
from szurubooru.db.session import (
|
from szurubooru.db.session import (
|
||||||
session,
|
session, sessionmaker, reset_query_count, get_query_count)
|
||||||
reset_query_count,
|
|
||||||
get_query_count)
|
|
||||||
import szurubooru.db.util
|
import szurubooru.db.util
|
||||||
|
|
|
@ -18,15 +18,12 @@ class QueryCounter(object):
|
||||||
return QueryCounter._query_count
|
return QueryCounter._query_count
|
||||||
|
|
||||||
|
|
||||||
def create_session():
|
|
||||||
_engine = sqlalchemy.create_engine(config.config['database'])
|
|
||||||
sqlalchemy.event.listen(
|
|
||||||
_engine, 'after_execute', lambda *args: QueryCounter.bump())
|
|
||||||
_session_maker = sqlalchemy.orm.sessionmaker(bind=_engine)
|
|
||||||
return sqlalchemy.orm.scoped_session(_session_maker)
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
session = create_session()
|
_engine = sqlalchemy.create_engine(config.config['database'])
|
||||||
|
sessionmaker = sqlalchemy.orm.sessionmaker(bind=_engine)
|
||||||
|
session = sqlalchemy.orm.scoped_session(sessionmaker)
|
||||||
reset_query_count = QueryCounter.reset
|
reset_query_count = QueryCounter.reset
|
||||||
get_query_count = QueryCounter.get
|
get_query_count = QueryCounter.get
|
||||||
|
|
||||||
|
sqlalchemy.event.listen(
|
||||||
|
_engine, 'after_execute', lambda *args: QueryCounter.bump())
|
||||||
|
|
|
@ -10,14 +10,17 @@ class Snapshot(Base):
|
||||||
OPERATION_CREATED = 'created'
|
OPERATION_CREATED = 'created'
|
||||||
OPERATION_MODIFIED = 'modified'
|
OPERATION_MODIFIED = 'modified'
|
||||||
OPERATION_DELETED = 'deleted'
|
OPERATION_DELETED = 'deleted'
|
||||||
|
OPERATION_MERGED = 'merged'
|
||||||
|
|
||||||
snapshot_id = Column('id', Integer, primary_key=True)
|
snapshot_id = Column('id', Integer, primary_key=True)
|
||||||
creation_time = Column('creation_time', DateTime, nullable=False)
|
creation_time = Column('creation_time', DateTime, nullable=False)
|
||||||
|
operation = Column('operation', Unicode(16), nullable=False)
|
||||||
resource_type = Column(
|
resource_type = Column(
|
||||||
'resource_type', Unicode(32), nullable=False, index=True)
|
'resource_type', Unicode(32), nullable=False, index=True)
|
||||||
resource_id = Column('resource_id', Integer, nullable=False, index=True)
|
resource_pkey = Column(
|
||||||
resource_repr = Column('resource_repr', Unicode(64), nullable=False)
|
'resource_pkey', Integer, nullable=False, index=True)
|
||||||
operation = Column('operation', Unicode(16), nullable=False)
|
resource_name = Column(
|
||||||
|
'resource_name', Unicode(64), nullable=False)
|
||||||
user_id = Column(
|
user_id = Column(
|
||||||
'user_id',
|
'user_id',
|
||||||
Integer,
|
Integer,
|
||||||
|
|
|
@ -16,13 +16,13 @@ def get_resource_info(entity):
|
||||||
assert primary_key is not None
|
assert primary_key is not None
|
||||||
assert len(primary_key) == 1
|
assert len(primary_key) == 1
|
||||||
|
|
||||||
resource_repr = serializers[resource_type](entity)
|
resource_name = serializers[resource_type](entity)
|
||||||
assert resource_repr
|
assert resource_name
|
||||||
|
|
||||||
resource_id = primary_key[0]
|
resource_pkey = primary_key[0]
|
||||||
assert resource_id
|
assert resource_pkey
|
||||||
|
|
||||||
return (resource_type, resource_id, resource_repr)
|
return (resource_type, resource_pkey, resource_name)
|
||||||
|
|
||||||
|
|
||||||
def get_aux_entity(session, get_table_info, entity, user):
|
def get_aux_entity(session, get_table_info, entity, user):
|
||||||
|
|
57
server/szurubooru/func/diff.py
Normal file
57
server/szurubooru/func/diff.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
def get_list_diff(old, new):
|
||||||
|
value = {'type': 'list change', 'added': [], 'removed': []}
|
||||||
|
equal = True
|
||||||
|
|
||||||
|
for item in old:
|
||||||
|
if item not in new:
|
||||||
|
equal = False
|
||||||
|
value['removed'].append(item)
|
||||||
|
|
||||||
|
for item in new:
|
||||||
|
if item not in old:
|
||||||
|
equal = False
|
||||||
|
value['added'].append(item)
|
||||||
|
|
||||||
|
return None if equal else value
|
||||||
|
|
||||||
|
|
||||||
|
def get_dict_diff(old, new):
|
||||||
|
value = {}
|
||||||
|
equal = True
|
||||||
|
|
||||||
|
for key in old.keys():
|
||||||
|
if key in new:
|
||||||
|
if old[key] != new[key]:
|
||||||
|
if isinstance(old[key], dict) and isinstance(new[key], dict):
|
||||||
|
value_diff = get_dict_diff(old[key], new[key])
|
||||||
|
if value_diff:
|
||||||
|
equal = False
|
||||||
|
value[key] = value_diff
|
||||||
|
elif isinstance(old[key], list) and isinstance(new[key], list):
|
||||||
|
value_diff = get_list_diff(old[key], new[key])
|
||||||
|
if value_diff:
|
||||||
|
equal = False
|
||||||
|
value[key] = value_diff
|
||||||
|
else:
|
||||||
|
equal = False
|
||||||
|
value[key] = {
|
||||||
|
'type': 'primitive change',
|
||||||
|
'old-value': old[key],
|
||||||
|
'new-value': new[key],
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
equal = False
|
||||||
|
value[key] = {
|
||||||
|
'type': 'deleted property',
|
||||||
|
'value': old[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in new.keys():
|
||||||
|
if key not in old:
|
||||||
|
equal = False
|
||||||
|
value[key] = {
|
||||||
|
'type': 'added property',
|
||||||
|
'value': new[key],
|
||||||
|
}
|
||||||
|
|
||||||
|
return None if equal else {'type': 'object change', 'value': value}
|
|
@ -2,7 +2,7 @@ import datetime
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from szurubooru import config, db, errors
|
from szurubooru import config, db, errors
|
||||||
from szurubooru.func import (
|
from szurubooru.func import (
|
||||||
users, snapshots, scores, comments, tags, util, mime, images, files)
|
users, scores, comments, tags, util, mime, images, files)
|
||||||
|
|
||||||
|
|
||||||
EMPTY_PIXEL = \
|
EMPTY_PIXEL = \
|
||||||
|
@ -165,7 +165,6 @@ def serialize_post(post, auth_user, options=None):
|
||||||
for comment in sorted(
|
for comment in sorted(
|
||||||
post.comments,
|
post.comments,
|
||||||
key=lambda comment: comment.creation_time)],
|
key=lambda comment: comment.creation_time)],
|
||||||
'snapshots': lambda: snapshots.get_serialized_history(post),
|
|
||||||
},
|
},
|
||||||
options)
|
options)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
import datetime
|
from datetime import datetime
|
||||||
from szurubooru import db
|
from szurubooru import db
|
||||||
|
from szurubooru.func import diff, users
|
||||||
|
|
||||||
|
|
||||||
|
def get_tag_category_snapshot(category):
|
||||||
|
assert category
|
||||||
|
return {
|
||||||
|
'name': category.name,
|
||||||
|
'color': category.color,
|
||||||
|
'default': True if category.default else False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_tag_snapshot(tag):
|
def get_tag_snapshot(tag):
|
||||||
|
assert tag
|
||||||
return {
|
return {
|
||||||
'names': [tag_name.name for tag_name in tag.names],
|
'names': [tag_name.name for tag_name in tag.names],
|
||||||
'category': tag.category.name,
|
'category': tag.category.name,
|
||||||
|
@ -12,132 +23,106 @@ def get_tag_snapshot(tag):
|
||||||
|
|
||||||
|
|
||||||
def get_post_snapshot(post):
|
def get_post_snapshot(post):
|
||||||
|
assert post
|
||||||
return {
|
return {
|
||||||
'source': post.source,
|
'source': post.source,
|
||||||
'safety': post.safety,
|
'safety': post.safety,
|
||||||
'checksum': post.checksum,
|
'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], key=lambda x: x['polygon']),
|
|
||||||
'flags': post.flags,
|
'flags': post.flags,
|
||||||
'featured': post.is_featured,
|
'featured': post.is_featured,
|
||||||
|
'tags': sorted([tag.first_name for tag in post.tags]),
|
||||||
|
'relations': sorted([rel.post_id for rel in post.relations]),
|
||||||
|
'notes': sorted([{
|
||||||
|
'polygon': [[point[0], point[1]] for point in note.polygon],
|
||||||
|
'text': note.text,
|
||||||
|
} for note in post.notes], key=lambda x: x['polygon']),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_tag_category_snapshot(category):
|
_snapshot_factories = {
|
||||||
return {
|
# lambdas allow mocking target functions in the tests
|
||||||
'name': category.name,
|
# pylint: disable=unnecessary-lambda
|
||||||
'color': category.color,
|
'tag_category': lambda entity: get_tag_category_snapshot(entity),
|
||||||
'default': True if category.default else False,
|
'tag': lambda entity: get_tag_snapshot(entity),
|
||||||
}
|
'post': lambda entity: get_post_snapshot(entity),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_previous_snapshot(snapshot):
|
def serialize_snapshot(snapshot, auth_user):
|
||||||
assert snapshot
|
assert snapshot
|
||||||
return db.session \
|
|
||||||
.query(db.Snapshot) \
|
|
||||||
.filter(db.Snapshot.resource_type == snapshot.resource_type) \
|
|
||||||
.filter(db.Snapshot.resource_id == snapshot.resource_id) \
|
|
||||||
.filter(db.Snapshot.creation_time < snapshot.creation_time) \
|
|
||||||
.order_by(db.Snapshot.creation_time.desc()) \
|
|
||||||
.limit(1) \
|
|
||||||
.first()
|
|
||||||
|
|
||||||
|
|
||||||
def get_snapshots(entity):
|
|
||||||
assert entity
|
|
||||||
resource_type, resource_id, _ = db.util.get_resource_info(entity)
|
|
||||||
return db.session \
|
|
||||||
.query(db.Snapshot) \
|
|
||||||
.filter(db.Snapshot.resource_type == resource_type) \
|
|
||||||
.filter(db.Snapshot.resource_id == resource_id) \
|
|
||||||
.order_by(db.Snapshot.creation_time.desc()) \
|
|
||||||
.all()
|
|
||||||
|
|
||||||
|
|
||||||
def serialize_snapshot(snapshot, earlier_snapshot=()):
|
|
||||||
assert snapshot
|
|
||||||
if earlier_snapshot is ():
|
|
||||||
earlier_snapshot = get_previous_snapshot(snapshot)
|
|
||||||
return {
|
return {
|
||||||
'operation': snapshot.operation,
|
'operation': snapshot.operation,
|
||||||
'type': snapshot.resource_type,
|
'type': snapshot.resource_type,
|
||||||
'id': snapshot.resource_repr,
|
'id': snapshot.resource_name,
|
||||||
'user': snapshot.user.name if snapshot.user else None,
|
'user': users.serialize_micro_user(snapshot.user, auth_user),
|
||||||
'data': snapshot.data,
|
'data': snapshot.data,
|
||||||
'earlier-data': earlier_snapshot.data if earlier_snapshot else None,
|
|
||||||
'time': snapshot.creation_time,
|
'time': snapshot.creation_time,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_serialized_history(entity):
|
def _create(operation, entity, auth_user):
|
||||||
if not entity:
|
resource_type, resource_pkey, resource_name = (
|
||||||
return []
|
|
||||||
ret = []
|
|
||||||
earlier_snapshot = None
|
|
||||||
for snapshot in reversed(get_snapshots(entity)):
|
|
||||||
ret.insert(0, serialize_snapshot(snapshot, earlier_snapshot))
|
|
||||||
earlier_snapshot = snapshot
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def _save(operation, entity, auth_user):
|
|
||||||
assert operation
|
|
||||||
assert entity
|
|
||||||
serializers = {
|
|
||||||
'tag': get_tag_snapshot,
|
|
||||||
'tag_category': get_tag_category_snapshot,
|
|
||||||
'post': get_post_snapshot,
|
|
||||||
}
|
|
||||||
|
|
||||||
resource_type, resource_id, resource_repr = (
|
|
||||||
db.util.get_resource_info(entity))
|
db.util.get_resource_info(entity))
|
||||||
now = datetime.datetime.utcnow()
|
|
||||||
|
|
||||||
snapshot = db.Snapshot()
|
snapshot = db.Snapshot()
|
||||||
snapshot.creation_time = now
|
snapshot.creation_time = datetime.utcnow()
|
||||||
snapshot.operation = operation
|
snapshot.operation = operation
|
||||||
snapshot.resource_type = resource_type
|
snapshot.resource_type = resource_type
|
||||||
snapshot.resource_id = resource_id
|
snapshot.resource_pkey = resource_pkey
|
||||||
snapshot.resource_repr = resource_repr
|
snapshot.resource_name = resource_name
|
||||||
snapshot.data = serializers[resource_type](entity)
|
|
||||||
snapshot.user = auth_user
|
snapshot.user = auth_user
|
||||||
|
return snapshot
|
||||||
earlier_snapshots = get_snapshots(entity)
|
|
||||||
|
|
||||||
delta = datetime.timedelta(minutes=10)
|
|
||||||
snapshots_left = len(earlier_snapshots)
|
|
||||||
while earlier_snapshots:
|
|
||||||
last_snapshot = earlier_snapshots.pop(0)
|
|
||||||
is_fresh = now - last_snapshot.creation_time <= delta
|
|
||||||
if snapshot.data != last_snapshot.data:
|
|
||||||
if not is_fresh or last_snapshot.user != auth_user:
|
|
||||||
break
|
|
||||||
db.session.delete(last_snapshot)
|
|
||||||
if snapshot.operation != db.Snapshot.OPERATION_DELETED:
|
|
||||||
snapshot.operation = last_snapshot.operation
|
|
||||||
snapshots_left -= 1
|
|
||||||
|
|
||||||
if not snapshots_left and operation == db.Snapshot.OPERATION_DELETED:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
db.session.add(snapshot)
|
|
||||||
|
|
||||||
|
|
||||||
def save_entity_creation(entity, auth_user):
|
def create(entity, auth_user):
|
||||||
assert entity
|
assert entity
|
||||||
_save(db.Snapshot.OPERATION_CREATED, entity, auth_user)
|
snapshot = _create(db.Snapshot.OPERATION_CREATED, entity, auth_user)
|
||||||
|
snapshot_factory = _snapshot_factories[snapshot.resource_type]
|
||||||
|
snapshot.data = snapshot_factory(entity)
|
||||||
|
db.session.add(snapshot)
|
||||||
|
|
||||||
|
|
||||||
def save_entity_modification(entity, auth_user):
|
# pylint: disable=protected-access
|
||||||
|
def modify(entity, auth_user):
|
||||||
assert entity
|
assert entity
|
||||||
_save(db.Snapshot.OPERATION_MODIFIED, entity, auth_user)
|
|
||||||
|
model = next((model
|
||||||
|
for model in db.Base._decl_class_registry.values()
|
||||||
|
if hasattr(model, '__table__')
|
||||||
|
and model.__table__.fullname == entity.__table__.fullname),
|
||||||
|
None)
|
||||||
|
assert model
|
||||||
|
|
||||||
|
snapshot = _create(db.Snapshot.OPERATION_MODIFIED, entity, auth_user)
|
||||||
|
snapshot_factory = _snapshot_factories[snapshot.resource_type]
|
||||||
|
|
||||||
|
detached_session = db.sessionmaker()
|
||||||
|
detached_entity = detached_session.query(model).get(snapshot.resource_pkey)
|
||||||
|
assert detached_entity, 'Entity not found in DB, have you committed it?'
|
||||||
|
detached_snapshot = snapshot_factory(detached_entity)
|
||||||
|
detached_session.close()
|
||||||
|
|
||||||
|
active_snapshot = snapshot_factory(entity)
|
||||||
|
|
||||||
|
snapshot.data = diff.get_dict_diff(detached_snapshot, active_snapshot)
|
||||||
|
if not snapshot.data:
|
||||||
|
return
|
||||||
|
db.session.add(snapshot)
|
||||||
|
|
||||||
|
|
||||||
def save_entity_deletion(entity, auth_user):
|
def delete(entity, auth_user):
|
||||||
assert entity
|
assert entity
|
||||||
_save(db.Snapshot.OPERATION_DELETED, entity, auth_user)
|
snapshot = _create(db.Snapshot.OPERATION_DELETED, entity, auth_user)
|
||||||
|
snapshot_factory = _snapshot_factories[snapshot.resource_type]
|
||||||
|
snapshot.data = snapshot_factory(entity)
|
||||||
|
db.session.add(snapshot)
|
||||||
|
|
||||||
|
|
||||||
|
def merge(source_entity, target_entity, auth_user):
|
||||||
|
assert source_entity
|
||||||
|
assert target_entity
|
||||||
|
snapshot = _create(db.Snapshot.OPERATION_MERGED, source_entity, auth_user)
|
||||||
|
resource_type, _resource_pkey, resource_name = (
|
||||||
|
db.util.get_resource_info(target_entity))
|
||||||
|
snapshot.data = [resource_type, resource_name]
|
||||||
|
db.session.add(snapshot)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import re
|
import re
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from szurubooru import config, db, errors
|
from szurubooru import config, db, errors
|
||||||
from szurubooru.func import util, snapshots, cache
|
from szurubooru.func import util, cache
|
||||||
|
|
||||||
|
|
||||||
class TagCategoryNotFoundError(errors.NotFoundError):
|
class TagCategoryNotFoundError(errors.NotFoundError):
|
||||||
|
@ -40,7 +40,6 @@ def serialize_category(category, options=None):
|
||||||
'color': lambda: category.color,
|
'color': lambda: category.color,
|
||||||
'usages': lambda: category.tag_count,
|
'usages': lambda: category.tag_count,
|
||||||
'default': lambda: category.default,
|
'default': lambda: category.default,
|
||||||
'snapshots': lambda: snapshots.get_serialized_history(category),
|
|
||||||
},
|
},
|
||||||
options)
|
options)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from szurubooru import config, db, errors
|
from szurubooru import config, db, errors
|
||||||
from szurubooru.func import util, tag_categories, snapshots
|
from szurubooru.func import util, tag_categories
|
||||||
|
|
||||||
|
|
||||||
class TagNotFoundError(errors.NotFoundError):
|
class TagNotFoundError(errors.NotFoundError):
|
||||||
|
@ -86,7 +86,6 @@ def serialize_tag(tag, options=None):
|
||||||
'implications': lambda: [
|
'implications': lambda: [
|
||||||
relation.names[0].name
|
relation.names[0].name
|
||||||
for relation in sort_tags(tag.implications)],
|
for relation in sort_tags(tag.implications)],
|
||||||
'snapshots': lambda: snapshots.get_serialized_history(tag),
|
|
||||||
},
|
},
|
||||||
options)
|
options)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
'''
|
||||||
|
Rename snapshot columns
|
||||||
|
|
||||||
|
Revision ID: 4a020f1d271a
|
||||||
|
Created at: 2016-08-16 09:25:38.350861
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
|
||||||
|
revision = '4a020f1d271a'
|
||||||
|
down_revision = '840b460c5613'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column(
|
||||||
|
'snapshot',
|
||||||
|
sa.Column('resource_name', sa.Unicode(length=64), nullable=False))
|
||||||
|
op.add_column(
|
||||||
|
'snapshot',
|
||||||
|
sa.Column('resource_pkey', sa.Integer(), nullable=False))
|
||||||
|
op.create_index(
|
||||||
|
op.f('ix_snapshot_resource_pkey'),
|
||||||
|
'snapshot',
|
||||||
|
['resource_pkey'],
|
||||||
|
unique=False)
|
||||||
|
op.drop_index('ix_snapshot_resource_id', table_name='snapshot')
|
||||||
|
op.drop_column('snapshot', 'resource_id')
|
||||||
|
op.drop_column('snapshot', 'resource_repr')
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.add_column(
|
||||||
|
'snapshot',
|
||||||
|
sa.Column(
|
||||||
|
'resource_repr',
|
||||||
|
sa.VARCHAR(length=64),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=False))
|
||||||
|
op.add_column(
|
||||||
|
'snapshot',
|
||||||
|
sa.Column(
|
||||||
|
'resource_id',
|
||||||
|
sa.INTEGER(),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=False))
|
||||||
|
op.create_index(
|
||||||
|
'ix_snapshot_resource_id', 'snapshot', ['resource_id'], unique=False)
|
||||||
|
op.drop_index(op.f('ix_snapshot_resource_pkey'), table_name='snapshot')
|
||||||
|
op.drop_column('snapshot', 'resource_pkey')
|
||||||
|
op.drop_column('snapshot', 'resource_name')
|
|
@ -14,7 +14,7 @@ class SnapshotSearchConfig(BaseSearchConfig):
|
||||||
def named_filters(self):
|
def named_filters(self):
|
||||||
return {
|
return {
|
||||||
'type': search_util.create_str_filter(db.Snapshot.resource_type),
|
'type': search_util.create_str_filter(db.Snapshot.resource_type),
|
||||||
'id': search_util.create_str_filter(db.Snapshot.resource_repr),
|
'id': search_util.create_str_filter(db.Snapshot.resource_name),
|
||||||
'date': search_util.create_date_filter(db.Snapshot.creation_time),
|
'date': search_util.create_date_filter(db.Snapshot.creation_time),
|
||||||
'time': search_util.create_date_filter(db.Snapshot.creation_time),
|
'time': search_util.create_date_filter(db.Snapshot.creation_time),
|
||||||
'operation': search_util.create_str_filter(db.Snapshot.operation),
|
'operation': search_util.create_str_filter(db.Snapshot.operation),
|
||||||
|
|
|
@ -31,7 +31,7 @@ def test_creating_minimal_posts(
|
||||||
patch('szurubooru.func.posts.update_post_thumbnail'), \
|
patch('szurubooru.func.posts.update_post_thumbnail'), \
|
||||||
patch('szurubooru.func.posts.serialize_post'), \
|
patch('szurubooru.func.posts.serialize_post'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'), \
|
patch('szurubooru.func.tags.export_to_json'), \
|
||||||
patch('szurubooru.func.snapshots.save_entity_creation'):
|
patch('szurubooru.func.snapshots.create'):
|
||||||
posts.create_post.return_value = (post, [])
|
posts.create_post.return_value = (post, [])
|
||||||
posts.serialize_post.return_value = 'serialized post'
|
posts.serialize_post.return_value = 'serialized post'
|
||||||
|
|
||||||
|
@ -61,8 +61,8 @@ def test_creating_minimal_posts(
|
||||||
post, 'post-thumbnail')
|
post, 'post-thumbnail')
|
||||||
posts.serialize_post.assert_called_once_with(
|
posts.serialize_post.assert_called_once_with(
|
||||||
post, auth_user, options=None)
|
post, auth_user, options=None)
|
||||||
|
snapshots.create.assert_called_once_with(post, auth_user)
|
||||||
tags.export_to_json.assert_called_once_with()
|
tags.export_to_json.assert_called_once_with()
|
||||||
snapshots.save_entity_creation.assert_called_once_with(post, auth_user)
|
|
||||||
|
|
||||||
|
|
||||||
def test_creating_full_posts(context_factory, post_factory, user_factory):
|
def test_creating_full_posts(context_factory, post_factory, user_factory):
|
||||||
|
@ -79,7 +79,7 @@ def test_creating_full_posts(context_factory, post_factory, user_factory):
|
||||||
patch('szurubooru.func.posts.update_post_flags'), \
|
patch('szurubooru.func.posts.update_post_flags'), \
|
||||||
patch('szurubooru.func.posts.serialize_post'), \
|
patch('szurubooru.func.posts.serialize_post'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'), \
|
patch('szurubooru.func.tags.export_to_json'), \
|
||||||
patch('szurubooru.func.snapshots.save_entity_creation'):
|
patch('szurubooru.func.snapshots.create'):
|
||||||
posts.create_post.return_value = (post, [])
|
posts.create_post.return_value = (post, [])
|
||||||
posts.serialize_post.return_value = 'serialized post'
|
posts.serialize_post.return_value = 'serialized post'
|
||||||
|
|
||||||
|
@ -110,8 +110,8 @@ def test_creating_full_posts(context_factory, post_factory, user_factory):
|
||||||
post, ['flag1', 'flag2'])
|
post, ['flag1', 'flag2'])
|
||||||
posts.serialize_post.assert_called_once_with(
|
posts.serialize_post.assert_called_once_with(
|
||||||
post, auth_user, options=None)
|
post, auth_user, options=None)
|
||||||
|
snapshots.create.assert_called_once_with(post, auth_user)
|
||||||
tags.export_to_json.assert_called_once_with()
|
tags.export_to_json.assert_called_once_with()
|
||||||
snapshots.save_entity_creation.assert_called_once_with(post, auth_user)
|
|
||||||
|
|
||||||
|
|
||||||
def test_anonymous_uploads(
|
def test_anonymous_uploads(
|
||||||
|
@ -122,7 +122,6 @@ def test_anonymous_uploads(
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
with patch('szurubooru.func.tags.export_to_json'), \
|
with patch('szurubooru.func.tags.export_to_json'), \
|
||||||
patch('szurubooru.func.snapshots.save_entity_creation'), \
|
|
||||||
patch('szurubooru.func.posts.serialize_post'), \
|
patch('szurubooru.func.posts.serialize_post'), \
|
||||||
patch('szurubooru.func.posts.create_post'), \
|
patch('szurubooru.func.posts.create_post'), \
|
||||||
patch('szurubooru.func.posts.update_post_source'):
|
patch('szurubooru.func.posts.update_post_source'):
|
||||||
|
@ -154,7 +153,6 @@ def test_creating_from_url_saves_source(
|
||||||
|
|
||||||
with patch('szurubooru.func.net.download'), \
|
with patch('szurubooru.func.net.download'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'), \
|
patch('szurubooru.func.tags.export_to_json'), \
|
||||||
patch('szurubooru.func.snapshots.save_entity_creation'), \
|
|
||||||
patch('szurubooru.func.posts.serialize_post'), \
|
patch('szurubooru.func.posts.serialize_post'), \
|
||||||
patch('szurubooru.func.posts.create_post'), \
|
patch('szurubooru.func.posts.create_post'), \
|
||||||
patch('szurubooru.func.posts.update_post_source'):
|
patch('szurubooru.func.posts.update_post_source'):
|
||||||
|
@ -186,7 +184,6 @@ def test_creating_from_url_with_source_specified(
|
||||||
|
|
||||||
with patch('szurubooru.func.net.download'), \
|
with patch('szurubooru.func.net.download'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'), \
|
patch('szurubooru.func.tags.export_to_json'), \
|
||||||
patch('szurubooru.func.snapshots.save_entity_creation'), \
|
|
||||||
patch('szurubooru.func.posts.serialize_post'), \
|
patch('szurubooru.func.posts.serialize_post'), \
|
||||||
patch('szurubooru.func.posts.create_post'), \
|
patch('szurubooru.func.posts.create_post'), \
|
||||||
patch('szurubooru.func.posts.update_post_source'):
|
patch('szurubooru.func.posts.update_post_source'):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
from szurubooru.func import posts, tags
|
from szurubooru.func import posts, tags, snapshots
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
@ -10,16 +10,17 @@ def inject_config(config_injector):
|
||||||
|
|
||||||
|
|
||||||
def test_deleting(user_factory, post_factory, context_factory):
|
def test_deleting(user_factory, post_factory, context_factory):
|
||||||
db.session.add(post_factory(id=1))
|
auth_user = user_factory(rank=db.User.RANK_REGULAR)
|
||||||
db.session.commit()
|
post = post_factory(id=1)
|
||||||
with patch('szurubooru.func.tags.export_to_json'):
|
db.session.add(post)
|
||||||
|
with patch('szurubooru.func.tags.export_to_json'), \
|
||||||
|
patch('szurubooru.func.snapshots.delete'):
|
||||||
result = api.post_api.delete_post(
|
result = api.post_api.delete_post(
|
||||||
context_factory(
|
context_factory(params={'version': 1}, user=auth_user),
|
||||||
params={'version': 1},
|
|
||||||
user=user_factory(rank=db.User.RANK_REGULAR)),
|
|
||||||
{'post_id': 1})
|
{'post_id': 1})
|
||||||
assert result == {}
|
assert result == {}
|
||||||
assert db.session.query(db.Post).count() == 0
|
assert db.session.query(db.Post).count() == 0
|
||||||
|
snapshots.delete.assert_called_once_with(post, auth_user)
|
||||||
tags.export_to_json.assert_called_once_with()
|
tags.export_to_json.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
from szurubooru.func import posts
|
from szurubooru.func import posts, snapshots
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
@ -15,15 +15,15 @@ def inject_config(config_injector):
|
||||||
|
|
||||||
|
|
||||||
def test_featuring(user_factory, post_factory, context_factory):
|
def test_featuring(user_factory, post_factory, context_factory):
|
||||||
db.session.add(post_factory(id=1))
|
auth_user = user_factory(rank=db.User.RANK_REGULAR)
|
||||||
db.session.commit()
|
post = post_factory(id=1)
|
||||||
|
db.session.add(post)
|
||||||
assert not posts.get_post_by_id(1).is_featured
|
assert not posts.get_post_by_id(1).is_featured
|
||||||
with patch('szurubooru.func.posts.serialize_post'):
|
with patch('szurubooru.func.posts.serialize_post'), \
|
||||||
|
patch('szurubooru.func.snapshots.modify'):
|
||||||
posts.serialize_post.return_value = 'serialized post'
|
posts.serialize_post.return_value = 'serialized post'
|
||||||
result = api.post_api.set_featured_post(
|
result = api.post_api.set_featured_post(
|
||||||
context_factory(
|
context_factory(params={'id': 1}, user=auth_user))
|
||||||
params={'id': 1},
|
|
||||||
user=user_factory(rank=db.User.RANK_REGULAR)))
|
|
||||||
assert result == 'serialized post'
|
assert result == 'serialized post'
|
||||||
assert posts.try_get_featured_post() is not None
|
assert posts.try_get_featured_post() is not None
|
||||||
assert posts.try_get_featured_post().post_id == 1
|
assert posts.try_get_featured_post().post_id == 1
|
||||||
|
@ -32,6 +32,7 @@ def test_featuring(user_factory, post_factory, context_factory):
|
||||||
context_factory(
|
context_factory(
|
||||||
user=user_factory(rank=db.User.RANK_REGULAR)))
|
user=user_factory(rank=db.User.RANK_REGULAR)))
|
||||||
assert result == 'serialized post'
|
assert result == 'serialized post'
|
||||||
|
snapshots.modify.assert_called_once_with(post, auth_user)
|
||||||
|
|
||||||
|
|
||||||
def test_trying_to_omit_required_parameter(user_factory, context_factory):
|
def test_trying_to_omit_required_parameter(user_factory, context_factory):
|
||||||
|
|
|
@ -40,7 +40,7 @@ def test_post_updating(
|
||||||
patch('szurubooru.func.posts.update_post_flags'), \
|
patch('szurubooru.func.posts.update_post_flags'), \
|
||||||
patch('szurubooru.func.posts.serialize_post'), \
|
patch('szurubooru.func.posts.serialize_post'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'), \
|
patch('szurubooru.func.tags.export_to_json'), \
|
||||||
patch('szurubooru.func.snapshots.save_entity_modification'), \
|
patch('szurubooru.func.snapshots.modify'), \
|
||||||
fake_datetime('1997-01-01'):
|
fake_datetime('1997-01-01'):
|
||||||
posts.serialize_post.return_value = 'serialized post'
|
posts.serialize_post.return_value = 'serialized post'
|
||||||
|
|
||||||
|
@ -77,9 +77,8 @@ def test_post_updating(
|
||||||
post, ['flag1', 'flag2'])
|
post, ['flag1', 'flag2'])
|
||||||
posts.serialize_post.assert_called_once_with(
|
posts.serialize_post.assert_called_once_with(
|
||||||
post, auth_user, options=None)
|
post, auth_user, options=None)
|
||||||
|
snapshots.modify.assert_called_once_with(post, auth_user)
|
||||||
tags.export_to_json.assert_called_once_with()
|
tags.export_to_json.assert_called_once_with()
|
||||||
snapshots.save_entity_modification.assert_called_once_with(
|
|
||||||
post, auth_user)
|
|
||||||
assert post.last_edit_time == datetime(1997, 1, 1)
|
assert post.last_edit_time == datetime(1997, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,10 +89,10 @@ def test_uploading_from_url_saves_source(
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
with patch('szurubooru.func.net.download'), \
|
with patch('szurubooru.func.net.download'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'), \
|
patch('szurubooru.func.tags.export_to_json'), \
|
||||||
patch('szurubooru.func.snapshots.save_entity_modification'), \
|
|
||||||
patch('szurubooru.func.posts.serialize_post'), \
|
patch('szurubooru.func.posts.serialize_post'), \
|
||||||
patch('szurubooru.func.posts.update_post_content'), \
|
patch('szurubooru.func.posts.update_post_content'), \
|
||||||
patch('szurubooru.func.posts.update_post_source'):
|
patch('szurubooru.func.posts.update_post_source'), \
|
||||||
|
patch('szurubooru.func.snapshots.modify'):
|
||||||
net.download.return_value = b'content'
|
net.download.return_value = b'content'
|
||||||
api.post_api.update_post(
|
api.post_api.update_post(
|
||||||
context_factory(
|
context_factory(
|
||||||
|
@ -112,10 +111,10 @@ def test_uploading_from_url_with_source_specified(
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
with patch('szurubooru.func.net.download'), \
|
with patch('szurubooru.func.net.download'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'), \
|
patch('szurubooru.func.tags.export_to_json'), \
|
||||||
patch('szurubooru.func.snapshots.save_entity_modification'), \
|
|
||||||
patch('szurubooru.func.posts.serialize_post'), \
|
patch('szurubooru.func.posts.serialize_post'), \
|
||||||
patch('szurubooru.func.posts.update_post_content'), \
|
patch('szurubooru.func.posts.update_post_content'), \
|
||||||
patch('szurubooru.func.posts.update_post_source'):
|
patch('szurubooru.func.posts.update_post_source'), \
|
||||||
|
patch('szurubooru.func.snapshots.modify'):
|
||||||
net.download.return_value = b'content'
|
net.download.return_value = b'content'
|
||||||
api.post_api.update_post(
|
api.post_api.update_post(
|
||||||
context_factory(
|
context_factory(
|
||||||
|
|
|
@ -7,8 +7,8 @@ def snapshot_factory():
|
||||||
snapshot = db.Snapshot()
|
snapshot = db.Snapshot()
|
||||||
snapshot.creation_time = datetime(1999, 1, 1)
|
snapshot.creation_time = datetime(1999, 1, 1)
|
||||||
snapshot.resource_type = 'dummy'
|
snapshot.resource_type = 'dummy'
|
||||||
snapshot.resource_id = 1
|
snapshot.resource_pkey = 1
|
||||||
snapshot.resource_repr = 'dummy'
|
snapshot.resource_name = 'dummy'
|
||||||
snapshot.operation = 'added'
|
snapshot.operation = 'added'
|
||||||
snapshot.data = '{}'
|
snapshot.data = '{}'
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
from szurubooru.func import tag_categories, tags
|
from szurubooru.func import tag_categories, tags, snapshots
|
||||||
|
|
||||||
|
|
||||||
def _update_category_name(category, name):
|
def _update_category_name(category, name):
|
||||||
|
@ -15,21 +15,26 @@ def inject_config(config_injector):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def test_creating_category(user_factory, context_factory):
|
def test_creating_category(
|
||||||
with patch('szurubooru.func.tag_categories.serialize_category'), \
|
tag_category_factory, user_factory, context_factory):
|
||||||
|
auth_user = user_factory(rank=db.User.RANK_REGULAR)
|
||||||
|
category = tag_category_factory(name='meta')
|
||||||
|
db.session.add(category)
|
||||||
|
|
||||||
|
with patch('szurubooru.func.tag_categories.create_category'), \
|
||||||
|
patch('szurubooru.func.tag_categories.serialize_category'), \
|
||||||
patch('szurubooru.func.tag_categories.update_category_name'), \
|
patch('szurubooru.func.tag_categories.update_category_name'), \
|
||||||
|
patch('szurubooru.func.snapshots.create'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'):
|
patch('szurubooru.func.tags.export_to_json'):
|
||||||
|
tag_categories.create_category.return_value = category
|
||||||
tag_categories.update_category_name.side_effect = _update_category_name
|
tag_categories.update_category_name.side_effect = _update_category_name
|
||||||
tag_categories.serialize_category.return_value = 'serialized category'
|
tag_categories.serialize_category.return_value = 'serialized category'
|
||||||
result = api.tag_category_api.create_tag_category(
|
result = api.tag_category_api.create_tag_category(
|
||||||
context_factory(
|
context_factory(
|
||||||
params={'name': 'meta', 'color': 'black'},
|
params={'name': 'meta', 'color': 'black'}, user=auth_user))
|
||||||
user=user_factory(rank=db.User.RANK_REGULAR)))
|
|
||||||
assert result == 'serialized category'
|
assert result == 'serialized category'
|
||||||
category = db.session.query(db.TagCategory).one()
|
tag_categories.create_category.assert_called_once_with('meta', 'black')
|
||||||
assert category.name == 'meta'
|
snapshots.create.assert_called_once_with(category, auth_user)
|
||||||
assert category.color == 'black'
|
|
||||||
assert category.tag_count == 0
|
|
||||||
tags.export_to_json.assert_called_once_with()
|
tags.export_to_json.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
from szurubooru.func import tag_categories, tags
|
from szurubooru.func import tag_categories, tags, snapshots
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
@ -12,18 +12,19 @@ def inject_config(config_injector):
|
||||||
|
|
||||||
|
|
||||||
def test_deleting(user_factory, tag_category_factory, context_factory):
|
def test_deleting(user_factory, tag_category_factory, context_factory):
|
||||||
|
auth_user = user_factory(rank=db.User.RANK_REGULAR)
|
||||||
|
category = tag_category_factory(name='category')
|
||||||
db.session.add(tag_category_factory(name='root'))
|
db.session.add(tag_category_factory(name='root'))
|
||||||
db.session.add(tag_category_factory(name='category'))
|
db.session.add(category)
|
||||||
db.session.commit()
|
with patch('szurubooru.func.snapshots.delete'), \
|
||||||
with patch('szurubooru.func.tags.export_to_json'):
|
patch('szurubooru.func.tags.export_to_json'):
|
||||||
result = api.tag_category_api.delete_tag_category(
|
result = api.tag_category_api.delete_tag_category(
|
||||||
context_factory(
|
context_factory(params={'version': 1}, user=auth_user),
|
||||||
params={'version': 1},
|
|
||||||
user=user_factory(rank=db.User.RANK_REGULAR)),
|
|
||||||
{'category_name': 'category'})
|
{'category_name': 'category'})
|
||||||
assert result == {}
|
assert result == {}
|
||||||
assert db.session.query(db.TagCategory).count() == 1
|
assert db.session.query(db.TagCategory).count() == 1
|
||||||
assert db.session.query(db.TagCategory).one().name == 'root'
|
assert db.session.query(db.TagCategory).one().name == 'root'
|
||||||
|
snapshots.delete.assert_called_once_with(category, auth_user)
|
||||||
tags.export_to_json.assert_called_once_with()
|
tags.export_to_json.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ def test_retrieving_single(
|
||||||
'color': 'dummy',
|
'color': 'dummy',
|
||||||
'usages': 0,
|
'usages': 0,
|
||||||
'default': False,
|
'default': False,
|
||||||
'snapshots': [],
|
|
||||||
'version': 1,
|
'version': 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
from szurubooru.func import tag_categories, tags
|
from szurubooru.func import tag_categories, tags, snapshots
|
||||||
|
|
||||||
|
|
||||||
def _update_category_name(category, name):
|
def _update_category_name(category, name):
|
||||||
|
@ -20,29 +20,27 @@ def inject_config(config_injector):
|
||||||
|
|
||||||
|
|
||||||
def test_simple_updating(user_factory, tag_category_factory, context_factory):
|
def test_simple_updating(user_factory, tag_category_factory, context_factory):
|
||||||
|
auth_user = user_factory(rank=db.User.RANK_REGULAR)
|
||||||
category = tag_category_factory(name='name', color='black')
|
category = tag_category_factory(name='name', color='black')
|
||||||
db.session.add(category)
|
db.session.add(category)
|
||||||
db.session.commit()
|
|
||||||
with patch('szurubooru.func.tag_categories.serialize_category'), \
|
with patch('szurubooru.func.tag_categories.serialize_category'), \
|
||||||
patch('szurubooru.func.tag_categories.update_category_name'), \
|
patch('szurubooru.func.tag_categories.update_category_name'), \
|
||||||
patch('szurubooru.func.tag_categories.update_category_color'), \
|
patch('szurubooru.func.tag_categories.update_category_color'), \
|
||||||
|
patch('szurubooru.func.snapshots.modify'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'):
|
patch('szurubooru.func.tags.export_to_json'):
|
||||||
tag_categories.update_category_name.side_effect = _update_category_name
|
tag_categories.update_category_name.side_effect = _update_category_name
|
||||||
tag_categories.serialize_category.return_value = 'serialized category'
|
tag_categories.serialize_category.return_value = 'serialized category'
|
||||||
result = api.tag_category_api.update_tag_category(
|
result = api.tag_category_api.update_tag_category(
|
||||||
context_factory(
|
context_factory(
|
||||||
params={
|
params={'name': 'changed', 'color': 'white', 'version': 1},
|
||||||
'name': 'changed',
|
user=auth_user),
|
||||||
'color': 'white',
|
|
||||||
'version': 1,
|
|
||||||
},
|
|
||||||
user=user_factory(rank=db.User.RANK_REGULAR)),
|
|
||||||
{'category_name': 'name'})
|
{'category_name': 'name'})
|
||||||
assert result == 'serialized category'
|
assert result == 'serialized category'
|
||||||
tag_categories.update_category_name.assert_called_once_with(
|
tag_categories.update_category_name.assert_called_once_with(
|
||||||
category, 'changed')
|
category, 'changed')
|
||||||
tag_categories.update_category_color.assert_called_once_with(
|
tag_categories.update_category_color.assert_called_once_with(
|
||||||
category, 'white')
|
category, 'white')
|
||||||
|
snapshots.modify.assert_called_once_with(category, auth_user)
|
||||||
tags.export_to_json.assert_called_once_with()
|
tags.export_to_json.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
from szurubooru.func import tags
|
from szurubooru.func import tags, snapshots
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
@ -10,12 +10,15 @@ def inject_config(config_injector):
|
||||||
|
|
||||||
|
|
||||||
def test_creating_simple_tags(tag_factory, user_factory, context_factory):
|
def test_creating_simple_tags(tag_factory, user_factory, context_factory):
|
||||||
|
auth_user = user_factory(rank=db.User.RANK_REGULAR)
|
||||||
|
tag = tag_factory()
|
||||||
with patch('szurubooru.func.tags.create_tag'), \
|
with patch('szurubooru.func.tags.create_tag'), \
|
||||||
patch('szurubooru.func.tags.get_or_create_tags_by_names'), \
|
patch('szurubooru.func.tags.get_or_create_tags_by_names'), \
|
||||||
patch('szurubooru.func.tags.serialize_tag'), \
|
patch('szurubooru.func.tags.serialize_tag'), \
|
||||||
|
patch('szurubooru.func.snapshots.create'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'):
|
patch('szurubooru.func.tags.export_to_json'):
|
||||||
tags.get_or_create_tags_by_names.return_value = ([], [])
|
tags.get_or_create_tags_by_names.return_value = ([], [])
|
||||||
tags.create_tag.return_value = tag_factory()
|
tags.create_tag.return_value = tag
|
||||||
tags.serialize_tag.return_value = 'serialized tag'
|
tags.serialize_tag.return_value = 'serialized tag'
|
||||||
result = api.tag_api.create_tag(
|
result = api.tag_api.create_tag(
|
||||||
context_factory(
|
context_factory(
|
||||||
|
@ -26,10 +29,11 @@ def test_creating_simple_tags(tag_factory, user_factory, context_factory):
|
||||||
'suggestions': ['sug1', 'sug2'],
|
'suggestions': ['sug1', 'sug2'],
|
||||||
'implications': ['imp1', 'imp2'],
|
'implications': ['imp1', 'imp2'],
|
||||||
},
|
},
|
||||||
user=user_factory(rank=db.User.RANK_REGULAR)))
|
user=auth_user))
|
||||||
assert result == 'serialized tag'
|
assert result == 'serialized tag'
|
||||||
tags.create_tag.assert_called_once_with(
|
tags.create_tag.assert_called_once_with(
|
||||||
['tag1', 'tag2'], 'meta', ['sug1', 'sug2'], ['imp1', 'imp2'])
|
['tag1', 'tag2'], 'meta', ['sug1', 'sug2'], ['imp1', 'imp2'])
|
||||||
|
snapshots.create.assert_called_once_with(tag, auth_user)
|
||||||
tags.export_to_json.assert_called_once_with()
|
tags.export_to_json.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
from szurubooru.func import tags
|
from szurubooru.func import tags, snapshots
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
@ -10,16 +10,18 @@ def inject_config(config_injector):
|
||||||
|
|
||||||
|
|
||||||
def test_deleting(user_factory, tag_factory, context_factory):
|
def test_deleting(user_factory, tag_factory, context_factory):
|
||||||
db.session.add(tag_factory(names=['tag']))
|
auth_user = user_factory(rank=db.User.RANK_REGULAR)
|
||||||
|
tag = tag_factory(names=['tag'])
|
||||||
|
db.session.add(tag)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
with patch('szurubooru.func.tags.export_to_json'):
|
with patch('szurubooru.func.tags.export_to_json'), \
|
||||||
|
patch('szurubooru.func.snapshots.delete'):
|
||||||
result = api.tag_api.delete_tag(
|
result = api.tag_api.delete_tag(
|
||||||
context_factory(
|
context_factory(params={'version': 1}, user=auth_user),
|
||||||
params={'version': 1},
|
|
||||||
user=user_factory(rank=db.User.RANK_REGULAR)),
|
|
||||||
{'tag_name': 'tag'})
|
{'tag_name': 'tag'})
|
||||||
assert result == {}
|
assert result == {}
|
||||||
assert db.session.query(db.Tag).count() == 0
|
assert db.session.query(db.Tag).count() == 0
|
||||||
|
snapshots.delete.assert_called_once_with(tag, auth_user)
|
||||||
tags.export_to_json.assert_called_once_with()
|
tags.export_to_json.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
from szurubooru.func import tags
|
from szurubooru.func import tags, snapshots
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
@ -10,6 +10,7 @@ def inject_config(config_injector):
|
||||||
|
|
||||||
|
|
||||||
def test_merging(user_factory, tag_factory, context_factory, post_factory):
|
def test_merging(user_factory, tag_factory, context_factory, post_factory):
|
||||||
|
auth_user = user_factory(rank=db.User.RANK_REGULAR)
|
||||||
source_tag = tag_factory(names=['source'])
|
source_tag = tag_factory(names=['source'])
|
||||||
target_tag = tag_factory(names=['target'])
|
target_tag = tag_factory(names=['target'])
|
||||||
db.session.add_all([source_tag, target_tag])
|
db.session.add_all([source_tag, target_tag])
|
||||||
|
@ -24,6 +25,7 @@ def test_merging(user_factory, tag_factory, context_factory, post_factory):
|
||||||
assert target_tag.post_count == 0
|
assert target_tag.post_count == 0
|
||||||
with patch('szurubooru.func.tags.serialize_tag'), \
|
with patch('szurubooru.func.tags.serialize_tag'), \
|
||||||
patch('szurubooru.func.tags.merge_tags'), \
|
patch('szurubooru.func.tags.merge_tags'), \
|
||||||
|
patch('szurubooru.func.snapshots.merge'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'):
|
patch('szurubooru.func.tags.export_to_json'):
|
||||||
api.tag_api.merge_tags(
|
api.tag_api.merge_tags(
|
||||||
context_factory(
|
context_factory(
|
||||||
|
@ -33,8 +35,10 @@ def test_merging(user_factory, tag_factory, context_factory, post_factory):
|
||||||
'remove': 'source',
|
'remove': 'source',
|
||||||
'mergeTo': 'target',
|
'mergeTo': 'target',
|
||||||
},
|
},
|
||||||
user=user_factory(rank=db.User.RANK_REGULAR)))
|
user=auth_user))
|
||||||
tags.merge_tags.called_once_with(source_tag, target_tag)
|
tags.merge_tags.called_once_with(source_tag, target_tag)
|
||||||
|
snapshots.merge.assert_called_once_with(
|
||||||
|
source_tag, target_tag, auth_user)
|
||||||
tags.export_to_json.assert_called_once_with()
|
tags.export_to_json.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
from szurubooru.func import tags
|
from szurubooru.func import tags, snapshots
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
@ -31,6 +31,7 @@ def test_simple_updating(user_factory, tag_factory, context_factory):
|
||||||
patch('szurubooru.func.tags.update_tag_suggestions'), \
|
patch('szurubooru.func.tags.update_tag_suggestions'), \
|
||||||
patch('szurubooru.func.tags.update_tag_implications'), \
|
patch('szurubooru.func.tags.update_tag_implications'), \
|
||||||
patch('szurubooru.func.tags.serialize_tag'), \
|
patch('szurubooru.func.tags.serialize_tag'), \
|
||||||
|
patch('szurubooru.func.snapshots.modify'), \
|
||||||
patch('szurubooru.func.tags.export_to_json'):
|
patch('szurubooru.func.tags.export_to_json'):
|
||||||
tags.get_or_create_tags_by_names.return_value = ([], [])
|
tags.get_or_create_tags_by_names.return_value = ([], [])
|
||||||
tags.serialize_tag.return_value = 'serialized tag'
|
tags.serialize_tag.return_value = 'serialized tag'
|
||||||
|
@ -57,6 +58,8 @@ def test_simple_updating(user_factory, tag_factory, context_factory):
|
||||||
tag, ['imp1', 'imp2'])
|
tag, ['imp1', 'imp2'])
|
||||||
tags.serialize_tag.assert_called_once_with(
|
tags.serialize_tag.assert_called_once_with(
|
||||||
tag, options=None)
|
tag, options=None)
|
||||||
|
snapshots.modify.assert_called_once_with(tag, auth_user)
|
||||||
|
tags.export_to_json.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
@ -145,8 +145,8 @@ def test_cascade_deletions(post_factory, user_factory, comment_factory):
|
||||||
snapshot.user = user
|
snapshot.user = user
|
||||||
snapshot.creation_time = datetime(1997, 1, 1)
|
snapshot.creation_time = datetime(1997, 1, 1)
|
||||||
snapshot.resource_type = '-'
|
snapshot.resource_type = '-'
|
||||||
snapshot.resource_id = 1
|
snapshot.resource_pkey = 1
|
||||||
snapshot.resource_repr = '-'
|
snapshot.resource_name = '-'
|
||||||
snapshot.operation = '-'
|
snapshot.operation = '-'
|
||||||
|
|
||||||
db.session.add_all([user, post, comment, snapshot])
|
db.session.add_all([user, post, comment, snapshot])
|
||||||
|
|
275
server/szurubooru/tests/func/test_diff.py
Normal file
275
server/szurubooru/tests/func/test_diff.py
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
import pytest
|
||||||
|
from szurubooru.func import diff
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('old,new,expected', [
|
||||||
|
(
|
||||||
|
[], [], None,
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
[],
|
||||||
|
['added'],
|
||||||
|
{'type': 'list change', 'added': ['added'], 'removed': []},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
['removed'],
|
||||||
|
[],
|
||||||
|
{'type': 'list change', 'added': [], 'removed': ['removed']},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
['untouched'],
|
||||||
|
['untouched'],
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
['untouched'],
|
||||||
|
['untouched', 'added'],
|
||||||
|
{'type': 'list change', 'added': ['added'], 'removed': []},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
['untouched', 'removed'],
|
||||||
|
['untouched'],
|
||||||
|
{'type': 'list change', 'added': [], 'removed': ['removed']},
|
||||||
|
),
|
||||||
|
])
|
||||||
|
def test_get_list_diff(old, new, expected):
|
||||||
|
assert diff.get_list_diff(old, new) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('old,new,expected', [
|
||||||
|
(
|
||||||
|
{}, {}, None,
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'removed key': 'removed value'},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'removed key':
|
||||||
|
{
|
||||||
|
'type': 'deleted property',
|
||||||
|
'value': 'removed value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{},
|
||||||
|
{'added key': 'added value'},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'added key':
|
||||||
|
{
|
||||||
|
'type': 'added property',
|
||||||
|
'value': 'added value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'key': 'old value'},
|
||||||
|
{'key': 'new value'},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'key':
|
||||||
|
{
|
||||||
|
'type': 'primitive change',
|
||||||
|
'old-value': 'old value',
|
||||||
|
'new-value': 'new value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'key': 'untouched'},
|
||||||
|
{'key': 'untouched'},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'key': 'untouched', 'removed key': 'removed value'},
|
||||||
|
{'key': 'untouched'},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'removed key':
|
||||||
|
{
|
||||||
|
'type': 'deleted property',
|
||||||
|
'value': 'removed value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'key': 'untouched'},
|
||||||
|
{'key': 'untouched', 'added key': 'added value'},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'added key':
|
||||||
|
{
|
||||||
|
'type': 'added property',
|
||||||
|
'value': 'added value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'key': 'untouched', 'changed key': 'old value'},
|
||||||
|
{'key': 'untouched', 'changed key': 'new value'},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'changed key':
|
||||||
|
{
|
||||||
|
'type': 'primitive change',
|
||||||
|
'old-value': 'old value',
|
||||||
|
'new-value': 'new value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'key': {'subkey': 'old value'}},
|
||||||
|
{'key': {'subkey': 'new value'}},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'key':
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'subkey':
|
||||||
|
{
|
||||||
|
'type': 'primitive change',
|
||||||
|
'old-value': 'old value',
|
||||||
|
'new-value': 'new value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'key': {}},
|
||||||
|
{'key': {'subkey': 'removed value'}},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'key':
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'subkey':
|
||||||
|
{
|
||||||
|
'type': 'added property',
|
||||||
|
'value': 'removed value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'key': {'subkey': 'removed value'}},
|
||||||
|
{'key': {}},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'key':
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'subkey':
|
||||||
|
{
|
||||||
|
'type': 'deleted property',
|
||||||
|
'value': 'removed value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'key': ['old value']},
|
||||||
|
{'key': ['new value']},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'key':
|
||||||
|
{
|
||||||
|
'type': 'list change',
|
||||||
|
'added': ['new value'],
|
||||||
|
'removed': ['old value'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'key': []},
|
||||||
|
{'key': ['new value']},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'key':
|
||||||
|
{
|
||||||
|
'type': 'list change',
|
||||||
|
'added': ['new value'],
|
||||||
|
'removed': [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'key': ['removed value']},
|
||||||
|
{'key': []},
|
||||||
|
{
|
||||||
|
'type': 'object change',
|
||||||
|
'value':
|
||||||
|
{
|
||||||
|
'key':
|
||||||
|
{
|
||||||
|
'type': 'list change',
|
||||||
|
'added': [],
|
||||||
|
'removed': ['removed value'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
])
|
||||||
|
def test_get_dict_diff(old, new, expected):
|
||||||
|
assert diff.get_dict_diff(old, new) == expected
|
|
@ -3,8 +3,7 @@ from unittest.mock import patch
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import db
|
from szurubooru import db
|
||||||
from szurubooru.func import (
|
from szurubooru.func import (posts, users, comments, tags, images, files, util)
|
||||||
posts, users, comments, snapshots, tags, images, files, util)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('input_mime_type,expected_url', [
|
@pytest.mark.parametrize('input_mime_type,expected_url', [
|
||||||
|
@ -78,14 +77,12 @@ def test_serialize_post(
|
||||||
config_injector({'data_url': 'http://example.com/'})
|
config_injector({'data_url': 'http://example.com/'})
|
||||||
with patch('szurubooru.func.comments.serialize_comment'), \
|
with patch('szurubooru.func.comments.serialize_comment'), \
|
||||||
patch('szurubooru.func.users.serialize_micro_user'), \
|
patch('szurubooru.func.users.serialize_micro_user'), \
|
||||||
patch('szurubooru.func.posts.files.has'), \
|
patch('szurubooru.func.posts.files.has'):
|
||||||
patch('szurubooru.func.snapshots.get_serialized_history'):
|
|
||||||
files.has.return_value = True
|
files.has.return_value = True
|
||||||
users.serialize_micro_user.side_effect \
|
users.serialize_micro_user.side_effect \
|
||||||
= lambda user, auth_user: user.name
|
= lambda user, auth_user: user.name
|
||||||
comments.serialize_comment.side_effect \
|
comments.serialize_comment.side_effect \
|
||||||
= lambda comment, auth_user: comment.user.name
|
= lambda comment, auth_user: comment.user.name
|
||||||
snapshots.get_serialized_history.return_value = 'snapshot history'
|
|
||||||
|
|
||||||
auth_user = user_factory(name='auth user')
|
auth_user = user_factory(name='auth user')
|
||||||
post = db.Post()
|
post = db.Post()
|
||||||
|
@ -178,7 +175,6 @@ def test_serialize_post(
|
||||||
'favoritedBy': ['fav1'],
|
'favoritedBy': ['fav1'],
|
||||||
'hasCustomThumbnail': True,
|
'hasCustomThumbnail': True,
|
||||||
'mimeType': 'image/jpeg',
|
'mimeType': 'image/jpeg',
|
||||||
'snapshots': 'snapshot history',
|
|
||||||
'comments': ['commenter1', 'commenter2'],
|
'comments': ['commenter1', 'commenter2'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,52 @@
|
||||||
|
from unittest.mock import patch
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import db
|
from szurubooru import db
|
||||||
from szurubooru.func import snapshots
|
from szurubooru.func import snapshots, users
|
||||||
|
|
||||||
|
|
||||||
def test_serializing_post(post_factory, user_factory, tag_factory):
|
def test_get_tag_category_snapshot(tag_category_factory):
|
||||||
|
category = tag_category_factory(name='name', color='color')
|
||||||
|
assert snapshots.get_tag_category_snapshot(category) == {
|
||||||
|
'name': 'name',
|
||||||
|
'color': 'color',
|
||||||
|
'default': False,
|
||||||
|
}
|
||||||
|
category.default = True
|
||||||
|
assert snapshots.get_tag_category_snapshot(category) == {
|
||||||
|
'name': 'name',
|
||||||
|
'color': 'color',
|
||||||
|
'default': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_tag_snapshot(tag_factory, tag_category_factory):
|
||||||
|
category = tag_category_factory(name='dummy')
|
||||||
|
tag = tag_factory(names=['main_name', 'alias'], category=category)
|
||||||
|
assert snapshots.get_tag_snapshot(tag) == {
|
||||||
|
'names': ['main_name', 'alias'],
|
||||||
|
'category': 'dummy',
|
||||||
|
'suggestions': [],
|
||||||
|
'implications': [],
|
||||||
|
}
|
||||||
|
tag = tag_factory(names=['main_name', 'alias'], category=category)
|
||||||
|
imp1 = tag_factory(names=['imp1_main_name', 'imp1_alias'])
|
||||||
|
imp2 = tag_factory(names=['imp2_main_name', 'imp2_alias'])
|
||||||
|
sug1 = tag_factory(names=['sug1_main_name', 'sug1_alias'])
|
||||||
|
sug2 = tag_factory(names=['sug2_main_name', 'sug2_alias'])
|
||||||
|
db.session.add_all([imp1, imp2, sug1, sug2])
|
||||||
|
tag.implications = [imp1, imp2]
|
||||||
|
tag.suggestions = [sug1, sug2]
|
||||||
|
db.session.flush()
|
||||||
|
assert snapshots.get_tag_snapshot(tag) == {
|
||||||
|
'names': ['main_name', 'alias'],
|
||||||
|
'category': 'dummy',
|
||||||
|
'implications': ['imp1_main_name', 'imp2_main_name'],
|
||||||
|
'suggestions': ['sug1_main_name', 'sug2_main_name'],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_post_snapshot(post_factory, user_factory, tag_factory):
|
||||||
user = user_factory(name='dummy-user')
|
user = user_factory(name='dummy-user')
|
||||||
tag1 = tag_factory(names=['dummy-tag1'])
|
tag1 = tag_factory(names=['dummy-tag1'])
|
||||||
tag2 = tag_factory(names=['dummy-tag2'])
|
tag2 = tag_factory(names=['dummy-tag2'])
|
||||||
|
@ -50,12 +92,10 @@ def test_serializing_post(post_factory, user_factory, tag_factory):
|
||||||
'checksum': 'deadbeef',
|
'checksum': 'deadbeef',
|
||||||
'featured': True,
|
'featured': True,
|
||||||
'flags': [],
|
'flags': [],
|
||||||
'notes': [
|
'notes': [{
|
||||||
{
|
'polygon': [[1, 1], [200, 1], [200, 200], [1, 200]],
|
||||||
'polygon': [(1, 1), (200, 1), (200, 200), (1, 200)],
|
'text': 'some text',
|
||||||
'text': 'some text',
|
}],
|
||||||
}
|
|
||||||
],
|
|
||||||
'relations': [2, 3],
|
'relations': [2, 3],
|
||||||
'safety': 'safe',
|
'safety': 'safe',
|
||||||
'source': 'example.com',
|
'source': 'example.com',
|
||||||
|
@ -63,243 +103,103 @@ def test_serializing_post(post_factory, user_factory, tag_factory):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_serializing_tag(tag_factory, tag_category_factory):
|
def test_serialize_snapshot(user_factory):
|
||||||
category = tag_category_factory(name='dummy')
|
auth_user = user_factory()
|
||||||
tag = tag_factory(names=['main_name', 'alias'], category=category)
|
snapshot = db.Snapshot()
|
||||||
assert snapshots.get_tag_snapshot(tag) == {
|
snapshot.operation = snapshot.OPERATION_CREATED
|
||||||
'names': ['main_name', 'alias'],
|
snapshot.resource_type = 'type'
|
||||||
'category': 'dummy',
|
snapshot.resource_name = 'id'
|
||||||
'suggestions': [],
|
snapshot.user = user_factory(name='issuer')
|
||||||
'implications': [],
|
snapshot.data = {'complex': list('object')}
|
||||||
}
|
snapshot.creation_time = datetime(1997, 1, 1)
|
||||||
|
with patch('szurubooru.func.users.serialize_micro_user'):
|
||||||
tag = tag_factory(names=['main_name', 'alias'], category=category)
|
users.serialize_micro_user.return_value = 'mocked'
|
||||||
imp1 = tag_factory(names=['imp1_main_name', 'imp1_alias'])
|
assert snapshots.serialize_snapshot(snapshot, auth_user) == {
|
||||||
imp2 = tag_factory(names=['imp2_main_name', 'imp2_alias'])
|
'operation': 'created',
|
||||||
sug1 = tag_factory(names=['sug1_main_name', 'sug1_alias'])
|
'type': 'type',
|
||||||
sug2 = tag_factory(names=['sug2_main_name', 'sug2_alias'])
|
'id': 'id',
|
||||||
db.session.add_all([imp1, imp2, sug1, sug2])
|
'user': 'mocked',
|
||||||
tag.implications = [imp1, imp2]
|
'data': {'complex': list('object')},
|
||||||
tag.suggestions = [sug1, sug2]
|
'time': datetime(1997, 1, 1),
|
||||||
db.session.flush()
|
}
|
||||||
assert snapshots.get_tag_snapshot(tag) == {
|
|
||||||
'names': ['main_name', 'alias'],
|
|
||||||
'category': 'dummy',
|
|
||||||
'implications': ['imp1_main_name', 'imp2_main_name'],
|
|
||||||
'suggestions': ['sug1_main_name', 'sug2_main_name'],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_serializing_tag_category(tag_category_factory):
|
def test_create(tag_factory, user_factory):
|
||||||
category = tag_category_factory(name='name', color='color')
|
|
||||||
assert snapshots.get_tag_category_snapshot(category) == {
|
|
||||||
'name': 'name',
|
|
||||||
'color': 'color',
|
|
||||||
'default': False,
|
|
||||||
}
|
|
||||||
category.default = True
|
|
||||||
assert snapshots.get_tag_category_snapshot(category) == {
|
|
||||||
'name': 'name',
|
|
||||||
'color': 'color',
|
|
||||||
'default': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_merging_modification_to_creation(tag_factory, user_factory):
|
|
||||||
tag = tag_factory(names=['dummy'])
|
tag = tag_factory(names=['dummy'])
|
||||||
user = user_factory()
|
db.session.add(tag)
|
||||||
db.session.add_all([tag, user])
|
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
snapshots.save_entity_creation(tag, user)
|
with patch('szurubooru.func.snapshots.get_tag_snapshot'):
|
||||||
tag.names = [db.TagName('changed')]
|
snapshots.get_tag_snapshot.return_value = 'mocked'
|
||||||
snapshots.save_entity_modification(tag, user)
|
snapshots.create(tag, user_factory())
|
||||||
results = db.session.query(db.Snapshot).all()
|
results = db.session.query(db.Snapshot).all()
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results[0].operation == db.Snapshot.OPERATION_CREATED
|
assert results[0].operation == db.Snapshot.OPERATION_CREATED
|
||||||
assert results[0].data['names'] == ['changed']
|
assert results[0].data == 'mocked'
|
||||||
|
|
||||||
|
|
||||||
def test_merging_modifications(fake_datetime, tag_factory, user_factory):
|
def test_modify_saves_non_empty_diffs(post_factory, user_factory):
|
||||||
tag = tag_factory(names=['dummy'])
|
if 'sqlite' in db.sessionmaker.kw['bind'].driver:
|
||||||
|
pytest.xfail(
|
||||||
|
'SQLite doesn\'t support transaction isolation, '
|
||||||
|
'which is required to retrieve original entity')
|
||||||
|
post = post_factory()
|
||||||
|
post.notes = [db.PostNote(polygon=[(0, 0), (0, 1), (1, 1)], text='old')]
|
||||||
user = user_factory()
|
user = user_factory()
|
||||||
db.session.add_all([tag, user])
|
db.session.add_all([post, user])
|
||||||
|
db.session.commit()
|
||||||
|
post.source = 'new source'
|
||||||
|
post.notes = [db.PostNote(polygon=[(0, 0), (0, 1), (1, 1)], text='new')]
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
with fake_datetime('13:00:00'):
|
snapshots.modify(post, user)
|
||||||
snapshots.save_entity_creation(tag, user)
|
|
||||||
tag.names = [db.TagName('changed')]
|
|
||||||
with fake_datetime('14:00:00'):
|
|
||||||
snapshots.save_entity_modification(tag, user)
|
|
||||||
tag.names = [db.TagName('changed again')]
|
|
||||||
with fake_datetime('14:00:01'):
|
|
||||||
snapshots.save_entity_modification(tag, user)
|
|
||||||
results = db.session.query(db.Snapshot).all()
|
|
||||||
assert len(results) == 2
|
|
||||||
assert results[0].operation == db.Snapshot.OPERATION_CREATED
|
|
||||||
assert results[1].operation == db.Snapshot.OPERATION_MODIFIED
|
|
||||||
assert results[0].data['names'] == ['dummy']
|
|
||||||
assert results[1].data['names'] == ['changed again']
|
|
||||||
|
|
||||||
|
|
||||||
def test_not_adding_snapshot_if_data_doesnt_change(
|
|
||||||
fake_datetime, tag_factory, user_factory):
|
|
||||||
tag = tag_factory(names=['dummy'])
|
|
||||||
user = user_factory()
|
|
||||||
db.session.add_all([tag, user])
|
|
||||||
db.session.flush()
|
|
||||||
with fake_datetime('13:00:00'):
|
|
||||||
snapshots.save_entity_creation(tag, user)
|
|
||||||
with fake_datetime('14:00:00'):
|
|
||||||
snapshots.save_entity_modification(tag, user)
|
|
||||||
results = db.session.query(db.Snapshot).all()
|
results = db.session.query(db.Snapshot).all()
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results[0].operation == db.Snapshot.OPERATION_CREATED
|
assert results[0].data == {
|
||||||
assert results[0].data['names'] == ['dummy']
|
'type': 'object change',
|
||||||
|
'value': {
|
||||||
|
'source': {
|
||||||
def test_not_merging_due_to_time_difference(
|
'type': 'primitive change',
|
||||||
fake_datetime, tag_factory, user_factory):
|
'old-value': None,
|
||||||
tag = tag_factory(names=['dummy'])
|
'new-value': 'new source',
|
||||||
user = user_factory()
|
},
|
||||||
db.session.add_all([tag, user])
|
'notes': {
|
||||||
db.session.flush()
|
'type': 'list change',
|
||||||
with fake_datetime('13:00:00'):
|
'removed': [
|
||||||
snapshots.save_entity_creation(tag, user)
|
{'polygon': [[0, 0], [0, 1], [1, 1]], 'text': 'old'}],
|
||||||
tag.names = [db.TagName('changed')]
|
'added': [
|
||||||
with fake_datetime('13:10:01'):
|
{'polygon': [[0, 0], [0, 1], [1, 1]], 'text': 'new'}],
|
||||||
snapshots.save_entity_modification(tag, user)
|
},
|
||||||
assert db.session.query(db.Snapshot).count() == 2
|
},
|
||||||
|
|
||||||
|
|
||||||
def test_not_merging_operations_by_different_users(
|
|
||||||
fake_datetime, tag_factory, user_factory):
|
|
||||||
tag = tag_factory(names=['dummy'])
|
|
||||||
user1, user2 = [user_factory(), user_factory()]
|
|
||||||
db.session.add_all([tag, user1, user2])
|
|
||||||
db.session.flush()
|
|
||||||
with fake_datetime('13:00:00'):
|
|
||||||
snapshots.save_entity_creation(tag, user1)
|
|
||||||
tag.names = [db.TagName('changed')]
|
|
||||||
snapshots.save_entity_modification(tag, user2)
|
|
||||||
assert db.session.query(db.Snapshot).count() == 2
|
|
||||||
|
|
||||||
|
|
||||||
def test_merging_resets_merging_time_window(
|
|
||||||
fake_datetime, tag_factory, user_factory):
|
|
||||||
tag = tag_factory(names=['dummy'])
|
|
||||||
user = user_factory()
|
|
||||||
db.session.add_all([tag, user])
|
|
||||||
db.session.flush()
|
|
||||||
with fake_datetime('13:00:00'):
|
|
||||||
snapshots.save_entity_creation(tag, user)
|
|
||||||
tag.names = [db.TagName('changed')]
|
|
||||||
with fake_datetime('13:09:59'):
|
|
||||||
snapshots.save_entity_modification(tag, user)
|
|
||||||
tag.names = [db.TagName('changed again')]
|
|
||||||
with fake_datetime('13:19:59'):
|
|
||||||
snapshots.save_entity_modification(tag, user)
|
|
||||||
results = db.session.query(db.Snapshot).all()
|
|
||||||
assert len(results) == 1
|
|
||||||
assert results[0].data['names'] == ['changed again']
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
'initial_operation',
|
|
||||||
[snapshots.save_entity_creation, snapshots.save_entity_modification])
|
|
||||||
def test_merging_deletion_to_modification_or_creation(
|
|
||||||
fake_datetime,
|
|
||||||
tag_factory,
|
|
||||||
tag_category_factory,
|
|
||||||
user_factory,
|
|
||||||
initial_operation):
|
|
||||||
category = tag_category_factory(name='dummy')
|
|
||||||
tag = tag_factory(names=['dummy'], category=category)
|
|
||||||
user = user_factory()
|
|
||||||
db.session.add_all([tag, user])
|
|
||||||
db.session.flush()
|
|
||||||
with fake_datetime('13:00:00'):
|
|
||||||
initial_operation(tag, user)
|
|
||||||
tag.names = [db.TagName('changed')]
|
|
||||||
with fake_datetime('14:00:00'):
|
|
||||||
snapshots.save_entity_modification(tag, user)
|
|
||||||
tag.names = [db.TagName('changed again')]
|
|
||||||
with fake_datetime('14:00:01'):
|
|
||||||
snapshots.save_entity_deletion(tag, user)
|
|
||||||
assert db.session.query(db.Snapshot).count() == 2
|
|
||||||
results = db.session \
|
|
||||||
.query(db.Snapshot) \
|
|
||||||
.order_by(db.Snapshot.snapshot_id.asc()) \
|
|
||||||
.all()
|
|
||||||
assert results[1].operation == db.Snapshot.OPERATION_DELETED
|
|
||||||
assert results[1].data == {
|
|
||||||
'names': ['changed again'],
|
|
||||||
'category': 'dummy',
|
|
||||||
'suggestions': [],
|
|
||||||
'implications': [],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_merging_deletion_all_the_way_deletes_all_snapshots(
|
def test_modify_doesnt_save_empty_diffs(tag_factory, user_factory):
|
||||||
fake_datetime, tag_factory, user_factory):
|
|
||||||
tag = tag_factory(names=['dummy'])
|
tag = tag_factory(names=['dummy'])
|
||||||
user = user_factory()
|
user = user_factory()
|
||||||
db.session.add_all([tag, user])
|
db.session.add_all([tag, user])
|
||||||
db.session.flush()
|
db.session.commit()
|
||||||
with fake_datetime('13:00:00'):
|
snapshots.modify(tag, user)
|
||||||
snapshots.save_entity_creation(tag, user)
|
|
||||||
tag.names = [db.TagName('changed')]
|
|
||||||
with fake_datetime('13:00:01'):
|
|
||||||
snapshots.save_entity_modification(tag, user)
|
|
||||||
tag.names = [db.TagName('changed again')]
|
|
||||||
with fake_datetime('13:00:02'):
|
|
||||||
snapshots.save_entity_deletion(tag, user)
|
|
||||||
assert db.session.query(db.Snapshot).count() == 0
|
assert db.session.query(db.Snapshot).count() == 0
|
||||||
|
|
||||||
|
|
||||||
def test_get_serialized_history(
|
def test_delete(tag_factory, user_factory):
|
||||||
fake_datetime, tag_factory, tag_category_factory, user_factory):
|
tag = tag_factory(names=['dummy'])
|
||||||
category = tag_category_factory(name='dummy')
|
db.session.add(tag)
|
||||||
tag = tag_factory(names=['dummy'], category=category)
|
|
||||||
user = user_factory(name='the-user')
|
|
||||||
db.session.add_all([tag, user])
|
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
with fake_datetime('2016-04-19 13:00:00'):
|
with patch('szurubooru.func.snapshots.get_tag_snapshot'):
|
||||||
snapshots.save_entity_creation(tag, user)
|
snapshots.get_tag_snapshot.return_value = 'mocked'
|
||||||
tag.names = [db.TagName('changed')]
|
snapshots.delete(tag, user_factory())
|
||||||
|
results = db.session.query(db.Snapshot).all()
|
||||||
|
assert len(results) == 1
|
||||||
|
assert results[0].operation == db.Snapshot.OPERATION_DELETED
|
||||||
|
assert results[0].data == 'mocked'
|
||||||
|
|
||||||
|
|
||||||
|
def test_merge(tag_factory, user_factory):
|
||||||
|
source_tag = tag_factory(names=['source'])
|
||||||
|
target_tag = tag_factory(names=['target'])
|
||||||
|
db.session.add_all([source_tag, target_tag])
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
with fake_datetime('2016-04-19 13:10:01'):
|
snapshots.merge(source_tag, target_tag, user_factory())
|
||||||
snapshots.save_entity_modification(tag, user)
|
result = db.session.query(db.Snapshot).one()
|
||||||
assert snapshots.get_serialized_history(tag) == [
|
assert result.operation == db.Snapshot.OPERATION_MERGED
|
||||||
{
|
assert result.data == ['tag', 'target']
|
||||||
'operation': 'modified',
|
|
||||||
'time': datetime(2016, 4, 19, 13, 10, 1),
|
|
||||||
'type': 'tag',
|
|
||||||
'id': 'changed',
|
|
||||||
'user': 'the-user',
|
|
||||||
'data': {
|
|
||||||
'names': ['changed'],
|
|
||||||
'category': 'dummy',
|
|
||||||
'suggestions': [],
|
|
||||||
'implications': [],
|
|
||||||
},
|
|
||||||
'earlier-data': {
|
|
||||||
'names': ['dummy'],
|
|
||||||
'category': 'dummy',
|
|
||||||
'suggestions': [],
|
|
||||||
'implications': [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'operation': 'created',
|
|
||||||
'time': datetime(2016, 4, 19, 13, 0, 0),
|
|
||||||
'type': 'tag',
|
|
||||||
'id': 'dummy',
|
|
||||||
'user': 'the-user',
|
|
||||||
'data': {
|
|
||||||
'names': ['dummy'],
|
|
||||||
'category': 'dummy',
|
|
||||||
'suggestions': [],
|
|
||||||
'implications': [],
|
|
||||||
},
|
|
||||||
'earlier-data': None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import db
|
from szurubooru import db
|
||||||
from szurubooru.func import tag_categories, cache, snapshots
|
from szurubooru.func import tag_categories, cache
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
@ -14,24 +14,21 @@ def test_serialize_category_when_empty():
|
||||||
|
|
||||||
|
|
||||||
def test_serialize_category(tag_category_factory, tag_factory):
|
def test_serialize_category(tag_category_factory, tag_factory):
|
||||||
with patch('szurubooru.func.snapshots.get_serialized_history'):
|
category = tag_category_factory(name='name', color='color')
|
||||||
snapshots.get_serialized_history.return_value = 'snapshot history'
|
category.category_id = 1
|
||||||
category = tag_category_factory(name='name', color='color')
|
category.default = True
|
||||||
category.category_id = 1
|
tag1 = tag_factory(category=category)
|
||||||
category.default = True
|
tag2 = tag_factory(category=category)
|
||||||
tag1 = tag_factory(category=category)
|
db.session.add_all([category, tag1, tag2])
|
||||||
tag2 = tag_factory(category=category)
|
db.session.flush()
|
||||||
db.session.add_all([category, tag1, tag2])
|
result = tag_categories.serialize_category(category)
|
||||||
db.session.flush()
|
assert result == {
|
||||||
result = tag_categories.serialize_category(category)
|
'name': 'name',
|
||||||
assert result == {
|
'color': 'color',
|
||||||
'name': 'name',
|
'default': True,
|
||||||
'color': 'color',
|
'version': 1,
|
||||||
'default': True,
|
'usages': 2,
|
||||||
'version': 1,
|
}
|
||||||
'snapshots': 'snapshot history',
|
|
||||||
'usages': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_category_when_first():
|
def test_create_category_when_first():
|
||||||
|
|
|
@ -4,7 +4,7 @@ from unittest.mock import patch
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import db
|
from szurubooru import db
|
||||||
from szurubooru.func import tags, tag_categories, cache, snapshots
|
from szurubooru.func import tags, tag_categories, cache
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
@ -44,39 +44,36 @@ def test_serialize_tag_when_empty():
|
||||||
|
|
||||||
|
|
||||||
def test_serialize_tag(post_factory, tag_factory, tag_category_factory):
|
def test_serialize_tag(post_factory, tag_factory, tag_category_factory):
|
||||||
with patch('szurubooru.func.snapshots.get_serialized_history'):
|
tag = tag_factory(
|
||||||
snapshots.get_serialized_history.return_value = 'snapshot history'
|
names=['tag1', 'tag2'],
|
||||||
tag = tag_factory(
|
category=tag_category_factory(name='cat'))
|
||||||
names=['tag1', 'tag2'],
|
tag.tag_id = 1
|
||||||
category=tag_category_factory(name='cat'))
|
tag.description = 'description'
|
||||||
tag.tag_id = 1
|
tag.suggestions = [
|
||||||
tag.description = 'description'
|
tag_factory(names=['sug1']), tag_factory(names=['sug2'])]
|
||||||
tag.suggestions = [
|
tag.implications = [
|
||||||
tag_factory(names=['sug1']), tag_factory(names=['sug2'])]
|
tag_factory(names=['impl1']), tag_factory(names=['impl2'])]
|
||||||
tag.implications = [
|
tag.last_edit_time = datetime(1998, 1, 1)
|
||||||
tag_factory(names=['impl1']), tag_factory(names=['impl2'])]
|
post1 = post_factory()
|
||||||
tag.last_edit_time = datetime(1998, 1, 1)
|
post2 = post_factory()
|
||||||
post1 = post_factory()
|
post1.tags = [tag]
|
||||||
post2 = post_factory()
|
post2.tags = [tag]
|
||||||
post1.tags = [tag]
|
db.session.add_all([tag, post1, post2])
|
||||||
post2.tags = [tag]
|
db.session.flush()
|
||||||
db.session.add_all([tag, post1, post2])
|
result = tags.serialize_tag(tag)
|
||||||
db.session.flush()
|
result['suggestions'].sort()
|
||||||
result = tags.serialize_tag(tag)
|
result['implications'].sort()
|
||||||
result['suggestions'].sort()
|
assert result == {
|
||||||
result['implications'].sort()
|
'names': ['tag1', 'tag2'],
|
||||||
assert result == {
|
'version': 1,
|
||||||
'names': ['tag1', 'tag2'],
|
'category': 'cat',
|
||||||
'version': 1,
|
'creationTime': datetime(1996, 1, 1, 0, 0),
|
||||||
'category': 'cat',
|
'lastEditTime': datetime(1998, 1, 1, 0, 0),
|
||||||
'creationTime': datetime(1996, 1, 1, 0, 0),
|
'description': 'description',
|
||||||
'lastEditTime': datetime(1998, 1, 1, 0, 0),
|
'suggestions': ['sug1', 'sug2'],
|
||||||
'description': 'description',
|
'implications': ['impl1', 'impl2'],
|
||||||
'suggestions': ['sug1', 'sug2'],
|
'usages': 2,
|
||||||
'implications': ['impl1', 'impl2'],
|
}
|
||||||
'usages': 2,
|
|
||||||
'snapshots': 'snapshot history',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_export_to_json(
|
def test_export_to_json(
|
||||||
|
|
Loading…
Reference in a new issue