diff --git a/PROJECT.org b/PROJECT.org index e2a1e43..0c78a46 100644 --- a/PROJECT.org +++ b/PROJECT.org @@ -88,7 +88,7 @@ fetching and simple saving. *** Metadata sources **** Scraper -* Backlog [0/24] :vrobbler:project:personal: +* Backlog [1/26] :vrobbler:project:personal: ** TODO [#C] After transition to linux add curl_cffi as webpage scrapper again :webpages:metadata: ** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :bug:music:scrobbles: :PROPERTIES: @@ -619,6 +619,19 @@ The Edit log form should have from top to bottom: - Expansion ids (which should a multi-select widget of expansions for this game) - Location (which should be a drop down of BoardGameLocations for this user) +** TODO Exclude some board games from auto-expansion imports :boardgames: + +*** Description + +Some board games, especially trading card games, have silly amounts of expansions. +We should have a setting SKIP_AUTO_EXPANSION_DOWNLOAD that is a list of BGG ids of +games where expansions should not be download automatically. This exclusion should also auto-include +any games with "Collectible Card Games" in it's family. + +** DONE Should be able to add new variants to board games via the log data form :boardgames: +:PROPERTIES: +:ID: 5ed0dd25-3026-3da8-dc5c-f2a75751af9a +:END: * Version 59.2 [1/1] ** DONE Fix test failure in discgolf app :discgolf:tests: :PROPERTIES: diff --git a/vrobbler/apps/boardgames/models.py b/vrobbler/apps/boardgames/models.py index 3d5e9be..3878f41 100644 --- a/vrobbler/apps/boardgames/models.py +++ b/vrobbler/apps/boardgames/models.py @@ -93,6 +93,7 @@ class BoardGameLogData(BaseLogData, LongPlayLogData): @classmethod def override_fields(cls) -> dict: + from boardgames.widgets import VariantSelectWidget from scrobbles.forms import NotesDictField fields = {} @@ -110,7 +111,7 @@ class BoardGameLogData(BaseLogData, LongPlayLogData): "variant_ids": forms.ModelMultipleChoiceField( queryset=BoardGameVariant.objects.all(), required=False, - widget=forms.SelectMultiple(attrs={"size": 5}), + widget=VariantSelectWidget(attrs={"size": 5}), ), "expansion_ids": forms.ModelMultipleChoiceField( queryset=BoardGame.objects.filter( diff --git a/vrobbler/apps/boardgames/templates/boardgames/widgets/variant_select.html b/vrobbler/apps/boardgames/templates/boardgames/widgets/variant_select.html new file mode 100644 index 0000000..e65d448 --- /dev/null +++ b/vrobbler/apps/boardgames/templates/boardgames/widgets/variant_select.html @@ -0,0 +1,97 @@ + + + +
+ + diff --git a/vrobbler/apps/boardgames/urls.py b/vrobbler/apps/boardgames/urls.py index 0e77e81..190a7a1 100644 --- a/vrobbler/apps/boardgames/urls.py +++ b/vrobbler/apps/boardgames/urls.py @@ -20,4 +20,9 @@ urlpatterns = [ views.BoardGamePublisherDetailView.as_view(), name="publisher_detail", ), + path( + "variants/ajax-create/", + views.ajax_create_variant, + name="ajax-create-variant", + ), ] diff --git a/vrobbler/apps/boardgames/views.py b/vrobbler/apps/boardgames/views.py index 987680f..7510e11 100644 --- a/vrobbler/apps/boardgames/views.py +++ b/vrobbler/apps/boardgames/views.py @@ -1,6 +1,9 @@ import datetime + +from django.http import JsonResponse from django.utils import timezone from django.views import generic +from django.views.decorators.http import require_POST from boardgames.models import BoardGame, BoardGameDesigner, BoardGamePublisher from scrobbles.models import Scrobble from scrobbles.views import ( @@ -10,6 +13,38 @@ from scrobbles.views import ( ) +@require_POST +def ajax_create_variant(request): + name = request.POST.get("name", "").strip() + board_game_id = request.POST.get("board_game_id") + description = request.POST.get("description", "").strip() + + if not name or not board_game_id: + return JsonResponse({"error": "Name and board game are required"}, status=400) + + try: + board_game_id = int(board_game_id) + except (ValueError, TypeError): + return JsonResponse({"error": "Invalid board game"}, status=400) + + from boardgames.models import BoardGameVariant + + variant = BoardGameVariant.objects.filter( + name__iexact=name, + board_game_id=board_game_id, + ).first() + if variant: + return JsonResponse({"id": variant.id, "name": variant.name}) + + variant = BoardGameVariant.objects.create( + name=name, + board_game_id=board_game_id, + description=description or None, + ) + + return JsonResponse({"id": variant.id, "name": variant.name}) + + class BoardGameListView(ScrobbleableListView): model = BoardGame diff --git a/vrobbler/apps/boardgames/widgets.py b/vrobbler/apps/boardgames/widgets.py new file mode 100644 index 0000000..aaad4ba --- /dev/null +++ b/vrobbler/apps/boardgames/widgets.py @@ -0,0 +1,9 @@ +from django import forms + + +class VariantSelectWidget(forms.SelectMultiple): + template_name = "boardgames/widgets/variant_select.html" + + def get_context(self, name, value, attrs): + context = super().get_context(name, value, attrs) + return context diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index a47612d..fe2a782 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -33,7 +33,7 @@ from django.http import ( JsonResponse, ) from django.shortcuts import get_object_or_404, redirect -from django.urls import reverse_lazy +from django.urls import reverse, reverse_lazy from django.utils import timezone from django.utils.dateformat import DateFormat from django.views.decorators.csrf import csrf_exempt @@ -1235,15 +1235,28 @@ class ScrobbleDetailView(DetailView): def get_form_class(self): return self.object.media_obj.logdata_cls().form() - def _update_expansion_ids_queryset(self, form): + def _update_board_game_widgets(self, form): from boardgames.models import BoardGame - if isinstance(self.object.media_obj, BoardGame) and "expansion_ids" in form.fields: + if not isinstance(self.object.media_obj, BoardGame): + return + + if "expansion_ids" in form.fields: expansions = BoardGame.objects.filter( expansion_for_boardgame=self.object.media_obj ) form.fields["expansion_ids"].queryset = expansions + if "variant_ids" in form.fields: + form.fields["variant_ids"].widget.attrs["data-board-game-id"] = ( + self.object.media_obj.id + ) + form.fields["variant_ids"].widget.attrs["data-ajax-url"] = ( + self.request.build_absolute_uri( + reverse("boardgames:ajax-create-variant") + ) + ) + def get_form(self): FormClass = self.get_form_class() @@ -1255,14 +1268,14 @@ class ScrobbleDetailView(DetailView): log["notes"] = self.object.logdata.notes_as_str(separator="\n") form = FormClass(initial=log) - self._update_expansion_ids_queryset(form) + self._update_board_game_widgets(form) return form def post(self, request, *args, **kwargs): self.object = self.get_object() FormClass = self.get_form_class() form = FormClass(request.POST) - self._update_expansion_ids_queryset(form) + self._update_board_game_widgets(form) if form.is_valid(): data = form.cleaned_data.copy()