From 64ec3c1cca6bbf0588ca08d6b16f4bc01519f267 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Fri, 20 Mar 2026 13:11:25 -0400 Subject: [PATCH] [widgets] Add first run at widgets --- vrobbler/apps/profiles/forms.py | 9 +- .../0029_userprofile_enable_public_widgets.py | 18 + vrobbler/apps/profiles/models.py | 6 +- vrobbler/apps/profiles/views.py | 3 +- vrobbler/apps/scrobbles/urls.py | 20 ++ vrobbler/apps/scrobbles/views.py | 315 ++++++++++++------ .../scrobbles/embeddable_top_artist.html | 87 +++++ .../embeddable_top_board_games_month.html | 87 +++++ .../embeddable_top_board_games_week.html | 87 +++++ .../embeddable_top_board_games_year.html | 87 +++++ 10 files changed, 600 insertions(+), 119 deletions(-) create mode 100644 vrobbler/apps/profiles/migrations/0029_userprofile_enable_public_widgets.py create mode 100644 vrobbler/templates/scrobbles/embeddable_top_artist.html create mode 100644 vrobbler/templates/scrobbles/embeddable_top_board_games_month.html create mode 100644 vrobbler/templates/scrobbles/embeddable_top_board_games_week.html create mode 100644 vrobbler/templates/scrobbles/embeddable_top_board_games_year.html diff --git a/vrobbler/apps/profiles/forms.py b/vrobbler/apps/profiles/forms.py index 84235e5..043a092 100644 --- a/vrobbler/apps/profiles/forms.py +++ b/vrobbler/apps/profiles/forms.py @@ -6,14 +6,10 @@ from profiles.models import UserProfile class UserProfileForm(forms.ModelForm): def __init__(self, *args, **kwargs): self.request = kwargs.pop("request") - self.profile = UserProfile.objects.filter( - user=self.request.user - ).first() + self.profile = UserProfile.objects.filter(user=self.request.user).first() if not self.profile: raise Exception - super(UserProfileForm, self).__init__( - *args, **kwargs, instance=self.profile - ) + super(UserProfileForm, self).__init__(*args, instance=self.profile, **kwargs) class Meta: model = UserProfile @@ -36,6 +32,7 @@ class UserProfileForm(forms.ModelForm): "ntfy_url", "ntfy_enabled", "redirect_to_webpage", + "enable_public_widgets", ] widgets = { "lastfm_password": forms.PasswordInput(render_value=True), diff --git a/vrobbler/apps/profiles/migrations/0029_userprofile_enable_public_widgets.py b/vrobbler/apps/profiles/migrations/0029_userprofile_enable_public_widgets.py new file mode 100644 index 0000000..35bdccc --- /dev/null +++ b/vrobbler/apps/profiles/migrations/0029_userprofile_enable_public_widgets.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.29 on 2026-03-20 16:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("profiles", "0028_alter_userprofile_timezone"), + ] + + operations = [ + migrations.AddField( + model_name="userprofile", + name="enable_public_widgets", + field=models.BooleanField(default=False), + ), + ] diff --git a/vrobbler/apps/profiles/models.py b/vrobbler/apps/profiles/models.py index 1337712..34ae732 100644 --- a/vrobbler/apps/profiles/models.py +++ b/vrobbler/apps/profiles/models.py @@ -17,9 +17,7 @@ logger = logging.getLogger(__name__) class UserProfile(TimeStampedModel): - user = models.OneToOneField( - User, on_delete=models.CASCADE, related_name="profile" - ) + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile") timezone = models.CharField( max_length=255, choices=PRETTY_TIMEZONE_CHOICES, default="UTC" ) @@ -63,6 +61,8 @@ class UserProfile(TimeStampedModel): redirect_to_webpage = models.BooleanField(default=True) + enable_public_widgets = models.BooleanField(default=False) + def __str__(self): return f"User profile for {self.user}" diff --git a/vrobbler/apps/profiles/views.py b/vrobbler/apps/profiles/views.py index c5484f2..eda9bd8 100644 --- a/vrobbler/apps/profiles/views.py +++ b/vrobbler/apps/profiles/views.py @@ -1,3 +1,4 @@ +from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse_lazy from django.http.response import HttpResponseBadRequest from django.views.generic import FormView @@ -5,7 +6,7 @@ from django.views.generic import FormView from profiles.forms import UserProfileForm -class ProfileFormView(FormView): +class ProfileFormView(LoginRequiredMixin, FormView): form_class = UserProfileForm template_name = "profiles/settings_form.html" success_url = reverse_lazy("profiles:profile_settings") diff --git a/vrobbler/apps/scrobbles/urls.py b/vrobbler/apps/scrobbles/urls.py index 9505f7b..a820560 100644 --- a/vrobbler/apps/scrobbles/urls.py +++ b/vrobbler/apps/scrobbles/urls.py @@ -6,6 +6,26 @@ app_name = "scrobbles" urlpatterns = [ path("status/", views.ScrobbleStatusView.as_view(), name="status"), + path( + "widget/top-artists//", + views.EmbeddableTopArtistWidget.as_view(), + name="embeddable-top-artists", + ), + path( + "widget/top-board-games/month//", + views.EmbeddableTopBoardGamesMonthWidget.as_view(), + name="embeddable-top-board-games-month", + ), + path( + "widget/top-board-games/week//", + views.EmbeddableTopBoardGamesWeekWidget.as_view(), + name="embeddable-top-board-games-week", + ), + path( + "widget/top-board-games/year//", + views.EmbeddableTopBoardGamesYearWidget.as_view(), + name="embeddable-top-board-games-year", + ), path( "manual/lookup/", views.ManualScrobbleView.as_view(), diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index 5bde3e8..fe7561a 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -9,10 +9,11 @@ from dateutil.relativedelta import relativedelta from django.apps import apps from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.auth import get_user_model from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.db.models import Count, Max, Q from django.db.models.query import QuerySet -from django.http import FileResponse, HttpResponseRedirect, JsonResponse +from django.http import FileResponse, Http404, HttpResponseRedirect, JsonResponse from django.shortcuts import redirect from django.urls import reverse_lazy from django.utils import timezone @@ -66,8 +67,11 @@ from scrobbles.utils import ( get_long_plays_completed, get_long_plays_in_progress, ) +from profiles.models import UserProfile from profiles.utils import now_user_timezone +User = get_user_model() + logger = logging.getLogger(__name__) @@ -163,25 +167,17 @@ class RecentScrobbleList(ListView): next_date = date + timedelta(weeks=+2) prev_date = date + timedelta(weeks=-1) if date.isocalendar()[1] < today.isocalendar()[1]: - data[ - "next_link" - ] = f"?date={next_date.strftime('%Y-W%W')}" + data["next_link"] = f"?date={next_date.strftime('%Y-W%W')}" data["prev_link"] = f"?date={prev_date.strftime('%Y-W%W')}" - data[ - "title" - ] = f"Week {date.strftime('%-W')} of {date.year}" + data["title"] = f"Week {date.strftime('%-W')} of {date.year}" data = data | Scrobble.as_dict_by_type( - Scrobble.for_week( - user_id, date.year, date.isocalendar()[1] - ) + Scrobble.for_week(user_id, date.year, date.isocalendar()[1]) ) elif date_str == "this_month" or date_str.count("-") == 1: next_date = date + relativedelta(months=1) prev_date = date + relativedelta(months=-1) if date.month < today.month: - data[ - "next_link" - ] = f"?date={next_date.strftime('%Y-%m')}" + data["next_link"] = f"?date={next_date.strftime('%Y-%m')}" data["title"] = f"{date.strftime('%B %Y')}" data["prev_link"] = f"?date={prev_date.strftime('%Y-%m')}" data = data | Scrobble.as_dict_by_type( @@ -190,24 +186,16 @@ class RecentScrobbleList(ListView): elif date_str == "today" or date_str.count("-") == 2: next_date = date + timedelta(days=1) prev_date = date - timedelta(days=1) - data[ - "prev_link" - ] = f"?date={prev_date.strftime('%Y-%m-%d')}" + data["prev_link"] = f"?date={prev_date.strftime('%Y-%m-%d')}" if date < today: - data[ - "next_link" - ] = f"?date={next_date.strftime('%Y-%m-%d')}" + data["next_link"] = f"?date={next_date.strftime('%Y-%m-%d')}" if date == today: data["title"] = "Today" else: data["title"] = f"{date.strftime('%Y-%m-%d')}" - data[ - "today_link" - ] = f"?date={today.strftime('%Y-%m-%d')}" + data["today_link"] = f"?date={today.strftime('%Y-%m-%d')}" data = data | Scrobble.as_dict_by_type( - Scrobble.for_day( - user_id, date.year, date.month, date.day - ) + Scrobble.for_day(user_id, date.year, date.month, date.day) ) elif date_str == "this_year" or date_str.count("-") == 0: next_date = date + relativedelta(years=+1) @@ -230,9 +218,7 @@ class RecentScrobbleList(ListView): data = data | Scrobble.as_dict_by_type( Scrobble.for_day(user_id, date.year, date.month, date.day) ) - data[ - "today_link" - ] = "" # f"?date={today.strftime('%Y-%m-%d')}" + data["today_link"] = "" # f"?date={today.strftime('%Y-%m-%d')}" data["active_imports"] = AudioScrobblerTSVImport.objects.filter( processing_started__isnull=False, @@ -261,9 +247,7 @@ class ScrobbleLongPlaysView(TemplateView): def get_context_data(self, **kwargs): context_data = super().get_context_data(**kwargs) context_data["view"] = self.request.GET.get("view", "grid") - context_data["in_progress"] = get_long_plays_in_progress( - self.request.user - ) + context_data["in_progress"] = get_long_plays_in_progress(self.request.user) context_data["completed"] = get_long_plays_completed(self.request.user) return context_data @@ -340,9 +324,7 @@ class ManualScrobbleView(FormView): scrobble_fn = MANUAL_SCROBBLE_FNS[key] scrobble = eval(scrobble_fn)(item_id, self.request.user.id) - return HttpResponseRedirect( - scrobble.redirect_url(self.request.user.id) - ) + return HttpResponseRedirect(scrobble.redirect_url(self.request.user.id)) class JsonableResponseMixin: @@ -388,9 +370,7 @@ class AudioScrobblerImportCreateView( return HttpResponseRedirect(self.request.META.get("HTTP_REFERER")) -class KoReaderImportCreateView( - LoginRequiredMixin, JsonableResponseMixin, CreateView -): +class KoReaderImportCreateView(LoginRequiredMixin, JsonableResponseMixin, CreateView): model = KoReaderImport fields = ["sqlite_file"] template_name = "scrobbles/upload_form.html" @@ -449,9 +429,7 @@ def web_scrobbler_webhook(request): playing_state = "started" if request.POST.get("isPlaying") == "false": playing_state = "stopped" - scrobble = web_scrobbler_scrobble_media( - youtube_id, user_id, status=playing_state - ) + scrobble = web_scrobbler_scrobble_media(youtube_id, user_id, status=playing_state) if not scrobble: logger.warning( "[web_scrobbler_webhook] failed", @@ -469,9 +447,7 @@ def web_scrobbler_webhook(request): "[web_scrobbler_webhook] finished", extra={"scrobble_id": scrobble.id}, ) - return JsonResponse( - {"scrobble_id": scrobble.id}, status=status.HTTP_200_OK - ) + return JsonResponse({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK) @csrf_exempt @@ -576,18 +552,12 @@ def import_audioscrobbler_file(request): scrobbles_created = [] # tsv_file = request.FILES[0] - file_serializer = serializers.AudioScrobblerTSVImportSerializer( - data=request.data - ) + file_serializer = serializers.AudioScrobblerTSVImportSerializer(data=request.data) if file_serializer.is_valid(): import_file = file_serializer.save() - return Response( - {"scrobble_ids": scrobbles_created}, status=status.HTTP_200_OK - ) + return Response({"scrobble_ids": scrobbles_created}, status=status.HTTP_200_OK) else: - return Response( - file_serializer.errors, status=status.HTTP_400_BAD_REQUEST - ) + return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST) @permission_classes([IsAuthenticated]) @@ -768,9 +738,7 @@ class ChartRecordView(TemplateView): ).order_by("rank") if charts.count() == 0: - ChartRecord.build( - user=self.request.user, model_str=media_type, **kwargs - ) + ChartRecord.build(user=self.request.user, model_str=media_type, **kwargs) charts = ChartRecord.objects.filter( media_filter, user=self.request.user, **kwargs ).order_by("rank") @@ -802,9 +770,7 @@ class ChartRecordView(TemplateView): media_type = self.request.GET.get("media", "Track") user = self.request.user params = {} - context_data["chart_type"] = self.request.GET.get( - "chart_type", "maloja" - ) + context_data["chart_type"] = self.request.GET.get("chart_type", "maloja") context_data["artist_charts"] = {} if not date: @@ -842,9 +808,7 @@ class ChartRecordView(TemplateView): tzinfo = now.tzinfo now = now.date() start_of_today = datetime.combine(now, datetime.min.time(), tzinfo) - start_day_of_week = start_of_today - timedelta( - days=now.isoweekday() % 7 - ) + start_day_of_week = start_of_today - timedelta(days=now.isoweekday() % 7) start_day_of_month = now.replace(day=1) # TV Series Scrobbles @@ -889,35 +853,25 @@ class ChartRecordView(TemplateView): context_data["tv_show_charts"] = { "today": list(live_tv_charts(user=user, chart_period="today")), "last7": list(live_tv_charts(user=user, chart_period="last7")), - "last30": list( - live_tv_charts(user=user, chart_period="last30") - ), + "last30": list(live_tv_charts(user=user, chart_period="last30")), "year": list(live_tv_charts(user=user, chart_period="year")), "all": list(live_tv_charts(user=user, chart_period="all")), } context_data["youtube_channel_charts"] = { "today": list( - live_youtube_channel_charts( - user=user, chart_period="today" - ) + live_youtube_channel_charts(user=user, chart_period="today") ), "last7": list( - live_youtube_channel_charts( - user=user, chart_period="last7" - ) + live_youtube_channel_charts(user=user, chart_period="last7") ), "last30": list( - live_youtube_channel_charts( - user=user, chart_period="last30" - ) + live_youtube_channel_charts(user=user, chart_period="last30") ), "year": list( live_youtube_channel_charts(user=user, chart_period="year") ), - "all": list( - live_youtube_channel_charts(user=user, chart_period="all") - ), + "all": list(live_youtube_channel_charts(user=user, chart_period="all")), } return context_data @@ -955,9 +909,7 @@ class ChartRecordView(TemplateView): params["day"] = day month_str = calendar.month_name[month] name = f"Chart for {month_str} {day}, {year}" - in_progress = ( - now.month == month and now.year == year and now.day == day - ) + in_progress = now.month == month and now.year == year and now.day == day media_filter = self.get_media_filter("Track") track_charts = ChartRecord.objects.filter( @@ -969,17 +921,13 @@ class ChartRecordView(TemplateView): ).order_by("rank") if track_charts.count() == 0 and not in_progress: - ChartRecord.build( - user=self.request.user, model_str="Track", **params - ) + ChartRecord.build(user=self.request.user, model_str="Track", **params) media_filter = self.get_media_filter("Track") track_charts = ChartRecord.objects.filter( media_filter, user=self.request.user, **params ).order_by("rank") if artist_charts.count() == 0 and not in_progress: - ChartRecord.build( - user=self.request.user, model_str="Artist", **params - ) + ChartRecord.build(user=self.request.user, model_str="Artist", **params) media_filter = self.get_media_filter("Artist") artist_charts = ChartRecord.objects.filter( media_filter, user=self.request.user, **params @@ -1000,38 +948,24 @@ class ScrobbleStatusView(LoginRequiredMixin, TemplateView): def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) user_scrobble_qs = Scrobble.objects.filter().order_by("-timestamp") - progress_plays = user_scrobble_qs.filter( - in_progress=True, is_paused=False - ) + progress_plays = user_scrobble_qs.filter(in_progress=True, is_paused=False) data["listening"] = progress_plays.filter( Q(track__isnull=False) | Q(podcast_episode__isnull=False) ).first() data["watching"] = progress_plays.filter(video__isnull=False).first() - data["going"] = progress_plays.filter( - geo_location__isnull=False - ).first() - data["playing"] = progress_plays.filter( - board_game__isnull=False - ).first() - data["sporting"] = progress_plays.filter( - sport_event__isnull=False - ).first() - data["browsing"] = progress_plays.filter( - web_page__isnull=False - ).first() - data["participating"] = progress_plays.filter( - life_event__isnull=False - ).first() + data["going"] = progress_plays.filter(geo_location__isnull=False).first() + data["playing"] = progress_plays.filter(board_game__isnull=False).first() + data["sporting"] = progress_plays.filter(sport_event__isnull=False).first() + data["browsing"] = progress_plays.filter(web_page__isnull=False).first() + data["participating"] = progress_plays.filter(life_event__isnull=False).first() data["working"] = progress_plays.filter(task__isnull=False).first() long_plays = user_scrobble_qs.filter( long_play_complete=False, played_to_completion=True ) data["reading"] = long_plays.filter(book__isnull=False).first() - data["sessioning"] = long_plays.filter( - video_game__isnull=False - ).first() + data["sessioning"] = long_plays.filter(video_game__isnull=False).first() return data @@ -1066,9 +1000,7 @@ class ScrobbleDetailView(DetailView): data[field_name] = original_value if data.get("with_people_ids", False): - data["with_people_ids"] = [ - p.id for p in data["with_people_ids"] - ] + data["with_people_ids"] = [p.id for p in data["with_people_ids"]] if data.get("platform_id", False): data["platform_id"] = data["platform_id"].id @@ -1085,3 +1017,168 @@ class ScrobbleDetailView(DetailView): if "log_form" not in context: context["log_form"] = self.get_form() return context + + +class EmbeddableTopArtistWidget(TemplateView): + template_name = "scrobbles/embeddable_top_artist.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + user_id = self.kwargs.get("user_id") + + try: + user = User.objects.get(id=user_id) + except User.DoesNotExist: + context["error"] = "User not found" + return context + + profile = user.profile + if not profile.enable_public_widgets: + raise Http404("Public widgets are not enabled for this user") + + context["user"] = user + + artist = { + "user": user, + "media_type": "Artist", + "limit": 10, + } + context["top_artists"] = list(live_charts(**artist, chart_period="last7")) + + return context + + +class EmbeddableTopBoardGamesMonthWidget(TemplateView): + template_name = "scrobbles/embeddable_top_board_games_month.html" + + def get_context_data(self, **kwargs): + from django.db.models import Count + from django.utils import timezone + from datetime import timedelta + from boardgames.models import BoardGame + + context = super().get_context_data(**kwargs) + user_id = self.kwargs.get("user_id") + + try: + user = User.objects.get(id=user_id) + except User.DoesNotExist: + context["error"] = "User not found" + return context + + profile = user.profile + if not profile.enable_public_widgets: + raise Http404("Public widgets are not enabled for this user") + + context["user"] = user + + now = timezone.now() + if user.is_authenticated: + now = now_user_timezone(user.profile) + now = now.date() + start_day_of_month = now.replace(day=1) + + top_games = ( + BoardGame.objects.filter( + scrobble__user=user, + scrobble__timestamp__gte=start_day_of_month, + scrobble__played_to_completion=True, + ) + .annotate(num_plays=Count("scrobble", distinct=True)) + .order_by("-num_plays")[:10] + ) + + context["top_board_games"] = list(top_games) + context["month"] = now.strftime("%B %Y") + + return context + + +class EmbeddableTopBoardGamesWeekWidget(TemplateView): + template_name = "scrobbles/embeddable_top_board_games_week.html" + + def get_context_data(self, **kwargs): + from django.db.models import Count + from django.utils import timezone + from datetime import timedelta + from boardgames.models import BoardGame + + context = super().get_context_data(**kwargs) + user_id = self.kwargs.get("user_id") + + try: + user = User.objects.get(id=user_id) + except User.DoesNotExist: + context["error"] = "User not found" + return context + + profile = user.profile + if not profile.enable_public_widgets: + raise Http404("Public widgets are not enabled for this user") + + context["user"] = user + + now = timezone.now() + if user.is_authenticated: + now = now_user_timezone(user.profile) + now = now.date() + start_day_of_week = now - timedelta(days=now.isoweekday() % 7) + + top_games = ( + BoardGame.objects.filter( + scrobble__user=user, + scrobble__timestamp__gte=start_day_of_week, + scrobble__played_to_completion=True, + ) + .annotate(num_plays=Count("scrobble", distinct=True)) + .order_by("-num_plays")[:10] + ) + + context["top_board_games"] = list(top_games) + + return context + + +class EmbeddableTopBoardGamesYearWidget(TemplateView): + template_name = "scrobbles/embeddable_top_board_games_year.html" + + def get_context_data(self, **kwargs): + from django.db.models import Count + from django.utils import timezone + from boardgames.models import BoardGame + + context = super().get_context_data(**kwargs) + user_id = self.kwargs.get("user_id") + + try: + user = User.objects.get(id=user_id) + except User.DoesNotExist: + context["error"] = "User not found" + return context + + profile = user.profile + if not profile.enable_public_widgets: + raise Http404("Public widgets are not enabled for this user") + + context["user"] = user + + now = timezone.now() + if user.is_authenticated: + now = now_user_timezone(user.profile) + now = now.date() + start_day_of_year = now.replace(month=1, day=1) + + top_games = ( + BoardGame.objects.filter( + scrobble__user=user, + scrobble__timestamp__gte=start_day_of_year, + scrobble__played_to_completion=True, + ) + .annotate(num_plays=Count("scrobble", distinct=True)) + .order_by("-num_plays")[:10] + ) + + context["top_board_games"] = list(top_games) + context["year"] = now.strftime("%Y") + + return context diff --git a/vrobbler/templates/scrobbles/embeddable_top_artist.html b/vrobbler/templates/scrobbles/embeddable_top_artist.html new file mode 100644 index 0000000..065a6d8 --- /dev/null +++ b/vrobbler/templates/scrobbles/embeddable_top_artist.html @@ -0,0 +1,87 @@ +{% load static %} + + +
+

