Add view_progress to nodes of type asset
This commit is contained in:
parent
0f7f7d5a66
commit
263d68071e
@ -94,13 +94,31 @@ def share_node(node_id):
|
|||||||
@blueprint.route('/tagged/<tag>')
|
@blueprint.route('/tagged/<tag>')
|
||||||
def tagged(tag=''):
|
def tagged(tag=''):
|
||||||
"""Return all tagged nodes of public projects as JSON."""
|
"""Return all tagged nodes of public projects as JSON."""
|
||||||
|
from pillar.auth import current_user
|
||||||
|
|
||||||
# We explicitly register the tagless endpoint to raise a 404, otherwise the PATCH
|
# We explicitly register the tagless endpoint to raise a 404, otherwise the PATCH
|
||||||
# handler on /api/nodes/<node_id> will return a 405 Method Not Allowed.
|
# handler on /api/nodes/<node_id> will return a 405 Method Not Allowed.
|
||||||
if not tag:
|
if not tag:
|
||||||
raise wz_exceptions.NotFound()
|
raise wz_exceptions.NotFound()
|
||||||
|
|
||||||
return _tagged(tag)
|
# Build the (cached) list of tagged nodes
|
||||||
|
agg_list = _tagged(tag)
|
||||||
|
|
||||||
|
# If the user is anonymous, no more information is needed and we return
|
||||||
|
if current_user.is_anonymous:
|
||||||
|
return jsonify(agg_list)
|
||||||
|
|
||||||
|
# If the user is authenticated, attach view_progress for video assets
|
||||||
|
view_progress = current_user.nodes['view_progress']
|
||||||
|
for node in agg_list:
|
||||||
|
node_id = str(node['_id'])
|
||||||
|
# View progress should be added only for nodes of type 'asset' and
|
||||||
|
# with content_type 'video', only if the video was already in the watched
|
||||||
|
# list for the current user.
|
||||||
|
if node_id in view_progress:
|
||||||
|
node['view_progress'] = view_progress[node_id]
|
||||||
|
|
||||||
|
return jsonify(agg_list)
|
||||||
|
|
||||||
|
|
||||||
def _tagged(tag: str):
|
def _tagged(tag: str):
|
||||||
@ -129,7 +147,8 @@ def _tagged(tag: str):
|
|||||||
|
|
||||||
{'$sort': {'_created': -1}}
|
{'$sort': {'_created': -1}}
|
||||||
])
|
])
|
||||||
return jsonify(list(agg))
|
|
||||||
|
return list(agg)
|
||||||
|
|
||||||
|
|
||||||
def generate_and_store_short_code(node):
|
def generate_and_store_short_code(node):
|
||||||
|
@ -479,61 +479,65 @@ class TextureSortFilesTest(AbstractPillarTest):
|
|||||||
|
|
||||||
|
|
||||||
class TaggedNodesTest(AbstractPillarTest):
|
class TaggedNodesTest(AbstractPillarTest):
|
||||||
def test_tagged_nodes_api(self):
|
def setUp(self, **kwargs):
|
||||||
|
super().setUp(**kwargs)
|
||||||
|
|
||||||
|
self.pid, _ = self.ensure_project_exists()
|
||||||
|
self.file_id, _ = self.ensure_file_exists()
|
||||||
|
self.uid = self.create_user()
|
||||||
|
|
||||||
from pillar.api.utils import utcnow
|
from pillar.api.utils import utcnow
|
||||||
|
self.fake_now = utcnow()
|
||||||
|
|
||||||
|
def test_tagged_nodes_api(self):
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
pid, _ = self.ensure_project_exists()
|
|
||||||
file_id, _ = self.ensure_file_exists()
|
|
||||||
uid = self.create_user()
|
|
||||||
|
|
||||||
now = utcnow()
|
|
||||||
base_node = {
|
base_node = {
|
||||||
'name': 'Just a node name',
|
'name': 'Just a node name',
|
||||||
'project': pid,
|
'project': self.pid,
|
||||||
'description': '',
|
'description': '',
|
||||||
'node_type': 'asset',
|
'node_type': 'asset',
|
||||||
'user': uid,
|
'user': self.uid,
|
||||||
}
|
}
|
||||||
base_props = {'status': 'published',
|
base_props = {'status': 'published',
|
||||||
'file': file_id,
|
'file': self.file_id,
|
||||||
'content_type': 'video',
|
'content_type': 'video',
|
||||||
'order': 0}
|
'order': 0}
|
||||||
# No tags, should never be returned.
|
# No tags, should never be returned.
|
||||||
self.create_node({
|
self.create_node({
|
||||||
'_created': now,
|
'_created': self.fake_now,
|
||||||
'properties': base_props,
|
'properties': base_props,
|
||||||
**base_node})
|
**base_node})
|
||||||
# Empty tag list, should never be returned.
|
# Empty tag list, should never be returned.
|
||||||
self.create_node({
|
self.create_node({
|
||||||
'_created': now + timedelta(seconds=1),
|
'_created': self.fake_now + timedelta(seconds=1),
|
||||||
'properties': {'tags': [], **base_props},
|
'properties': {'tags': [], **base_props},
|
||||||
**base_node})
|
**base_node})
|
||||||
# Empty string as tag, should never be returned.
|
# Empty string as tag, should never be returned.
|
||||||
self.create_node({
|
self.create_node({
|
||||||
'_created': now + timedelta(seconds=1),
|
'_created': self.fake_now + timedelta(seconds=1),
|
||||||
'properties': {'tags': [''], **base_props},
|
'properties': {'tags': [''], **base_props},
|
||||||
**base_node})
|
**base_node})
|
||||||
nid_single_tag = self.create_node({
|
nid_single_tag = self.create_node({
|
||||||
'_created': now + timedelta(seconds=2),
|
'_created': self.fake_now + timedelta(seconds=2),
|
||||||
# 'एनिमेशन' is 'animation' in Hindi.
|
# 'एनिमेशन' is 'animation' in Hindi.
|
||||||
'properties': {'tags': ['एनिमेशन'], **base_props},
|
'properties': {'tags': ['एनिमेशन'], **base_props},
|
||||||
**base_node,
|
**base_node,
|
||||||
})
|
})
|
||||||
nid_double_tag = self.create_node({
|
nid_double_tag = self.create_node({
|
||||||
'_created': now + timedelta(hours=3),
|
'_created': self.fake_now + timedelta(hours=3),
|
||||||
'properties': {'tags': ['एनिमेशन', 'rigging'], **base_props},
|
'properties': {'tags': ['एनिमेशन', 'rigging'], **base_props},
|
||||||
**base_node,
|
**base_node,
|
||||||
})
|
})
|
||||||
nid_other_tag = self.create_node({
|
nid_other_tag = self.create_node({
|
||||||
'_deleted': False,
|
'_deleted': False,
|
||||||
'_created': now + timedelta(days=4),
|
'_created': self.fake_now + timedelta(days=4),
|
||||||
'properties': {'tags': ['producción'], **base_props},
|
'properties': {'tags': ['producción'], **base_props},
|
||||||
**base_node,
|
**base_node,
|
||||||
})
|
})
|
||||||
# Matching tag but deleted node, should never be returned.
|
# Matching tag but deleted node, should never be returned.
|
||||||
self.create_node({
|
self.create_node({
|
||||||
'_created': now + timedelta(seconds=1),
|
'_created': self.fake_now + timedelta(seconds=1),
|
||||||
'_deleted': True,
|
'_deleted': True,
|
||||||
'properties': {'tags': ['एनिमेशन'], **base_props},
|
'properties': {'tags': ['एनिमेशन'], **base_props},
|
||||||
**base_node})
|
**base_node})
|
||||||
@ -556,3 +560,76 @@ class TaggedNodesTest(AbstractPillarTest):
|
|||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
invalid_url = flask.url_for('nodes_api.tagged', tag='')
|
invalid_url = flask.url_for('nodes_api.tagged', tag='')
|
||||||
self.get(invalid_url, expected_status=404)
|
self.get(invalid_url, expected_status=404)
|
||||||
|
|
||||||
|
def test_tagged_nodes_asset_video_with_progress_api(self):
|
||||||
|
from datetime import timedelta
|
||||||
|
from pillar.auth import current_user
|
||||||
|
|
||||||
|
base_node = {
|
||||||
|
'name': 'Spring hair rig setup',
|
||||||
|
'project': self.pid,
|
||||||
|
'description': '',
|
||||||
|
'node_type': 'asset',
|
||||||
|
'user': self.uid,
|
||||||
|
}
|
||||||
|
base_props = {'status': 'published',
|
||||||
|
'file': self.file_id,
|
||||||
|
'content_type': 'video',
|
||||||
|
'order': 0}
|
||||||
|
|
||||||
|
# Create one node of type asset video
|
||||||
|
nid_single_tag = self.create_node({
|
||||||
|
'_created': self.fake_now + timedelta(seconds=2),
|
||||||
|
# 'एनिमेशन' is 'animation' in Hindi.
|
||||||
|
'properties': {'tags': ['एनिमेशन'], **base_props},
|
||||||
|
**base_node,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create another node
|
||||||
|
self.create_node({
|
||||||
|
'_created': self.fake_now + timedelta(seconds=2),
|
||||||
|
# 'एनिमेशन' is 'animation' in Hindi.
|
||||||
|
'properties': {'tags': ['एनिमेशन'], **base_props},
|
||||||
|
**base_node,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add video watch progress for the self.uid user
|
||||||
|
with self.app.app_context():
|
||||||
|
users_coll = self.app.db('users')
|
||||||
|
# Define video progress
|
||||||
|
progress_in_sec = 333
|
||||||
|
video_progress = {
|
||||||
|
'progress_in_sec': progress_in_sec,
|
||||||
|
'progress_in_percent': 70,
|
||||||
|
'done': False,
|
||||||
|
'last_watched': self.fake_now + timedelta(seconds=2),
|
||||||
|
}
|
||||||
|
users_coll.update_one(
|
||||||
|
{'_id': self.uid},
|
||||||
|
{'$set': {f'nodes.view_progress.{nid_single_tag}': video_progress}})
|
||||||
|
|
||||||
|
# Utility to fetch tagged nodes and return them as JSON list
|
||||||
|
def do_query():
|
||||||
|
animation_tags_url = flask.url_for('nodes_api.tagged', tag='एनिमेशन')
|
||||||
|
return self.get(animation_tags_url).json
|
||||||
|
|
||||||
|
# Ensure that anonymous users get videos with no view_progress info
|
||||||
|
with self.app.app_context():
|
||||||
|
resp = do_query()
|
||||||
|
for node in resp:
|
||||||
|
self.assertNotIn('view_progress', node)
|
||||||
|
|
||||||
|
# Ensure that an authenticated user gets view_progress info if the video was watched
|
||||||
|
with self.login_as(self.uid):
|
||||||
|
resp = do_query()
|
||||||
|
for node in resp:
|
||||||
|
if node['_id'] in current_user.nodes['view_progress']:
|
||||||
|
self.assertIn('view_progress', node)
|
||||||
|
self.assertEqual(progress_in_sec, node['view_progress']['progress_in_sec'])
|
||||||
|
|
||||||
|
# Ensure that another user with no view progress does not get any view progress info
|
||||||
|
other_user = self.create_user(user_id=ObjectId())
|
||||||
|
with self.login_as(other_user):
|
||||||
|
resp = do_query()
|
||||||
|
for node in resp:
|
||||||
|
self.assertNotIn('view_progress', node)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user