[beers] Allow scrobbling from URLS

This commit is contained in:
2024-10-23 12:48:55 -04:00
parent f555a49746
commit 7671644e87
4 changed files with 178 additions and 6 deletions

View File

@ -8,6 +8,10 @@ from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit from imagekit.processors import ResizeToFit
from scrobbles.dataclasses import BeerLogData from scrobbles.dataclasses import BeerLogData
from scrobbles.mixins import ScrobblableMixin from scrobbles.mixins import ScrobblableMixin
from vrobbler.apps.beers.untappd import (
get_beer_from_untappd_id,
get_rating_from_soup,
)
BNULL = {"blank": True, "null": True} BNULL = {"blank": True, "null": True}
@ -17,6 +21,9 @@ class BeerStyle(TimeStampedModel):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
description = models.TextField(**BNULL) description = models.TextField(**BNULL)
def __str__(self):
return self.name
class BeerProducer(TimeStampedModel): class BeerProducer(TimeStampedModel):
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL) uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
@ -29,6 +36,9 @@ class BeerProducer(TimeStampedModel):
def find_or_create(cls, title: str) -> "BeerProducer": def find_or_create(cls, title: str) -> "BeerProducer":
return cls.objects.filter(title=title).first() return cls.objects.filter(title=title).first()
def __str__(self):
return self.name
class Beer(ScrobblableMixin): class Beer(ScrobblableMixin):
description = models.TextField(**BNULL) description = models.TextField(**BNULL)
@ -60,7 +70,8 @@ class Beer(ScrobblableMixin):
def get_absolute_url(self) -> str: def get_absolute_url(self) -> str:
return reverse("beers:beer_detail", kwargs={"slug": self.uuid}) return reverse("beers:beer_detail", kwargs={"slug": self.uuid})
def beeradvocate_link(self): def beeradvocate_link(self) -> str:
link = ""
if self.producer and self.beeradvocate_id: if self.producer and self.beeradvocate_id:
if self.beeradvocate_id: if self.beeradvocate_id:
link = f"https://www.beeradvocate.com/beer/profile/{self.producer.beeradvocate_id}/{self.beeradvocate_id}/" link = f"https://www.beeradvocate.com/beer/profile/{self.producer.beeradvocate_id}/{self.beeradvocate_id}/"
@ -74,8 +85,8 @@ class Beer(ScrobblableMixin):
def primary_image_url(self) -> str: def primary_image_url(self) -> str:
url = "" url = ""
if self.beeradvocate_image: if self.untappd_image:
url = self.beeradvocate_image.url url = self.untappd_image.url
return url return url
@property @property
@ -84,7 +95,32 @@ class Beer(ScrobblableMixin):
@classmethod @classmethod
def find_or_create(cls, untappd_id: str) -> "Beer": def find_or_create(cls, untappd_id: str) -> "Beer":
return cls.objects.filter(untappd_id=untappd_id).first() beer = cls.objects.filter(untappd_id=untappd_id).first()
if not beer:
beer_dict = get_beer_from_untappd_id(untappd_id)
producer_dict = {}
style_ids = []
for key in list(beer_dict.keys()):
if "producer__" in key:
pkey = key.replace("producer__", "")
producer_dict[pkey] = beer_dict.pop(key)
if "styles" in key:
for style in beer_dict.pop("styles"):
style_inst, created = BeerStyle.objects.get_or_create(
name=style
)
style_ids.append(style_inst.id)
producer, _created = BeerProducer.objects.get_or_create(
**producer_dict
)
beer_dict["producer_id"] = producer.id
beer = Beer.objects.create(**beer_dict)
for style_id in style_ids:
beer.styles.add(style_id)
return beer
def scrobbles(self, user_id): def scrobbles(self, user_id):
Scrobble = apps.get_model("scrobbles", "Scrobble") Scrobble = apps.get_model("scrobbles", "Scrobble")

View File

@ -0,0 +1,136 @@
import json
import logging
import urllib
from typing import Optional
import requests
from bs4 import BeautifulSoup
logger = logging.getLogger(__name__)
UNTAPPD_URL = "https://untappd.com/beer/{id}"
def get_first(key: str, result: dict) -> str:
obj = ""
if obj_list := result.get(key):
obj = obj_list[0]
return obj
def get_title_from_soup(soup) -> str:
title = ""
try:
title = soup.find("h1").get_text()
except AttributeError:
pass
except ValueError:
pass
return title
def get_description_from_soup(soup) -> str:
desc = ""
try:
desc = soup.find(class_="beer-description-read-less").get_text()
except AttributeError:
pass
except ValueError:
pass
return desc
def get_styles_from_soup(soup) -> list[str]:
styles = []
try:
styles = soup.find("p", class_="style").get_text().split(" - ")
except AttributeError:
pass
except ValueError:
pass
return styles
def get_abv_from_soup(soup) -> Optional[float]:
abv = None
try:
abv = soup.find(class_="abv").get_text()
if abv:
abv = float(abv.strip("\n").strip("% ABV").strip())
except AttributeError:
pass
except ValueError:
pass
return abv
def get_ibu_from_soup(soup) -> Optional[int]:
ibu = None
try:
ibu = soup.find(class_="ibu").get_text()
if ibu:
ibu = int(ibu.strip("\n").strip(" IBU").strip())
except AttributeError:
pass
except ValueError:
ibu = None
return ibu
def get_rating_from_soup(soup) -> str:
rating = ""
try:
rating = soup.find(class_="num").get_text().strip("(").strip(")")
except AttributeError:
pass
except ValueError:
pass
return rating
def get_producer_id_from_soup(soup) -> str:
id = ""
try:
id = soup.find(class_="brewery").find("a")["href"].strip("/")
except ValueError:
pass
except IndexError:
pass
return id
def get_producer_name_from_soup(soup) -> str:
name = ""
try:
name = soup.find(class_="brewery").find("a").get_text()
except AttributeError:
pass
except ValueError:
pass
return name
def get_beer_from_untappd_id(untappd_id: str) -> dict:
beer_url = UNTAPPD_URL.format(id=untappd_id)
headers = {"User-Agent": "Vrobbler 0.11.12"}
response = requests.get(beer_url, headers=headers)
beer_dict = {"untappd_id": untappd_id}
if response.status_code != 200:
logger.warn(
"Bad response from untappd.com", extra={"response": response}
)
return beer_dict
soup = BeautifulSoup(response.text, "html.parser")
beer_dict["untappd_id"] = untappd_id
beer_dict["title"] = get_title_from_soup(soup)
beer_dict["description"] = get_description_from_soup(soup)
beer_dict["styles"] = get_styles_from_soup(soup)
beer_dict["abv"] = get_abv_from_soup(soup)
beer_dict["ibu"] = get_ibu_from_soup(soup)
beer_dict["untappd_rating"] = get_rating_from_soup(soup)
beer_dict["producer__untappd_id"] = get_producer_id_from_soup(soup)
beer_dict["producer__name"] = get_producer_name_from_soup(soup)
return beer_dict

View File

@ -221,7 +221,7 @@ class WebPage(ScrobblableMixin):
self.save() self.save()
@classmethod @classmethod
def find_or_create(cls, data_dict: Dict) -> "GeoLocation": def find_or_create(cls, data_dict: Dict) -> "WebPage":
"""Given a data dict from an manual URL scrobble, does the heavy lifting of looking up """Given a data dict from an manual URL scrobble, does the heavy lifting of looking up
the url, creating if if doesn't exist yet. the url, creating if if doesn't exist yet.

View File

@ -34,7 +34,7 @@
<hr /> <hr />
{% endif %} {% endif %}
<p style="float:right;"> <p style="float:right;">
<a href="{{object.beeradvocate_link}}"><img src="{% static "images/beeradvoate-logo.png" %}" width=35></a> <a href="{{object.untappd_link}}"><img src="{% static "images/untappd-logo.png" %}" width=35></a>
</p> </p>
</div> </div>
</div> </div>