Last 7 Days Top Artists{% if user %} - {{ user.username }}{% endif %}

+ + {% if error %} +

{{ error }}

+ {% elif top_artists %} +
    + {% for artist in top_artists %} +
  • + {{ forloop.counter }} + {% if artist.thumbnail %} + {{ artist.name }} + {% else %} + {{ artist.name }} + {% endif %} +
    +
    {{ artist.name }}
    +
    {{ artist.num_scrobbles }} scrobbles
    +
    +
  • + {% endfor %} +
+ {% else %} +

No scrobbles in the last 7 days

+ {% endif %} +
diff --git a/vrobbler/templates/scrobbles/embeddable_top_board_games_month.html b/vrobbler/templates/scrobbles/embeddable_top_board_games_month.html new file mode 100644 index 0000000..2c82221 --- /dev/null +++ b/vrobbler/templates/scrobbles/embeddable_top_board_games_month.html @@ -0,0 +1,87 @@ +{% load static %} + + +
+

Top Board Games - {{ month }}{% if user %} - {{ user.username }}{% endif %}

+ + {% if error %} +

{{ error }}

+ {% elif top_board_games %} +
    + {% for game in top_board_games %} +
  • + {{ forloop.counter }} + {% if game.cover %} + {{ game.title }} + {% else %} + {{ game.title }} + {% endif %} +
    +
    {{ game.title }}
    +
    {{ game.num_plays }} plays
    +
    +
  • + {% endfor %} +
