Skip to content

Commit

Permalink
Merge pull request #264 from spellew/label-entity-support
Browse files Browse the repository at this point in the history
CB-359: Implemented the reviewal of labels
  • Loading branch information
paramsingh authored Aug 15, 2019
2 parents 37c3373 + aa643e9 commit 7064672
Show file tree
Hide file tree
Showing 23 changed files with 440 additions and 6 deletions.
1 change: 1 addition & 0 deletions admin/schema_changes/16.sql
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
ALTER TYPE entity_types ADD VALUE 'artist' AFTER 'place';
ALTER TYPE entity_types ADD VALUE 'label' AFTER 'artist';
3 changes: 2 additions & 1 deletion admin/sql/create_types.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ CREATE TYPE entity_types AS ENUM (
'release_group',
'event',
'place',
'artist'
'artist',
'label'
);
1 change: 1 addition & 0 deletions critiquebrainz/db/review.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"place",
"release_group",
"artist",
"label",
]


Expand Down
2 changes: 2 additions & 0 deletions critiquebrainz/frontend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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')
Expand Down
6 changes: 6 additions & 0 deletions critiquebrainz/frontend/external/musicbrainz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
8 changes: 8 additions & 0 deletions critiquebrainz/frontend/external/musicbrainz_db/entities.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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(
Expand All @@ -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,
))
Expand All @@ -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':
Expand Down
23 changes: 23 additions & 0 deletions critiquebrainz/frontend/external/musicbrainz_db/label.py
Original file line number Diff line number Diff line change
@@ -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)
76 changes: 76 additions & 0 deletions critiquebrainz/frontend/external/relationships/label.py
Original file line number Diff line number Diff line change
@@ -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'])
7 changes: 7 additions & 0 deletions critiquebrainz/frontend/static/styles/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
@artist-color: @blue;
@event-color: @green;
@place-color: @yellow;
@label-color: @blue;

body {
padding-bottom: 25px;
Expand Down Expand Up @@ -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%);
}
Expand Down Expand Up @@ -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%);
}
Expand Down
2 changes: 2 additions & 0 deletions critiquebrainz/frontend/templates/entity_review.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
artist = entity['artist-credit-phrase'] | default(_('[Unknown artist]'))) }}
{% elif review.entity_type == 'artist' %}
{{ _('%(artist)s', artist = '<b>'|safe + entity.name | default(_('[Unknown artist]')) + '</b>'|safe) }}
{% elif review.entity_type == 'label' %}
{{ _('%(label)s', label = '<b>'|safe + entity.name | default(_('[Unknown label]')) + '</b>'|safe) }}
{% elif review.entity_type == 'event' %}
{{ _('%(event)s', event = '<b>'|safe + entity.name | default(_('[Unknown event]')) + '</b>'|safe) }}
{% elif review.entity_type == 'place' %}
Expand Down
121 changes: 121 additions & 0 deletions critiquebrainz/frontend/templates/label/entity.html
Original file line number Diff line number Diff line change
@@ -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 %}
<div class="clearfix">
<h2 class="pull-left">
{{ label.name }}
{% if label.disambiguation %}
<small>({{ label.disambiguation }})</small>
{% endif %}
</h2>

{% if not my_review %}
<a id="write-review" href="{{ url_for('review.create', label=id) }}"
role="button" class="btn btn-success pull-right">
<span class="glyphicon glyphicon-asterisk"></span> {{ _('Write a review') }}
</a>
{% else %}
<a id="edit-review" href="{{ url_for('review.edit', id=my_review.id) }}"
role="button" class="btn btn-primary pull-right">
<span class="glyphicon glyphicon-edit"></span> {{ _('Edit my review') }}
</a>
{% endif %}
</div>

