Skip to content

Commit

Permalink
Topics upgrades : add a created_at field and allow to filter on tags …
Browse files Browse the repository at this point in the history
…or name (#2904)
  • Loading branch information
quaxsze authored Oct 17, 2023
1 parent 1fa4863 commit d324409
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 12 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
- Topics creation, update and deletion are now opened to all users [#2898](https://github.com/opendatateam/udata/pull/2898)
- Topics are now `db.Owned` and searchable by `id` in dataset search [#2901](https://github.com/opendatateam/udata/pull/2901)
- Remove `deleted` api field that does not exist [#2903](https://github.com/opendatateam/udata/pull/2903)
- Add `created_at`field to topic's model [#2904](https://github.com/opendatateam/udata/pull/2904)
- Topics can now be filtered by `tag` field [#2904](https://github.com/opendatateam/udata/pull/2904)
- Topics can now be queried by test search in `name` field with `q` argument [#2904](https://github.com/opendatateam/udata/pull/2904)
- Fix site title and keywords never get updated [#2900](https://github.com/opendatateam/udata/pull/2900)
- Reuse's extras are now exposed by API [#2905](https://github.com/opendatateam/udata/pull/2905)
- Add German to udata translations [2899](https://github.com/opendatateam/udata/pull/2899)[2909](https://github.com/opendatateam/udata/pull/2909)
Expand Down
45 changes: 36 additions & 9 deletions udata/core/topic/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from udata.api import api, fields, API


from udata.api.parsers import ModelApiParser
from udata.core.dataset.api_fields import dataset_fields
from udata.core.organization.api_fields import org_ref_fields
from udata.core.reuse.api_fields import reuse_fields
Expand All @@ -9,6 +8,8 @@
from .models import Topic
from .forms import TopicForm

DEFAULT_SORTING = '-created_at'

ns = api.namespace('topics', 'Topics related operations')

topic_fields = api.model('Topic', {
Expand All @@ -28,8 +29,6 @@
'private': fields.Boolean(description='Is the topic private'),
'created_at': fields.ISODateTime(
description='The topic creation date', readonly=True),
'last_modified': fields.ISODateTime(
description='The topic last modification date', readonly=True),
'organization': fields.Nested(
org_ref_fields, allow_null=True,
description='The publishing organization', readonly=True),
Expand All @@ -48,20 +47,48 @@

topic_page_fields = api.model('TopicPage', fields.pager(topic_fields))

parser = api.page_parser()

class TopicApiParser(ModelApiParser):
sorts = {
'name': 'name',
'created': 'created_at'
}

def __init__(self):
super().__init__()
self.parser.add_argument('tag', type=str, location='args')

@staticmethod
def parse_filters(topics, args):
if args.get('q'):
# Following code splits the 'q' argument by spaces to surround
# every word in it with quotes before rebuild it.
# This allows the search_text method to tokenise with an AND
# between tokens whereas an OR is used without it.
phrase_query = ' '.join([f'"{elem}"' for elem in args['q'].split(' ')])
topics = topics.search_text(phrase_query)
if args.get('tag'):
topics = topics.filter(tags=args['tag'])
return topics


topic_parser = TopicApiParser()


@ns.route('/', endpoint='topics')
class TopicsAPI(API):

@api.doc('list_topics')
@api.expect(parser)
@api.expect(topic_parser.parser)
@api.marshal_with(topic_page_fields)
def get(self):
'''List all topics'''
args = parser.parse_args()
return (Topic.objects.order_by('-created')
.paginate(args['page'], args['page_size']))
args = topic_parser.parse()
topics = Topic.objects()
topics = topic_parser.parse_filters(topics, args)
sort = args['sort'] or ('$text_score' if args['q'] else None) or DEFAULT_SORTING
return (topics.order_by(sort)
.paginate(args['page'], args['page_size']))

@api.doc('create_topic')
@api.expect(topic_fields)
Expand Down
15 changes: 14 additions & 1 deletion udata/core/topic/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import datetime
from flask import url_for

from mongoengine.fields import DateTimeField
from mongoengine.signals import pre_save
from udata.models import db
from udata.search import reindex
Expand Down Expand Up @@ -27,6 +28,18 @@ class Topic(db.Document, db.Owned):
private = db.BooleanField()
extras = db.ExtrasField()

created_at = DateTimeField(default=datetime.utcnow, required=True)

meta = {
'indexes': [
'$name',
'created_at',
'slug'
] + db.Owned.meta['indexes'],
'ordering': ['-created_at'],
'auto_create_index_on_save': True
}

def __str__(self):
return self.name

Expand Down
16 changes: 14 additions & 2 deletions udata/tests/api/test_topics_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,23 @@ class TopicsAPITest(APITestCase):

def test_topic_api_list(self):
'''It should fetch a topic list from the API'''
topics = TopicFactory.create_batch(3)
TopicFactory.create_batch(3)
tag_topic = TopicFactory(tags=['energy'])
name_topic = TopicFactory(name='topic-for-query')

response = self.get(url_for('api.topics'))
self.assert200(response)
self.assertEqual(len(response.json['data']), len(topics))
self.assertEqual(len(response.json['data']), 5)

response = self.get(url_for('api.topics', q='topic-for'))
self.assert200(response)
self.assertEqual(len(response.json['data']), 1)
self.assertEqual(response.json['data'][0]['id'], str(name_topic.id))

response = self.get(url_for('api.topics', tag='energy'))
self.assert200(response)
self.assertEqual(len(response.json['data']), 1)
self.assertEqual(response.json['data'][0]['id'], str(tag_topic.id))

def test_topic_api_get(self):
'''It should fetch a topic from the API'''
Expand Down

0 comments on commit d324409

Please sign in to comment.