+ {% else %} +

No board games played this month

+ {% endif %} +
\ No newline at end of file diff --git a/vrobbler/templates/scrobbles/embeddable_top_board_games_week.html b/vrobbler/templates/scrobbles/embeddable_top_board_games_week.html new file mode 100644 index 0000000..5fbd144 --- /dev/null +++ b/vrobbler/templates/scrobbles/embeddable_top_board_games_week.html @@ -0,0 +1,87 @@ +{% load static %} + + +
+

Top Board Games - This Week{% if user %} - {{ user.username }}{% endif %}

+ + {% if error %} +

{{ error }}

+ {% elif top_board_games %} +
    + {% for game in top_board_games %} +
  • + {{ forloop.counter }} + {% if game.cover %} + {{ game.title }} + {% else %} + {{ game.title }} + {% endif %} +
    +
    {{ game.title }}
    +
    {{ game.num_plays }} plays
    +
    +
  • + {% endfor %} +
+ {% else %} +

No board games played this week

+ {% endif %} +
\ No newline at end of file diff --git a/vrobbler/templates/scrobbles/embeddable_top_board_games_year.html b/vrobbler/templates/scrobbles/embeddable_top_board_games_year.html new file mode 100644 index 0000000..8950cd2 --- /dev/null +++ b/vrobbler/templates/scrobbles/embeddable_top_board_games_year.html @@ -0,0 +1,87 @@ +{% load static %} + + +
+

Top Board Games - {{ year }}{% if user %} - {{ user.username }}{% endif %}

+ + {% if error %} +

{{ error }}

+ {% elif top_board_games %} +
    + {% for game in top_board_games %} +
  • + {{ forloop.counter }} + {% if game.cover %} + {{ game.title }} + {% else %} + {{ game.title }} + {% endif %} +
    +
    {{ game.title }}
    +
    {{ game.num_plays }} plays
    +
    +
  • + {% endfor %} +
+ {% else %} +

No board games played this year

+ {% endif %} +
\ No newline at end of file