<div class="row">
<div class="col-sm-9">
<h4 style="margin-bottom:0;">{{ _('Reviews') }}</h4>
{% if not reviews %}
<p class="lead" style="text-align: center;">{{ _('No reviews found') }}</p>
{% else %}
<table class="table table-condensed table-hover">
<thead>
<tr>
<th></th>
<th>{{ _('Published on') }}</th>
<th>{{ _('Votes (+/-)') }}</th>
</tr>
</thead>
<tbody>
{% for review in reviews %}
<tr data-review-id="{{ review.id }}">
<td>
<a href="{{ url_for('review.entity', id=review.id) }}">
{{ _('by %(reviewer)s', reviewer='<img class="avatar" src="%s&s=16" /> '|safe % review.user.avatar + review.user.display_name) }}
</a>
</td>
<td>{{ review.published_on | date }}</td>
<td>{{ review.votes_positive_count }}/{{ review.votes_negative_count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="col-md-12">
<ul class="pager">
{% if not reviews_limit %}
<li class="next"><a href="{{ url_for('label.entity', id=id) }}">{{ _('Hide reviews') }}</a></li>
{% elif reviews_count > reviews_limit %}
<li class="next"><a href="{{ url_for('label.entity', id=id, reviews='all') }}">{{ _('Show all reviews') }}</a></li>
{% endif %}
</ul>
</div>
{% endif %}
<ul class="pagination"></ul>
</div>

<div class="col-sm-3">
<h4>{{ _('Label information') }}</h4>
{% if label.type %}<p class="text-muted">{{ label.type }}</p>{% endif %}

{% if label['external-urls'] %}
<b>{{ _('External links') }}</b>
<ul class="list-unstyled external-links">
{% for url in label['external-urls'] %}
<li class="clearfix">
<div class="favicon-container">
{% if url.icon %}
<img src="{{ get_static_path('favicons/' + url.icon) }}" />
{% else %}
<img src="{{ get_static_path('favicons/external-16.png') }}" />
{% endif %}
</div>
<a href="{{ url.url.url }}">{{ url.name }}</a>
{% if url.disambiguation %}<span class="text-muted">({{ url.disambiguation }})</span>{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}

<div class="external-links">
<div class="favicon-container"><img src="{{ get_static_path('favicons/musicbrainz-16.svg') }}" /></div>
<a href="https://musicbrainz.org/label/{{ label.id }}"><em>{{ _('Edit on MusicBrainz') }}</em></a>
</div>

<br/><br/>
{{ entity_rate_form('label', 'label') }}
{% if avg_rating %}
<div class="avg-rating">
{{ show_avg_rating(avg_rating.rating, avg_rating.count, show_glyphicon = false) }}
</div>
{% endif %}
</div>
</div>
{% endblock %}

{% block scripts %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css"/>
<script>
$(document).ready(function() {
$("#ratestars").click(function(e){
// only submit rating form when user clicks on <i></i> tag (see generated HTML for rating stars on this page)
if (e.target.tagName === 'I') {
$("#rating-form").submit();
}
});
});
</script>
<script src="{{ get_static_path('rating.js') }}"></script>
{% endblock %}
13 changes: 11 additions & 2 deletions critiquebrainz/frontend/templates/macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@
{{ _('Artist') }}
</span>
{% endif %}
{% elif entity_type == 'label' %}
<img src="{{ get_static_path('images/placeholder_disc.svg') }}" {{ attributes }} />
{% if overlay_type %}
<span class="entity-type label">
{{ _('Label') }}
</span>
{% endif %}
{% else %} {# release-group #}
<img {{ attributes }} />
{% if overlay_type %}
Expand Down Expand Up @@ -153,8 +160,10 @@
{% endif %}
{% endmacro %}

{% macro show_avg_rating(rating, count) %}
<i class="glyphicon glyphicon-star"></i>
{% macro show_avg_rating(rating, count, show_glyphicon=True) %}
{% if show_glyphicon %}
<i class="glyphicon glyphicon-star"></i>
{% endif %}
<span style="font-size: 16px;">{{ rating }}</span><em class="text-muted">/5 {{ _('based on %(number)s ratings', number=count) }}</em>
{% endmacro %}

Expand Down
3 changes: 2 additions & 1 deletion critiquebrainz/frontend/templates/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,10 @@
<div class="form-group">
<select id="type-selector" name="type" class="form-control input-sm">
<option value="artist">{{ _('Artist') }}</option>
<option value="release-group">{{ _('Release group') }}</option>
<option value="label">{{ _('Label') }}</option>
<option value="event">{{ _('Event') }}</option>
<option value="place">{{ _('Place') }}</option>
<option value="release-group">{{ _('Release group') }}</option>
</select>
</div>
<button type="submit" class="btn input-sm"><span class="glyphicon glyphicon-search"></span></button>
Expand Down
2 changes: 2 additions & 0 deletions critiquebrainz/frontend/templates/review/browse.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ <h2>{{ _('Reviews') }}</h2>
<a href="{{ url_for('review.browse', entity_type='release_group') }}">{{ _('Release group') }}</a></li>
<li role="presentation" {{ "class=active" if entity_type == 'artist' }}>
<a href="{{ url_for('review.browse', entity_type='artist') }}">{{ _('Artist') }}</a></li>
<li role="presentation" {{ "class=active" if entity_type == 'label' }}>
<a href="{{ url_for('review.browse', entity_type='label') }}">{{ _('Label') }}</a></li>
<li role="presentation" {{ "class=active" if entity_type == 'event' }}>
<a href="{{ url_for('review.browse', entity_type='event') }}">{{ _('Event') }}</a></li>
<li role="presentation" {{ "class=active" if entity_type == 'place' }}>
Expand Down
Loading

0 comments on commit 7064672

Please sign in to comment.