Compare commits

...

11 Commits

27 changed files with 1555 additions and 605 deletions

1094
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "vrobbler" name = "vrobbler"
version = "0.15.2" version = "0.15.4"
description = "" description = ""
authors = ["Colin Powell <colin@unbl.ink>"] authors = ["Colin Powell <colin@unbl.ink>"]
@ -38,7 +38,6 @@ honcho = "^1.1.0"
howlongtobeatpy = "^1.0.5" howlongtobeatpy = "^1.0.5"
beautifulsoup4 = "^4.11.2" beautifulsoup4 = "^4.11.2"
django-storages = "^1.13.2" django-storages = "^1.13.2"
boto3 = "^1.26.98"
stream-sqlite = "^0.0.41" stream-sqlite = "^0.0.41"
ipython = "^8.14.0" ipython = "^8.14.0"
pendulum = "^2.1.2" pendulum = "^2.1.2"
@ -46,6 +45,8 @@ trafilatura = "^1.6.3"
django-imagekit = "^5.0.0" django-imagekit = "^5.0.0"
thefuzz = "^0.22.1" thefuzz = "^0.22.1"
dataclass-wizard = "0.22.0" dataclass-wizard = "0.22.0"
webdavclient3 = "^3.14.6"
boto3 = "^1.35.14"
[tool.poetry.group.dev] [tool.poetry.group.dev]
optional = true optional = true

View File

@ -1,8 +1,15 @@
import csv
import json
import logging import logging
from typing import Optional from typing import TYPE_CHECKING, Optional
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from django.contrib.auth import get_user_model
User = get_user_model()
if TYPE_CHECKING:
from scrobbles.models import Scrobble
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -90,3 +97,56 @@ def lookup_boardgame_from_bgg(lookup_id: str) -> dict:
} }
return game_dict return game_dict
def push_scrobble_to_bgg(scrobble: "Scrobble", user: User) -> Optional[bool]:
bgg_username = user.profile.bgg_username
bgg_password = user.profile.bgg_password
if not bgg_username or bgg_password:
return
login_payload = {
"credentials": {"username": bgg_username, "password": bgg_password}
}
headers = {"content-type": "application/json"}
# TODO Look up past plays for scrobble.media_obj.bggeek_id, and make sure we haven't scrobbled this before
with requests.Session() as s:
p = s.post(
"https://boardgamegeek.com/login/api/v1",
data=json.dumps(login_payload),
headers=headers,
)
players = []
if scrobble.metadata:
for player in scrobble.metadata.players:
if player["user_id"]:
player_user = User.objects.filter(
id=player["user_id"]
).first()
if player_user:
if player_user.bgg_username:
player["username"] = player_user.bgg_username
else:
player["name"] = player_user.username
player["win"] = player.get("win")
player["color"] = player.get("color")
player["new"] = player.get("new")
player["score"] = player.get("score")
players.append(player)
play_payload = {
"playdate": scrobble.timestamp.date.strftime("%Y-%m-%d"),
"length": scrobble.playback_position_seconds / 60,
"comments": "Uploaded from Vrobbler",
"location": scrobble.log.location or None,
"objectid": scrobble.media_obj.bggeek_id,
"quantity": "1",
"action": "save",
"players": players,
"objecttype": "thing",
"ajax": 1,
}

View File

