Skip to content

Commit

Permalink
Merge pull request #438 from metabrainz/sampledb-missing-entities
Browse files Browse the repository at this point in the history
Always return dummy data in debug mode if it's not in MusicBrainz
  • Loading branch information
alastair committed Jun 29, 2022
2 parents dc0d847 + f516800 commit f858811
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 11 deletions.
7 changes: 6 additions & 1 deletion critiquebrainz/frontend/__init__.py
Expand Up @@ -127,13 +127,18 @@ def create_app(debug=None, config_path=None):
app.jinja_env.add_extension('jinja2.ext.do')
from critiquebrainz.utils import reformat_date, reformat_datetime, track_length, track_length_ms, parameterize
from critiquebrainz.frontend.external.musicbrainz_db.entities import get_entity_by_id
from critiquebrainz.frontend.external.musicbrainz_db import mbstore, development_get_entity_by_id
mbstore.init_app(app)
from critiquebrainz.frontend.forms.utils import get_language_name
app.jinja_env.filters['date'] = reformat_date
app.jinja_env.filters['datetime'] = reformat_datetime
app.jinja_env.filters['track_length'] = track_length
app.jinja_env.filters['track_length_ms'] = track_length_ms
app.jinja_env.filters['parameterize'] = parameterize
app.jinja_env.filters['entity_details'] = get_entity_by_id
if app.config["DEBUG"]:
app.jinja_env.filters['entity_details'] = development_get_entity_by_id
else:
app.jinja_env.filters['entity_details'] = get_entity_by_id
app.jinja_env.filters['language_name'] = get_language_name
app.context_processor(lambda: dict(get_static_path=static_manager.get_static_path))

Expand Down
78 changes: 78 additions & 0 deletions critiquebrainz/frontend/external/musicbrainz_db/__init__.py
@@ -1 +1,79 @@
from flask import current_app, _app_ctx_stack

DEFAULT_CACHE_EXPIRATION = 12 * 60 * 60 # seconds (12 hours)

from critiquebrainz.frontend.external.musicbrainz_db.entities import get_entity_by_id, get_multiple_entities

class MBDataAccess(object):
"""A data access object which switches between database get methods or development versions
This is useful because we won't show a review if we cannot find its metadata in the
musicbrainz database. If a developer is using the musicbrainz sample database (for size reasons)
then they will only be able to see very few reviews.
During development mode, we replace the access methods (get_entity_by_id and get_multiple_entities)
with a development version which always returns some dummy metadata so that reviews are always shown.
"""
def __init__(self, app=None):
self.app = app
if app:
self.init_app(self.app)

def init_app(self, app):
if self.app is None:
self.app = app

from critiquebrainz.frontend.external.musicbrainz_db.entities import get_entity_by_id, get_multiple_entities
if self.app.config["DEBUG"]:
self.get_entity_by_id_method = development_get_entity_by_id
self.get_multiple_entities_method = development_get_multiple_entities
else:
self.get_entity_by_id_method = get_entity_by_id
self.get_multiple_entities_method = get_multiple_entities

@property
def get_entity_by_id(self):
ctx = _app_ctx_stack.top
if ctx is not None:
if not hasattr(ctx, 'get_entity_by_id'):
ctx.get_entity_by_id = self.get_entity_by_id_method
return ctx.get_entity_by_id

@property
def get_multiple_entities(self):
ctx = _app_ctx_stack.top
if ctx is not None:
if not hasattr(ctx, 'get_multiple_entities'):
ctx.get_multiple_entities = self.get_multiple_entities_method
return ctx.get_multiple_entities

mbstore = MBDataAccess()


def development_get_multiple_entities(entities):
"""Same as get_multiple_entities, but always returns items for all entities even if one
isn't in the MusicBrainz database. Used in development with a sample database."""
data = get_multiple_entities(entities)
missing_entities = [(mbid, entity_type) for mbid, entity_type in entities if mbid not in data]
if missing_entities:
current_app.logger.info("returning dummy entities in development mode")
for mbid, entity_type in missing_entities:
data[mbid] = get_dummy_item(mbid, entity_type)
return data

def development_get_entity_by_id(entity_id, entity_type):
"""Same as get_entity_by_id, but always returns a dummy item if the requested entity
isn't in the MusicBrainz database. Used in development with a sample database."""
entity = get_entity_by_id(entity_id, entity_type)
if entity is None and current_app.config["DEBUG"]:
current_app.logger.info("returning dummy entity in development mode")
return get_dummy_item(entity_id, entity_type)
return entity


