From da8d26fcd9e9ee140e614fc6bb83f444ec857199 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Mon, 16 Jun 2025 10:20:37 -0400 Subject: [PATCH] [trails] Add alltrails and gaiagps ids to model --- ...005_trail_alltrails_id_trail_gaiagps_id.py | 23 +++++++ vrobbler/apps/trails/models.py | 14 +++-- vrobbler/apps/trails/sources/gaiagps.py | 61 +++++++++++++++++++ 3 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 vrobbler/apps/trails/migrations/0005_trail_alltrails_id_trail_gaiagps_id.py create mode 100644 vrobbler/apps/trails/sources/gaiagps.py diff --git a/vrobbler/apps/trails/migrations/0005_trail_alltrails_id_trail_gaiagps_id.py b/vrobbler/apps/trails/migrations/0005_trail_alltrails_id_trail_gaiagps_id.py new file mode 100644 index 0000000..774c3b5 --- /dev/null +++ b/vrobbler/apps/trails/migrations/0005_trail_alltrails_id_trail_gaiagps_id.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.19 on 2025-06-16 14:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("trails", "0004_alter_trail_run_time_seconds"), + ] + + operations = [ + migrations.AddField( + model_name="trail", + name="alltrails_id", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name="trail", + name="gaiagps_id", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/vrobbler/apps/trails/models.py b/vrobbler/apps/trails/models.py index 8a452a6..2f56cda 100644 --- a/vrobbler/apps/trails/models.py +++ b/vrobbler/apps/trails/models.py @@ -9,9 +9,7 @@ from locations.models import GeoLocation BNULL = {"blank": True, "null": True} - class Trail(ScrobblableMixin): - class PrincipalType(models.TextChoices): WOODS = "WOODS" ROAD = "ROAD" @@ -19,7 +17,7 @@ class Trail(ScrobblableMixin): MOUNTAIN = "MOUNTAIN" class ActivityType(models.TextChoices): - WALK= "WALK" + WALK = "WALK" HIKE = "HIKE" RUN = "RUN" BIKE = "BIKE" @@ -39,8 +37,14 @@ class Trail(ScrobblableMixin): ) strava_id = models.CharField(max_length=255, **BNULL) trailforks_id = models.CharField(max_length=255, **BNULL) - principal_type = models.CharField(max_length=10, choices=PrincipalType.choices, **BNULL) - default_activity_type = models.CharField(max_length=10, choices=ActivityType.choices, **BNULL) + alltrails_id = models.CharField(max_length=255, **BNULL) + gaiagps_id = models.CharField(max_length=255, **BNULL) + principal_type = models.CharField( + max_length=10, choices=PrincipalType.choices, **BNULL + ) + default_activity_type = models.CharField( + max_length=10, choices=ActivityType.choices, **BNULL + ) def get_absolute_url(self): return reverse("trails:trail_detail", kwargs={"slug": self.uuid}) diff --git a/vrobbler/apps/trails/sources/gaiagps.py b/vrobbler/apps/trails/sources/gaiagps.py new file mode 100644 index 0000000..8cf157a --- /dev/null +++ b/vrobbler/apps/trails/sources/gaiagps.py @@ -0,0 +1,61 @@ +from typing import Optional +import requests +from bs4 import BeautifulSoup + + +def get_description_from_soup(soup) -> Optional[int]: + rating = None + try: + potential_desc = soup.find("div", id_="descriptionContainer") + potential_rating = soup.find("div", class_="allmusic-rating") + if potential_rating: + rating = int(strip_and_clean(potential_rating.get_text())) + except ValueError: + pass + return rating + + +def scrape_gaia_trail(url): + headers = {"User-Agent": "Mozilla/5.0"} + r = requests.get(url, headers=headers) + r.raise_for_status() + soup = BeautifulSoup(r.text, "html.parser") + + # Title + title = soup.select_one("h1").get_text(strip=True) + + # Stats are inside divs with classes like "flex" and label-value pairs + stats = {} + stat_blocks = soup.select("div.flex.flex-col.items-start.text-sm span") + for i in range(0, len(stat_blocks) - 1, 2): + label = stat_blocks[i].get_text(strip=True) + value = stat_blocks[i + 1].get_text(strip=True) + stats[label] = value + + # Description (under 'Overview' header) + overview_heading = soup.find( + lambda tag: tag.name == "h2" and "Overview" in tag.text + ) + if overview_heading: + desc_parts = [] + for sib in overview_heading.next_siblings: + if sib.name and sib.name.startswith("h"): + break + if sib.name == "p": + desc_parts.append(sib.get_text(strip=True)) + description = "\n\n".join(desc_parts).strip() + else: + description = None + + return { + "url": url, + "title": title, + "stats": stats, + "description": description, + } + + +# Example usage +url = "https://www.gaiagps.com/hike/318136/" +trail_data = scrape_gaia_trail(url) +print(trail_data)