@ -6,10 +6,12 @@ app_name = "boardgames"
urlpatterns = [ urlpatterns = [
path( path(
"board-game/", views.BoardGameListView.as_view(), name="boardgame_list" "board-games/",
views.BoardGameListView.as_view(),
name="boardgame_list",
), ),
path( path(
"board-game/<slug:slug>/", "board-games/<slug:slug>/",
views.BoardGameDetailView.as_view(), views.BoardGameDetailView.as_view(),
name="boardgame_detail", name="boardgame_detail",
), ),

View File

@ -296,7 +296,7 @@ def build_scrobbles_from_book_map(
log_data = { log_data = {
"koreader_hash": book_dict.get("hash"), "koreader_hash": book_dict.get("hash"),
"page_data": scrobble_page_data, "page_data": scrobble_page_data,
"pages_read": cur_page_number, "pages_read": len(scrobble_page_data.keys()),
} }
scrobbles_to_create.append( scrobbles_to_create.append(
Scrobble( Scrobble(

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from enum import Enum
from typing import Optional from typing import Optional
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import requests import requests

View File

@ -1,7 +1,6 @@
from django.apps import apps from django.apps import apps
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
import pendulum
from scrobbles.dataclasses import LifeEventLogData from scrobbles.dataclasses import LifeEventLogData
from scrobbles.mixins import ScrobblableMixin from scrobbles.mixins import ScrobblableMixin
@ -9,8 +8,6 @@ BNULL = {"blank": True, "null": True}
class LifeEvent(ScrobblableMixin): class LifeEvent(ScrobblableMixin):
COMPLETION_PERCENT = 100
description = models.TextField(**BNULL) description = models.TextField(**BNULL)
def __str__(self): def __str__(self):

View File

@ -5,9 +5,9 @@ app_name = "moods"
urlpatterns = [ urlpatterns = [
path("mood/", views.MoodListView.as_view(), name="mood-list"), path("moods/", views.MoodListView.as_view(), name="mood-list"),
path( path(
"mood/<slug:slug>/", "moods/<slug:slug>/",
views.MoodDetailView.as_view(), views.MoodDetailView.as_view(),
name="mood-detail", name="mood-detail",
), ),

View File

@ -0,0 +1,601 @@
# Generated by Django 4.2.16 on 2024-09-09 16:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("profiles", "0015_userprofile_bgg_username"),
]
operations = [
migrations.AlterField(
model_name="userprofile",
name="timezone",
field=models.CharField(
choices=[
("Pacific/Midway", "(GMT-1100) Pacific/Midway"),
("Pacific/Niue", "(GMT-1100) Pacific/Niue"),
("Pacific/Pago_Pago", "(GMT-1100) Pacific/Pago_Pago"),
("Pacific/Honolulu", "(GMT-1000) Pacific/Honolulu"),
("Pacific/Rarotonga", "(GMT-1000) Pacific/Rarotonga"),
("Pacific/Tahiti", "(GMT-1000) Pacific/Tahiti"),
("US/Hawaii", "(GMT-1000) US/Hawaii"),
("Pacific/Marquesas", "(GMT-0930) Pacific/Marquesas"),
("America/Adak", "(GMT-0900) America/Adak"),
("Pacific/Gambier", "(GMT-0900) Pacific/Gambier"),
("America/Anchorage", "(GMT-0800) America/Anchorage"),
("America/Juneau", "(GMT-0800) America/Juneau"),
("America/Metlakatla", "(GMT-0800) America/Metlakatla"),
("America/Nome", "(GMT-0800) America/Nome"),
("America/Sitka", "(GMT-0800) America/Sitka"),
("America/Yakutat", "(GMT-0800) America/Yakutat"),
("Pacific/Pitcairn", "(GMT-0800) Pacific/Pitcairn"),
("US/Alaska", "(GMT-0800) US/Alaska"),
("America/Creston", "(GMT-0700) America/Creston"),
("America/Dawson", "(GMT-0700) America/Dawson"),
(
"America/Dawson_Creek",
"(GMT-0700) America/Dawson_Creek",
),
("America/Fort_Nelson", "(GMT-0700) America/Fort_Nelson"),
("America/Hermosillo", "(GMT-0700) America/Hermosillo"),
("America/Los_Angeles", "(GMT-0700) America/Los_Angeles"),
("America/Mazatlan", "(GMT-0700) America/Mazatlan"),
("America/Phoenix", "(GMT-0700) America/Phoenix"),
("America/Tijuana", "(GMT-0700) America/Tijuana"),
("America/Vancouver", "(GMT-0700) America/Vancouver"),
("America/Whitehorse", "(GMT-0700) America/Whitehorse"),
("Canada/Pacific", "(GMT-0700) Canada/Pacific"),
("US/Arizona", "(GMT-0700) US/Arizona"),
("US/Pacific", "(GMT-0700) US/Pacific"),
(
"America/Bahia_Banderas",
"(GMT-0600) America/Bahia_Banderas",
),
("America/Belize", "(GMT-0600) America/Belize"),
("America/Boise", "(GMT-0600) America/Boise"),
(
"America/Cambridge_Bay",
"(GMT-0600) America/Cambridge_Bay",
),
("America/Chihuahua", "(GMT-0600) America/Chihuahua"),
(
"America/Ciudad_Juarez",
"(GMT-0600) America/Ciudad_Juarez",
),
("America/Costa_Rica", "(GMT-0600) America/Costa_Rica"),
("America/Denver", "(GMT-0600) America/Denver"),
("America/Edmonton", "(GMT-0600) America/Edmonton"),
("America/El_Salvador", "(GMT-0600) America/El_Salvador"),
("America/Guatemala", "(GMT-0600) America/Guatemala"),
("America/Inuvik", "(GMT-0600) America/Inuvik"),
("America/Managua", "(GMT-0600) America/Managua"),
("America/Merida", "(GMT-0600) America/Merida"),
("America/Mexico_City", "(GMT-0600) America/Mexico_City"),
("America/Monterrey", "(GMT-0600) America/Monterrey"),
("America/Regina", "(GMT-0600) America/Regina"),
(
"America/Swift_Current",
"(GMT-0600) America/Swift_Current",
),
("America/Tegucigalpa", "(GMT-0600) America/Tegucigalpa"),
("America/Yellowknife", "(GMT-0600) America/Yellowknife"),
("Canada/Mountain", "(GMT-0600) Canada/Mountain"),
("Pacific/Galapagos", "(GMT-0600) Pacific/Galapagos"),
("US/Mountain", "(GMT-0600) US/Mountain"),
("America/Atikokan", "(GMT-0500) America/Atikokan"),
("America/Bogota", "(GMT-0500) America/Bogota"),
("America/Cancun", "(GMT-0500) America/Cancun"),
("America/Cayman", "(GMT-0500) America/Cayman"),
("America/Chicago", "(GMT-0500) America/Chicago"),
("America/Eirunepe", "(GMT-0500) America/Eirunepe"),
("America/Guayaquil", "(GMT-0500) America/Guayaquil"),
(
"America/Indiana/Knox",
"(GMT-0500) America/Indiana/Knox",
),
(
"America/Indiana/Tell_City",
"(GMT-0500) America/Indiana/Tell_City",
),
("America/Jamaica", "(GMT-0500) America/Jamaica"),
("America/Lima", "(GMT-0500) America/Lima"),
("America/Matamoros", "(GMT-0500) America/Matamoros"),
("America/Menominee", "(GMT-0500) America/Menominee"),
(
"America/North_Dakota/Beulah",
"(GMT-0500) America/North_Dakota/Beulah",
),
(
"America/North_Dakota/Center",
"(GMT-0500) America/North_Dakota/Center",
),
(
"America/North_Dakota/New_Salem",
"(GMT-0500) America/North_Dakota/New_Salem",
),
("America/Ojinaga", "(GMT-0500) America/Ojinaga"),
("America/Panama", "(GMT-0500) America/Panama"),
(
"America/Rankin_Inlet",
"(GMT-0500) America/Rankin_Inlet",
),
("America/Resolute", "(GMT-0500) America/Resolute"),
("America/Rio_Branco", "(GMT-0500) America/Rio_Branco"),
("America/Winnipeg", "(GMT-0500) America/Winnipeg"),
("Canada/Central", "(GMT-0500) Canada/Central"),
("Pacific/Easter", "(GMT-0500) Pacific/Easter"),
("US/Central", "(GMT-0500) US/Central"),
("America/Anguilla", "(GMT-0400) America/Anguilla"),
("America/Antigua", "(GMT-0400) America/Antigua"),
("America/Aruba", "(GMT-0400) America/Aruba"),
("America/Asuncion", "(GMT-0400) America/Asuncion"),
("America/Barbados", "(GMT-0400) America/Barbados"),
(
"America/Blanc-Sablon",
"(GMT-0400) America/Blanc-Sablon",
),
("America/Boa_Vista", "(GMT-0400) America/Boa_Vista"),
(
"America/Campo_Grande",
"(GMT-0400) America/Campo_Grande",
),
("America/Caracas", "(GMT-0400) America/Caracas"),
("America/Cuiaba", "(GMT-0400) America/Cuiaba"),
("America/Curacao", "(GMT-0400) America/Curacao"),
("America/Detroit", "(GMT-0400) America/Detroit"),
("America/Dominica", "(GMT-0400) America/Dominica"),
("America/Grand_Turk", "(GMT-0400) America/Grand_Turk"),
("America/Grenada", "(GMT-0400) America/Grenada"),
("America/Guadeloupe", "(GMT-0400) America/Guadeloupe"),
("America/Guyana", "(GMT-0400) America/Guyana"),
("America/Havana", "(GMT-0400) America/Havana"),
(
"America/Indiana/Indianapolis",
"(GMT-0400) America/Indiana/Indianapolis",
),
(
"America/Indiana/Marengo",
"(GMT-0400) America/Indiana/Marengo",
),
(
"America/Indiana/Petersburg",
"(GMT-0400) America/Indiana/Petersburg",
),
(
"America/Indiana/Vevay",
"(GMT-0400) America/Indiana/Vevay",
),
(
"America/Indiana/Vincennes",
"(GMT-0400) America/Indiana/Vincennes",
),
(
"America/Indiana/Winamac",
"(GMT-0400) America/Indiana/Winamac",
),
("America/Iqaluit", "(GMT-0400) America/Iqaluit"),
(
"America/Kentucky/Louisville",
"(GMT-0400) America/Kentucky/Louisville",
),
(
"America/Kentucky/Monticello",
"(GMT-0400) America/Kentucky/Monticello",
),
("America/Kralendijk", "(GMT-0400) America/Kralendijk"),
("America/La_Paz", "(GMT-0400) America/La_Paz"),
(
"America/Lower_Princes",
"(GMT-0400) America/Lower_Princes",
),
("America/Manaus", "(GMT-0400) America/Manaus"),
("America/Marigot", "(GMT-0400) America/Marigot"),
("America/Martinique", "(GMT-0400) America/Martinique"),
("America/Montserrat", "(GMT-0400) America/Montserrat"),
("America/Nassau", "(GMT-0400) America/Nassau"),
("America/New_York", "(GMT-0400) America/New_York"),
(
"America/Port-au-Prince",
"(GMT-0400) America/Port-au-Prince",
),
(
"America/Port_of_Spain",
"(GMT-0400) America/Port_of_Spain",
),
("America/Porto_Velho", "(GMT-0400) America/Porto_Velho"),
("America/Puerto_Rico", "(GMT-0400) America/Puerto_Rico"),
(
"America/Santo_Domingo",
"(GMT-0400) America/Santo_Domingo",
),
(
"America/St_Barthelemy",
"(GMT-0400) America/St_Barthelemy",
),
("America/St_Kitts", "(GMT-0400) America/St_Kitts"),
("America/St_Lucia", "(GMT-0400) America/St_Lucia"),
("America/St_Thomas", "(GMT-0400) America/St_Thomas"),
("America/St_Vincent", "(GMT-0400) America/St_Vincent"),
("America/Toronto", "(GMT-0400) America/Toronto"),
("America/Tortola", "(GMT-0400) America/Tortola"),
("Canada/Eastern", "(GMT-0400) Canada/Eastern"),
("US/Eastern", "(GMT-0400) US/Eastern"),
("America/Araguaina", "(GMT-0300) America/Araguaina"),
(
"America/Argentina/Buenos_Aires",
"(GMT-0300) America/Argentina/Buenos_Aires",
),
(
"America/Argentina/Catamarca",
"(GMT-0300) America/Argentina/Catamarca",
),
(
"America/Argentina/Cordoba",
"(GMT-0300) America/Argentina/Cordoba",
),
(
"America/Argentina/Jujuy",
"(GMT-0300) America/Argentina/Jujuy",
),
(
"America/Argentina/La_Rioja",
"(GMT-0300) America/Argentina/La_Rioja",
),
(
"America/Argentina/Mendoza",
"(GMT-0300) America/Argentina/Mendoza",
),
(
"America/Argentina/Rio_Gallegos",
"(GMT-0300) America/Argentina/Rio_Gallegos",
),
(
"America/Argentina/Salta",
"(GMT-0300) America/Argentina/Salta",
),
(
"America/Argentina/San_Juan",
"(GMT-0300) America/Argentina/San_Juan",
),
(
"America/Argentina/San_Luis",
"(GMT-0300) America/Argentina/San_Luis",
),
(
"America/Argentina/Tucuman",
"(GMT-0300) America/Argentina/Tucuman",
),
(
"America/Argentina/Ushuaia",
"(GMT-0300) America/Argentina/Ushuaia",
),
("America/Bahia", "(GMT-0300) America/Bahia"),
("America/Belem", "(GMT-0300) America/Belem"),
("America/Cayenne", "(GMT-0300) America/Cayenne"),
("America/Fortaleza", "(GMT-0300) America/Fortaleza"),
("America/Glace_Bay", "(GMT-0300) America/Glace_Bay"),
("America/Goose_Bay", "(GMT-0300) America/Goose_Bay"),
("America/Halifax", "(GMT-0300) America/Halifax"),
("America/Maceio", "(GMT-0300) America/Maceio"),
("America/Moncton", "(GMT-0300) America/Moncton"),
("America/Montevideo", "(GMT-0300) America/Montevideo"),
("America/Paramaribo", "(GMT-0300) America/Paramaribo"),
(
"America/Punta_Arenas",
"(GMT-0300) America/Punta_Arenas",
),
("America/Recife", "(GMT-0300) America/Recife"),
("America/Santarem", "(GMT-0300) America/Santarem"),
("America/Santiago", "(GMT-0300) America/Santiago"),
("America/Sao_Paulo", "(GMT-0300) America/Sao_Paulo"),
("America/Thule", "(GMT-0300) America/Thule"),
("Antarctica/Palmer", "(GMT-0300) Antarctica/Palmer"),
("Antarctica/Rothera", "(GMT-0300) Antarctica/Rothera"),
("Atlantic/Bermuda", "(GMT-0300) Atlantic/Bermuda"),
("Atlantic/Stanley", "(GMT-0300) Atlantic/Stanley"),
("Canada/Atlantic", "(GMT-0300) Canada/Atlantic"),
("America/St_Johns", "(GMT-0230) America/St_Johns"),
("Canada/Newfoundland", "(GMT-0230) Canada/Newfoundland"),
("America/Miquelon", "(GMT-0200) America/Miquelon"),
("America/Noronha", "(GMT-0200) America/Noronha"),
("America/Nuuk", "(GMT-0200) America/Nuuk"),
(
"Atlantic/South_Georgia",
"(GMT-0200) Atlantic/South_Georgia",
),
("Atlantic/Cape_Verde", "(GMT-0100) Atlantic/Cape_Verde"),
("Africa/Abidjan", "(GMT+0000) Africa/Abidjan"),
("Africa/Accra", "(GMT+0000) Africa/Accra"),
("Africa/Bamako", "(GMT+0000) Africa/Bamako"),
("Africa/Banjul", "(GMT+0000) Africa/Banjul"),
("Africa/Bissau", "(GMT+0000) Africa/Bissau"),
("Africa/Conakry", "(GMT+0000) Africa/Conakry"),
("Africa/Dakar", "(GMT+0000) Africa/Dakar"),
("Africa/Freetown", "(GMT+0000) Africa/Freetown"),
("Africa/Lome", "(GMT+0000) Africa/Lome"),
("Africa/Monrovia", "(GMT+0000) Africa/Monrovia"),
("Africa/Nouakchott", "(GMT+0000) Africa/Nouakchott"),
("Africa/Ouagadougou", "(GMT+0000) Africa/Ouagadougou"),
("Africa/Sao_Tome", "(GMT+0000) Africa/Sao_Tome"),
(
"America/Danmarkshavn",
"(GMT+0000) America/Danmarkshavn",
),
(
"America/Scoresbysund",
"(GMT+0000) America/Scoresbysund",
),
("Atlantic/Azores", "(GMT+0000) Atlantic/Azores"),
("Atlantic/Reykjavik", "(GMT+0000) Atlantic/Reykjavik"),
("Atlantic/St_Helena", "(GMT+0000) Atlantic/St_Helena"),
("GMT", "(GMT+0000) GMT"),
("UTC", "(GMT+0000) UTC"),
("Africa/Algiers", "(GMT+0100) Africa/Algiers"),
("Africa/Bangui", "(GMT+0100) Africa/Bangui"),
("Africa/Brazzaville", "(GMT+0100) Africa/Brazzaville"),
("Africa/Casablanca", "(GMT+0100) Africa/Casablanca"),
("Africa/Douala", "(GMT+0100) Africa/Douala"),
("Africa/El_Aaiun", "(GMT+0100) Africa/El_Aaiun"),
("Africa/Kinshasa", "(GMT+0100) Africa/Kinshasa"),
("Africa/Lagos", "(GMT+0100) Africa/Lagos"),
("Africa/Libreville", "(GMT+0100) Africa/Libreville"),
("Africa/Luanda", "(GMT+0100) Africa/Luanda"),
("Africa/Malabo", "(GMT+0100) Africa/Malabo"),
("Africa/Ndjamena", "(GMT+0100) Africa/Ndjamena"),
("Africa/Niamey", "(GMT+0100) Africa/Niamey"),
("Africa/Porto-Novo", "(GMT+0100) Africa/Porto-Novo"),
("Africa/Tunis", "(GMT+0100) Africa/Tunis"),
("Atlantic/Canary", "(GMT+0100) Atlantic/Canary"),
("Atlantic/Faroe", "(GMT+0100) Atlantic/Faroe"),
("Atlantic/Madeira", "(GMT+0100) Atlantic/Madeira"),
("Europe/Dublin", "(GMT+0100) Europe/Dublin"),
("Europe/Guernsey", "(GMT+0100) Europe/Guernsey"),
("Europe/Isle_of_Man", "(GMT+0100) Europe/Isle_of_Man"),
("Europe/Jersey", "(GMT+0100) Europe/Jersey"),
("Europe/Lisbon", "(GMT+0100) Europe/Lisbon"),
("Europe/London", "(GMT+0100) Europe/London"),
("Africa/Blantyre", "(GMT+0200) Africa/Blantyre"),
("Africa/Bujumbura", "(GMT+0200) Africa/Bujumbura"),
("Africa/Cairo", "(GMT+0200) Africa/Cairo"),
("Africa/Ceuta", "(GMT+0200) Africa/Ceuta"),
("Africa/Gaborone", "(GMT+0200) Africa/Gaborone"),
("Africa/Harare", "(GMT+0200) Africa/Harare"),
("Africa/Johannesburg", "(GMT+0200) Africa/Johannesburg"),
("Africa/Juba", "(GMT+0200) Africa/Juba"),
("Africa/Khartoum", "(GMT+0200) Africa/Khartoum"),
("Africa/Kigali", "(GMT+0200) Africa/Kigali"),
("Africa/Lubumbashi", "(GMT+0200) Africa/Lubumbashi"),
("Africa/Lusaka", "(GMT+0200) Africa/Lusaka"),
("Africa/Maputo", "(GMT+0200) Africa/Maputo"),
("Africa/Maseru", "(GMT+0200) Africa/Maseru"),
("Africa/Mbabane", "(GMT+0200) Africa/Mbabane"),
("Africa/Tripoli", "(GMT+0200) Africa/Tripoli"),
("Africa/Windhoek", "(GMT+0200) Africa/Windhoek"),
("Antarctica/Troll", "(GMT+0200) Antarctica/Troll"),
("Arctic/Longyearbyen", "(GMT+0200) Arctic/Longyearbyen"),
("Europe/Amsterdam", "(GMT+0200) Europe/Amsterdam"),
("Europe/Andorra", "(GMT+0200) Europe/Andorra"),
("Europe/Belgrade", "(GMT+0200) Europe/Belgrade"),
("Europe/Berlin", "(GMT+0200) Europe/Berlin"),
("Europe/Bratislava", "(GMT+0200) Europe/Bratislava"),
("Europe/Brussels", "(GMT+0200) Europe/Brussels"),
("Europe/Budapest", "(GMT+0200) Europe/Budapest"),
("Europe/Busingen", "(GMT+0200) Europe/Busingen"),
("Europe/Copenhagen", "(GMT+0200) Europe/Copenhagen"),
("Europe/Gibraltar", "(GMT+0200) Europe/Gibraltar"),
("Europe/Kaliningrad", "(GMT+0200) Europe/Kaliningrad"),
("Europe/Ljubljana", "(GMT+0200) Europe/Ljubljana"),
("Europe/Luxembourg", "(GMT+0200) Europe/Luxembourg"),
("Europe/Madrid", "(GMT+0200) Europe/Madrid"),
("Europe/Malta", "(GMT+0200) Europe/Malta"),
("Europe/Monaco", "(GMT+0200) Europe/Monaco"),
("Europe/Oslo", "(GMT+0200) Europe/Oslo"),
("Europe/Paris", "(GMT+0200) Europe/Paris"),
("Europe/Podgorica", "(GMT+0200) Europe/Podgorica"),
("Europe/Prague", "(GMT+0200) Europe/Prague"),
("Europe/Rome", "(GMT+0200) Europe/Rome"),
("Europe/San_Marino", "(GMT+0200) Europe/San_Marino"),
("Europe/Sarajevo", "(GMT+0200) Europe/Sarajevo"),
("Europe/Skopje", "(GMT+0200) Europe/Skopje"),
("Europe/Stockholm", "(GMT+0200) Europe/Stockholm"),
("Europe/Tirane", "(GMT+0200) Europe/Tirane"),
("Europe/Vaduz", "(GMT+0200) Europe/Vaduz"),
("Europe/Vatican", "(GMT+0200) Europe/Vatican"),
("Europe/Vienna", "(GMT+0200) Europe/Vienna"),
("Europe/Warsaw", "(GMT+0200) Europe/Warsaw"),
("Europe/Zagreb", "(GMT+0200) Europe/Zagreb"),
("Europe/Zurich", "(GMT+0200) Europe/Zurich"),
("Africa/Addis_Ababa", "(GMT+0300) Africa/Addis_Ababa"),
("Africa/Asmara", "(GMT+0300) Africa/Asmara"),
(
"Africa/Dar_es_Salaam",
"(GMT+0300) Africa/Dar_es_Salaam",
),
("Africa/Djibouti", "(GMT+0300) Africa/Djibouti"),
("Africa/Kampala", "(GMT+0300) Africa/Kampala"),
("Africa/Mogadishu", "(GMT+0300) Africa/Mogadishu"),
("Africa/Nairobi", "(GMT+0300) Africa/Nairobi"),
("Antarctica/Syowa", "(GMT+0300) Antarctica/Syowa"),
("Asia/Aden", "(GMT+0300) Asia/Aden"),
("Asia/Amman", "(GMT+0300) Asia/Amman"),
("Asia/Baghdad", "(GMT+0300) Asia/Baghdad"),
("Asia/Bahrain", "(GMT+0300) Asia/Bahrain"),
("Asia/Beirut", "(GMT+0300) Asia/Beirut"),
("Asia/Damascus", "(GMT+0300) Asia/Damascus"),
("Asia/Famagusta", "(GMT+0300) Asia/Famagusta"),
("Asia/Gaza", "(GMT+0300) Asia/Gaza"),
("Asia/Hebron", "(GMT+0300) Asia/Hebron"),
("Asia/Jerusalem", "(GMT+0300) Asia/Jerusalem"),
("Asia/Kuwait", "(GMT+0300) Asia/Kuwait"),
("Asia/Nicosia", "(GMT+0300) Asia/Nicosia"),
("Asia/Qatar", "(GMT+0300) Asia/Qatar"),
("Asia/Riyadh", "(GMT+0300) Asia/Riyadh"),
("Europe/Athens", "(GMT+0300) Europe/Athens"),
("Europe/Bucharest", "(GMT+0300) Europe/Bucharest"),
("Europe/Chisinau", "(GMT+0300) Europe/Chisinau"),
("Europe/Helsinki", "(GMT+0300) Europe/Helsinki"),
("Europe/Istanbul", "(GMT+0300) Europe/Istanbul"),
("Europe/Kirov", "(GMT+0300) Europe/Kirov"),
("Europe/Kyiv", "(GMT+0300) Europe/Kyiv"),
("Europe/Mariehamn", "(GMT+0300) Europe/Mariehamn"),
("Europe/Minsk", "(GMT+0300) Europe/Minsk"),
("Europe/Moscow", "(GMT+0300) Europe/Moscow"),
("Europe/Riga", "(GMT+0300) Europe/Riga"),
("Europe/Simferopol", "(GMT+0300) Europe/Simferopol"),
("Europe/Sofia", "(GMT+0300) Europe/Sofia"),
("Europe/Tallinn", "(GMT+0300) Europe/Tallinn"),
("Europe/Vilnius", "(GMT+0300) Europe/Vilnius"),
("Europe/Volgograd", "(GMT+0300) Europe/Volgograd"),
("Indian/Antananarivo", "(GMT+0300) Indian/Antananarivo"),
("Indian/Comoro", "(GMT+0300) Indian/Comoro"),
("Indian/Mayotte", "(GMT+0300) Indian/Mayotte"),
("Asia/Tehran", "(GMT+0330) Asia/Tehran"),
("Asia/Baku", "(GMT+0400) Asia/Baku"),
("Asia/Dubai", "(GMT+0400) Asia/Dubai"),
("Asia/Muscat", "(GMT+0400) Asia/Muscat"),
("Asia/Tbilisi", "(GMT+0400) Asia/Tbilisi"),
("Asia/Yerevan", "(GMT+0400) Asia/Yerevan"),
("Europe/Astrakhan", "(GMT+0400) Europe/Astrakhan"),
("Europe/Samara", "(GMT+0400) Europe/Samara"),
("Europe/Saratov", "(GMT+0400) Europe/Saratov"),
("Europe/Ulyanovsk", "(GMT+0400) Europe/Ulyanovsk"),
("Indian/Mahe", "(GMT+0400) Indian/Mahe"),
("Indian/Mauritius", "(GMT+0400) Indian/Mauritius"),
("Indian/Reunion", "(GMT+0400) Indian/Reunion"),
("Asia/Kabul", "(GMT+0430) Asia/Kabul"),
("Antarctica/Mawson", "(GMT+0500) Antarctica/Mawson"),
("Asia/Aqtau", "(GMT+0500) Asia/Aqtau"),
("Asia/Aqtobe", "(GMT+0500) Asia/Aqtobe"),
("Asia/Ashgabat", "(GMT+0500) Asia/Ashgabat"),
("Asia/Atyrau", "(GMT+0500) Asia/Atyrau"),
("Asia/Dushanbe", "(GMT+0500) Asia/Dushanbe"),
("Asia/Karachi", "(GMT+0500) Asia/Karachi"),
("Asia/Oral", "(GMT+0500) Asia/Oral"),
("Asia/Qyzylorda", "(GMT+0500) Asia/Qyzylorda"),
("Asia/Samarkand", "(GMT+0500) Asia/Samarkand"),
("Asia/Tashkent", "(GMT+0500) Asia/Tashkent"),
("Asia/Yekaterinburg", "(GMT+0500) Asia/Yekaterinburg"),
("Indian/Kerguelen", "(GMT+0500) Indian/Kerguelen"),
("Indian/Maldives", "(GMT+0500) Indian/Maldives"),
("Asia/Colombo", "(GMT+0530) Asia/Colombo"),
("Asia/Kolkata", "(GMT+0530) Asia/Kolkata"),
("Asia/Kathmandu", "(GMT+0545) Asia/Kathmandu"),
("Antarctica/Vostok", "(GMT+0600) Antarctica/Vostok"),
("Asia/Almaty", "(GMT+0600) Asia/Almaty"),
("Asia/Bishkek", "(GMT+0600) Asia/Bishkek"),
("Asia/Dhaka", "(GMT+0600) Asia/Dhaka"),
("Asia/Omsk", "(GMT+0600) Asia/Omsk"),
("Asia/Qostanay", "(GMT+0600) Asia/Qostanay"),
("Asia/Thimphu", "(GMT+0600) Asia/Thimphu"),
("Asia/Urumqi", "(GMT+0600) Asia/Urumqi"),
("Indian/Chagos", "(GMT+0600) Indian/Chagos"),
("Asia/Yangon", "(GMT+0630) Asia/Yangon"),
("Indian/Cocos", "(GMT+0630) Indian/Cocos"),
("Antarctica/Davis", "(GMT+0700) Antarctica/Davis"),
("Asia/Bangkok", "(GMT+0700) Asia/Bangkok"),
("Asia/Barnaul", "(GMT+0700) Asia/Barnaul"),
("Asia/Ho_Chi_Minh", "(GMT+0700) Asia/Ho_Chi_Minh"),
("Asia/Hovd", "(GMT+0700) Asia/Hovd"),
("Asia/Jakarta", "(GMT+0700) Asia/Jakarta"),
("Asia/Krasnoyarsk", "(GMT+0700) Asia/Krasnoyarsk"),
("Asia/Novokuznetsk", "(GMT+0700) Asia/Novokuznetsk"),
("Asia/Novosibirsk", "(GMT+0700) Asia/Novosibirsk"),
("Asia/Phnom_Penh", "(GMT+0700) Asia/Phnom_Penh"),
("Asia/Pontianak", "(GMT+0700) Asia/Pontianak"),
("Asia/Tomsk", "(GMT+0700) Asia/Tomsk"),
("Asia/Vientiane", "(GMT+0700) Asia/Vientiane"),
("Indian/Christmas", "(GMT+0700) Indian/Christmas"),
("Asia/Brunei", "(GMT+0800) Asia/Brunei"),
("Asia/Choibalsan", "(GMT+0800) Asia/Choibalsan"),
("Asia/Hong_Kong", "(GMT+0800) Asia/Hong_Kong"),
("Asia/Irkutsk", "(GMT+0800) Asia/Irkutsk"),
("Asia/Kuala_Lumpur", "(GMT+0800) Asia/Kuala_Lumpur"),
("Asia/Kuching", "(GMT+0800) Asia/Kuching"),
("Asia/Macau", "(GMT+0800) Asia/Macau"),
("Asia/Makassar", "(GMT+0800) Asia/Makassar"),
("Asia/Manila", "(GMT+0800) Asia/Manila"),
("Asia/Shanghai", "(GMT+0800) Asia/Shanghai"),
("Asia/Singapore", "(GMT+0800) Asia/Singapore"),
("Asia/Taipei", "(GMT+0800) Asia/Taipei"),
("Asia/Ulaanbaatar", "(GMT+0800) Asia/Ulaanbaatar"),
("Australia/Perth", "(GMT+0800) Australia/Perth"),
("Australia/Eucla", "(GMT+0845) Australia/Eucla"),
("Asia/Chita", "(GMT+0900) Asia/Chita"),
("Asia/Dili", "(GMT+0900) Asia/Dili"),
("Asia/Jayapura", "(GMT+0900) Asia/Jayapura"),
("Asia/Khandyga", "(GMT+0900) Asia/Khandyga"),
("Asia/Pyongyang", "(GMT+0900) Asia/Pyongyang"),
("Asia/Seoul", "(GMT+0900) Asia/Seoul"),
("Asia/Tokyo", "(GMT+0900) Asia/Tokyo"),
("Asia/Yakutsk", "(GMT+0900) Asia/Yakutsk"),
("Pacific/Palau", "(GMT+0900) Pacific/Palau"),
("Australia/Adelaide", "(GMT+0930) Australia/Adelaide"),
(
"Australia/Broken_Hill",
"(GMT+0930) Australia/Broken_Hill",
),
("Australia/Darwin", "(GMT+0930) Australia/Darwin"),
(
"Antarctica/DumontDUrville",
"(GMT+1000) Antarctica/DumontDUrville",
),
(
"Antarctica/Macquarie",
"(GMT+1000) Antarctica/Macquarie",
),
("Asia/Ust-Nera", "(GMT+1000) Asia/Ust-Nera"),
("Asia/Vladivostok", "(GMT+1000) Asia/Vladivostok"),
("Australia/Brisbane", "(GMT+1000) Australia/Brisbane"),
("Australia/Hobart", "(GMT+1000) Australia/Hobart"),
("Australia/Lindeman", "(GMT+1000) Australia/Lindeman"),
("Australia/Melbourne", "(GMT+1000) Australia/Melbourne"),
("Australia/Sydney", "(GMT+1000) Australia/Sydney"),
("Pacific/Chuuk", "(GMT+1000) Pacific/Chuuk"),
("Pacific/Guam", "(GMT+1000) Pacific/Guam"),
(
"Pacific/Port_Moresby",
"(GMT+1000) Pacific/Port_Moresby",
),
("Pacific/Saipan", "(GMT+1000) Pacific/Saipan"),
("Australia/Lord_Howe", "(GMT+1030) Australia/Lord_Howe"),
("Antarctica/Casey", "(GMT+1100) Antarctica/Casey"),
("Asia/Magadan", "(GMT+1100) Asia/Magadan"),
("Asia/Sakhalin", "(GMT+1100) Asia/Sakhalin"),
("Asia/Srednekolymsk", "(GMT+1100) Asia/Srednekolymsk"),
(
"Pacific/Bougainville",
"(GMT+1100) Pacific/Bougainville",
),
("Pacific/Efate", "(GMT+1100) Pacific/Efate"),
("Pacific/Guadalcanal", "(GMT+1100) Pacific/Guadalcanal"),
("Pacific/Kosrae", "(GMT+1100) Pacific/Kosrae"),
("Pacific/Norfolk", "(GMT+1100) Pacific/Norfolk"),
("Pacific/Noumea", "(GMT+1100) Pacific/Noumea"),
("Pacific/Pohnpei", "(GMT+1100) Pacific/Pohnpei"),
("Antarctica/McMurdo", "(GMT+1200) Antarctica/McMurdo"),
("Asia/Anadyr", "(GMT+1200) Asia/Anadyr"),
("Asia/Kamchatka", "(GMT+1200) Asia/Kamchatka"),
("Pacific/Auckland", "(GMT+1200) Pacific/Auckland"),
("Pacific/Fiji", "(GMT+1200) Pacific/Fiji"),
("Pacific/Funafuti", "(GMT+1200) Pacific/Funafuti"),
("Pacific/Kwajalein", "(GMT+1200) Pacific/Kwajalein"),
("Pacific/Majuro", "(GMT+1200) Pacific/Majuro"),
("Pacific/Nauru", "(GMT+1200) Pacific/Nauru"),
("Pacific/Tarawa", "(GMT+1200) Pacific/Tarawa"),
("Pacific/Wake", "(GMT+1200) Pacific/Wake"),
("Pacific/Wallis", "(GMT+1200) Pacific/Wallis"),
("Pacific/Chatham", "(GMT+1245) Pacific/Chatham"),
("Pacific/Apia", "(GMT+1300) Pacific/Apia"),
("Pacific/Fakaofo", "(GMT+1300) Pacific/Fakaofo"),
("Pacific/Kanton", "(GMT+1300) Pacific/Kanton"),
("Pacific/Tongatapu", "(GMT+1300) Pacific/Tongatapu"),
("Pacific/Kiritimati", "(GMT+1400) Pacific/Kiritimati"),
],
default="UTC",
max_length=255,
),
),
]

View File

@ -23,6 +23,7 @@ class ScrobbleInline(admin.TabularInline):
"sport_event", "sport_event",
"board_game", "board_game",
"geo_location", "geo_location",
"trail",
"web_page", "web_page",
"life_event", "life_event",
"user", "user",
@ -95,7 +96,7 @@ class ChartRecordAdmin(admin.ModelAdmin):
@admin.register(Scrobble) @admin.register(Scrobble)
class ScrobbleAdmin(admin.ModelAdmin): class ScrobbleAdmin(admin.ModelAdmin):
date_hierarchy = "timestamp" # date_hierarchy = "timestamp"
list_display = ( list_display = (
"timestamp", "timestamp",
"media_name", "media_name",

View File

@ -180,3 +180,12 @@ class BrickSetLogData(LongPlayLogData, WithOthersLogData):
long_play_complete: bool = False long_play_complete: bool = False
with_user_ids: Optional[list[int]] = None with_user_ids: Optional[list[int]] = None
with_names_str: Optional[list[str]] = None with_names_str: Optional[list[str]] = None
@dataclass
class TrailLogData(WithOthersLogData):
with_user_ids: Optional[list[int]] = None
with_names_str: Optional[list[str]] = None
details: Optional[str] = None
effort: Optional[str] = None
difficulty: Optional[str] = None

View File

@ -0,0 +1,48 @@
# Generated by Django 4.2.16 on 2024-09-09 16:11
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("trails", "0001_initial"),
("scrobbles", "0061_alter_scrobble_media_type"),
]
operations = [
migrations.AddField(
model_name="scrobble",
name="trail",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to="trails.trail",
),
),
migrations.AlterField(
model_name="scrobble",
name="media_type",
field=models.CharField(
choices=[
("Video", "Video"),
("Track", "Track"),
("PodcastEpisode", "Podcast episode"),
("SportEvent", "Sport event"),
("Book", "Book"),
("VideoGame", "Video game"),
("BoardGame", "Board game"),
("GeoLocation", "GeoLocation"),
("Trail", "Trail"),
("WebPage", "Web Page"),
("LifeEvent", "Life event"),
("Mood", "Mood"),
("BrickSet", "Brick set"),
],
default="Video",
max_length=14,
),
),
]

View File

@ -41,6 +41,7 @@ from sports.models import SportEvent
from videogames import retroarch from videogames import retroarch
from videogames.models import VideoGame from videogames.models import VideoGame
from videos.models import Series, Video from videos.models import Series, Video
from trails.models import Trail
from webpages.models import WebPage from webpages.models import WebPage
from vrobbler.apps.scrobbles.constants import MEDIA_END_PADDING_SECONDS from vrobbler.apps.scrobbles.constants import MEDIA_END_PADDING_SECONDS
@ -483,6 +484,7 @@ class Scrobble(TimeStampedModel):
VIDEO_GAME = "VideoGame", "Video game" VIDEO_GAME = "VideoGame", "Video game"
BOARD_GAME = "BoardGame", "Board game" BOARD_GAME = "BoardGame", "Board game"
GEO_LOCATION = "GeoLocation", "GeoLocation" GEO_LOCATION = "GeoLocation", "GeoLocation"
TRAIL = "Trail", "Trail"
WEBPAGE = "WebPage", "Web Page" WEBPAGE = "WebPage", "Web Page"
LIFE_EVENT = "LifeEvent", "Life event" LIFE_EVENT = "LifeEvent", "Life event"
MOOD = "Mood", "Mood" MOOD = "Mood", "Mood"
@ -507,12 +509,15 @@ class Scrobble(TimeStampedModel):
geo_location = models.ForeignKey( geo_location = models.ForeignKey(
GeoLocation, on_delete=models.DO_NOTHING, **BNULL GeoLocation, on_delete=models.DO_NOTHING, **BNULL
) )
trail = models.ForeignKey(Trail, on_delete=models.DO_NOTHING, **BNULL)
web_page = models.ForeignKey(WebPage, on_delete=models.DO_NOTHING, **BNULL) web_page = models.ForeignKey(WebPage, on_delete=models.DO_NOTHING, **BNULL)
life_event = models.ForeignKey( life_event = models.ForeignKey(
LifeEvent, on_delete=models.DO_NOTHING, **BNULL LifeEvent, on_delete=models.DO_NOTHING, **BNULL
) )
mood = models.ForeignKey(Mood, on_delete=models.DO_NOTHING, **BNULL) mood = models.ForeignKey(Mood, on_delete=models.DO_NOTHING, **BNULL)
brickset = models.ForeignKey(BrickSet, on_delete=models.DO_NOTHING, **BNULL) brickset = models.ForeignKey(
BrickSet, on_delete=models.DO_NOTHING, **BNULL
)
media_type = models.CharField( media_type = models.CharField(
max_length=14, choices=MediaType.choices, default=MediaType.VIDEO max_length=14, choices=MediaType.choices, default=MediaType.VIDEO
) )
@ -894,15 +899,15 @@ class Scrobble(TimeStampedModel):
def calc_reading_duration(self) -> int: def calc_reading_duration(self) -> int:
duration = 0 duration = 0
if self.book_page_data: if self.logdata.page_data:
for k, v in self.book_page_data.items(): for k, v in self.logdata.page_data.items():
duration += v.get("duration") duration += v.get("duration")
return duration return duration
def calc_pages_read(self) -> int: def calc_pages_read(self) -> int:
pages_read = 0 pages_read = 0
if self.book_page_data: if self.logdata.page_data:
pages = [int(k) for k in self.book_page_data.keys()] pages = [int(k) for k in self.logdata.page_data.keys()]
pages.sort() pages.sort()
if len(pages) == 1: if len(pages) == 1:
pages_read = 1 pages_read = 1
@ -915,8 +920,8 @@ class Scrobble(TimeStampedModel):
@property @property
def last_page_read(self) -> int: def last_page_read(self) -> int:
last_page = 0 last_page = 0
if self.book_page_data: if self.logdata.page_data:
pages = [int(k) for k in self.book_page_data.keys()] pages = [int(k) for k in self.logdata.page_data.keys()]
pages.sort() pages.sort()
last_page = pages[-1] last_page = pages[-1]
return last_page return last_page

View File

View File

@ -0,0 +1,29 @@
from django.contrib import admin
from trails.models import Trail
from scrobbles.admin import ScrobbleInline
class TrailInline(admin.TabularInline):
model = Trail
extra = 0
@admin.register(Trail)
class TrailAdmin(admin.ModelAdmin):
date_hierarchy = "created"
list_display = (
"uuid",
"title",
"trailhead_location",
)
raw_id_fields = (
"trailhead_location",
"trail_terminus_location",
)
ordering = ("-created",)
search_fields = ("title",)
inlines = [
ScrobbleInline,
]

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class TrailsConfig(AppConfig):
name = "trails"

View File

@ -0,0 +1,109 @@
# Generated by Django 4.2.16 on 2024-09-09 16:11
from django.db import migrations, models
import django.db.models.deletion
import django_extensions.db.fields
import taggit.managers
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
("scrobbles", "0061_alter_scrobble_media_type"),
("locations", "0006_delete_rawgeolocation"),
]
operations = [
migrations.CreateModel(
name="Trail",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
),
),
(
"uuid",
models.UUIDField(
blank=True,
default=uuid.uuid4,
editable=False,
null=True,
),
),
(
"title",
models.CharField(blank=True, max_length=255, null=True),
),
(
"run_time_seconds",
models.IntegerField(blank=True, null=True),
),
(
"run_time_ticks",
models.PositiveBigIntegerField(blank=True, null=True),
),
("description", models.TextField(blank=True, null=True)),
(
"strava_id",
models.CharField(blank=True, max_length=255, null=True),
),
(
"trailforks_id",
models.CharField(blank=True, max_length=255, null=True),
),
(
"genre",
taggit.managers.TaggableManager(
blank=True,
help_text="A comma-separated list of tags.",
through="scrobbles.ObjectWithGenres",
to="scrobbles.Genre",
verbose_name="Tags",
),
),
(
"trail_terminus_location",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="trail_termini",
to="locations.geolocation",
),
),
(
"trailhead_location",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="trailheads",
to="locations.geolocation",
),
),
],
options={
"abstract": False,
},
),
]

View File

@ -0,0 +1,49 @@
from enum import Enum
from django.apps import apps
from django.db import models
from django.urls import reverse
from scrobbles.dataclasses import TrailLogData
from scrobbles.mixins import ScrobblableMixin
from locations.models import GeoLocation
BNULL = {"blank": True, "null": True}
class TrailType(Enum):
WOODS = "Woods"
ROAD = "Road"
class Trail(ScrobblableMixin):
description = models.TextField(**BNULL)
trailhead_location = models.ForeignKey(
GeoLocation,
related_name="trailheads",
on_delete=models.DO_NOTHING,
**BNULL,
)
trail_terminus_location = models.ForeignKey(
GeoLocation,
related_name="trail_termini",
on_delete=models.DO_NOTHING,
**BNULL,
)
strava_id = models.CharField(max_length=255, **BNULL)
trailforks_id = models.CharField(max_length=255, **BNULL)
def get_absolute_url(self):
return reverse("trails:trail_detail", kwargs={"slug": self.uuid})
@property
def logdata_cls(self):
return TrailLogData
@classmethod
def find_or_create(cls, title: str) -> "Trail":
return cls.objects.filter(title=title).first()
def scrobbles(self, user_id):
Scrobble = apps.get_model("scrobbles", "Scrobble")
return Scrobble.objects.filter(
user_id=user_id, life_event=self
).order_by("-timestamp")

View File

@ -0,0 +1,14 @@
from django.urls import path
from trails import views
app_name = "trials"
urlpatterns = [
path("trails/", views.TrailListView.as_view(), name="trail-list"),
path(
"trails/<slug:slug>/",
views.TrailDetailView.as_view(),
name="trail-detail",
),
]

View File

@ -0,0 +1,11 @@
from trails.models import Trail
from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView
class TrailListView(ScrobbleableListView):
model = Trail
class TrailDetailView(ScrobbleableDetailView):
model = Trail

View File

@ -5,9 +5,9 @@ app_name = "webpages"
urlpatterns = [ urlpatterns = [
path("webpage/", views.WebPageListView.as_view(), name="webpage-list"), path("webpages/", views.WebPageListView.as_view(), name="webpage-list"),
path( path(
"webpage/<slug:slug>/", "webpages/<slug:slug>/",
views.WebPageDetailView.as_view(), views.WebPageDetailView.as_view(),
name="webpage-detail", name="webpage-detail",
), ),

View File

@ -1,5 +1,4 @@
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Count
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse

View File

@ -111,6 +111,7 @@ INSTALLED_APPS = [
"videogames", "videogames",
"locations", "locations",
"webpages", "webpages",
"trails",
"lifeevents", "lifeevents",
"moods", "moods",
"mathfilters", "mathfilters",

View File

@ -125,6 +125,7 @@ INSTALLED_APPS = [
"videogames", "videogames",
"locations", "locations",
"webpages", "webpages",
"trails",
"lifeevents", "lifeevents",
"moods", "moods",
"mathfilters", "mathfilters",

View File

@ -1,82 +1,43 @@
{% extends "base_list.html" %} {% extends "base_list.html" %}
{% load urlreplace %}
{% block title %}Albums{% endblock %} {% block title %}Board Games{% endblock %}
{% block head_extra %}
<style>
dl { width: 210px; float:left; margin-right: 10px; }
dt a { color:white; text-decoration: none; font-size:smaller; }
img { height:200px; width: 200px; object-fit: cover; }
dd .right { float:right; }
</style>
{% endblock %}
{% block lists %} {% block lists %}
<div class="row"> <div class="row">
<p class="view">
<span class="view-links">
{% if view == 'grid' %}
View as <a href="?{% urlreplace view='list' %}">List</a>
{% else %}
View as <a href="?{% urlreplace view='grid' %}">Grid</a>
{% endif %}
</span>
</p>
<p class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="?{% urlreplace page=page_obj.previous_page_number %}">prev</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?{% urlreplace page=page_obj.next_page_number %}">next</a>
{% endif %}
</span>
</p>
<hr />
{% if view == 'grid' %}
<div>
{% for game in object_list %}
{% if game.cover %}
<dl style="width: 130px; float: left; margin-right:10px;">
<dd><img src="{{game.cover.url}}" width=120 height=120 /></dd>
</dl>
{% endif %}
{% endfor %}
</div>
{% else %}
<div class="col-md"> <div class="col-md">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
<tr> <tr>
<th scope="col">Title</th>
<th scope="col">Scrobbles</th> <th scope="col">Scrobbles</th>
<th scope="col">Name</th> <th scope="col">Start</th>
<th scope="col">Publisher</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for game in object_list %} {% for obj in object_list %}
<tr> <tr>
<td>{{game.scrobbles.count}}</td> <td><a href="{{obj.get_absolute_url}}">{{obj}}</a></td>
<td><a href="{{game.get_absolute_url}}">{{game}}</a></td> {% if request.user.is_authenticated %}
<td><a href="{{game.publisher.get_absolute_url}}">{{game.publisher}}</a></td> <td>{{obj.scrobble_count}}</td>
<td><a type="button" class="btn btn-sm btn-primary" href="{{obj.get_start_url}}">Scrobble</a></td>
{% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
{% endif %}
<div class="pagination" style="margin-bottom:50px;">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="?{% urlreplace page=page_obj.previous_page_number %}">prev</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?{% urlreplace page=page_obj.next_page_number %}">next</a>
{% endif %}
</span>
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -13,6 +13,7 @@ from vrobbler.apps.videogames import urls as videogame_urls
from vrobbler.apps.boardgames import urls as boardgame_urls from vrobbler.apps.boardgames import urls as boardgame_urls
from vrobbler.apps.locations import urls as locations_urls from vrobbler.apps.locations import urls as locations_urls
from vrobbler.apps.lifeevents import urls as lifeevents_urls from vrobbler.apps.lifeevents import urls as lifeevents_urls
from vrobbler.apps.trails import urls as trails_urls
from vrobbler.apps.webpages import urls as webpages_urls from vrobbler.apps.webpages import urls as webpages_urls
from vrobbler.apps.moods import urls as moods_urls from vrobbler.apps.moods import urls as moods_urls
from vrobbler.apps.music.api.views import ( from vrobbler.apps.music.api.views import (
@ -73,6 +74,7 @@ urlpatterns = [
path("", include(bricksets_urls, namespace="bricksets")), path("", include(bricksets_urls, namespace="bricksets")),
path("", include(sports_urls, namespace="sports")), path("", include(sports_urls, namespace="sports")),
path("", include(locations_urls, namespace="locations")), path("", include(locations_urls, namespace="locations")),
path("", include(trails_urls, namespace="trails")),
path("", include(webpages_urls, namespace="webpages")), path("", include(webpages_urls, namespace="webpages")),
path("", include(podcast_urls, namespace="podcasts")), path("", include(podcast_urls, namespace="podcasts")),
path("", include(lifeevents_urls, namespace="life-events")), path("", include(lifeevents_urls, namespace="life-events")),