server/snapshots: change snapshot representation
This commit is contained in:
parent
8ebf113ba4
commit
c2a39a0fd5
7 changed files with 267 additions and 92 deletions
239
API.md
239
API.md
|
@ -40,7 +40,9 @@
|
||||||
3. [Resources](#resources)
|
3. [Resources](#resources)
|
||||||
|
|
||||||
- [User](#user)
|
- [User](#user)
|
||||||
|
- [Tag category](#tag-category)
|
||||||
- [Tag](#tag)
|
- [Tag](#tag)
|
||||||
|
- [Snapshot](#snapshot)
|
||||||
|
|
||||||
4. [Search](#search)
|
4. [Search](#search)
|
||||||
|
|
||||||
|
@ -146,9 +148,9 @@ data.
|
||||||
{
|
{
|
||||||
"tagCategory": <tag-category>,
|
"tagCategory": <tag-category>,
|
||||||
"snapshots": [
|
"snapshots": [
|
||||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>}
|
<snapshot>
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -188,9 +190,9 @@ data.
|
||||||
{
|
{
|
||||||
"tagCategory": <tag-category>,
|
"tagCategory": <tag-category>,
|
||||||
"snapshots": [
|
"snapshots": [
|
||||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>}
|
<snapshot>
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -223,9 +225,9 @@ data.
|
||||||
{
|
{
|
||||||
"tagCategory": <tag-category>,
|
"tagCategory": <tag-category>,
|
||||||
"snapshots": [
|
"snapshots": [
|
||||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>}
|
<snapshot>
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -275,17 +277,15 @@ data.
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"query": "haruhi",
|
"query": <query>, // same as in input
|
||||||
|
"page": <page>, // same as in input
|
||||||
|
"pageSize": <page-size>,
|
||||||
|
"total": <total-count>,
|
||||||
"tags": [
|
"tags": [
|
||||||
<tag>,
|
|
||||||
<tag>,
|
|
||||||
<tag>,
|
<tag>,
|
||||||
<tag>,
|
<tag>,
|
||||||
<tag>
|
<tag>
|
||||||
],
|
]
|
||||||
"page": 1,
|
|
||||||
"pageSize": 5,
|
|
||||||
"total": 7
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
...where `<tag>` is a [tag resource](#tag) and `query` contains standard
|
...where `<tag>` is a [tag resource](#tag) and `query` contains standard
|
||||||
|
@ -373,9 +373,9 @@ data.
|
||||||
{
|
{
|
||||||
"tag": <tag>,
|
"tag": <tag>,
|
||||||
"snapshots": [
|
"snapshots": [
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>}
|
<snapshot>
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -427,9 +427,9 @@ data.
|
||||||
{
|
{
|
||||||
"tag": <tag>,
|
"tag": <tag>,
|
||||||
"snapshots": [
|
"snapshots": [
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>}
|
<snapshot>
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -469,9 +469,9 @@ data.
|
||||||
{
|
{
|
||||||
"tag": <tag>,
|
"tag": <tag>,
|
||||||
"snapshots": [
|
"snapshots": [
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>}
|
<snapshot>
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -519,8 +519,8 @@ data.
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"remove": "source-tag",
|
"remove": <source-tag-name>,
|
||||||
"merge-to": "target-tag"
|
"merge-to": <target-tag-name>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -530,9 +530,9 @@ data.
|
||||||
{
|
{
|
||||||
"tag": <tag>,
|
"tag": <tag>,
|
||||||
"snapshots": [
|
"snapshots": [
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
<snapshot>,
|
||||||
{"data": <tag-snapshot>, "time": <snapshot-time>}
|
<snapshot>
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -565,11 +565,11 @@ data.
|
||||||
"siblings": [
|
"siblings": [
|
||||||
{
|
{
|
||||||
"tag": <tag>,
|
"tag": <tag>,
|
||||||
"occurrences": 2
|
"occurrences": <occurrence-count>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": <tag>,
|
"tag": <tag>,
|
||||||
"occurrences": 1
|
"occurrences": <occurrence-count>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -597,17 +597,17 @@ data.
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"query": "rr-",
|
"query": <query>, // same as in input
|
||||||
|
"page": <page>, // same as in input
|
||||||
|
"pageSize": <page-size>,
|
||||||
|
"total": <total-count>,
|
||||||
"users": [
|
"users": [
|
||||||
<user>,
|
<user>,
|
||||||
<user>,
|
<user>,
|
||||||
<user>,
|
<user>,
|
||||||
<user>,
|
<user>,
|
||||||
<user>
|
<user>
|
||||||
],
|
]
|
||||||
"page": 1,
|
|
||||||
"pageSize": 5,
|
|
||||||
"total": 7
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
...where `<user>` is a [user resource](#user) and `query` contains standard
|
...where `<user>` is a [user resource](#user) and `query` contains standard
|
||||||
|
@ -865,62 +865,171 @@ data.
|
||||||
# Resources
|
# Resources
|
||||||
|
|
||||||
## User
|
## User
|
||||||
|
**Description**
|
||||||
|
|
||||||
|
A single user.
|
||||||
|
|
||||||
|
**Structure**
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"name": "rr-",
|
"name": <name>,
|
||||||
"email": "rr-@sakuya.pl", // available only if the request is authenticated by the same user
|
"email": <email>,
|
||||||
"rank": "admin", // controlled by server's configuration
|
"rank": <rank>,
|
||||||
"rankName": "Administrator", // controlled by server's configuration
|
"rankName": <rank-name>,
|
||||||
"lastLoginTime": "2016-04-08T20:20:16.570517",
|
"lastLoginTime": <last-login-time>,
|
||||||
"creationTime": "2016-03-28T13:37:01.755461",
|
"creationTime": <creation-time>,
|
||||||
"avatarStyle": "gravatar", // "gravatar" or "manual"
|
"avatarStyle": <avatar-style>,
|
||||||
"avatarUrl": "http://gravatar.com/(...)"
|
"avatarUrl": <avatar-url>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Field meaning**
|
||||||
|
- `<name>`: the user name.
|
||||||
|
- `<email>`: the user email. It is available only if the request is
|
||||||
|
authenticated by the same user.
|
||||||
|
- `<rank>`: the user rank, which effectively affects their privileges. The
|
||||||
|
available ranks are stored in the server configuration.
|
||||||
|
- `<rank-name>`: the text representation of user's rank. Like `<rank>`, the
|
||||||
|
possible values depend on the server configuration.
|
||||||
|
- `<last-login-time>`: the last login time, formatted as per RFC 3339.
|
||||||
|
- `<creation-time>`: the user registration time, formatted as per RFC 3339.
|
||||||
|
- `<avatarStyle>`: how to render the user avatar.
|
||||||
|
|
||||||
|
Possible values:
|
||||||
|
|
||||||
|
- `"gravatar"`: the user uses Gravatar.
|
||||||
|
- `"manual"`: the user has uploaded a picture manually.
|
||||||
|
|
||||||
|
- `<avatarUrl>`: the URL to the avatar.
|
||||||
|
|
||||||
## Tag category
|
## Tag category
|
||||||
|
**Description**
|
||||||
|
|
||||||
|
A single tag category. The primary purpose of tag categories is to distinguish
|
||||||
|
certain tag types (such as characters, media type etc.), which improves user
|
||||||
|
experience.
|
||||||
|
|
||||||
|
**Structure**
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"name": "character",
|
"name": <name>,
|
||||||
"color": "#FF0000" // used to colorize certain tag types in the web client
|
"color": <color>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tag category snapshot
|
**Field meaning**
|
||||||
|
|
||||||
```json5
|
- `<name>`: the category name.
|
||||||
{
|
- `<color>`: the category color.
|
||||||
"name": "character",
|
|
||||||
"color": "#FF0000"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tag
|
## Tag
|
||||||
|
**Description**
|
||||||
|
|
||||||
|
A single tag. Tags are used to let users search for posts.
|
||||||
|
|
||||||
|
**Structure**
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"names": ["tag1", "tag2", "tag3"],
|
"names": <names>,
|
||||||
"category": "plain",
|
"category": <category>,
|
||||||
"implications": ["implied-tag1", "implied-tag2", "implied-tag3"],
|
"implications": <implications>,
|
||||||
"suggestions": ["suggested-tag1", "suggested-tag2", "suggested-tag3"],
|
"suggestions": <suggestions>,
|
||||||
"creationTime": "2016-03-28T13:37:01.755461",
|
"creationTime": <creation-time>,
|
||||||
"lastEditTime": "2016-04-08T20:20:16.570517"
|
"lastEditTime": <last-edit-time>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tag snapshot
|
**Field meaning**
|
||||||
|
|
||||||
|
- `<names>`: a list of tag names (aliases). Tagging a post with any name will
|
||||||
|
automatically assign the first name from this list.
|
||||||
|
- `<category>`: the name of the category the given tag belongs to.
|
||||||
|
- `<implications>`: a list of implied tag names. Implied tags are automatically
|
||||||
|
appended by the web client on usage.
|
||||||
|
- `<suggestions>`: a list of suggested tag names. Suggested tags are shown to
|
||||||
|
the user by the web client on usage.
|
||||||
|
- `<creation-time>`: time the tag was created, formatted as per RFC 3339.
|
||||||
|
- `<creation-time>`: time the tag was edited, formatted as per RFC 3339.
|
||||||
|
|
||||||
|
## Snapshot
|
||||||
|
**Description**
|
||||||
|
|
||||||
|
A snapshot is a version of a database resource.
|
||||||
|
|
||||||
|
**Structure**
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"names": ["tag1", "tag2", "tag3"],
|
"operation": <operation>,
|
||||||
"category": "plain",
|
"type": <resource-type>
|
||||||
"implications": ["imp1", "imp2", "imp3"],
|
"id": <resource-id>,
|
||||||
"suggestions": ["sug1", "sug2", "sug3"]
|
"user": <user-name>,
|
||||||
|
"data": <data>,
|
||||||
|
"earlier-data": <earlier-data>,
|
||||||
|
"time": <time>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Field meaning**
|
||||||
|
|
||||||
|
- `<operation>`: what happened to the resource.
|
||||||
|
|
||||||
|
The value can be either of values below:
|
||||||
|
|
||||||
|
- `"created"` - the resource has been created
|
||||||
|
- `"modified"` - the resource has been modified
|
||||||
|
- `"deleted"` - the resource has been deleted
|
||||||
|
|
||||||
|
- `<resource-type>` and `<resource-id>`: the resource that was changed.
|
||||||
|
|
||||||
|
The values are correlated as per table below:
|
||||||
|
|
||||||
|
| `<resource-type>` | `<resource-id>` |
|
||||||
|
| ----------------- | ------------------------------- |
|
||||||
|
| `"tag"` | first tag name at given time |
|
||||||
|
| `"tag_category"` | tag category name at given time |
|
||||||
|
| `"post"` | post ID |
|
||||||
|
|
||||||
|
- `<user-name>`: name of the user who has made the change.
|
||||||
|
|
||||||
|
- `<data>`: the snapshot data.
|
||||||
|
|
||||||
|
The value can be either of structures below:
|
||||||
|
|
||||||
|
- Tag category snapshot data (`<resource-type> = "tag"`)
|
||||||
|
|
||||||
|
*Example*
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
"name": "character",
|
||||||
|
"color": "#FF0000"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Tag snapshot data (`<resource-type> = "tag"`)
|
||||||
|
|
||||||
|
*Example*
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
"names": ["tag1", "tag2", "tag3"],
|
||||||
|
"category": "plain",
|
||||||
|
"implications": ["imp1", "imp2", "imp3"],
|
||||||
|
"suggestions": ["sug1", "sug2", "sug3"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `<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),
|
||||||
|
formatted as per RFC 3339.
|
||||||
|
|
||||||
|
|
||||||
# Search
|
# Search
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ def _serialize_tag(tag):
|
||||||
def _serialize_tag_with_details(tag):
|
def _serialize_tag_with_details(tag):
|
||||||
return {
|
return {
|
||||||
'tag': _serialize_tag(tag),
|
'tag': _serialize_tag(tag),
|
||||||
'snapshots': snapshots.get_data(tag),
|
'snapshots': snapshots.get_serialized_history(tag),
|
||||||
}
|
}
|
||||||
|
|
||||||
class TagListApi(BaseApi):
|
class TagListApi(BaseApi):
|
||||||
|
@ -89,6 +89,7 @@ class TagDetailApi(BaseApi):
|
||||||
tags.update_implications(tag, ctx.get_param_as_list('implications'))
|
tags.update_implications(tag, ctx.get_param_as_list('implications'))
|
||||||
|
|
||||||
tag.last_edit_time = datetime.datetime.now()
|
tag.last_edit_time = datetime.datetime.now()
|
||||||
|
ctx.session.flush()
|
||||||
snapshots.modify(tag, ctx.user)
|
snapshots.modify(tag, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
|
@ -126,8 +127,8 @@ class TagMergeApi(BaseApi):
|
||||||
raise tags.InvalidTagRelationError(
|
raise tags.InvalidTagRelationError(
|
||||||
'Cannot merge tag with itself.')
|
'Cannot merge tag with itself.')
|
||||||
auth.verify_privilege(ctx.user, 'tags:merge')
|
auth.verify_privilege(ctx.user, 'tags:merge')
|
||||||
tags.merge_tags(source_tag, target_tag)
|
|
||||||
snapshots.delete(source_tag, ctx.user)
|
snapshots.delete(source_tag, ctx.user)
|
||||||
|
tags.merge_tags(source_tag, target_tag)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
return _serialize_tag_with_details(target_tag)
|
return _serialize_tag_with_details(target_tag)
|
||||||
|
|
|
@ -10,7 +10,7 @@ def _serialize_category(category):
|
||||||
def _serialize_category_with_details(category):
|
def _serialize_category_with_details(category):
|
||||||
return {
|
return {
|
||||||
'tagCategory': _serialize_category(category),
|
'tagCategory': _serialize_category(category),
|
||||||
'snapshots': snapshots.get_data(category),
|
'snapshots': snapshots.get_serialized_history(category),
|
||||||
}
|
}
|
||||||
|
|
||||||
class TagCategoryListApi(BaseApi):
|
class TagCategoryListApi(BaseApi):
|
||||||
|
@ -56,6 +56,7 @@ class TagCategoryDetailApi(BaseApi):
|
||||||
auth.verify_privilege(ctx.user, 'tag_categories:edit:color')
|
auth.verify_privilege(ctx.user, 'tag_categories:edit:color')
|
||||||
tag_categories.update_color(
|
tag_categories.update_color(
|
||||||
category, ctx.get_param_as_string('color'))
|
category, ctx.get_param_as_string('color'))
|
||||||
|
ctx.session.flush()
|
||||||
snapshots.modify(category, ctx.user)
|
snapshots.modify(category, ctx.user)
|
||||||
ctx.session.commit()
|
ctx.session.commit()
|
||||||
tags.export_to_json()
|
tags.export_to_json()
|
||||||
|
|
|
@ -5,7 +5,7 @@ from szurubooru.db.base import Base
|
||||||
class Snapshot(Base):
|
class Snapshot(Base):
|
||||||
__tablename__ = 'snapshot'
|
__tablename__ = 'snapshot'
|
||||||
|
|
||||||
OPERATION_CREATED = 'added'
|
OPERATION_CREATED = 'created'
|
||||||
OPERATION_MODIFIED = 'modified'
|
OPERATION_MODIFIED = 'modified'
|
||||||
OPERATION_DELETED = 'deleted'
|
OPERATION_DELETED = 'deleted'
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ class Snapshot(Base):
|
||||||
creation_time = Column('creation_time', DateTime, nullable=False)
|
creation_time = Column('creation_time', DateTime, nullable=False)
|
||||||
resource_type = Column('resource_type', String(32), nullable=False)
|
resource_type = Column('resource_type', String(32), nullable=False)
|
||||||
resource_id = Column('resource_id', Integer, nullable=False)
|
resource_id = Column('resource_id', Integer, nullable=False)
|
||||||
|
resource_repr = Column('resource_repr', String(64), nullable=False)
|
||||||
operation = Column('operation', String(16), nullable=False)
|
operation = Column('operation', String(16), nullable=False)
|
||||||
user_id = Column('user_id', Integer, ForeignKey('user.id'))
|
user_id = Column('user_id', Integer, ForeignKey('user.id'))
|
||||||
data = Column('data', PickleType)
|
data = Column('data', PickleType)
|
||||||
|
|
|
@ -19,44 +19,69 @@ def get_tag_category_snapshot(category):
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
serializers = {
|
serializers = {
|
||||||
'tag': get_tag_snapshot,
|
'tag': (
|
||||||
'tag_category': get_tag_category_snapshot,
|
get_tag_snapshot,
|
||||||
|
lambda tag: tag.first_name),
|
||||||
|
'tag_category': (
|
||||||
|
get_tag_category_snapshot,
|
||||||
|
lambda category: category.name),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_resource_info(entity):
|
def get_resource_info(entity):
|
||||||
table_name = entity.__table__.name
|
resource_type = entity.__table__.name
|
||||||
|
assert resource_type in serializers
|
||||||
|
|
||||||
primary_key = inspect(entity).identity
|
primary_key = inspect(entity).identity
|
||||||
assert table_name in serializers
|
|
||||||
assert primary_key is not None
|
assert primary_key is not None
|
||||||
assert len(primary_key) == 1
|
assert len(primary_key) == 1
|
||||||
primary_key = primary_key[0]
|
|
||||||
return (table_name, primary_key)
|
resource_repr = serializers[resource_type][1](entity)
|
||||||
|
assert resource_repr
|
||||||
|
|
||||||
|
resource_id = primary_key[0]
|
||||||
|
assert resource_id
|
||||||
|
|
||||||
|
return (resource_type, resource_id, resource_repr)
|
||||||
|
|
||||||
def get_snapshots(entity):
|
def get_snapshots(entity):
|
||||||
table_name, primary_key = get_resource_info(entity)
|
resource_type, resource_id, _ = get_resource_info(entity)
|
||||||
return db.session \
|
return db.session \
|
||||||
.query(db.Snapshot) \
|
.query(db.Snapshot) \
|
||||||
.filter(db.Snapshot.resource_type == table_name) \
|
.filter(db.Snapshot.resource_type == resource_type) \
|
||||||
.filter(db.Snapshot.resource_id == primary_key) \
|
.filter(db.Snapshot.resource_id == resource_id) \
|
||||||
.order_by(db.Snapshot.creation_time.desc()) \
|
.order_by(db.Snapshot.creation_time.desc()) \
|
||||||
.all()
|
.all()
|
||||||
|
|
||||||
def get_data(entity):
|
def serialize_snapshot(snapshot, earlier_snapshot):
|
||||||
|
return {
|
||||||
|
'operation': snapshot.operation,
|
||||||
|
'type': snapshot.resource_type,
|
||||||
|
'id': snapshot.resource_repr,
|
||||||
|
'user': snapshot.user.name,
|
||||||
|
'data': snapshot.data,
|
||||||
|
'earlier-data': earlier_snapshot.data if earlier_snapshot else None,
|
||||||
|
'time': snapshot.creation_time,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_serialized_history(entity):
|
||||||
ret = []
|
ret = []
|
||||||
for snapshot in get_snapshots(entity):
|
earlier_snapshot = None
|
||||||
ret.append({'data': snapshot.data, 'time': snapshot.creation_time})
|
for snapshot in reversed(get_snapshots(entity)):
|
||||||
|
ret.insert(0, serialize_snapshot(snapshot, earlier_snapshot))
|
||||||
|
earlier_snapshot = snapshot
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def save(operation, entity, auth_user):
|
def save(operation, entity, auth_user):
|
||||||
table_name, primary_key = get_resource_info(entity)
|
resource_type, resource_id, resource_repr = get_resource_info(entity)
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
|
|
||||||
snapshot = db.Snapshot()
|
snapshot = db.Snapshot()
|
||||||
snapshot.creation_time = now
|
snapshot.creation_time = now
|
||||||
snapshot.operation = operation
|
snapshot.operation = operation
|
||||||
snapshot.resource_type = table_name
|
snapshot.resource_type = resource_type
|
||||||
snapshot.resource_id = primary_key
|
snapshot.resource_id = resource_id
|
||||||
snapshot.data = serializers[table_name](entity)
|
snapshot.resource_repr = resource_repr
|
||||||
|
snapshot.data = serializers[resource_type][0](entity)
|
||||||
snapshot.user = auth_user
|
snapshot.user = auth_user
|
||||||
|
|
||||||
earlier_snapshots = get_snapshots(entity)
|
earlier_snapshots = get_snapshots(entity)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
'''
|
||||||
|
Add snapshot resource_repr column
|
||||||
|
|
||||||
|
Revision ID: 46cd5229839b
|
||||||
|
Created at: 2016-04-21 19:00:48.087069
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision = '46cd5229839b'
|
||||||
|
down_revision = '565e01e3cf6d'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column(
|
||||||
|
'snapshot',
|
||||||
|
sa.Column('resource_repr', sa.String(length=64), nullable=False))
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_column('snapshot', 'resource_repr')
|
|
@ -173,33 +173,49 @@ def test_merging_deletion_all_the_way_deletes_all_snapshots(
|
||||||
snapshots.delete(tag, user)
|
snapshots.delete(tag, user)
|
||||||
assert db.session.query(db.Snapshot).count() == 0
|
assert db.session.query(db.Snapshot).count() == 0
|
||||||
|
|
||||||
def test_get_data(fake_datetime, tag_factory, user_factory):
|
def test_get_serialized_history(fake_datetime, tag_factory, user_factory):
|
||||||
tag = tag_factory(names=['dummy'])
|
tag = tag_factory(names=['dummy'])
|
||||||
user = user_factory()
|
user = user_factory(name='the-user')
|
||||||
db.session.add_all([tag, user])
|
db.session.add_all([tag, user])
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
with fake_datetime('2016-04-19 13:00:00'):
|
with fake_datetime('2016-04-19 13:00:00'):
|
||||||
snapshots.create(tag, user)
|
snapshots.create(tag, user)
|
||||||
tag.names = [db.TagName('changed')]
|
tag.names = [db.TagName('changed')]
|
||||||
|
db.session.flush()
|
||||||
with fake_datetime('2016-04-19 13:10:01'):
|
with fake_datetime('2016-04-19 13:10:01'):
|
||||||
snapshots.modify(tag, user)
|
snapshots.modify(tag, user)
|
||||||
assert snapshots.get_data(tag) == [
|
assert snapshots.get_serialized_history(tag) == [
|
||||||
{
|
{
|
||||||
|
'operation': 'modified',
|
||||||
'time': datetime.datetime(2016, 4, 19, 13, 10, 1),
|
'time': datetime.datetime(2016, 4, 19, 13, 10, 1),
|
||||||
|
'type': 'tag',
|
||||||
|
'id': 'changed',
|
||||||
|
'user': 'the-user',
|
||||||
'data': {
|
'data': {
|
||||||
'names': ['changed'],
|
'names': ['changed'],
|
||||||
'category': 'dummy',
|
'category': 'dummy',
|
||||||
'suggestions': [],
|
'suggestions': [],
|
||||||
'implications': [],
|
'implications': [],
|
||||||
},
|
},
|
||||||
},
|
'earlier-data': {
|
||||||
{
|
|
||||||
'time': datetime.datetime(2016, 4, 19, 13, 0, 0),
|
|
||||||
'data': {
|
|
||||||
'names': ['dummy'],
|
'names': ['dummy'],
|
||||||
'category': 'dummy',
|
'category': 'dummy',
|
||||||
'suggestions': [],
|
'suggestions': [],
|
||||||
'implications': [],
|
'implications': [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'operation': 'created',
|
||||||
|
'time': datetime.datetime(2016, 4, 19, 13, 0, 0),
|
||||||
|
'type': 'tag',
|
||||||
|
'id': 'dummy',
|
||||||
|
'user': 'the-user',
|
||||||
|
'data': {
|
||||||
|
'names': ['dummy'],
|
||||||
|
'category': 'dummy',
|
||||||
|
'suggestions': [],
|
||||||
|
'implications': [],
|
||||||
|
},
|
||||||
|
'earlier-data': None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue