diff --git a/vrobbler/apps/beers/models.py b/vrobbler/apps/beers/models.py index d43ea46..276c881 100644 --- a/vrobbler/apps/beers/models.py +++ b/vrobbler/apps/beers/models.py @@ -8,6 +8,10 @@ from imagekit.models import ImageSpecField from imagekit.processors import ResizeToFit from scrobbles.dataclasses import BeerLogData 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} @@ -17,6 +21,9 @@ class BeerStyle(TimeStampedModel): name = models.CharField(max_length=255) description = models.TextField(**BNULL) + def __str__(self): + return self.name + class BeerProducer(TimeStampedModel): uuid = models.UUIDField(default=uuid4, editable=False, **BNULL) @@ -29,6 +36,9 @@ class BeerProducer(TimeStampedModel): def find_or_create(cls, title: str) -> "BeerProducer": return cls.objects.filter(title=title).first() + def __str__(self): + return self.name + class Beer(ScrobblableMixin): description = models.TextField(**BNULL) @@ -60,7 +70,8 @@ class Beer(ScrobblableMixin): def get_absolute_url(self) -> str: 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.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: url = "" - if self.beeradvocate_image: - url = self.beeradvocate_image.url + if self.untappd_image: + url = self.untappd_image.url return url @property @@ -84,7 +95,32 @@ class Beer(ScrobblableMixin): @classmethod 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): Scrobble = apps.get_model("scrobbles", "Scrobble") diff --git a/vrobbler/apps/beers/untappd.py b/vrobbler/apps/beers/untappd.py new file mode 100644 index 0000000..9cab3ca --- /dev/null +++ b/vrobbler/apps/beers/untappd.py @@ -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 diff --git a/vrobbler/apps/webpages/models.py b/vrobbler/apps/webpages/models.py index 60437dc..388d798 100644 --- a/vrobbler/apps/webpages/models.py +++ b/vrobbler/apps/webpages/models.py @@ -221,7 +221,7 @@ class WebPage(ScrobblableMixin): self.save() @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 the url, creating if if doesn't exist yet. diff --git a/vrobbler/templates/beers/beer_detail.html b/vrobbler/templates/beers/beer_detail.html index 0627c6f..f986582 100644 --- a/vrobbler/templates/beers/beer_detail.html +++ b/vrobbler/templates/beers/beer_detail.html @@ -34,7 +34,7 @@
{% endif %}

- +