diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml new file mode 100644 index 0000000..e767950 --- /dev/null +++ b/.github/workflows/opencode.yml @@ -0,0 +1,36 @@ +name: opencode + +on: + pull_request: + types: [opened, synchronize, ready_for_review] + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +jobs: + opencode: + if: | + github.event_name == 'pull_request' || + contains(github.event.comment.body, ' /oc') || + startsWith(github.event.comment.body, '/oc') || + contains(github.event.comment.body, ' /opencode') || + startsWith(github.event.comment.body, '/opencode') + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + pull-requests: write + issues: read + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Run opencode + uses: anomalyco/opencode/github@latest + env: + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + with: + model: opencode/kimi-k2.5 diff --git a/main/admin.py b/main/admin.py index cffe95f..bc52b44 100644 --- a/main/admin.py +++ b/main/admin.py @@ -1,53 +1,65 @@ from django.contrib import admin from .models import Merchant, Category + @admin.register(Category) class CategoryAdmin(admin.ModelAdmin): - list_display = ['name', 'description'] - search_fields = ['name', 'description'] + list_display = ["name", "description"] + search_fields = ["name", "description"] + @admin.register(Merchant) class MerchantAdmin(admin.ModelAdmin): list_display = [ - 'name', - 'city', - 'country', - 'get_categories', - 'test_shop', - 'verified', - 'last_transaction_date' - ] - list_filter = [ - 'test_shop', - 'verified', - 'country', - 'city', - 'categories' - ] - search_fields = [ - 'name', - 'city', - 'country', - 'description' + "name", + "city", + "country", + "get_categories", + "active", + "test_shop", + "verified", + "last_transaction_date", ] + list_filter = ["active", "test_shop", "verified", "country", "city", "categories"] + search_fields = ["name", "city", "country", "description"] fieldsets = ( - ('Basic Information', { - 'fields': ('name', 'categories', 'description', 'website_url', 'gmap_business_link', 'test_shop', 'verified') - }), - ('Location', { - 'fields': ( - 'landmark', 'location', 'street', 'town', 'city', - 'province', 'state', 'country', 'longitude', 'latitude' - ) - }), - ('Logo', { - 'fields': ('logo_size', 'logo_url') - }), - ('Dates', { - 'fields': ('last_transaction_date', 'last_update') - }), + ( + "Basic Information", + { + "fields": ( + "name", + "categories", + "description", + "website_url", + "gmap_business_link", + "active", + "test_shop", + "verified", + ) + }, + ), + ( + "Location", + { + "fields": ( + "landmark", + "location", + "street", + "town", + "city", + "province", + "state", + "country", + "longitude", + "latitude", + ) + }, + ), + ("Logo", {"fields": ("logo_size", "logo_url")}), + ("Dates", {"fields": ("last_transaction_date", "last_update")}), ) def get_categories(self, obj): return ", ".join([category.name for category in obj.categories.all()]) - get_categories.short_description = 'Categories' + + get_categories.short_description = "Categories" diff --git a/main/management/commands/sync_to_sheets.py b/main/management/commands/sync_to_sheets.py new file mode 100644 index 0000000..6e60f66 --- /dev/null +++ b/main/management/commands/sync_to_sheets.py @@ -0,0 +1,198 @@ +import logging +from django.core.management.base import BaseCommand +from django.db import models +from main.models import Merchant +import gspread +from google.oauth2.service_account import Credentials +from django.conf import settings +from datetime import datetime + +logger = logging.getLogger(__name__) + + +# Category mapping to numbers as defined by stakeholders +CATEGORY_MAPPING = { + "Tourism": 0, + "Clothing": 1, + "Digital Consulting": 2, + "Services": 3, + "Food and Beverages": 4, + "Home Appliances": 5, + "Gastronomy": 6, + "Hobbies": 7, + "Hygiene and Beauty": 8, + "Toys and Books": 9, + "Garden and Gifts": 10, + "Apparel and Footwear": 11, + "Transport and Customs": 12, + "Office Supplies": 13, + "Computing": 14, + "Other": 15, +} + + +class Command(BaseCommand): + help = "Syncs merchant data to Google Sheets for external stakeholders" + + def get_service_account_credentials(self): + """Get service account credentials from settings or environment.""" + credentials_info = { + "type": "service_account", + "project_id": getattr(settings, "GOOGLE_PROJECT_ID", ""), + "private_key_id": getattr(settings, "GOOGLE_PRIVATE_KEY_ID", ""), + "private_key": getattr(settings, "GOOGLE_PRIVATE_KEY", "").replace( + "\\n", "\n" + ), + "client_email": getattr(settings, "GOOGLE_CLIENT_EMAIL", ""), + "client_id": getattr(settings, "GOOGLE_CLIENT_ID", ""), + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": getattr(settings, "GOOGLE_CLIENT_CERT_URL", ""), + "universe_domain": "googleapis.com", + } + + scopes = ["https://www.googleapis.com/auth/spreadsheets"] + return Credentials.from_service_account_info(credentials_info, scopes=scopes) + + def get_category_number(self, merchant): + """Get the category number for a merchant.""" + categories = merchant.categories.all() + if not categories: + return 15 # Other + + # Try to match the first category to our mapping + first_category = categories[0].name + for key, value in CATEGORY_MAPPING.items(): + if key.lower() in first_category.lower(): + return value + + return 15 # Default to Other if no match + + def get_coordinates(self, merchant): + """Format coordinates as 'latitude,longitude'.""" + if merchant.latitude is None or merchant.longitude is None: + return "" + return f"{merchant.latitude},{merchant.longitude}" + + def get_direction(self, merchant): + """Combine address fields into Direction.""" + parts = [] + if merchant.street: + parts.append(merchant.street) + if merchant.landmark: + parts.append(merchant.landmark) + if merchant.location: + parts.append(merchant.location) + + return ", ".join(parts) if parts else "" + + def get_keywords(self, merchant): + """Generate keywords from merchant name and categories.""" + keywords = [merchant.name] + for category in merchant.categories.all(): + keywords.append(category.name) + return ", ".join(keywords) + + def get_merchants_data(self): + """Fetch merchant data formatted for external stakeholders.""" + merchants = ( + Merchant.objects.filter(test_shop=False, verified=True, active=True) + .filter( + models.Q(last_transaction_date__isnull=False) + | models.Q(categories__name="Hotels / Resorts by Hiverooms") + ) + .filter(longitude__isnull=False, latitude__isnull=False) + .distinct() + ) + + data = [] + for merchant in merchants: + # Display: 1 if verified and has transactions, 0 otherwise + display = 1 if merchant.verified and merchant.last_transaction_date else 0 + + row = [ + display, # Display + "", # Discount - not in our DB + merchant.name, # Business Name + self.get_category_number(merchant), # Business Category (number) + self.get_direction(merchant), # Direction + merchant.city or "", # City + merchant.state or merchant.province or "", # State + merchant.country or "", # Country + "", # Business Number - not in our DB + "", # Email - not in our DB + merchant.website_url or "", # Website + self.get_keywords(merchant), # Key Words + self.get_coordinates(merchant), # Coordinates + 2, # Business Type - default to Local Store (2) + merchant.logo_url or "", # Logo + merchant.last_update.isoformat() + if merchant.last_update + else datetime.now().isoformat(), # Timestamp + ] + data.append(row) + + return data + + def sync_to_sheet(self, spreadsheet_id, sheet_name="Sheet1"): + """Sync merchant data to Google Sheet.""" + try: + credentials = self.get_service_account_credentials() + gc = gspread.authorize(credentials) + + # Open spreadsheet + spreadsheet = gc.open_by_key(spreadsheet_id) + + # Try to get the worksheet, create if it doesn't exist + try: + worksheet = spreadsheet.worksheet(sheet_name) + except gspread.WorksheetNotFound: + worksheet = spreadsheet.add_worksheet( + title=sheet_name, rows=1000, cols=16 + ) + + # Get data before clearing + data = self.get_merchants_data() + + # Clear existing data rows (starting from row 2, preserving headers in row 1) + # First, get the current number of rows + current_rows = worksheet.row_count + if current_rows > 1: + # Delete all rows from row 2 onwards + worksheet.delete_rows(2, current_rows) + + # Resize worksheet to accommodate new data (keep row 1 for headers) + if data: + total_rows_needed = len(data) + 1 # +1 for header row + if total_rows_needed > worksheet.row_count: + worksheet.add_rows(total_rows_needed - worksheet.row_count) + + # Insert data starting from row 2 + if data: + worksheet.insert_rows(data, row=2) + + logger.info(f"Successfully synced {len(data)} merchants to Google Sheet") + return True + + except Exception as e: + logger.error(f"Error syncing to Google Sheets: {str(e)}") + return False + + def handle(self, *args, **options): + spreadsheet_id = getattr(settings, "GOOGLE_SHEETS_SPREADSHEET_ID", "") + + if not spreadsheet_id: + logger.error("GOOGLE_SHEETS_SPREADSHEET_ID not configured in settings") + return + + success = self.sync_to_sheet(spreadsheet_id) + + if success: + self.stdout.write( + self.style.SUCCESS("Successfully synced merchants to Google Sheets") + ) + else: + self.stdout.write( + self.style.ERROR("Failed to sync merchants to Google Sheets") + ) diff --git a/main/management/commands/update_merchants.py b/main/management/commands/update_merchants.py index 8d6327c..5475875 100644 --- a/main/management/commands/update_merchants.py +++ b/main/management/commands/update_merchants.py @@ -1,4 +1,5 @@ from django.core.management.base import BaseCommand +from django.core.management import call_command from main.models import Merchant import dateutil.parser as parser from django.utils import timezone @@ -22,94 +23,116 @@ def parse_last_transaction_date(datetime_string): def _save_merchant(merchant_data): logger.info(merchant_data) - last_transaction_date_str = merchant_data['last_transaction_date'] + last_transaction_date_str = merchant_data["last_transaction_date"] last_transaction_date = parse_last_transaction_date(last_transaction_date_str) - location_data = merchant_data['location'] - logos_data = merchant_data['logos'] - logo_120x120 = logos_data.get('120x120') if logos_data else None + location_data = merchant_data["location"] + logos_data = merchant_data["logos"] + logo_120x120 = logos_data.get("120x120") if logos_data else None merchant = Merchant.objects.create( - watchtower_merchant_id=merchant_data['id'], - name=merchant_data['name'], - website_url=merchant_data['website_url'], - description=merchant_data['description'], - gmap_business_link=merchant_data['gmap_business_link'], + watchtower_merchant_id=merchant_data["id"], + name=merchant_data["name"], + website_url=merchant_data["website_url"], + description=merchant_data["description"], + gmap_business_link=merchant_data["gmap_business_link"], last_transaction_date=last_transaction_date, last_update=timezone.now(), - verified=merchant_data.get('verified', False), + verified=merchant_data.get("verified", False), # Location fields - landmark=location_data['landmark'], - street=location_data['street'], - town=location_data['town'], - city=location_data['city'], - province=location_data['province'], - state=location_data['state'], - country=location_data['country'], - longitude=float(location_data['longitude']) if location_data['longitude'] is not None else None, - latitude=float(location_data['latitude']) if location_data['latitude'] is not None else None, + landmark=location_data["landmark"], + street=location_data["street"], + town=location_data["town"], + city=location_data["city"], + province=location_data["province"], + state=location_data["state"], + country=location_data["country"], + longitude=float(location_data["longitude"]) + if location_data["longitude"] is not None + else None, + latitude=float(location_data["latitude"]) + if location_data["latitude"] is not None + else None, # Logo fields - logo_size='120x120' if logo_120x120 else None, - logo_url=logo_120x120 + logo_size="120x120" if logo_120x120 else None, + logo_url=logo_120x120, ) - logger.info(f'Saved: {merchant.name}') + logger.info(f"Saved: {merchant.name}") + return True def _update_merchant(merchant_data): logger.info(merchant_data) - merchant = Merchant.objects.get(watchtower_merchant_id=merchant_data['id']) + merchant = Merchant.objects.get(watchtower_merchant_id=merchant_data["id"]) # Update location - location_data = merchant_data['location'] + location_data = merchant_data["location"] if location_data: - merchant.landmark = location_data['landmark'] - merchant.street = location_data['street'] - merchant.town = location_data['town'] - merchant.city = location_data['city'] - merchant.province = location_data['province'] - merchant.state = location_data['state'] - merchant.country = location_data['country'] - merchant.longitude = float(location_data['longitude']) if location_data['longitude'] is not None else None - merchant.latitude = float(location_data['latitude']) if location_data['latitude'] is not None else None + merchant.landmark = location_data["landmark"] + merchant.street = location_data["street"] + merchant.town = location_data["town"] + merchant.city = location_data["city"] + merchant.province = location_data["province"] + merchant.state = location_data["state"] + merchant.country = location_data["country"] + merchant.longitude = ( + float(location_data["longitude"]) + if location_data["longitude"] is not None + else None + ) + merchant.latitude = ( + float(location_data["latitude"]) + if location_data["latitude"] is not None + else None + ) # Update logo - logos_data = merchant_data['logos'] - logo_120x120 = logos_data.get('120x120') if logos_data else None + logos_data = merchant_data["logos"] + logo_120x120 = logos_data.get("120x120") if logos_data else None if logo_120x120: - merchant.logo_size = '120x120' + merchant.logo_size = "120x120" merchant.logo_url = logo_120x120 # Update merchant details - last_transaction_date_str = merchant_data['last_transaction_date'] + last_transaction_date_str = merchant_data["last_transaction_date"] last_transaction_date = parse_last_transaction_date(last_transaction_date_str) - merchant.name = merchant_data['name'] - merchant.website_url = merchant_data['website_url'] - merchant.description = merchant_data['description'] - merchant.gmap_business_link = merchant_data['gmap_business_link'] + merchant.name = merchant_data["name"] + merchant.website_url = merchant_data["website_url"] + merchant.description = merchant_data["description"] + merchant.gmap_business_link = merchant_data["gmap_business_link"] + merchant.verified = merchant_data.get("verified", False) merchant.last_transaction_date = last_transaction_date merchant.last_update = timezone.now() merchant.save() - logger.info(f'Updated: {merchant.name}') + logger.info(f"Updated: {merchant.name}") + return True def _fetch_merchants(check_last_update=True): - source_url = 'https://watchtower.cash/api/paytacapos/merchants/?active=true&has_pagination=false' + source_url = "https://watchtower.cash/api/paytacapos/merchants/?active=true&has_pagination=false" resp = requests.get(source_url) + + any_updates = False + if resp.status_code == 200: merchants = resp.json() for merchant_data in merchants: - merchant_check = Merchant.objects.filter(watchtower_merchant_id=merchant_data['id']) + merchant_check = Merchant.objects.filter( + watchtower_merchant_id=merchant_data["id"] + ) if merchant_check.exists(): merchant = merchant_check.last() proceed_update = False if check_last_update: - if 'last_update' in merchant_data.keys(): - if merchant_data['last_update']: + if "last_update" in merchant_data.keys(): + if merchant_data["last_update"]: if merchant.last_update: - _last_update = parser.parse(merchant_data['last_update']) + _last_update = parser.parse( + merchant_data["last_update"] + ) if merchant.last_update < _last_update: proceed_update = True else: @@ -119,27 +142,43 @@ def _fetch_merchants(check_last_update=True): else: proceed_update = True - last_transaction_date_str = merchant_data['last_transaction_date'] - last_transaction_date = parse_last_transaction_date(last_transaction_date_str) + last_transaction_date_str = merchant_data["last_transaction_date"] + last_transaction_date = parse_last_transaction_date( + last_transaction_date_str + ) if last_transaction_date: - last_transaction_date = parser.parse(last_transaction_date_str) - if merchant.last_transaction_date and merchant.last_transaction_date < last_transaction_date: + if ( + merchant.last_transaction_date + and merchant.last_transaction_date < last_transaction_date + ): proceed_update = True if proceed_update: _update_merchant(merchant_data) + any_updates = True else: - location_data = merchant_data['location'] - if location_data['longitude']: + location_data = merchant_data["location"] + if location_data["longitude"]: _save_merchant(merchant_data) + any_updates = True + + return any_updates class Command(BaseCommand): - help = 'Saves or updates merchants fetched from Watchtower.cash' + help = "Saves or updates merchants fetched from Watchtower.cash" def handle(self, *args, **kwargs): # Update merchants every 60 seconds while True: time.sleep(60) - logger.info('Fetching merchants...') - _fetch_merchants() + logger.info("Fetching merchants...") + any_updates = _fetch_merchants() + + if any_updates: + logger.info("Triggering sync to sheets...") + try: + call_command("sync_to_sheets") + logger.info("Sync to sheets completed") + except Exception as e: + logger.error(f"Error syncing to sheets: {str(e)}") diff --git a/main/migrations/0019_merchant_active.py b/main/migrations/0019_merchant_active.py new file mode 100644 index 0000000..193855c --- /dev/null +++ b/main/migrations/0019_merchant_active.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.27 on 2026-03-19 09:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("main", "0018_create_cache_table"), + ] + + operations = [ + migrations.AddField( + model_name="merchant", + name="active", + field=models.BooleanField(default=True), + ), + ] diff --git a/main/models.py b/main/models.py index 6c754ce..185b05e 100644 --- a/main/models.py +++ b/main/models.py @@ -3,15 +3,23 @@ from django.db.models.functions import Coalesce from django.core.validators import RegexValidator + class Category(models.Model): name = models.CharField(max_length=255, unique=True, db_index=True) - short_name = models.CharField(max_length=50, unique=True, db_index=True, null=True, blank=True, validators=[ - RegexValidator( - regex='^[a-zA-Z0-9]+$', - message='Short name must contain only alphanumeric characters with no spaces', - code='invalid_short_name' - ) - ]) + short_name = models.CharField( + max_length=50, + unique=True, + db_index=True, + null=True, + blank=True, + validators=[ + RegexValidator( + regex="^[a-zA-Z0-9]+$", + message="Short name must contain only alphanumeric characters with no spaces", + code="invalid_short_name", + ) + ], + ) description = models.TextField(blank=True, null=True) def __str__(self): @@ -19,14 +27,15 @@ def __str__(self): class Meta: verbose_name_plural = "Categories" - ordering = ['name'] + ordering = ["name"] + class MerchantQuerySet(models.QuerySet): - def with_effective_date(self): return self.annotate( - effective_date=Coalesce('last_transaction_date', 'last_update') - ).order_by('-effective_date') + effective_date=Coalesce("last_transaction_date", "last_update") + ).order_by("-effective_date") + class Merchant(models.Model): name = models.CharField(max_length=255, db_index=True) @@ -38,6 +47,7 @@ class Merchant(models.Model): last_update = models.DateTimeField(null=True, blank=True) test_shop = models.BooleanField(default=False) verified = models.BooleanField(default=False) + active = models.BooleanField(default=True) # Location fields landmark = models.CharField(max_length=255, blank=True, null=True) @@ -64,4 +74,4 @@ def __str__(self): return self.name class Meta: - ordering = ['-last_transaction_date', '-last_update'] + ordering = ["-last_transaction_date", "-last_update"] diff --git a/main/views.py b/main/views.py index 7c72e2c..1e6328c 100644 --- a/main/views.py +++ b/main/views.py @@ -46,7 +46,7 @@ def get(self, request): merchants = ( Merchant.objects.with_effective_date() - .filter(test_shop=False) + .filter(active=True, test_shop=False) .exclude(name__regex=r"(^|\s)Test(\s|$)") ) merchants = merchants.filter( @@ -89,7 +89,7 @@ def get(self, request): return Response(cached_data) merchants = ( - Merchant.objects.filter(test_shop=False) + Merchant.objects.filter(active=True, test_shop=False) .filter( models.Q(last_transaction_date__isnull=False) | models.Q(categories__name="Hotels / Resorts by Hiverooms") @@ -166,7 +166,7 @@ def get(self, request): return Response(cached_data) merchants = ( - Merchant.objects.filter(test_shop=False) + Merchant.objects.filter(active=True, test_shop=False) .filter( models.Q(last_transaction_date__isnull=False) | models.Q(categories__name="Hotels / Resorts by Hiverooms") diff --git a/paytacamap/settings.py b/paytacamap/settings.py index 4da5216..1ee7399 100644 --- a/paytacamap/settings.py +++ b/paytacamap/settings.py @@ -149,6 +149,15 @@ MERCHANTS_CACHE_TIMEOUT = 300 # 5 minutes - merchants change occasionally CATEGORIES_CACHE_TIMEOUT = 3600 # 1 hour - categories rarely change +# Google Sheets Configuration +GOOGLE_SHEETS_SPREADSHEET_ID = config("GOOGLE_SHEETS_SPREADSHEET_ID", default="") +GOOGLE_PROJECT_ID = config("GOOGLE_PROJECT_ID", default="") +GOOGLE_PRIVATE_KEY_ID = config("GOOGLE_PRIVATE_KEY_ID", default="") +GOOGLE_PRIVATE_KEY = config("GOOGLE_PRIVATE_KEY", default="") +GOOGLE_CLIENT_EMAIL = config("GOOGLE_CLIENT_EMAIL", default="") +GOOGLE_CLIENT_ID = config("GOOGLE_CLIENT_ID", default="") +GOOGLE_CLIENT_CERT_URL = config("GOOGLE_CLIENT_CERT_URL", default="") + LOGGING = { "version": 1, "disable_existing_loggers": False, diff --git a/requirements.txt b/requirements.txt index d6ac35a..84b4892 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,6 @@ supervisor==4.2.5 typing_extensions==4.11.0 pytz==2024.1 ipython==8.26.0 +gspread==6.1.2 +google-auth==2.31.0 +google-auth-oauthlib==1.2.0 diff --git a/supervisord.conf b/supervisord.conf index 0aed758..d2916d6 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -46,3 +46,14 @@ stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 stopasgroup=true + + +[program:sheets_sync] +command=bash -c "while true; do python manage.py sync_to_sheets; sleep 3600; done" +directory=/code +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +stopasgroup=true diff --git a/update_merchant_urls.py b/update_merchant_urls.py new file mode 100644 index 0000000..b4daa08 --- /dev/null +++ b/update_merchant_urls.py @@ -0,0 +1,85 @@ +# Django shell script to update merchant website URLs +# Can be run standalone with: python update_merchant_urls.py +# Or pasted into: python manage.py shell + +import os +import sys + +# Setup Django +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "paytaca_map.settings") +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import django + +django.setup() + +from main.models import Merchant + +# Map of merchant IDs to website URLs +merchant_urls = { + 263: "https://book.hiverooms.com/booking-page.php?id=8-newtown-boulevard-by-hiverooms", + 266: "https://book.hiverooms.com/booking-page.php?id=ariella-mangrove-eco-resort-by-hiverooms", + 267: "https://book.hiverooms.com/booking-page.php?id=badian-island-wellness-resort", + 268: "https://book.hiverooms.com/booking-page.php?id=bluewaves-basdaku", + 269: "https://book.hiverooms.com/booking-page.php?id=bluewaves-hostel-villa-panagsama", + 270: "https://book.hiverooms.com/booking-page.php?id=casay-beach-house-by-hiverooms", + 271: "https://book.hiverooms.com/booking-page.php?id=ceda-guesthouse-by-hiverooms", + 272: "https://book.hiverooms.com/booking-page.php?id=fandc-guest-house", + 273: "https://book.hiverooms.com/booking-page.php?id=fazenda-de-faburada-ecofarm-and-mountain-resort-by-hiverooms", + 274: "https://book.hiverooms.com/booking-page.php?id=fins-pacific-coral-bay", + 275: "https://book.hiverooms.com/booking-page.php?id=hagdan-reef-dive-spot", + 276: "https://book.hiverooms.com/booking-page.php?id=jvr-island-in-the-sky-resort-by-hiverooms", + 277: "https://book.hiverooms.com/booking-page.php?id=kalaparan-farm-house-by-hiverooms", + 279: "https://book.hiverooms.com/booking-page.php?id=mca-suites-by-hiverooms", + 283: "https://book.hiverooms.com/booking-page.php?id=mandala-beach-cebu", + 284: "https://book.hiverooms.com/booking-page.php?id=bogo-northomes-pensione", + 286: "https://book.hiverooms.com/booking-page.php?id=one-manchester-place-by-hiverooms", + 287: "https://book.hiverooms.com/booking-page.php?id=one-pacific-residences-by-hiverooms", + 288: "https://book.hiverooms.com/booking-page.php?id=rajah-park-hotel-by-hiverooms", + 289: "https://book.hiverooms.com/booking-page.php?id=rockwalled-adventure-resort-by-hiverooms", + 290: "https://book.hiverooms.com/booking-page.php?id=serenity-farm-and-resort", + 295: "https://book.hiverooms.com/booking-page.php?id=sonrisa-golden-meadows", + 296: "https://book.hiverooms.com/booking-page.php?id=sonrisa-resort-de-playa-by-hiverooms", + 297: "https://book.hiverooms.com/booking-page.php?id=tambuli-seaside-resort-and-spa", + 300: "https://book.hiverooms.com/booking-page.php?id=ipark-hotel-by-hiverooms", + 378: "https://book.hiverooms.com/booking-page.php?id=upperhouse-hotel-formerly-pacifico-boutique-by-hiverooms", + 379: "https://book.hiverooms.com/booking-page.php?id=serenity-home-7", + 380: "https://book.hiverooms.com/booking-page.php?id=serenity-home-9", + 381: "https://book.hiverooms.com/booking-page.php?id=serenity-home-11", + 382: "https://book.hiverooms.com/booking-page.php?id=casa-de-vacacion-in-orani-bataan", +} + +# Update merchants +from django.db import transaction + +updated = [] +not_found = [] + +with transaction.atomic(): + for merchant_id, url in merchant_urls.items(): + try: + # Replace 'Merchant' with your actual model name + merchant = Merchant.objects.get(id=merchant_id) + merchant.website_url = url + merchant.save(update_fields=["website_url"]) + updated.append(merchant_id) + print(f"Updated ID {merchant_id}: {merchant.name}") + except Merchant.DoesNotExist: + not_found.append(merchant_id) + print(f"Not found: ID {merchant_id}") + +print(f"\n=== Summary ===") +print(f"Updated: {len(updated)} merchants") +print(f"Not found: {len(not_found)} merchants") + +# These merchants were not found in the API: +# 265 Almond Suites +# 280 Maayo Argao +# 281 Maayo Hotel +# 282 Maayo San Remigio +# 291 Solea Coast Resort +# 292 Solea Mactan Resort +# 293 Solea Palm Resort +# 294 Solea Seaview Resort +# 298 The Beach Park Hadsan +# 299 Torre de Alcoy