def get_dummy_item(entity_id, entity_type):
"""Get something that looks just enough like a MusicBrainz entity to display in a CB template"""
return {"mbid": entity_id,
"title": entity_type + " missing from sample database",
"name": entity_type + " missing from sample database",
"artist-credit-phrase": "Artist",
"artist-credit": [{"artist": {"mbid": "6a0b0138-dc06-4d5c-87b3-fab64f0fd326", "name": "No one"}}],
"comment": "This dummy item exists if DEBUG=True so that the review can be viewed"}
16 changes: 8 additions & 8 deletions critiquebrainz/frontend/views/review.py
Expand Up @@ -18,7 +18,7 @@
from critiquebrainz.db.review import ENTITY_TYPES
from critiquebrainz.frontend import flash
from critiquebrainz.frontend.external import mbspotify, soundcloud, notify_moderators
from critiquebrainz.frontend.external.musicbrainz_db.entities import get_multiple_entities, get_entity_by_id
from critiquebrainz.frontend.external.musicbrainz_db import mbstore
from critiquebrainz.frontend.forms.comment import CommentEditForm
from critiquebrainz.frontend.forms.log import AdminActionForm
from critiquebrainz.frontend.forms.review import ReviewCreateForm, ReviewEditForm, ReviewReportForm
Expand Down Expand Up @@ -86,7 +86,7 @@ def browse():

# Loading info about entities for reviews
entities = [(str(review["entity_id"]), review["entity_type"]) for review in reviews]
entities_info = get_multiple_entities(entities)
entities_info = mbstore.get_multiple_entities(entities)

# If we don't have metadata for a review, remove it from the list
# This will have the effect of removing an item from the 3x9 grid of reviews, but it
Expand Down Expand Up @@ -137,7 +137,7 @@ def entity(id, rev=None):
spotify_mappings = mbspotify.mappings(str(review["entity_id"]))
soundcloud_url = soundcloud.get_url(str(review["entity_id"]))

entity = get_entity_by_id(review["entity_id"], review["entity_type"])
entity = mbstore.get_entity_by_id(review["entity_id"], review["entity_type"])
if not entity:
raise NotFound("This review is for an item that doesn't exist")

Expand Down Expand Up @@ -196,7 +196,7 @@ def redirect_to_entity(review_id, revision_id):
@review_bp.route('/<uuid:id>/revisions/compare')
def compare(id):
review = get_review_or_404(id)
entity = get_entity_by_id(review["entity_id"], review["entity_type"])
entity = mbstore.get_entity_by_id(review["entity_id"], review["entity_type"])
if not entity:
raise NotFound("This review is for an item that doesn't exist")

Expand Down Expand Up @@ -231,7 +231,7 @@ def compare(id):
@review_bp.route('/<uuid:id>/revisions')
def revisions(id):
review = get_review_or_404(id)
entity = get_entity_by_id(review["entity_id"], review["entity_type"])
entity = mbstore.get_entity_by_id(review["entity_id"], review["entity_type"])
if not entity:
raise NotFound("This review is for an item that doesn't exist")

Expand All @@ -255,7 +255,7 @@ def revisions(id):
@review_bp.route('/<uuid:id>/revisions/more')
def revisions_more(id):
review = get_review_or_404(id)
entity = get_entity_by_id(review["entity_id"], review["entity_type"])
entity = mbstore.get_entity_by_id(review["entity_id"], review["entity_type"])
if not entity:
raise NotFound("This review is for an item that doesn't exist")

Expand Down Expand Up @@ -345,7 +345,7 @@ def create(entity_type=None, entity_id=None):
flash.success(gettext("Review has been published!"))
return redirect(url_for('.entity', id=review['id']))

_entity = get_entity_by_id(entity_id, entity_type)
_entity = mbstore.get_entity_by_id(entity_id, entity_type)
data = {
"form": form,
"entity_type": entity_type,
Expand Down Expand Up @@ -427,7 +427,7 @@ def edit(id):
data["spotify_mappings"] = mbspotify.mappings(str(review["entity_id"]))
data["soundcloud_url"] = soundcloud.get_url(str(review["entity_id"]))

_entity = get_entity_by_id(review["entity_id"], review["entity_type"])
_entity = mbstore.get_entity_by_id(review["entity_id"], review["entity_type"])
data["entity_title"] = get_entity_title(_entity)
data["entity"] = _entity
return render_template('review/modify/edit.html', **data)
Expand Down
4 changes: 2 additions & 2 deletions critiquebrainz/frontend/views/user.py
@@ -1,5 +1,5 @@
from flask import Blueprint, render_template, request, redirect, url_for
from critiquebrainz.frontend.external.musicbrainz_db.entities import get_multiple_entities
from critiquebrainz.frontend.external.musicbrainz_db import mbstore
from flask_babel import gettext
from flask_login import login_required, current_user
from werkzeug.exceptions import NotFound, BadRequest
Expand Down Expand Up @@ -42,7 +42,7 @@ def reviews(user_id):

# Load info about entities for reviews
entities = [(str(review["entity_id"]), review["entity_type"]) for review in reviews]
entities_info = get_multiple_entities(entities)
entities_info = mbstore.get_multiple_entities(entities)

# If we don't have metadata for a review, remove it from the list
# This will have the effect of removing an item from the 3x9 grid of reviews, but it
Expand Down

0 comments on commit f858811

Please sign in to comment.