diff --git a/admin/schema_changes/16.sql b/admin/schema_changes/16.sql index b1f4e5377..fe266c554 100644 --- a/admin/schema_changes/16.sql +++ b/admin/schema_changes/16.sql @@ -1 +1,2 @@ ALTER TYPE entity_types ADD VALUE 'artist' AFTER 'place'; +ALTER TYPE entity_types ADD VALUE 'label' AFTER 'artist'; diff --git a/admin/sql/create_types.sql b/admin/sql/create_types.sql index ea9218c2d..1d83c6939 100644 --- a/admin/sql/create_types.sql +++ b/admin/sql/create_types.sql @@ -9,5 +9,6 @@ CREATE TYPE entity_types AS ENUM ( 'release_group', 'event', 'place', - 'artist' + 'artist', + 'label' ); diff --git a/critiquebrainz/db/review.py b/critiquebrainz/db/review.py index fc92fc19d..6da2f8740 100644 --- a/critiquebrainz/db/review.py +++ b/critiquebrainz/db/review.py @@ -20,6 +20,7 @@ "place", "release_group", "artist", + "label", ] diff --git a/critiquebrainz/frontend/__init__.py b/critiquebrainz/frontend/__init__.py index 6082ba867..5dd9b2a31 100644 --- a/critiquebrainz/frontend/__init__.py +++ b/critiquebrainz/frontend/__init__.py @@ -131,6 +131,7 @@ def create_app(debug=None, config_path=None): from critiquebrainz.frontend.views.review import review_bp from critiquebrainz.frontend.views.search import search_bp from critiquebrainz.frontend.views.artist import artist_bp + from critiquebrainz.frontend.views.label import label_bp from critiquebrainz.frontend.views.release_group import release_group_bp from critiquebrainz.frontend.views.release import release_bp from critiquebrainz.frontend.views.event import event_bp @@ -152,6 +153,7 @@ def create_app(debug=None, config_path=None): app.register_blueprint(review_bp, url_prefix='/review') app.register_blueprint(search_bp, url_prefix='/search') app.register_blueprint(artist_bp, url_prefix='/artist') + app.register_blueprint(label_bp, url_prefix='/label') app.register_blueprint(release_group_bp, url_prefix='/release-group') app.register_blueprint(release_bp, url_prefix='/release') app.register_blueprint(event_bp, url_prefix='/event') diff --git a/critiquebrainz/frontend/external/musicbrainz.py b/critiquebrainz/frontend/external/musicbrainz.py index f4711f69b..94cb113ba 100644 --- a/critiquebrainz/frontend/external/musicbrainz.py +++ b/critiquebrainz/frontend/external/musicbrainz.py @@ -44,3 +44,9 @@ def search_places(query='', limit=None, offset=None): """Search for places.""" api_resp = musicbrainzngs.search_places(query=query, limit=limit, offset=offset) return api_resp.get('place-count'), api_resp.get('place-list') + + +def search_labels(query='', limit=None, offset=None): + """Search for labels.""" + api_resp = musicbrainzngs.search_labels(query=query, limit=limit, offset=offset) + return api_resp.get('label-count'), api_resp.get('label-list') diff --git a/critiquebrainz/frontend/external/musicbrainz_db/entities.py b/critiquebrainz/frontend/external/musicbrainz_db/entities.py index 7210e20ef..c7b40cce7 100644 --- a/critiquebrainz/frontend/external/musicbrainz_db/entities.py +++ b/critiquebrainz/frontend/external/musicbrainz_db/entities.py @@ -1,10 +1,12 @@ from brainzutils.musicbrainz_db.artist import fetch_multiple_artists +from brainzutils.musicbrainz_db.label import fetch_multiple_labels from brainzutils.musicbrainz_db.place import fetch_multiple_places from brainzutils.musicbrainz_db.event import fetch_multiple_events from brainzutils.musicbrainz_db.release_group import fetch_multiple_release_groups from critiquebrainz.frontend.external.musicbrainz_db.release_group import get_release_group_by_id from critiquebrainz.frontend.external.musicbrainz_db.place import get_place_by_id from critiquebrainz.frontend.external.musicbrainz_db.event import get_event_by_id +from critiquebrainz.frontend.external.musicbrainz_db.label import get_label_by_id from critiquebrainz.frontend.external.musicbrainz_db.artist import get_artist_by_id @@ -26,6 +28,7 @@ def get_multiple_entities(entities): entities_info = {} release_group_mbids = [entity[0] for entity in filter(lambda entity: entity[1] == 'release_group', entities)] artist_mbids = [entity[0] for entity in filter(lambda entity: entity[1] == 'artist', entities)] + label_mbids = [entity[0] for entity in filter(lambda entity: entity[1] == 'label', entities)] place_mbids = [entity[0] for entity in filter(lambda entity: entity[1] == 'place', entities)] event_mbids = [entity[0] for entity in filter(lambda entity: entity[1] == 'event', entities)] entities_info.update(fetch_multiple_release_groups( @@ -35,6 +38,9 @@ def get_multiple_entities(entities): entities_info.update(fetch_multiple_artists( artist_mbids, )) + entities_info.update(fetch_multiple_labels( + label_mbids, + )) entities_info.update(fetch_multiple_places( place_mbids, )) @@ -50,6 +56,8 @@ def get_entity_by_id(id, type='release_group'): entity = get_release_group_by_id(str(id)) elif type == 'artist': entity = get_artist_by_id(str(id)) + elif type == 'label': + entity = get_label_by_id(str(id)) elif type == 'place': entity = get_place_by_id(str(id)) elif type == 'event': diff --git a/critiquebrainz/frontend/external/musicbrainz_db/label.py b/critiquebrainz/frontend/external/musicbrainz_db/label.py new file mode 100644 index 000000000..0cb48995b --- /dev/null +++ b/critiquebrainz/frontend/external/musicbrainz_db/label.py @@ -0,0 +1,23 @@ +from brainzutils import cache +from brainzutils.musicbrainz_db.label import fetch_multiple_labels +from critiquebrainz.frontend.external.musicbrainz_db import DEFAULT_CACHE_EXPIRATION +from critiquebrainz.frontend.external.relationships import label as label_rel + + +def get_label_by_id(mbid): + """Get label with MusicBrainz ID. + + Args: + mbid (uuid): MBID(gid) of the label. + Returns: + Dictionary containing the label information + """ + key = cache.gen_key(mbid) + label = cache.get(key) + if not label: + label = fetch_multiple_labels( + [mbid], + includes=['artist-rels', 'url-rels'], + ).get(mbid) + cache.set(key=key, val=label, time=DEFAULT_CACHE_EXPIRATION) + return label_rel.process(label) diff --git a/critiquebrainz/frontend/external/relationships/label.py b/critiquebrainz/frontend/external/relationships/label.py new file mode 100644 index 000000000..150684f43 --- /dev/null +++ b/critiquebrainz/frontend/external/relationships/label.py @@ -0,0 +1,76 @@ +""" +Relationship processor for label entity. +""" +import urllib.parse +from flask_babel import lazy_gettext + + +def process(label): + """Handles processing supported relation lists.""" + if 'url-rels' in label and label['url-rels']: + label['external-urls'] = _url(label['url-rels']) + return label + + +def _url(url_list): + """Processor for Label-URL relationship.""" + basic_types = { + 'wikidata': {'name': lazy_gettext('Wikidata'), 'icon': 'wikidata-16.png', }, + 'discogs': {'name': lazy_gettext('Discogs'), 'icon': 'discogs-16.png', }, + 'allmusic': {'name': lazy_gettext('Allmusic'), 'icon': 'allmusic-16.png', }, + 'bandcamp': {'name': lazy_gettext('Bandcamp'), 'icon': 'bandcamp-16.png', }, + 'official homepage': {'name': lazy_gettext('Official homepage'), 'icon': 'home-16.png', }, + 'BBC Music page': {'name': lazy_gettext('BBC Music'), }, + } + external_urls = [] + for relation in url_list: + if relation['type'] in basic_types: + external_urls.append(dict(list(relation.items()) + list(basic_types[relation['type']].items()))) + else: + try: + target = urllib.parse.urlparse(relation['target']) + if relation['type'] == 'lyrics': + external_urls.append(dict( + relation.items() + { + 'name': lazy_gettext('Lyrics'), + 'disambiguation': target.netloc, + }.items())) + elif relation['type'] == 'wikipedia': + external_urls.append(dict( + relation.items() + { + 'name': lazy_gettext('Wikipedia'), + 'disambiguation': ( + target.netloc.split('.')[0] + + ':' + + urllib.parse.unquote(target.path.split('/')[2]).decode('utf8').replace("_", " ") + ), + 'icon': 'wikipedia-16.png', + }.items())) + elif relation['type'] == 'youtube': + path = target.path.split('/') + if path[1] == 'user' or path[1] == 'channel': + disambiguation = path[2] + else: + disambiguation = path[1] + external_urls.append(dict( + relation.items() + { + 'name': lazy_gettext('YouTube'), + 'disambiguation': disambiguation, + 'icon': 'youtube-16.png', + }.items())) + elif relation['type'] == 'social network': + if target.netloc == 'twitter.com': + external_urls.append(dict( + relation.items() + { + 'name': lazy_gettext('Twitter'), + 'disambiguation': target.path.split('/')[1], + 'icon': 'twitter-16.png', + }.items())) + else: + # TODO(roman): Process other types here + pass + except Exception: # FIXME(roman): Too broad exception clause. + # TODO(roman): Log error. + pass + + return sorted(external_urls, key=lambda k: k['name']) diff --git a/critiquebrainz/frontend/static/styles/main.less b/critiquebrainz/frontend/static/styles/main.less index 47e333899..ef2509dcf 100644 --- a/critiquebrainz/frontend/static/styles/main.less +++ b/critiquebrainz/frontend/static/styles/main.less @@ -13,6 +13,7 @@ @artist-color: @blue; @event-color: @green; @place-color: @yellow; +@label-color: @blue; body { padding-bottom: 25px; @@ -131,6 +132,9 @@ ul.sharing { &.place { background-color: fade(@place-color, 70%); } + &.label { + background-color: fade(@label-color, 70%); + } &.event { background-color: fade(@event-color, 70%); } @@ -493,6 +497,9 @@ a#edit-review { margin-top: 20px; } &.place { background-color: fade(@place-color, 70%); } + &.label { + background-color: fade(@label-color, 70%); + } &.event { background-color: fade(@event-color, 70%); } diff --git a/critiquebrainz/frontend/templates/entity_review.html b/critiquebrainz/frontend/templates/entity_review.html index f5f26fa06..1e40b261e 100644 --- a/critiquebrainz/frontend/templates/entity_review.html +++ b/critiquebrainz/frontend/templates/entity_review.html @@ -5,6 +5,8 @@ artist = entity['artist-credit-phrase'] | default(_('[Unknown artist]'))) }} {% elif review.entity_type == 'artist' %} {{ _('%(artist)s', artist = ''|safe + entity.name | default(_('[Unknown artist]')) + ''|safe) }} + {% elif review.entity_type == 'label' %} + {{ _('%(label)s', label = ''|safe + entity.name | default(_('[Unknown label]')) + ''|safe) }} {% elif review.entity_type == 'event' %} {{ _('%(event)s', event = ''|safe + entity.name | default(_('[Unknown event]')) + ''|safe) }} {% elif review.entity_type == 'place' %} diff --git a/critiquebrainz/frontend/templates/label/entity.html b/critiquebrainz/frontend/templates/label/entity.html new file mode 100644 index 000000000..17ff6fbfc --- /dev/null +++ b/critiquebrainz/frontend/templates/label/entity.html @@ -0,0 +1,121 @@ +{% extends 'base.html' %} +{% from 'macros.html' import entity_rate_form, show_avg_rating with context %} + +{% block title %}{{ label.name }} - CritiqueBrainz{% endblock %} + +{% block content %} +
{{ _('No reviews found') }}
+ {% else %} ++ | {{ _('Published on') }} | +{{ _('Votes (+/-)') }} | +
---|---|---|
+ + {{ _('by %(reviewer)s', reviewer=' '|safe % review.user.avatar + review.user.display_name) }} + + | +{{ review.published_on | date }} | +{{ review.votes_positive_count }}/{{ review.votes_negative_count }} | +
{{ label.type }}
{% endif %} + + {% if label['external-urls'] %} + {{ _('External links') }} +