* Vietnamese translation incomplete

Destination Models

Destination Models (English fallback)

Aug. 17, 2025

Posted by admin

Destination Models

Nhom

Notes

 

  • Country on/off drop down list is_published
  • Destination Category – Limit selection

 

 

 

from django.db import models

from django.urls import reverse

from django.contrib.auth.models import User

from django.core.validators import MinValueValidator, MaxValueValidator

from django.utils.translation import gettext_lazy as _

from decimal import Decimal

from apps.geo.models import Location

 

  •  

1

class Country(models.Model):

    """Comprehensive country-level information for travel planning"""

   

    # ================================

    # 🏳️ COUNTRY OVERVIEW

    # ================================

    name_en = models.CharField(max_length=100, unique=True, verbose_name="Country Name (English)")

    name_vi = models.CharField(max_length=100, blank=True, verbose_name="Country Name (Vietnamese)")

    official_name_en = models.CharField(max_length=150, blank=True, verbose_name="Official Name (English)")

    official_name_vi = models.CharField(max_length=150, blank=True, verbose_name="Official Name (Vietnamese)")

    country_code = models.CharField(max_length=3, unique=True, help_text="ISO 3166-1 alpha-3 code")

    continent = models.CharField(max_length=50)

    flag_image = models.ImageField(upload_to='countries/flags/', blank=True, null=True)

   

    # Basic geographic info

    capital_en = models.CharField(max_length=100, blank=True, verbose_name="Capital (English)")

    capital_vi = models.CharField(max_length=100, blank=True, verbose_name="Capital (Vietnamese)")

    major_cities_en = models.TextField(blank=True, verbose_name="Major Cities (English)", help_text="Comma-separated list")

    major_cities_vi = models.TextField(blank=True, verbose_name="Major Cities (Vietnamese)", help_text="Comma-separated list")

    population = models.BigIntegerField(blank=True, null=True)

    area_km2 = models.BigIntegerField(blank=True, null=True, help_text="Area in square kilometers")

    gdp_per_capita = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)

   

    # Languages and communication

    official_languages_en = models.CharField(max_length=200, blank=True, verbose_name="Official Languages (English)")

    official_languages_vi = models.CharField(max_length=200, blank=True, verbose_name="Official Languages (Vietnamese)")

    spoken_languages_en = models.CharField(max_length=300, blank=True, verbose_name="Commonly Spoken Languages (English)")

    spoken_languages_vi = models.CharField(max_length=300, blank=True, verbose_name="Commonly Spoken Languages (Vietnamese)")

   

    # Time and location

    time_zones = models.CharField(max_length=200, blank=True, verbose_name="Time Zones")

    calling_code = models.CharField(max_length=10, blank=True, verbose_name="Country Calling Code")

    internet_domain = models.CharField(max_length=10, blank=True, verbose_name="Internet Domain")

   

    # Infrastructure basics

    driving_side = models.CharField(max_length=10, choices=[('left', 'Left'), ('right', 'Right')], blank=True)

    power_plugs = models.CharField(max_length=50, blank=True, help_text="e.g., Type A, B, C")

    voltage = models.CharField(max_length=20, blank=True, help_text="e.g., 110V, 220V")

    frequency = models.CharField(max_length=10, blank=True, help_text="e.g., 50Hz, 60Hz")

   

    # ================================

    # 💰 CURRENCY & FINANCIAL INFO

    # ================================

    currency_name_en = models.CharField(max_length=50, blank=True, verbose_name="Currency Name (English)")

    currency_name_vi = models.CharField(max_length=50, blank=True, verbose_name="Currency Name (Vietnamese)")

    currency_code = models.CharField(max_length=10, blank=True, verbose_name="Currency Code (ISO)")

    currency_symbol = models.CharField(max_length=10, blank=True, verbose_name="Currency Symbol")

    currency_subdivisions_en = models.CharField(max_length=100, blank=True, verbose_name="Currency Subdivisions (English)")

    currency_subdivisions_vi = models.CharField(max_length=100, blank=True, verbose_name="Currency Subdivisions (Vietnamese)")

   

    # Money exchange and usage

    usd_accepted = models.BooleanField(default=False, verbose_name="USD Widely Accepted")

    euro_accepted = models.BooleanField(default=False, verbose_name="EUR Widely Accepted")

    exchange_info_en = models.TextField(blank=True, verbose_name="Money Exchange Info (English)")

    exchange_info_vi = models.TextField(blank=True, verbose_name="Money Exchange Info (Vietnamese)")

    atm_availability_en = models.TextField(blank=True, verbose_name="ATM Availability (English)")

    atm_availability_vi = models.TextField(blank=True, verbose_name="ATM Availability (Vietnamese)")

    credit_cards_accepted = models.BooleanField(default=True, verbose_name="Credit Cards Widely Accepted")

   

    # Tipping and costs

    tipping_culture_en = models.TextField(blank=True, verbose_name="Tipping Culture (English)")

    tipping_culture_vi = models.TextField(blank=True, verbose_name="Tipping Culture (Vietnamese)")

    daily_budget_backpacker = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True, verbose_name="Daily Budget - Backpacker (USD)")

    daily_budget_mid_range = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True, verbose_name="Daily Budget - Mid-range (USD)")

    daily_budget_luxury = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True, verbose_name="Daily Budget - Luxury (USD)")

   

    # ================================

    # 📜 HISTORY & CULTURE

    # ================================

    historical_background_en = models.TextField(blank=True, verbose_name="Historical Background (English)")

    historical_background_vi = models.TextField(blank=True, verbose_name="Historical Background (Vietnamese)")

    cultural_highlights_en = models.TextField(blank=True, verbose_name="Cultural Highlights (English)")

    cultural_highlights_vi = models.TextField(blank=True, verbose_name="Cultural Highlights (Vietnamese)")

   

    # Religion and festivals

    major_religions_en = models.CharField(max_length=200, blank=True, verbose_name="Major Religions (English)")

    major_religions_vi = models.CharField(max_length=200, blank=True, verbose_name="Major Religions (Vietnamese)")

    festivals_holidays_en = models.TextField(blank=True, verbose_name="Major Festivals & Holidays (English)")

    festivals_holidays_vi = models.TextField(blank=True, verbose_name="Major Festivals & Holidays (Vietnamese)")

   

    # Social customs

    customs_traditions_en = models.TextField(blank=True, verbose_name="Customs & Traditions (English)")

    customs_traditions_vi = models.TextField(blank=True, verbose_name="Customs & Traditions (Vietnamese)")

    etiquette_tips_en = models.TextField(blank=True, verbose_name="Etiquette Tips (English)")

    etiquette_tips_vi = models.TextField(blank=True, verbose_name="Etiquette Tips (Vietnamese)")

    cultural_taboos_en = models.TextField(blank=True, verbose_name="Cultural Taboos (English)")

    cultural_taboos_vi = models.TextField(blank=True, verbose_name="Cultural Taboos (Vietnamese)")

   

    # ================================

    # ✈️ ENTRY & VISA REQUIREMENTS

    # ================================

    visa_requirements_vietnamese_en = models.TextField(blank=True, verbose_name="Visa for Vietnamese Citizens (English)")

    visa_requirements_vietnamese_vi = models.TextField(blank=True, verbose_name="Visa for Vietnamese Citizens (Vietnamese)")

    visa_requirements_us_en = models.TextField(blank=True, verbose_name="Visa for US Citizens (English)")

    visa_requirements_us_vi = models.TextField(blank=True, verbose_name="Visa for US Citizens (Vietnamese)")

    visa_requirements_eu_en = models.TextField(blank=True, verbose_name="Visa for EU Citizens (English)")

    visa_requirements_eu_vi = models.TextField(blank=True, verbose_name="Visa for EU Citizens (Vietnamese)")

    visa_requirements_general_en = models.TextField(blank=True, verbose_name="General Visa Requirements (English)")

    visa_requirements_general_vi = models.TextField(blank=True, verbose_name="General Visa Requirements (Vietnamese)")

   

    # Stay duration and documents

    max_stay_duration = models.CharField(max_length=100, blank=True, verbose_name="Maximum Stay Duration")

    required_documents_en = models.TextField(blank=True, verbose_name="Required Documents (English)")

    required_documents_vi = models.TextField(blank=True, verbose_name="Required Documents (Vietnamese)")

    vaccination_requirements_en = models.TextField(blank=True, verbose_name="Vaccination Requirements (English)")

    vaccination_requirements_vi = models.TextField(blank=True, verbose_name="Vaccination Requirements (Vietnamese)")

   

    # Customs and entry

    customs_duty_free_en = models.TextField(blank=True, verbose_name="Duty-Free Allowances (English)")

    customs_duty_free_vi = models.TextField(blank=True, verbose_name="Duty-Free Allowances (Vietnamese)")

    customs_restrictions_en = models.TextField(blank=True, verbose_name="Customs Restrictions (English)")

    customs_restrictions_vi = models.TextField(blank=True, verbose_name="Customs Restrictions (Vietnamese)")

    entry_requirements_en = models.TextField(blank=True, verbose_name="Entry Requirements (English)")

    entry_requirements_vi = models.TextField(blank=True, verbose_name="Entry Requirements (Vietnamese)")

   

    # ================================

    # 📱 TECHNOLOGY & CONNECTIVITY

    # ================================

    mobile_carriers_en = models.TextField(blank=True, verbose_name="Mobile Carriers (English)")

    mobile_carriers_vi = models.TextField(blank=True, verbose_name="Mobile Carriers (Vietnamese)")

    sim_card_info_en = models.TextField(blank=True, verbose_name="SIM Card Information (English)")

    sim_card_info_vi = models.TextField(blank=True, verbose_name="SIM Card Information (Vietnamese)")

    esim_available = models.BooleanField(default=False, verbose_name="eSIM Available")

    esim_providers_en = models.TextField(blank=True, verbose_name="eSIM Providers (English)")

    esim_providers_vi = models.TextField(blank=True, verbose_name="eSIM Providers (Vietnamese)")

   

    # Internet and connectivity

    internet_coverage_en = models.TextField(blank=True, verbose_name="Internet Coverage (English)")

    internet_coverage_vi = models.TextField(blank=True, verbose_name="Internet Coverage (Vietnamese)")

    wifi_availability_en = models.TextField(blank=True, verbose_name="WiFi Availability (English)")

    wifi_availability_vi = models.TextField(blank=True, verbose_name="WiFi Availability (Vietnamese)")

    internet_speed_quality = models.CharField(max_length=20, choices=[

        ('excellent', 'Excellent'),

        ('good', 'Good'),

        ('moderate', 'Moderate'),

        ('poor', 'Poor')

    ], blank=True, verbose_name="Internet Speed Quality")

   

    # Useful apps

    maps_apps_en = models.TextField(blank=True, verbose_name="Recommended Maps Apps (English)")

    maps_apps_vi = models.TextField(blank=True, verbose_name="Recommended Maps Apps (Vietnamese)")

    transport_apps_en = models.TextField(blank=True, verbose_name="Transport Apps (English)")

    transport_apps_vi = models.TextField(blank=True, verbose_name="Transport Apps (Vietnamese)")

    translation_apps_en = models.TextField(blank=True, verbose_name="Translation Apps (English)")

    translation_apps_vi = models.TextField(blank=True, verbose_name="Translation Apps (Vietnamese)")

    food_delivery_apps_en = models.TextField(blank=True, verbose_name="Food Delivery Apps (English)")

    food_delivery_apps_vi = models.TextField(blank=True, verbose_name="Food Delivery Apps (Vietnamese)")

    other_useful_apps_en = models.TextField(blank=True, verbose_name="Other Useful Apps (English)")

    other_useful_apps_vi = models.TextField(blank=True, verbose_name="Other Useful Apps (Vietnamese)")

   

    # ================================

    # 🚌 TRANSPORTATION

    # ================================

    airports_info_en = models.TextField(blank=True, verbose_name="Airport Information (English)")

    airports_info_vi = models.TextField(blank=True, verbose_name="Airport Information (Vietnamese)")

    airport_transfer_en = models.TextField(blank=True, verbose_name="Airport Transfer Options (English)")

    airport_transfer_vi = models.TextField(blank=True, verbose_name="Airport Transfer Options (Vietnamese)")

   

    # Public transportation

    public_transport_overview_en = models.TextField(blank=True, verbose_name="Public Transport Overview (English)")

    public_transport_overview_vi = models.TextField(blank=True, verbose_name="Public Transport Overview (Vietnamese)")

    metro_system_en = models.TextField(blank=True, verbose_name="Metro/Subway System (English)")

    metro_system_vi = models.TextField(blank=True, verbose_name="Metro/Subway System (Vietnamese)")

    bus_system_en = models.TextField(blank=True, verbose_name="Bus System (English)")

    bus_system_vi = models.TextField(blank=True, verbose_name="Bus System (Vietnamese)")

    taxi_ridehailing_en = models.TextField(blank=True, verbose_name="Taxi & Ride-hailing (English)")

    taxi_ridehailing_vi = models.TextField(blank=True, verbose_name="Taxi & Ride-hailing (Vietnamese)")

   

    # Vehicle rental and intercity travel

    car_rental_info_en = models.TextField(blank=True, verbose_name="Car Rental Information (English)")

    car_rental_info_vi = models.TextField(blank=True, verbose_name="Car Rental Information (Vietnamese)")

    scooter_rental_info_en = models.TextField(blank=True, verbose_name="Scooter/Bike Rental (English)")

    scooter_rental_info_vi = models.TextField(blank=True, verbose_name="Scooter/Bike Rental (Vietnamese)")

    train_system_en = models.TextField(blank=True, verbose_name="Train System (English)")

    train_system_vi = models.TextField(blank=True, verbose_name="Train System (Vietnamese)")

    intercity_travel_en = models.TextField(blank=True, verbose_name="Intercity Travel Options (English)")

    intercity_travel_vi = models.TextField(blank=True, verbose_name="Intercity Travel Options (Vietnamese)")

   

    # ================================

    # 🛟 SAFETY & HEALTH

    # ================================

    safety_overview_en = models.TextField(blank=True, verbose_name="Safety Overview (English)")

    safety_overview_vi = models.TextField(blank=True, verbose_name="Safety Overview (Vietnamese)")

    safety_rating = models.CharField(max_length=20, choices=[

        ('very_safe', 'Very Safe'),

        ('safe', 'Safe'),

        ('moderately_safe', 'Moderately Safe'),

        ('caution_advised', 'Caution Advised'),

        ('high_risk', 'High Risk')

    ], blank=True, verbose_name="Safety Rating")

   

    # Common issues and scams

    common_scams_en = models.TextField(blank=True, verbose_name="Common Scams (English)")

    common_scams_vi = models.TextField(blank=True, verbose_name="Common Scams (Vietnamese)")

    safety_tips_en = models.TextField(blank=True, verbose_name="Safety Tips (English)")

    safety_tips_vi = models.TextField(blank=True, verbose_name="Safety Tips (Vietnamese)")

   

    # Health and medical

    health_precautions_en = models.TextField(blank=True, verbose_name="Health Precautions (English)")

    health_precautions_vi = models.TextField(blank=True, verbose_name="Health Precautions (Vietnamese)")

    medical_facilities_en = models.TextField(blank=True, verbose_name="Medical Facilities (English)")

    medical_facilities_vi = models.TextField(blank=True, verbose_name="Medical Facilities (Vietnamese)")

    travel_insurance_tips_en = models.TextField(blank=True, verbose_name="Travel Insurance Tips (English)")

    travel_insurance_tips_vi = models.TextField(blank=True, verbose_name="Travel Insurance Tips (Vietnamese)")

   

    # Emergency information

    emergency_numbers = models.TextField(blank=True, help_text="JSON format: {'police': '911', 'fire': '911', 'medical': '911'}")

    emergency_contacts_en = models.TextField(blank=True, verbose_name="Emergency Contacts (English)")

    emergency_contacts_vi = models.TextField(blank=True, verbose_name="Emergency Contacts (Vietnamese)")

   

    # ================================

    # 🌤️ CLIMATE & PACKING

    # ================================

    climate_overview_en = models.TextField(blank=True, verbose_name="Climate Overview (English)")

    climate_overview_vi = models.TextField(blank=True, verbose_name="Climate Overview (Vietnamese)")

    seasons_description_en = models.TextField(blank=True, verbose_name="Seasons Description (English)")

    seasons_description_vi = models.TextField(blank=True, verbose_name="Seasons Description (Vietnamese)")

    best_time_to_visit_en = models.CharField(max_length=100, blank=True, verbose_name="Best Time to Visit (English)")

    best_time_to_visit_vi = models.CharField(max_length=100, blank=True, verbose_name="Best Time to Visit (Vietnamese)")

   

    # Weather patterns

    rainy_season_en = models.CharField(max_length=100, blank=True, verbose_name="Rainy Season (English)")

    rainy_season_vi = models.CharField(max_length=100, blank=True, verbose_name="Rainy Season (Vietnamese)")

    dry_season_en = models.CharField(max_length=100, blank=True, verbose_name="Dry Season (English)")

    dry_season_vi = models.CharField(max_length=100, blank=True, verbose_name="Dry Season (Vietnamese)")

    temperature_range = models.CharField(max_length=50, blank=True, verbose_name="Temperature Range")

    humidity_levels = models.CharField(max_length=50, blank=True, verbose_name="Humidity Levels")

   

    # Packing recommendations

    packing_essentials_en = models.TextField(blank=True, verbose_name="Packing Essentials (English)")

    packing_essentials_vi = models.TextField(blank=True, verbose_name="Packing Essentials (Vietnamese)")

    clothing_recommendations_en = models.TextField(blank=True, verbose_name="Clothing Recommendations (English)")

    clothing_recommendations_vi = models.TextField(blank=True, verbose_name="Clothing Recommendations (Vietnamese)")

    cultural_dress_code_en = models.TextField(blank=True, verbose_name="Cultural Dress Code (English)")

    cultural_dress_code_vi = models.TextField(blank=True, verbose_name="Cultural Dress Code (Vietnamese)")

    prohibited_items_en = models.TextField(blank=True, verbose_name="Prohibited Items (English)")

    prohibited_items_vi = models.TextField(blank=True, verbose_name="Prohibited Items (Vietnamese)")

   

    # ================================

    # 🎯 ADDITIONAL FEATURES

    # ================================

    travel_style_recommendations_en = models.TextField(blank=True, verbose_name="Travel Style Recommendations (English)")

    travel_style_recommendations_vi = models.TextField(blank=True, verbose_name="Travel Style Recommendations (Vietnamese)")

    solo_travel_tips_en = models.TextField(blank=True, verbose_name="Solo Travel Tips (English)")

    solo_travel_tips_vi = models.TextField(blank=True, verbose_name="Solo Travel Tips (Vietnamese)")

    family_travel_tips_en = models.TextField(blank=True, verbose_name="Family Travel Tips (English)")

    family_travel_tips_vi = models.TextField(blank=True, verbose_name="Family Travel Tips (Vietnamese)")

    backpacker_tips_en = models.TextField(blank=True, verbose_name="Backpacker Tips (English)")

    backpacker_tips_vi = models.TextField(blank=True, verbose_name="Backpacker Tips (Vietnamese)")

   

    # Country comparison and ranking

    tourism_ranking = models.PositiveIntegerField(blank=True, null=True, verbose_name="Global Tourism Ranking")

    ease_of_travel_score = models.DecimalField(max_digits=3, decimal_places=1, blank=True, null=True, validators=[MinValueValidator(0), MaxValueValidator(10)], verbose_name="Ease of Travel Score (1-10)")

    english_proficiency = models.CharField(max_length=20, choices=[

        ('excellent', 'Excellent'),

        ('good', 'Good'),

        ('moderate', 'Moderate'),

        ('limited', 'Limited'),

        ('very_limited', 'Very Limited')

    ], blank=True, verbose_name="English Proficiency Level")

   

    # Special notes and recommendations

    special_notes_en = models.TextField(blank=True, verbose_name="Special Notes (English)")

    special_notes_vi = models.TextField(blank=True, verbose_name="Special Notes (Vietnamese)")

    insider_tips_en = models.TextField(blank=True, verbose_name="Insider Tips (English)")

    insider_tips_vi = models.TextField(blank=True, verbose_name="Insider Tips (Vietnamese)")

   

    # ================================

    # 📊 ADMINISTRATIVE

    # ================================

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

    is_popular = models.BooleanField(default=False, help_text="Mark as popular destination country")

    featured_order = models.PositiveIntegerField(default=0, help_text="Order for featured countries")

    is_complete_profile = models.BooleanField(default=False, verbose_name="Complete Profile", help_text="Mark if country profile is fully completed")

    profile_completion_percentage = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(100)], verbose_name="Profile Completion %")

   

    class Meta:

        verbose_name = _("Country")

        verbose_name_plural = _("Countries")

        verbose_name_plural = "Countries"

        ordering = ['name_en']

   

    def __str__(self):

        return self.name_en

   

    def get_absolute_url(self):

        return reverse('destinations:country_detail', args=[str(self.id)])

 

 

2

class DestinationCategory(models.Model):

    """Categories for destinations (Beach, Mountain, City, etc.)"""

    name_en = models.CharField(max_length=100)

    name_vi = models.CharField(max_length=100, blank=True)

    description_en = models.TextField(blank=True)

    description_vi = models.TextField(blank=True)

    icon = models.CharField(max_length=50, blank=True, help_text="Font Awesome icon class")

    color = models.CharField(max_length=7, default="#007bff", help_text="Hex color code")

   

    def __str__(self):

        return self.name_en

   

    class Meta:

        verbose_name_plural = "Destination Categories"

 

2.1

class Destination(models.Model):

    """Enhanced destination model with country relationship and TripAdvisor-style features"""

   

    # Basic relationships

    location = models.OneToOneField(Location, on_delete=models.CASCADE, limit_choices_to={'type': 'city'})

    country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='destinations', null=True, blank=True)

    categories = models.ManyToManyField(DestinationCategory, blank=True, help_text="Beach, Mountain, City, etc.")

   

    # Basic info

    title_en = models.CharField(max_length=200)

    title_vi = models.CharField(max_length=200, blank=True)

    description_en = models.TextField()

    description_vi = models.TextField(blank=True)

    short_description_en = models.CharField(max_length=300, blank=True, help_text="For cards and previews")

    short_description_vi = models.CharField(max_length=300, blank=True)

   

    # Images

    main_image = models.ImageField(upload_to='destinations/', blank=True, null=True)

    hero_image = models.ImageField(upload_to='destinations/hero/', blank=True, null=True)

    gallery_images = models.TextField(blank=True, help_text="JSON array of image URLs")

   

    # TripAdvisor-style ratings and rankings

    overall_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                       validators=[MinValueValidator(0), MaxValueValidator(5)])

    total_reviews = models.PositiveIntegerField(default=0)

    ranking_in_country = models.PositiveIntegerField(blank=True, null=True)

    ranking_worldwide = models.PositiveIntegerField(blank=True, null=True)

    traveler_choice_award = models.BooleanField(default=False)

   

    # Visit statistics

    annual_visitors = models.PositiveIntegerField(blank=True, null=True, help_text="Annual visitor count")

    peak_season_months = models.CharField(max_length=100, blank=True, help_text="e.g., June-August")

    off_season_months = models.CharField(max_length=100, blank=True, help_text="e.g., December-February")

   

    # Local destination-specific overrides (if different from country)

    local_currency_notes_en = models.TextField(blank=True, help_text="Destination-specific currency info")

    local_currency_notes_vi = models.TextField(blank=True)

    exchange_rate_note_en = models.TextField(blank=True)

    exchange_rate_note_vi = models.TextField(blank=True)

    payment_methods_en = models.CharField(max_length=200, blank=True)

    payment_methods_vi = models.CharField(max_length=200, blank=True)

    atm_info_en = models.TextField(blank=True)

    atm_info_vi = models.TextField(blank=True)

   

    # Local cultural info

    local_customs_en = models.TextField(blank=True, help_text="City/region specific customs")

    local_customs_vi = models.TextField(blank=True)

    local_etiquette_en = models.TextField(blank=True)

    local_etiquette_vi = models.TextField(blank=True)

    local_language_notes_en = models.TextField(blank=True, help_text="Local dialects, language tips")

    local_language_notes_vi = models.TextField(blank=True)

   

    # Destination-specific practical info

    tipping_culture_en = models.TextField(blank=True)

    tipping_culture_vi = models.TextField(blank=True)

    local_emergency_contacts = models.TextField(blank=True, help_text="JSON format for local emergency numbers")

    hospital_info_en = models.TextField(blank=True)

    hospital_info_vi = models.TextField(blank=True)

   

    # Climate and timing

    climate_info_en = models.TextField(blank=True)

    climate_info_vi = models.TextField(blank=True)

    best_time_to_visit_en = models.CharField(max_length=100, blank=True)

    best_time_to_visit_vi = models.CharField(max_length=100, blank=True)

    weather_highlights_en = models.TextField(blank=True)

    weather_highlights_vi = models.TextField(blank=True)

   

    # Technology and connectivity

    sim_info_en = models.TextField(blank=True)

    sim_info_vi = models.TextField(blank=True)

    wifi_availability_en = models.TextField(blank=True)

    wifi_availability_vi = models.TextField(blank=True)

    connectivity_info_en = models.TextField(blank=True)

    connectivity_info_vi = models.TextField(blank=True)

   

    # Accessibility

    accessibility_info_en = models.TextField(blank=True)

    accessibility_info_vi = models.TextField(blank=True)

    wheelchair_friendly = models.BooleanField(default=False)

    accessibility_rating = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1), MaxValueValidator(5)])

   

    # Transportation

    getting_there_en = models.TextField(blank=True, help_text="How to reach this destination")

    getting_there_vi = models.TextField(blank=True)

    getting_around_en = models.TextField(blank=True, help_text="Local transportation options")

    getting_around_vi = models.TextField(blank=True)

    airport_info_en = models.TextField(blank=True)

    airport_info_vi = models.TextField(blank=True)

   

    # Budget information

    budget_range = models.CharField(max_length=20, choices=[

        ('budget', 'Budget ($)'),

        ('moderate', 'Moderate ($$)'),

        ('expensive', 'Expensive ($$$)'),

        ('luxury', 'Luxury ($$$$)')

    ], blank=True)

    daily_budget_low = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    daily_budget_mid = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    daily_budget_high = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    budget_tips_en = models.TextField(blank=True)

    budget_tips_vi = models.TextField(blank=True)

   

    # SEO and metadata

    meta_description_en = models.CharField(max_length=160, blank=True)

    meta_description_vi = models.CharField(max_length=160, blank=True)

    keywords_en = models.CharField(max_length=200, blank=True, help_text="Comma-separated keywords")

    keywords_vi = models.CharField(max_length=200, blank=True)

   

    # Administrative

    is_featured = models.BooleanField(default=False)

    is_popular = models.BooleanField(default=False)

    is_published = models.BooleanField(default=True)

    featured_order = models.PositiveIntegerField(default=0)

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        verbose_name = _("Destination")

        verbose_name_plural = _("Destinations")

        ordering = ['-is_featured', '-overall_rating', 'title_en']

        indexes = [

            models.Index(fields=['country', 'is_published']),

            models.Index(fields=['overall_rating', 'total_reviews']),

            models.Index(fields=['is_featured', 'is_popular']),

        ]

 

    def __str__(self):

        return self.title_en

 

    def get_absolute_url(self):

        """Return the URL to the destination detail page."""

        return reverse('destinations:destination_detail', args=[str(self.id)])

 

 

 

 

3

class WhatToSee(models.Model):

    """Enhanced attractions model with TripAdvisor-style features"""

    TYPE_CHOICES = [

        ('museum', 'Museum'),

        ('park', 'Park'),

        ('landmark', 'Landmark'),

        ('shopping', 'Shopping'),

        ('nature', 'Nature'),

        ('religious', 'Religious Site'),

        ('entertainment', 'Entertainment'),

        ('beach', 'Beach'),

        ('viewpoint', 'Viewpoint'),

        ('historical', 'Historical Site'),

        ('cultural', 'Cultural Site'),

        ('adventure', 'Adventure Activity'),

        ('other', 'Other'),

    ]

   

    PRICE_RANGE_CHOICES = [

        ('free', 'Free'),

        ('budget', 'Budget ($)'),

        ('moderate', 'Moderate ($$)'),

        ('expensive', 'Expensive ($$$)'),

    ]

   

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='attractions')

    name_en = models.CharField(max_length=200)

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=30, choices=TYPE_CHOICES, default='other')

    description_en = models.TextField()

    description_vi = models.TextField(blank=True, verbose_name="Vietnamese Description")

   

    # TripAdvisor-style ratings

    rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                               validators=[MinValueValidator(0), MaxValueValidator(5)])

    review_count = models.PositiveIntegerField(default=0)

    ranking = models.PositiveIntegerField(blank=True, null=True, help_text="Ranking in destination")

   

    # Practical information

    address_en = models.CharField(max_length=300, blank=True)

    address_vi = models.CharField(max_length=300, blank=True)

    phone = models.CharField(max_length=50, blank=True)

    website = models.URLField(blank=True)

    email = models.EmailField(blank=True)

   

    # Pricing and duration

    price_range = models.CharField(max_length=20, choices=PRICE_RANGE_CHOICES, blank=True)

    entry_fee_notes_en = models.TextField(blank=True)

    entry_fee_notes_vi = models.TextField(blank=True)

    suggested_duration_hours = models.DecimalField(max_digits=4, decimal_places=1, blank=True, null=True)

   

    # Operating information

    opening_hours = models.TextField(blank=True, help_text="JSON format for operating hours")

    best_time_to_visit_en = models.CharField(max_length=200, blank=True)

    best_time_to_visit_vi = models.CharField(max_length=200, blank=True)

   

    # Features and amenities

    accessibility_features = models.TextField(blank=True, help_text="JSON array of accessibility features")

    amenities = models.TextField(blank=True, help_text="JSON array of amenities")

    languages_spoken = models.CharField(max_length=200, blank=True)

   

    # Media

    image = models.ImageField(upload_to='attractions/', blank=True, null=True)

    gallery_images = models.TextField(blank=True, help_text="JSON array of image URLs")

    virtual_tour_url = models.URLField(blank=True)

   

    # Tips and notes

    insider_tips_en = models.TextField(blank=True)

    insider_tips_vi = models.TextField(blank=True)

    what_to_bring_en = models.TextField(blank=True)

    what_to_bring_vi = models.TextField(blank=True)

   

    # Administrative

    is_featured = models.BooleanField(default=False)

    is_popular = models.BooleanField(default=False)

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        ordering = ['-is_featured', '-rating', 'name_en']

        verbose_name = "Attraction"

        verbose_name_plural = "Attractions"

   

    def __str__(self):

        return self.name_en

 

    def get_absolute_url(self):

        """Return the URL to the destination detail page, scrolled to this attraction."""

        return self.destination.get_absolute_url() + f"#attraction-{self.id}"

 

4

class WhereToEat(models.Model):

    """Enhanced restaurant model with TripAdvisor-style features"""

    TYPE_CHOICES = [

        ('restaurant', 'Restaurant'),

        ('cafe', 'Cafe'),

        ('street_food', 'Street Food'),

        ('bar', 'Bar'),

        ('fast_food', 'Fast Food'),

        ('fine_dining', 'Fine Dining'),

        ('food_court', 'Food Court'),

        ('bakery', 'Bakery'),

        ('ice_cream', 'Ice Cream'),

        ('other', 'Other'),

    ]

   

    CUISINE_CHOICES = [

        ('vietnamese', 'Vietnamese'),

        ('italian', 'Italian'),

        ('french', 'French'),

        ('chinese', 'Chinese'),

        ('japanese', 'Japanese'),

        ('korean', 'Korean'),

        ('thai', 'Thai'),

        ('indian', 'Indian'),

        ('american', 'American'),

        ('mediterranean', 'Mediterranean'),

        ('mexican', 'Mexican'),

        ('seafood', 'Seafood'),

        ('vegetarian', 'Vegetarian'),

        ('vegan', 'Vegan'),

        ('international', 'International'),

        ('fusion', 'Fusion'),

        ('other', 'Other'),

    ]

   

    PRICE_RANGE_CHOICES = [

        ('budget', 'Budget ($)'),

        ('moderate', 'Moderate ($$)'),

        ('expensive', 'Expensive ($$$)'),

        ('luxury', 'Luxury ($$$$)'),

    ]

   

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='restaurants')

    name_en = models.CharField(max_length=200)

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=30, choices=TYPE_CHOICES, default='restaurant')

    cuisine = models.CharField(max_length=30, choices=CUISINE_CHOICES, default='other')

    description_en = models.TextField()

    description_vi = models.TextField(blank=True, verbose_name="Vietnamese Description")

   

    # TripAdvisor-style ratings

    rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                               validators=[MinValueValidator(0), MaxValueValidator(5)])

    review_count = models.PositiveIntegerField(default=0)

    ranking = models.PositiveIntegerField(blank=True, null=True, help_text="Ranking in destination")

   

    # Detailed ratings

    food_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                    validators=[MinValueValidator(0), MaxValueValidator(5)])

    service_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                       validators=[MinValueValidator(0), MaxValueValidator(5)])

    value_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                     validators=[MinValueValidator(0), MaxValueValidator(5)])

    atmosphere_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                          validators=[MinValueValidator(0), MaxValueValidator(5)])

   

    # Contact and location

    address_en = models.CharField(max_length=300, blank=True)

    address_vi = models.CharField(max_length=300, blank=True)

    phone = models.CharField(max_length=50, blank=True)

    website = models.URLField(blank=True)

    email = models.EmailField(blank=True)

   

    # Pricing and details

    price_range = models.CharField(max_length=20, choices=PRICE_RANGE_CHOICES, blank=True)

    average_cost_for_two = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    currency = models.CharField(max_length=10, blank=True)

   

    # Operating information

    opening_hours = models.TextField(blank=True, help_text="JSON format for operating hours")

    reservation_required = models.BooleanField(default=False)

    delivery_available = models.BooleanField(default=False)

    takeaway_available = models.BooleanField(default=False)

   

    # Features and amenities

    features = models.TextField(blank=True, help_text="JSON array: outdoor seating, wifi, parking, etc.")

    dietary_options = models.TextField(blank=True, help_text="JSON array: vegetarian, vegan, gluten-free, etc.")

    payment_methods = models.CharField(max_length=200, blank=True)

    languages_spoken = models.CharField(max_length=200, blank=True)

   

    # Special features

    happy_hour = models.BooleanField(default=False)

    live_music = models.BooleanField(default=False)

    outdoor_seating = models.BooleanField(default=False)

    wifi_available = models.BooleanField(default=False)

    parking_available = models.BooleanField(default=False)

    wheelchair_accessible = models.BooleanField(default=False)

   

    # Media

    image = models.ImageField(upload_to='restaurants/', blank=True, null=True)

    gallery_images = models.TextField(blank=True, help_text="JSON array of image URLs")

    menu_url = models.URLField(blank=True)

   

    # Recommendations

    signature_dishes_en = models.TextField(blank=True)

    signature_dishes_vi = models.TextField(blank=True)

    chef_recommendations_en = models.TextField(blank=True)

    chef_recommendations_vi = models.TextField(blank=True)

   

    # Administrative

    is_featured = models.BooleanField(default=False)

    is_popular = models.BooleanField(default=False)

    traveler_choice = models.BooleanField(default=False)

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        ordering = ['-is_featured', '-rating', 'name_en']

        verbose_name = "Restaurant"

        verbose_name_plural = "Restaurants"

 

    def __str__(self):

        return self.name_en

 

    def get_absolute_url(self):

        """Return the URL to the destination detail page, scrolled to this restaurant."""

        return self.destination.get_absolute_url() + f"#restaurant-{self.id}"

 

 

5

class WhereToStay(models.Model):

    """Enhanced accommodation model with TripAdvisor-style features"""

    TYPE_CHOICES = [

        ('hotel', 'Hotel'),

        ('resort', 'Resort'),

        ('hostel', 'Hostel'),

        ('guesthouse', 'Guesthouse'),

        ('villa', 'Villa'),

        ('apartment', 'Apartment'),

        ('homestay', 'Homestay'),

        ('boutique', 'Boutique Hotel'),

        ('luxury', 'Luxury Hotel'),

        ('budget', 'Budget Hotel'),

        ('spa', 'Spa Resort'),

        ('eco', 'Eco Lodge'),

        ('beach', 'Beach Resort'),

        ('city', 'City Hotel'),

        ('other', 'Other'),

    ]

   

    STAR_RATING_CHOICES = [

        ('1', '1 Star'),

        ('2', '2 Stars'),

        ('3', '3 Stars'),

        ('4', '4 Stars'),

        ('5', '5 Stars'),

        ('unrated', 'Unrated'),

    ]

   

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='accommodations')

    name_en = models.CharField(max_length=200)

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=30, choices=TYPE_CHOICES, default='hotel')

    star_rating = models.CharField(max_length=20, choices=STAR_RATING_CHOICES, blank=True, default='unrated')

    description_en = models.TextField()

    description_vi = models.TextField(blank=True, verbose_name="Vietnamese Description")

   

    # TripAdvisor-style ratings

    rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                               validators=[MinValueValidator(0), MaxValueValidator(5)])

    review_count = models.PositiveIntegerField(default=0)

    ranking = models.PositiveIntegerField(blank=True, null=True, help_text="Ranking in destination")

   

    # Detailed ratings

    cleanliness_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                           validators=[MinValueValidator(0), MaxValueValidator(5)])

    service_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                       validators=[MinValueValidator(0), MaxValueValidator(5)])

    value_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                     validators=[MinValueValidator(0), MaxValueValidator(5)])

    location_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                        validators=[MinValueValidator(0), MaxValueValidator(5)])

    comfort_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                       validators=[MinValueValidator(0), MaxValueValidator(5)])

   

    # Contact and location

    address_en = models.CharField(max_length=300, blank=True)

    address_vi = models.CharField(max_length=300, blank=True)

    phone = models.CharField(max_length=50, blank=True)

    website = models.URLField(blank=True)

    email = models.EmailField(blank=True)

   

    # Pricing information

    price_per_night_min = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    price_per_night_max = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    currency = models.CharField(max_length=10, blank=True)

    includes_breakfast = models.BooleanField(default=False)

    includes_wifi = models.BooleanField(default=False)

    includes_parking = models.BooleanField(default=False)

   

    # Property details

    total_rooms = models.PositiveIntegerField(blank=True, null=True)

    check_in_time = models.TimeField(blank=True, null=True)

    check_out_time = models.TimeField(blank=True, null=True)

   

    # Amenities and facilities

    amenities = models.TextField(blank=True, help_text="JSON array: pool, spa, gym, restaurant, etc.")

    room_features = models.TextField(blank=True, help_text="JSON array: AC, TV, minibar, balcony, etc.")

    services = models.TextField(blank=True, help_text="JSON array: room service, concierge, laundry, etc.")

   

    # Accessibility and policies

    wheelchair_accessible = models.BooleanField(default=False)

    pet_friendly = models.BooleanField(default=False)

    family_friendly = models.BooleanField(default=False)

    smoking_allowed = models.BooleanField(default=False)

    age_restriction = models.CharField(max_length=100, blank=True)

   

    # Hotel facilities

    has_restaurant = models.BooleanField(default=False)

    has_bar = models.BooleanField(default=False)

    has_pool = models.BooleanField(default=False)

    has_spa = models.BooleanField(default=False)

    has_gym = models.BooleanField(default=False)

    has_beach_access = models.BooleanField(default=False)

    has_airport_shuttle = models.BooleanField(default=False)

   

    # Connectivity

    wifi_available = models.BooleanField(default=False)

    wifi_free = models.BooleanField(default=False)

    business_center = models.BooleanField(default=False)

   

    # Transportation

    distance_to_city_center = models.CharField(max_length=50, blank=True)

    distance_to_airport = models.CharField(max_length=50, blank=True)

    parking_available = models.BooleanField(default=False)

    parking_free = models.BooleanField(default=False)

   

    # Media

    image = models.ImageField(upload_to='accommodations/', blank=True, null=True)

    gallery_images = models.TextField(blank=True, help_text="JSON array of image URLs")

    virtual_tour_url = models.URLField(blank=True)

   

    # Awards and certifications

    awards = models.TextField(blank=True, help_text="JSON array of awards and certifications")

    eco_certified = models.BooleanField(default=False)

    covid_safety_measures = models.TextField(blank=True)

   

    # Booking information

    booking_url = models.URLField(blank=True)

    instant_booking = models.BooleanField(default=False)

    free_cancellation = models.BooleanField(default=False)

    cancellation_policy_en = models.TextField(blank=True)

    cancellation_policy_vi = models.TextField(blank=True)

   

    # Special features

    is_featured = models.BooleanField(default=False)

    is_popular = models.BooleanField(default=False)

    traveler_choice = models.BooleanField(default=False)

    best_value = models.BooleanField(default=False)

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        ordering = ['-is_featured', '-rating', 'name_en']

        verbose_name = "Accommodation"

        verbose_name_plural = "Accommodations"

 

    def __str__(self):

        return self.name_en

 

    def get_absolute_url(self):

        """Return the URL to the destination detail page, scrolled to this accommodation."""

        return self.destination.get_absolute_url() + f"#accommodation-{self.id}"

 

6

class Event(models.Model):

    """Enhanced event model with TripAdvisor-style features"""

    TYPE_CHOICES = [

        ('festival', 'Festival'),

        ('concert', 'Concert'),

        ('exhibition', 'Exhibition'),

        ('show', 'Show'),

        ('sports', 'Sports Event'),

        ('cultural', 'Cultural Event'),

        ('seasonal', 'Seasonal Event'),

        ('food', 'Food & Wine'),

        ('nightlife', 'Nightlife'),

        ('outdoor', 'Outdoor Activity'),

        ('family', 'Family Event'),

        ('business', 'Business Event'),

        ('religious', 'Religious Event'),

        ('market', 'Market/Fair'),

        ('other', 'Other'),

    ]

   

    FREQUENCY_CHOICES = [

        ('one_time', 'One-time Event'),

        ('daily', 'Daily'),

        ('weekly', 'Weekly'),

        ('monthly', 'Monthly'),

        ('seasonal', 'Seasonal'),

        ('annual', 'Annual'),

        ('irregular', 'Irregular'),

    ]

   

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='events')

    name_en = models.CharField(max_length=200)

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=30, choices=TYPE_CHOICES, default='other')

    description_en = models.TextField()

    description_vi = models.TextField(blank=True, verbose_name="Vietnamese Description")

   

    # TripAdvisor-style ratings

    rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                               validators=[MinValueValidator(0), MaxValueValidator(5)])

    review_count = models.PositiveIntegerField(default=0)

   

    # Event scheduling

    start_date = models.DateField(blank=True, null=True)

    end_date = models.DateField(blank=True, null=True)

    start_time = models.TimeField(blank=True, null=True)

    end_time = models.TimeField(blank=True, null=True)

    frequency = models.CharField(max_length=20, choices=FREQUENCY_CHOICES, default='one_time')

    duration = models.CharField(max_length=100, blank=True, help_text="e.g., 2 hours, 3 days")

   

    # Location details

    venue_name_en = models.CharField(max_length=200, blank=True)

    venue_name_vi = models.CharField(max_length=200, blank=True)

    address_en = models.CharField(max_length=300, blank=True)

    address_vi = models.CharField(max_length=300, blank=True)

   

    # Contact and booking

    website = models.URLField(blank=True)

    phone = models.CharField(max_length=50, blank=True)

    email = models.EmailField(blank=True)

    booking_required = models.BooleanField(default=False)

    booking_url = models.URLField(blank=True)

   

    # Pricing

    is_free = models.BooleanField(default=False)

    price_min = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    price_max = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    currency = models.CharField(max_length=10, blank=True)

    price_details_en = models.TextField(blank=True)

    price_details_vi = models.TextField(blank=True)

   

    # Event features

    age_restrictions = models.CharField(max_length=100, blank=True)

    dress_code = models.CharField(max_length=100, blank=True)

    languages = models.CharField(max_length=200, blank=True, help_text="Languages supported")

    accessibility_info_en = models.TextField(blank=True)

    accessibility_info_vi = models.TextField(blank=True)

   

    # Event amenities

    food_available = models.BooleanField(default=False)

    drinks_available = models.BooleanField(default=False)

    parking_available = models.BooleanField(default=False)

    photography_allowed = models.BooleanField(default=True)

    wheelchair_accessible = models.BooleanField(default=False)

   

    # Weather dependency

    indoor_event = models.BooleanField(default=False)

    weather_dependent = models.BooleanField(default=False)

    rain_alternative_en = models.TextField(blank=True)

    rain_alternative_vi = models.TextField(blank=True)

   

    # Media

    image = models.ImageField(upload_to='events/', blank=True, null=True)

    gallery_images = models.TextField(blank=True, help_text="JSON array of image URLs")

   

    # Event highlights

    highlights_en = models.TextField(blank=True)

    highlights_vi = models.TextField(blank=True)

    what_to_expect_en = models.TextField(blank=True)

    what_to_expect_vi = models.TextField(blank=True)

    what_to_bring_en = models.TextField(blank=True)

    what_to_bring_vi = models.TextField(blank=True)

   

    # Popularity indicators

    is_featured = models.BooleanField(default=False)

    is_popular = models.BooleanField(default=False)

    traveler_choice = models.BooleanField(default=False)

    sold_out = models.BooleanField(default=False)

   

    # Administrative

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        ordering = ['-is_featured', '-start_date', '-rating', 'name_en']

        verbose_name = "Event"

        verbose_name_plural = "Events"

 

    def __str__(self):

        return self.name_en

 

    def get_absolute_url(self):

        """Return the URL to the destination detail page, scrolled to this event."""

        return self.destination.get_absolute_url() + f"#event-{self.id}"

 

    @property

    def is_upcoming(self):

        """Check if event is upcoming"""

        if self.start_date:

            from django.utils import timezone

            return self.start_date >= timezone.now().date()

        return False

 

    @property

    def is_ongoing(self):

        """Check if event is currently ongoing"""

        if self.start_date and self.end_date:

            from django.utils import timezone

            today = timezone.now().date()

            return self.start_date <= today <= self.end_date

        return False

 

7

class SafetyTip(models.Model):

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='safety_tips')

    tip_en = models.TextField()

    tip_vi = models.TextField(blank=True, verbose_name="Vietnamese Safety Tip")

 

    def __str__(self):

        return self.tip_en[:50]

 

8

class UserReview(models.Model):

    """User reviews for destinations and related items"""

    REVIEW_TYPE_CHOICES = [

        ('destination', 'Destination'),

        ('attraction', 'Attraction'),

        ('restaurant', 'Restaurant'),

        ('accommodation', 'Accommodation'),

        ('event', 'Event'),

    ]

   

    TRAVELER_TYPE_CHOICES = [

        ('family', 'Family'),

        ('couple', 'Couple'),

        ('solo', 'Solo Traveler'),

        ('business', 'Business'),

        ('friends', 'Friends'),

        ('other', 'Other'),

    ]

   

    VISIT_TYPE_CHOICES = [

        ('first_time', 'First Time'),

        ('repeat', 'Repeat Visitor'),

    ]

   

    # Review target (polymorphic relationship)

    review_type = models.CharField(max_length=20, choices=REVIEW_TYPE_CHOICES)

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, null=True, blank=True, related_name='user_reviews')

    attraction = models.ForeignKey(WhatToSee, on_delete=models.CASCADE, null=True, blank=True, related_name='user_reviews')

    restaurant = models.ForeignKey(WhereToEat, on_delete=models.CASCADE, null=True, blank=True, related_name='user_reviews')

    accommodation = models.ForeignKey(WhereToStay, on_delete=models.CASCADE, null=True, blank=True, related_name='user_reviews')

    event = models.ForeignKey(Event, on_delete=models.CASCADE, null=True, blank=True, related_name='user_reviews')

   

    # Reviewer information

    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='destination_reviews')

    reviewer_name = models.CharField(max_length=100)

    reviewer_location = models.CharField(max_length=100, blank=True)

    traveler_type = models.CharField(max_length=20, choices=TRAVELER_TYPE_CHOICES, blank=True)

    visit_type = models.CharField(max_length=20, choices=VISIT_TYPE_CHOICES, blank=True)

   

    # Review content

    title = models.CharField(max_length=200)

    review_text = models.TextField()

   

    # Ratings

    overall_rating = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])

   

    # Specific ratings (for different review types)

    service_rating = models.PositiveIntegerField(null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(5)])

    value_rating = models.PositiveIntegerField(null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(5)])

    location_rating = models.PositiveIntegerField(null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(5)])

    cleanliness_rating = models.PositiveIntegerField(null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(5)])

   

    # Visit details

    visit_date = models.DateField(null=True, blank=True)

    stay_duration = models.CharField(max_length=50, blank=True, help_text="e.g., 3 days, 1 week")

   

    # Review metadata

    helpful_votes = models.PositiveIntegerField(default=0)

    total_votes = models.PositiveIntegerField(default=0)

    is_verified = models.BooleanField(default=False)

    is_featured = models.BooleanField(default=False)

   

    # Administrative

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

    is_published = models.BooleanField(default=True)

   

    class Meta:

        ordering = ['-created_at']

        unique_together = [['user', 'review_type', 'destination'], ['user', 'review_type', 'attraction'],

                          ['user', 'review_type', 'restaurant'], ['user', 'review_type', 'accommodation'],

                          ['user', 'review_type', 'event']]

        verbose_name = "User Review"

        verbose_name_plural = "User Reviews"

 

    def __str__(self):

        return f"{self.title} by {self.reviewer_name}"

 

    @property

    def helpful_percentage(self):

        """Calculate helpful vote percentage"""

        if self.total_votes > 0:

            return round((self.helpful_votes / self.total_votes) * 100, 1)

        return 0

 

9

class TravelTip(models.Model):

    """Travel tips and insider advice for destinations"""

    TIP_CATEGORY_CHOICES = [

        ('general', 'General Tips'),

        ('transportation', 'Transportation'),

        ('accommodation', 'Accommodation'),

        ('food', 'Food & Dining'),

        ('shopping', 'Shopping'),

        ('culture', 'Culture & Customs'),

        ('safety', 'Safety'),

        ('money', 'Money & Costs'),

        ('weather', 'Weather & Climate'),

        ('activities', 'Activities'),

        ('photography', 'Photography'),

        ('health', 'Health & Medical'),

        ('packing', 'Packing'),

        ('communication', 'Communication'),

        ('etiquette', 'Local Etiquette'),

    ]

   

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='travel_tips')

    category = models.CharField(max_length=30, choices=TIP_CATEGORY_CHOICES)

    title_en = models.CharField(max_length=200)

    title_vi = models.CharField(max_length=200, blank=True)

    tip_en = models.TextField()

    tip_vi = models.TextField(blank=True)

   

    # Tip metadata

    is_insider_tip = models.BooleanField(default=False)

    is_featured = models.BooleanField(default=False)

    helpful_votes = models.PositiveIntegerField(default=0)

   

    # Author information

    author = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)

    author_name = models.CharField(max_length=100, blank=True)

   

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        ordering = ['-is_featured', '-helpful_votes', '-created_at']

        verbose_name = "Travel Tip"

        verbose_name_plural = "Travel Tips"

 

    def __str__(self):

        return f"{self.title_en} ({self.get_category_display()})"

 

10

class UsefulPhrase(models.Model):

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='phrases')

    phrase_en = models.CharField(max_length=200, default='', verbose_name="English Phrase")

    phrase_vi = models.CharField(max_length=200, default='', verbose_name="Vietnamese Phrase")

    translation = models.CharField(max_length=200, blank=True, verbose_name="Pronunciation Guide")

    note_en = models.CharField(max_length=200, blank=True, verbose_name="English Note")

    note_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Note")

 

    def __str__(self):

        return f"{self.phrase_en} - {self.phrase_vi}"

 

Vietnamese translation is not available for this article. Showing English content:

Destination Models

Nhom

Notes

 

  • Country on/off drop down list is_published
  • Destination Category – Limit selection

 

 

 

from django.db import models

from django.urls import reverse

from django.contrib.auth.models import User

from django.core.validators import MinValueValidator, MaxValueValidator

from django.utils.translation import gettext_lazy as _

from decimal import Decimal

from apps.geo.models import Location

 

  •  

1

class Country(models.Model):

    """Comprehensive country-level information for travel planning"""

   

    # ================================

    # 🏳️ COUNTRY OVERVIEW

    # ================================

    name_en = models.CharField(max_length=100, unique=True, verbose_name="Country Name (English)")

    name_vi = models.CharField(max_length=100, blank=True, verbose_name="Country Name (Vietnamese)")

    official_name_en = models.CharField(max_length=150, blank=True, verbose_name="Official Name (English)")

    official_name_vi = models.CharField(max_length=150, blank=True, verbose_name="Official Name (Vietnamese)")

    country_code = models.CharField(max_length=3, unique=True, help_text="ISO 3166-1 alpha-3 code")

    continent = models.CharField(max_length=50)

    flag_image = models.ImageField(upload_to='countries/flags/', blank=True, null=True)

   

    # Basic geographic info

    capital_en = models.CharField(max_length=100, blank=True, verbose_name="Capital (English)")

    capital_vi = models.CharField(max_length=100, blank=True, verbose_name="Capital (Vietnamese)")

    major_cities_en = models.TextField(blank=True, verbose_name="Major Cities (English)", help_text="Comma-separated list")

    major_cities_vi = models.TextField(blank=True, verbose_name="Major Cities (Vietnamese)", help_text="Comma-separated list")

    population = models.BigIntegerField(blank=True, null=True)

    area_km2 = models.BigIntegerField(blank=True, null=True, help_text="Area in square kilometers")

    gdp_per_capita = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)

   

    # Languages and communication

    official_languages_en = models.CharField(max_length=200, blank=True, verbose_name="Official Languages (English)")

    official_languages_vi = models.CharField(max_length=200, blank=True, verbose_name="Official Languages (Vietnamese)")

    spoken_languages_en = models.CharField(max_length=300, blank=True, verbose_name="Commonly Spoken Languages (English)")

    spoken_languages_vi = models.CharField(max_length=300, blank=True, verbose_name="Commonly Spoken Languages (Vietnamese)")

   

    # Time and location

    time_zones = models.CharField(max_length=200, blank=True, verbose_name="Time Zones")

    calling_code = models.CharField(max_length=10, blank=True, verbose_name="Country Calling Code")

    internet_domain = models.CharField(max_length=10, blank=True, verbose_name="Internet Domain")

   

    # Infrastructure basics

    driving_side = models.CharField(max_length=10, choices=[('left', 'Left'), ('right', 'Right')], blank=True)

    power_plugs = models.CharField(max_length=50, blank=True, help_text="e.g., Type A, B, C")

    voltage = models.CharField(max_length=20, blank=True, help_text="e.g., 110V, 220V")

    frequency = models.CharField(max_length=10, blank=True, help_text="e.g., 50Hz, 60Hz")

   

    # ================================

    # 💰 CURRENCY & FINANCIAL INFO

    # ================================

    currency_name_en = models.CharField(max_length=50, blank=True, verbose_name="Currency Name (English)")

    currency_name_vi = models.CharField(max_length=50, blank=True, verbose_name="Currency Name (Vietnamese)")

    currency_code = models.CharField(max_length=10, blank=True, verbose_name="Currency Code (ISO)")

    currency_symbol = models.CharField(max_length=10, blank=True, verbose_name="Currency Symbol")

    currency_subdivisions_en = models.CharField(max_length=100, blank=True, verbose_name="Currency Subdivisions (English)")

    currency_subdivisions_vi = models.CharField(max_length=100, blank=True, verbose_name="Currency Subdivisions (Vietnamese)")

   

    # Money exchange and usage

    usd_accepted = models.BooleanField(default=False, verbose_name="USD Widely Accepted")

    euro_accepted = models.BooleanField(default=False, verbose_name="EUR Widely Accepted")

    exchange_info_en = models.TextField(blank=True, verbose_name="Money Exchange Info (English)")

    exchange_info_vi = models.TextField(blank=True, verbose_name="Money Exchange Info (Vietnamese)")

    atm_availability_en = models.TextField(blank=True, verbose_name="ATM Availability (English)")

    atm_availability_vi = models.TextField(blank=True, verbose_name="ATM Availability (Vietnamese)")

    credit_cards_accepted = models.BooleanField(default=True, verbose_name="Credit Cards Widely Accepted")

   

    # Tipping and costs

    tipping_culture_en = models.TextField(blank=True, verbose_name="Tipping Culture (English)")

    tipping_culture_vi = models.TextField(blank=True, verbose_name="Tipping Culture (Vietnamese)")

    daily_budget_backpacker = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True, verbose_name="Daily Budget - Backpacker (USD)")

    daily_budget_mid_range = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True, verbose_name="Daily Budget - Mid-range (USD)")

    daily_budget_luxury = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True, verbose_name="Daily Budget - Luxury (USD)")

   

    # ================================

    # 📜 HISTORY & CULTURE

    # ================================

    historical_background_en = models.TextField(blank=True, verbose_name="Historical Background (English)")

    historical_background_vi = models.TextField(blank=True, verbose_name="Historical Background (Vietnamese)")

    cultural_highlights_en = models.TextField(blank=True, verbose_name="Cultural Highlights (English)")

    cultural_highlights_vi = models.TextField(blank=True, verbose_name="Cultural Highlights (Vietnamese)")

   

    # Religion and festivals

    major_religions_en = models.CharField(max_length=200, blank=True, verbose_name="Major Religions (English)")

    major_religions_vi = models.CharField(max_length=200, blank=True, verbose_name="Major Religions (Vietnamese)")

    festivals_holidays_en = models.TextField(blank=True, verbose_name="Major Festivals & Holidays (English)")

    festivals_holidays_vi = models.TextField(blank=True, verbose_name="Major Festivals & Holidays (Vietnamese)")

   

    # Social customs

    customs_traditions_en = models.TextField(blank=True, verbose_name="Customs & Traditions (English)")

    customs_traditions_vi = models.TextField(blank=True, verbose_name="Customs & Traditions (Vietnamese)")

    etiquette_tips_en = models.TextField(blank=True, verbose_name="Etiquette Tips (English)")

    etiquette_tips_vi = models.TextField(blank=True, verbose_name="Etiquette Tips (Vietnamese)")

    cultural_taboos_en = models.TextField(blank=True, verbose_name="Cultural Taboos (English)")

    cultural_taboos_vi = models.TextField(blank=True, verbose_name="Cultural Taboos (Vietnamese)")

   

    # ================================

    # ✈️ ENTRY & VISA REQUIREMENTS

    # ================================

    visa_requirements_vietnamese_en = models.TextField(blank=True, verbose_name="Visa for Vietnamese Citizens (English)")

    visa_requirements_vietnamese_vi = models.TextField(blank=True, verbose_name="Visa for Vietnamese Citizens (Vietnamese)")

    visa_requirements_us_en = models.TextField(blank=True, verbose_name="Visa for US Citizens (English)")

    visa_requirements_us_vi = models.TextField(blank=True, verbose_name="Visa for US Citizens (Vietnamese)")

    visa_requirements_eu_en = models.TextField(blank=True, verbose_name="Visa for EU Citizens (English)")

    visa_requirements_eu_vi = models.TextField(blank=True, verbose_name="Visa for EU Citizens (Vietnamese)")

    visa_requirements_general_en = models.TextField(blank=True, verbose_name="General Visa Requirements (English)")

    visa_requirements_general_vi = models.TextField(blank=True, verbose_name="General Visa Requirements (Vietnamese)")

   

    # Stay duration and documents

    max_stay_duration = models.CharField(max_length=100, blank=True, verbose_name="Maximum Stay Duration")

    required_documents_en = models.TextField(blank=True, verbose_name="Required Documents (English)")

    required_documents_vi = models.TextField(blank=True, verbose_name="Required Documents (Vietnamese)")

    vaccination_requirements_en = models.TextField(blank=True, verbose_name="Vaccination Requirements (English)")

    vaccination_requirements_vi = models.TextField(blank=True, verbose_name="Vaccination Requirements (Vietnamese)")

   

    # Customs and entry

    customs_duty_free_en = models.TextField(blank=True, verbose_name="Duty-Free Allowances (English)")

    customs_duty_free_vi = models.TextField(blank=True, verbose_name="Duty-Free Allowances (Vietnamese)")

    customs_restrictions_en = models.TextField(blank=True, verbose_name="Customs Restrictions (English)")

    customs_restrictions_vi = models.TextField(blank=True, verbose_name="Customs Restrictions (Vietnamese)")

    entry_requirements_en = models.TextField(blank=True, verbose_name="Entry Requirements (English)")

    entry_requirements_vi = models.TextField(blank=True, verbose_name="Entry Requirements (Vietnamese)")

   

    # ================================

    # 📱 TECHNOLOGY & CONNECTIVITY

    # ================================

    mobile_carriers_en = models.TextField(blank=True, verbose_name="Mobile Carriers (English)")

    mobile_carriers_vi = models.TextField(blank=True, verbose_name="Mobile Carriers (Vietnamese)")

    sim_card_info_en = models.TextField(blank=True, verbose_name="SIM Card Information (English)")

    sim_card_info_vi = models.TextField(blank=True, verbose_name="SIM Card Information (Vietnamese)")

    esim_available = models.BooleanField(default=False, verbose_name="eSIM Available")

    esim_providers_en = models.TextField(blank=True, verbose_name="eSIM Providers (English)")

    esim_providers_vi = models.TextField(blank=True, verbose_name="eSIM Providers (Vietnamese)")

   

    # Internet and connectivity

    internet_coverage_en = models.TextField(blank=True, verbose_name="Internet Coverage (English)")

    internet_coverage_vi = models.TextField(blank=True, verbose_name="Internet Coverage (Vietnamese)")

    wifi_availability_en = models.TextField(blank=True, verbose_name="WiFi Availability (English)")

    wifi_availability_vi = models.TextField(blank=True, verbose_name="WiFi Availability (Vietnamese)")

    internet_speed_quality = models.CharField(max_length=20, choices=[

        ('excellent', 'Excellent'),

        ('good', 'Good'),

        ('moderate', 'Moderate'),

        ('poor', 'Poor')

    ], blank=True, verbose_name="Internet Speed Quality")

   

    # Useful apps

    maps_apps_en = models.TextField(blank=True, verbose_name="Recommended Maps Apps (English)")

    maps_apps_vi = models.TextField(blank=True, verbose_name="Recommended Maps Apps (Vietnamese)")

    transport_apps_en = models.TextField(blank=True, verbose_name="Transport Apps (English)")

    transport_apps_vi = models.TextField(blank=True, verbose_name="Transport Apps (Vietnamese)")

    translation_apps_en = models.TextField(blank=True, verbose_name="Translation Apps (English)")

    translation_apps_vi = models.TextField(blank=True, verbose_name="Translation Apps (Vietnamese)")

    food_delivery_apps_en = models.TextField(blank=True, verbose_name="Food Delivery Apps (English)")

    food_delivery_apps_vi = models.TextField(blank=True, verbose_name="Food Delivery Apps (Vietnamese)")

    other_useful_apps_en = models.TextField(blank=True, verbose_name="Other Useful Apps (English)")

    other_useful_apps_vi = models.TextField(blank=True, verbose_name="Other Useful Apps (Vietnamese)")

   

    # ================================

    # 🚌 TRANSPORTATION

    # ================================

    airports_info_en = models.TextField(blank=True, verbose_name="Airport Information (English)")

    airports_info_vi = models.TextField(blank=True, verbose_name="Airport Information (Vietnamese)")

    airport_transfer_en = models.TextField(blank=True, verbose_name="Airport Transfer Options (English)")

    airport_transfer_vi = models.TextField(blank=True, verbose_name="Airport Transfer Options (Vietnamese)")

   

    # Public transportation

    public_transport_overview_en = models.TextField(blank=True, verbose_name="Public Transport Overview (English)")

    public_transport_overview_vi = models.TextField(blank=True, verbose_name="Public Transport Overview (Vietnamese)")

    metro_system_en = models.TextField(blank=True, verbose_name="Metro/Subway System (English)")

    metro_system_vi = models.TextField(blank=True, verbose_name="Metro/Subway System (Vietnamese)")

    bus_system_en = models.TextField(blank=True, verbose_name="Bus System (English)")

    bus_system_vi = models.TextField(blank=True, verbose_name="Bus System (Vietnamese)")

    taxi_ridehailing_en = models.TextField(blank=True, verbose_name="Taxi & Ride-hailing (English)")

    taxi_ridehailing_vi = models.TextField(blank=True, verbose_name="Taxi & Ride-hailing (Vietnamese)")

   

    # Vehicle rental and intercity travel

    car_rental_info_en = models.TextField(blank=True, verbose_name="Car Rental Information (English)")

    car_rental_info_vi = models.TextField(blank=True, verbose_name="Car Rental Information (Vietnamese)")

    scooter_rental_info_en = models.TextField(blank=True, verbose_name="Scooter/Bike Rental (English)")

    scooter_rental_info_vi = models.TextField(blank=True, verbose_name="Scooter/Bike Rental (Vietnamese)")

    train_system_en = models.TextField(blank=True, verbose_name="Train System (English)")

    train_system_vi = models.TextField(blank=True, verbose_name="Train System (Vietnamese)")

    intercity_travel_en = models.TextField(blank=True, verbose_name="Intercity Travel Options (English)")

    intercity_travel_vi = models.TextField(blank=True, verbose_name="Intercity Travel Options (Vietnamese)")

   

    # ================================

    # 🛟 SAFETY & HEALTH

    # ================================

    safety_overview_en = models.TextField(blank=True, verbose_name="Safety Overview (English)")

    safety_overview_vi = models.TextField(blank=True, verbose_name="Safety Overview (Vietnamese)")

    safety_rating = models.CharField(max_length=20, choices=[

        ('very_safe', 'Very Safe'),

        ('safe', 'Safe'),

        ('moderately_safe', 'Moderately Safe'),

        ('caution_advised', 'Caution Advised'),

        ('high_risk', 'High Risk')

    ], blank=True, verbose_name="Safety Rating")

   

    # Common issues and scams

    common_scams_en = models.TextField(blank=True, verbose_name="Common Scams (English)")

    common_scams_vi = models.TextField(blank=True, verbose_name="Common Scams (Vietnamese)")

    safety_tips_en = models.TextField(blank=True, verbose_name="Safety Tips (English)")

    safety_tips_vi = models.TextField(blank=True, verbose_name="Safety Tips (Vietnamese)")

   

    # Health and medical

    health_precautions_en = models.TextField(blank=True, verbose_name="Health Precautions (English)")

    health_precautions_vi = models.TextField(blank=True, verbose_name="Health Precautions (Vietnamese)")

    medical_facilities_en = models.TextField(blank=True, verbose_name="Medical Facilities (English)")

    medical_facilities_vi = models.TextField(blank=True, verbose_name="Medical Facilities (Vietnamese)")

    travel_insurance_tips_en = models.TextField(blank=True, verbose_name="Travel Insurance Tips (English)")

    travel_insurance_tips_vi = models.TextField(blank=True, verbose_name="Travel Insurance Tips (Vietnamese)")

   

    # Emergency information

    emergency_numbers = models.TextField(blank=True, help_text="JSON format: {'police': '911', 'fire': '911', 'medical': '911'}")

    emergency_contacts_en = models.TextField(blank=True, verbose_name="Emergency Contacts (English)")

    emergency_contacts_vi = models.TextField(blank=True, verbose_name="Emergency Contacts (Vietnamese)")

   

    # ================================

    # 🌤️ CLIMATE & PACKING

    # ================================

    climate_overview_en = models.TextField(blank=True, verbose_name="Climate Overview (English)")

    climate_overview_vi = models.TextField(blank=True, verbose_name="Climate Overview (Vietnamese)")

    seasons_description_en = models.TextField(blank=True, verbose_name="Seasons Description (English)")

    seasons_description_vi = models.TextField(blank=True, verbose_name="Seasons Description (Vietnamese)")

    best_time_to_visit_en = models.CharField(max_length=100, blank=True, verbose_name="Best Time to Visit (English)")

    best_time_to_visit_vi = models.CharField(max_length=100, blank=True, verbose_name="Best Time to Visit (Vietnamese)")

   

    # Weather patterns

    rainy_season_en = models.CharField(max_length=100, blank=True, verbose_name="Rainy Season (English)")

    rainy_season_vi = models.CharField(max_length=100, blank=True, verbose_name="Rainy Season (Vietnamese)")

    dry_season_en = models.CharField(max_length=100, blank=True, verbose_name="Dry Season (English)")

    dry_season_vi = models.CharField(max_length=100, blank=True, verbose_name="Dry Season (Vietnamese)")

    temperature_range = models.CharField(max_length=50, blank=True, verbose_name="Temperature Range")

    humidity_levels = models.CharField(max_length=50, blank=True, verbose_name="Humidity Levels")

   

    # Packing recommendations

    packing_essentials_en = models.TextField(blank=True, verbose_name="Packing Essentials (English)")

    packing_essentials_vi = models.TextField(blank=True, verbose_name="Packing Essentials (Vietnamese)")

    clothing_recommendations_en = models.TextField(blank=True, verbose_name="Clothing Recommendations (English)")

    clothing_recommendations_vi = models.TextField(blank=True, verbose_name="Clothing Recommendations (Vietnamese)")

    cultural_dress_code_en = models.TextField(blank=True, verbose_name="Cultural Dress Code (English)")

    cultural_dress_code_vi = models.TextField(blank=True, verbose_name="Cultural Dress Code (Vietnamese)")

    prohibited_items_en = models.TextField(blank=True, verbose_name="Prohibited Items (English)")

    prohibited_items_vi = models.TextField(blank=True, verbose_name="Prohibited Items (Vietnamese)")

   

    # ================================

    # 🎯 ADDITIONAL FEATURES

    # ================================

    travel_style_recommendations_en = models.TextField(blank=True, verbose_name="Travel Style Recommendations (English)")

    travel_style_recommendations_vi = models.TextField(blank=True, verbose_name="Travel Style Recommendations (Vietnamese)")

    solo_travel_tips_en = models.TextField(blank=True, verbose_name="Solo Travel Tips (English)")

    solo_travel_tips_vi = models.TextField(blank=True, verbose_name="Solo Travel Tips (Vietnamese)")

    family_travel_tips_en = models.TextField(blank=True, verbose_name="Family Travel Tips (English)")

    family_travel_tips_vi = models.TextField(blank=True, verbose_name="Family Travel Tips (Vietnamese)")

    backpacker_tips_en = models.TextField(blank=True, verbose_name="Backpacker Tips (English)")

    backpacker_tips_vi = models.TextField(blank=True, verbose_name="Backpacker Tips (Vietnamese)")

   

    # Country comparison and ranking

    tourism_ranking = models.PositiveIntegerField(blank=True, null=True, verbose_name="Global Tourism Ranking")

    ease_of_travel_score = models.DecimalField(max_digits=3, decimal_places=1, blank=True, null=True, validators=[MinValueValidator(0), MaxValueValidator(10)], verbose_name="Ease of Travel Score (1-10)")

    english_proficiency = models.CharField(max_length=20, choices=[

        ('excellent', 'Excellent'),

        ('good', 'Good'),

        ('moderate', 'Moderate'),

        ('limited', 'Limited'),

        ('very_limited', 'Very Limited')

    ], blank=True, verbose_name="English Proficiency Level")

   

    # Special notes and recommendations

    special_notes_en = models.TextField(blank=True, verbose_name="Special Notes (English)")

    special_notes_vi = models.TextField(blank=True, verbose_name="Special Notes (Vietnamese)")

    insider_tips_en = models.TextField(blank=True, verbose_name="Insider Tips (English)")

    insider_tips_vi = models.TextField(blank=True, verbose_name="Insider Tips (Vietnamese)")

   

    # ================================

    # 📊 ADMINISTRATIVE

    # ================================

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

    is_popular = models.BooleanField(default=False, help_text="Mark as popular destination country")

    featured_order = models.PositiveIntegerField(default=0, help_text="Order for featured countries")

    is_complete_profile = models.BooleanField(default=False, verbose_name="Complete Profile", help_text="Mark if country profile is fully completed")

    profile_completion_percentage = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(100)], verbose_name="Profile Completion %")

   

    class Meta:

        verbose_name = _("Country")

        verbose_name_plural = _("Countries")

        verbose_name_plural = "Countries"

        ordering = ['name_en']

   

    def __str__(self):

        return self.name_en

   

    def get_absolute_url(self):

        return reverse('destinations:country_detail', args=[str(self.id)])

 

 

2

class DestinationCategory(models.Model):

    """Categories for destinations (Beach, Mountain, City, etc.)"""

    name_en = models.CharField(max_length=100)

    name_vi = models.CharField(max_length=100, blank=True)

    description_en = models.TextField(blank=True)

    description_vi = models.TextField(blank=True)

    icon = models.CharField(max_length=50, blank=True, help_text="Font Awesome icon class")

    color = models.CharField(max_length=7, default="#007bff", help_text="Hex color code")

   

    def __str__(self):

        return self.name_en

   

    class Meta:

        verbose_name_plural = "Destination Categories"

 

2.1

class Destination(models.Model):

    """Enhanced destination model with country relationship and TripAdvisor-style features"""

   

    # Basic relationships

    location = models.OneToOneField(Location, on_delete=models.CASCADE, limit_choices_to={'type': 'city'})

    country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='destinations', null=True, blank=True)

    categories = models.ManyToManyField(DestinationCategory, blank=True, help_text="Beach, Mountain, City, etc.")

   

    # Basic info

    title_en = models.CharField(max_length=200)

    title_vi = models.CharField(max_length=200, blank=True)

    description_en = models.TextField()

    description_vi = models.TextField(blank=True)

    short_description_en = models.CharField(max_length=300, blank=True, help_text="For cards and previews")

    short_description_vi = models.CharField(max_length=300, blank=True)

   

    # Images

    main_image = models.ImageField(upload_to='destinations/', blank=True, null=True)

    hero_image = models.ImageField(upload_to='destinations/hero/', blank=True, null=True)

    gallery_images = models.TextField(blank=True, help_text="JSON array of image URLs")

   

    # TripAdvisor-style ratings and rankings

    overall_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                       validators=[MinValueValidator(0), MaxValueValidator(5)])

    total_reviews = models.PositiveIntegerField(default=0)

    ranking_in_country = models.PositiveIntegerField(blank=True, null=True)

    ranking_worldwide = models.PositiveIntegerField(blank=True, null=True)

    traveler_choice_award = models.BooleanField(default=False)

   

    # Visit statistics

    annual_visitors = models.PositiveIntegerField(blank=True, null=True, help_text="Annual visitor count")

    peak_season_months = models.CharField(max_length=100, blank=True, help_text="e.g., June-August")

    off_season_months = models.CharField(max_length=100, blank=True, help_text="e.g., December-February")

   

    # Local destination-specific overrides (if different from country)

    local_currency_notes_en = models.TextField(blank=True, help_text="Destination-specific currency info")

    local_currency_notes_vi = models.TextField(blank=True)

    exchange_rate_note_en = models.TextField(blank=True)

    exchange_rate_note_vi = models.TextField(blank=True)

    payment_methods_en = models.CharField(max_length=200, blank=True)

    payment_methods_vi = models.CharField(max_length=200, blank=True)

    atm_info_en = models.TextField(blank=True)

    atm_info_vi = models.TextField(blank=True)

   

    # Local cultural info

    local_customs_en = models.TextField(blank=True, help_text="City/region specific customs")

    local_customs_vi = models.TextField(blank=True)

    local_etiquette_en = models.TextField(blank=True)

    local_etiquette_vi = models.TextField(blank=True)

    local_language_notes_en = models.TextField(blank=True, help_text="Local dialects, language tips")

    local_language_notes_vi = models.TextField(blank=True)

   

    # Destination-specific practical info

    tipping_culture_en = models.TextField(blank=True)

    tipping_culture_vi = models.TextField(blank=True)

    local_emergency_contacts = models.TextField(blank=True, help_text="JSON format for local emergency numbers")

    hospital_info_en = models.TextField(blank=True)

    hospital_info_vi = models.TextField(blank=True)

   

    # Climate and timing

    climate_info_en = models.TextField(blank=True)

    climate_info_vi = models.TextField(blank=True)

    best_time_to_visit_en = models.CharField(max_length=100, blank=True)

    best_time_to_visit_vi = models.CharField(max_length=100, blank=True)

    weather_highlights_en = models.TextField(blank=True)

    weather_highlights_vi = models.TextField(blank=True)

   

    # Technology and connectivity

    sim_info_en = models.TextField(blank=True)

    sim_info_vi = models.TextField(blank=True)

    wifi_availability_en = models.TextField(blank=True)

    wifi_availability_vi = models.TextField(blank=True)

    connectivity_info_en = models.TextField(blank=True)

    connectivity_info_vi = models.TextField(blank=True)

   

    # Accessibility

    accessibility_info_en = models.TextField(blank=True)

    accessibility_info_vi = models.TextField(blank=True)

    wheelchair_friendly = models.BooleanField(default=False)

    accessibility_rating = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1), MaxValueValidator(5)])

   

    # Transportation

    getting_there_en = models.TextField(blank=True, help_text="How to reach this destination")

    getting_there_vi = models.TextField(blank=True)

    getting_around_en = models.TextField(blank=True, help_text="Local transportation options")

    getting_around_vi = models.TextField(blank=True)

    airport_info_en = models.TextField(blank=True)

    airport_info_vi = models.TextField(blank=True)

   

    # Budget information

    budget_range = models.CharField(max_length=20, choices=[

        ('budget', 'Budget ($)'),

        ('moderate', 'Moderate ($$)'),

        ('expensive', 'Expensive ($$$)'),

        ('luxury', 'Luxury ($$$$)')

    ], blank=True)

    daily_budget_low = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    daily_budget_mid = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    daily_budget_high = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    budget_tips_en = models.TextField(blank=True)

    budget_tips_vi = models.TextField(blank=True)

   

    # SEO and metadata

    meta_description_en = models.CharField(max_length=160, blank=True)

    meta_description_vi = models.CharField(max_length=160, blank=True)

    keywords_en = models.CharField(max_length=200, blank=True, help_text="Comma-separated keywords")

    keywords_vi = models.CharField(max_length=200, blank=True)

   

    # Administrative

    is_featured = models.BooleanField(default=False)

    is_popular = models.BooleanField(default=False)

    is_published = models.BooleanField(default=True)

    featured_order = models.PositiveIntegerField(default=0)

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        verbose_name = _("Destination")

        verbose_name_plural = _("Destinations")

        ordering = ['-is_featured', '-overall_rating', 'title_en']

        indexes = [

            models.Index(fields=['country', 'is_published']),

            models.Index(fields=['overall_rating', 'total_reviews']),

            models.Index(fields=['is_featured', 'is_popular']),

        ]

 

    def __str__(self):

        return self.title_en

 

    def get_absolute_url(self):

        """Return the URL to the destination detail page."""

        return reverse('destinations:destination_detail', args=[str(self.id)])

 

 

 

 

3

class WhatToSee(models.Model):

    """Enhanced attractions model with TripAdvisor-style features"""

    TYPE_CHOICES = [

        ('museum', 'Museum'),

        ('park', 'Park'),

        ('landmark', 'Landmark'),

        ('shopping', 'Shopping'),

        ('nature', 'Nature'),

        ('religious', 'Religious Site'),

        ('entertainment', 'Entertainment'),

        ('beach', 'Beach'),

        ('viewpoint', 'Viewpoint'),

        ('historical', 'Historical Site'),

        ('cultural', 'Cultural Site'),

        ('adventure', 'Adventure Activity'),

        ('other', 'Other'),

    ]

   

    PRICE_RANGE_CHOICES = [

        ('free', 'Free'),

        ('budget', 'Budget ($)'),

        ('moderate', 'Moderate ($$)'),

        ('expensive', 'Expensive ($$$)'),

    ]

   

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='attractions')

    name_en = models.CharField(max_length=200)

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=30, choices=TYPE_CHOICES, default='other')

    description_en = models.TextField()

    description_vi = models.TextField(blank=True, verbose_name="Vietnamese Description")

   

    # TripAdvisor-style ratings

    rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                               validators=[MinValueValidator(0), MaxValueValidator(5)])

    review_count = models.PositiveIntegerField(default=0)

    ranking = models.PositiveIntegerField(blank=True, null=True, help_text="Ranking in destination")

   

    # Practical information

    address_en = models.CharField(max_length=300, blank=True)

    address_vi = models.CharField(max_length=300, blank=True)

    phone = models.CharField(max_length=50, blank=True)

    website = models.URLField(blank=True)

    email = models.EmailField(blank=True)

   

    # Pricing and duration

    price_range = models.CharField(max_length=20, choices=PRICE_RANGE_CHOICES, blank=True)

    entry_fee_notes_en = models.TextField(blank=True)

    entry_fee_notes_vi = models.TextField(blank=True)

    suggested_duration_hours = models.DecimalField(max_digits=4, decimal_places=1, blank=True, null=True)

   

    # Operating information

    opening_hours = models.TextField(blank=True, help_text="JSON format for operating hours")

    best_time_to_visit_en = models.CharField(max_length=200, blank=True)

    best_time_to_visit_vi = models.CharField(max_length=200, blank=True)

   

    # Features and amenities

    accessibility_features = models.TextField(blank=True, help_text="JSON array of accessibility features")

    amenities = models.TextField(blank=True, help_text="JSON array of amenities")

    languages_spoken = models.CharField(max_length=200, blank=True)

   

    # Media

    image = models.ImageField(upload_to='attractions/', blank=True, null=True)

    gallery_images = models.TextField(blank=True, help_text="JSON array of image URLs")

    virtual_tour_url = models.URLField(blank=True)

   

    # Tips and notes

    insider_tips_en = models.TextField(blank=True)

    insider_tips_vi = models.TextField(blank=True)

    what_to_bring_en = models.TextField(blank=True)

    what_to_bring_vi = models.TextField(blank=True)

   

    # Administrative

    is_featured = models.BooleanField(default=False)

    is_popular = models.BooleanField(default=False)

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        ordering = ['-is_featured', '-rating', 'name_en']

        verbose_name = "Attraction"

        verbose_name_plural = "Attractions"

   

    def __str__(self):

        return self.name_en

 

    def get_absolute_url(self):

        """Return the URL to the destination detail page, scrolled to this attraction."""

        return self.destination.get_absolute_url() + f"#attraction-{self.id}"

 

4

class WhereToEat(models.Model):

    """Enhanced restaurant model with TripAdvisor-style features"""

    TYPE_CHOICES = [

        ('restaurant', 'Restaurant'),

        ('cafe', 'Cafe'),

        ('street_food', 'Street Food'),

        ('bar', 'Bar'),

        ('fast_food', 'Fast Food'),

        ('fine_dining', 'Fine Dining'),

        ('food_court', 'Food Court'),

        ('bakery', 'Bakery'),

        ('ice_cream', 'Ice Cream'),

        ('other', 'Other'),

    ]

   

    CUISINE_CHOICES = [

        ('vietnamese', 'Vietnamese'),

        ('italian', 'Italian'),

        ('french', 'French'),

        ('chinese', 'Chinese'),

        ('japanese', 'Japanese'),

        ('korean', 'Korean'),

        ('thai', 'Thai'),

        ('indian', 'Indian'),

        ('american', 'American'),

        ('mediterranean', 'Mediterranean'),

        ('mexican', 'Mexican'),

        ('seafood', 'Seafood'),

        ('vegetarian', 'Vegetarian'),

        ('vegan', 'Vegan'),

        ('international', 'International'),

        ('fusion', 'Fusion'),

        ('other', 'Other'),

    ]

   

    PRICE_RANGE_CHOICES = [

        ('budget', 'Budget ($)'),

        ('moderate', 'Moderate ($$)'),

        ('expensive', 'Expensive ($$$)'),

        ('luxury', 'Luxury ($$$$)'),

    ]

   

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='restaurants')

    name_en = models.CharField(max_length=200)

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=30, choices=TYPE_CHOICES, default='restaurant')

    cuisine = models.CharField(max_length=30, choices=CUISINE_CHOICES, default='other')

    description_en = models.TextField()

    description_vi = models.TextField(blank=True, verbose_name="Vietnamese Description")

   

    # TripAdvisor-style ratings

    rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                               validators=[MinValueValidator(0), MaxValueValidator(5)])

    review_count = models.PositiveIntegerField(default=0)

    ranking = models.PositiveIntegerField(blank=True, null=True, help_text="Ranking in destination")

   

    # Detailed ratings

    food_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                    validators=[MinValueValidator(0), MaxValueValidator(5)])

    service_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                       validators=[MinValueValidator(0), MaxValueValidator(5)])

    value_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                     validators=[MinValueValidator(0), MaxValueValidator(5)])

    atmosphere_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                          validators=[MinValueValidator(0), MaxValueValidator(5)])

   

    # Contact and location

    address_en = models.CharField(max_length=300, blank=True)

    address_vi = models.CharField(max_length=300, blank=True)

    phone = models.CharField(max_length=50, blank=True)

    website = models.URLField(blank=True)

    email = models.EmailField(blank=True)

   

    # Pricing and details

    price_range = models.CharField(max_length=20, choices=PRICE_RANGE_CHOICES, blank=True)

    average_cost_for_two = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    currency = models.CharField(max_length=10, blank=True)

   

    # Operating information

    opening_hours = models.TextField(blank=True, help_text="JSON format for operating hours")

    reservation_required = models.BooleanField(default=False)

    delivery_available = models.BooleanField(default=False)

    takeaway_available = models.BooleanField(default=False)

   

    # Features and amenities

    features = models.TextField(blank=True, help_text="JSON array: outdoor seating, wifi, parking, etc.")

    dietary_options = models.TextField(blank=True, help_text="JSON array: vegetarian, vegan, gluten-free, etc.")

    payment_methods = models.CharField(max_length=200, blank=True)

    languages_spoken = models.CharField(max_length=200, blank=True)

   

    # Special features

    happy_hour = models.BooleanField(default=False)

    live_music = models.BooleanField(default=False)

    outdoor_seating = models.BooleanField(default=False)

    wifi_available = models.BooleanField(default=False)

    parking_available = models.BooleanField(default=False)

    wheelchair_accessible = models.BooleanField(default=False)

   

    # Media

    image = models.ImageField(upload_to='restaurants/', blank=True, null=True)

    gallery_images = models.TextField(blank=True, help_text="JSON array of image URLs")

    menu_url = models.URLField(blank=True)

   

    # Recommendations

    signature_dishes_en = models.TextField(blank=True)

    signature_dishes_vi = models.TextField(blank=True)

    chef_recommendations_en = models.TextField(blank=True)

    chef_recommendations_vi = models.TextField(blank=True)

   

    # Administrative

    is_featured = models.BooleanField(default=False)

    is_popular = models.BooleanField(default=False)

    traveler_choice = models.BooleanField(default=False)

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        ordering = ['-is_featured', '-rating', 'name_en']

        verbose_name = "Restaurant"

        verbose_name_plural = "Restaurants"

 

    def __str__(self):

        return self.name_en

 

    def get_absolute_url(self):

        """Return the URL to the destination detail page, scrolled to this restaurant."""

        return self.destination.get_absolute_url() + f"#restaurant-{self.id}"

 

 

5

class WhereToStay(models.Model):

    """Enhanced accommodation model with TripAdvisor-style features"""

    TYPE_CHOICES = [

        ('hotel', 'Hotel'),

        ('resort', 'Resort'),

        ('hostel', 'Hostel'),

        ('guesthouse', 'Guesthouse'),

        ('villa', 'Villa'),

        ('apartment', 'Apartment'),

        ('homestay', 'Homestay'),

        ('boutique', 'Boutique Hotel'),

        ('luxury', 'Luxury Hotel'),

        ('budget', 'Budget Hotel'),

        ('spa', 'Spa Resort'),

        ('eco', 'Eco Lodge'),

        ('beach', 'Beach Resort'),

        ('city', 'City Hotel'),

        ('other', 'Other'),

    ]

   

    STAR_RATING_CHOICES = [

        ('1', '1 Star'),

        ('2', '2 Stars'),

        ('3', '3 Stars'),

        ('4', '4 Stars'),

        ('5', '5 Stars'),

        ('unrated', 'Unrated'),

    ]

   

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='accommodations')

    name_en = models.CharField(max_length=200)

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=30, choices=TYPE_CHOICES, default='hotel')

    star_rating = models.CharField(max_length=20, choices=STAR_RATING_CHOICES, blank=True, default='unrated')

    description_en = models.TextField()

    description_vi = models.TextField(blank=True, verbose_name="Vietnamese Description")

   

    # TripAdvisor-style ratings

    rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                               validators=[MinValueValidator(0), MaxValueValidator(5)])

    review_count = models.PositiveIntegerField(default=0)

    ranking = models.PositiveIntegerField(blank=True, null=True, help_text="Ranking in destination")

   

    # Detailed ratings

    cleanliness_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                           validators=[MinValueValidator(0), MaxValueValidator(5)])

    service_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                       validators=[MinValueValidator(0), MaxValueValidator(5)])

    value_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                     validators=[MinValueValidator(0), MaxValueValidator(5)])

    location_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                        validators=[MinValueValidator(0), MaxValueValidator(5)])

    comfort_rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                                       validators=[MinValueValidator(0), MaxValueValidator(5)])

   

    # Contact and location

    address_en = models.CharField(max_length=300, blank=True)

    address_vi = models.CharField(max_length=300, blank=True)

    phone = models.CharField(max_length=50, blank=True)

    website = models.URLField(blank=True)

    email = models.EmailField(blank=True)

   

    # Pricing information

    price_per_night_min = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    price_per_night_max = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    currency = models.CharField(max_length=10, blank=True)

    includes_breakfast = models.BooleanField(default=False)

    includes_wifi = models.BooleanField(default=False)

    includes_parking = models.BooleanField(default=False)

   

    # Property details

    total_rooms = models.PositiveIntegerField(blank=True, null=True)

    check_in_time = models.TimeField(blank=True, null=True)

    check_out_time = models.TimeField(blank=True, null=True)

   

    # Amenities and facilities

    amenities = models.TextField(blank=True, help_text="JSON array: pool, spa, gym, restaurant, etc.")

    room_features = models.TextField(blank=True, help_text="JSON array: AC, TV, minibar, balcony, etc.")

    services = models.TextField(blank=True, help_text="JSON array: room service, concierge, laundry, etc.")

   

    # Accessibility and policies

    wheelchair_accessible = models.BooleanField(default=False)

    pet_friendly = models.BooleanField(default=False)

    family_friendly = models.BooleanField(default=False)

    smoking_allowed = models.BooleanField(default=False)

    age_restriction = models.CharField(max_length=100, blank=True)

   

    # Hotel facilities

    has_restaurant = models.BooleanField(default=False)

    has_bar = models.BooleanField(default=False)

    has_pool = models.BooleanField(default=False)

    has_spa = models.BooleanField(default=False)

    has_gym = models.BooleanField(default=False)

    has_beach_access = models.BooleanField(default=False)

    has_airport_shuttle = models.BooleanField(default=False)

   

    # Connectivity

    wifi_available = models.BooleanField(default=False)

    wifi_free = models.BooleanField(default=False)

    business_center = models.BooleanField(default=False)

   

    # Transportation

    distance_to_city_center = models.CharField(max_length=50, blank=True)

    distance_to_airport = models.CharField(max_length=50, blank=True)

    parking_available = models.BooleanField(default=False)

    parking_free = models.BooleanField(default=False)

   

    # Media

    image = models.ImageField(upload_to='accommodations/', blank=True, null=True)

    gallery_images = models.TextField(blank=True, help_text="JSON array of image URLs")

    virtual_tour_url = models.URLField(blank=True)

   

    # Awards and certifications

    awards = models.TextField(blank=True, help_text="JSON array of awards and certifications")

    eco_certified = models.BooleanField(default=False)

    covid_safety_measures = models.TextField(blank=True)

   

    # Booking information

    booking_url = models.URLField(blank=True)

    instant_booking = models.BooleanField(default=False)

    free_cancellation = models.BooleanField(default=False)

    cancellation_policy_en = models.TextField(blank=True)

    cancellation_policy_vi = models.TextField(blank=True)

   

    # Special features

    is_featured = models.BooleanField(default=False)

    is_popular = models.BooleanField(default=False)

    traveler_choice = models.BooleanField(default=False)

    best_value = models.BooleanField(default=False)

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        ordering = ['-is_featured', '-rating', 'name_en']

        verbose_name = "Accommodation"

        verbose_name_plural = "Accommodations"

 

    def __str__(self):

        return self.name_en

 

    def get_absolute_url(self):

        """Return the URL to the destination detail page, scrolled to this accommodation."""

        return self.destination.get_absolute_url() + f"#accommodation-{self.id}"

 

6

class Event(models.Model):

    """Enhanced event model with TripAdvisor-style features"""

    TYPE_CHOICES = [

        ('festival', 'Festival'),

        ('concert', 'Concert'),

        ('exhibition', 'Exhibition'),

        ('show', 'Show'),

        ('sports', 'Sports Event'),

        ('cultural', 'Cultural Event'),

        ('seasonal', 'Seasonal Event'),

        ('food', 'Food & Wine'),

        ('nightlife', 'Nightlife'),

        ('outdoor', 'Outdoor Activity'),

        ('family', 'Family Event'),

        ('business', 'Business Event'),

        ('religious', 'Religious Event'),

        ('market', 'Market/Fair'),

        ('other', 'Other'),

    ]

   

    FREQUENCY_CHOICES = [

        ('one_time', 'One-time Event'),

        ('daily', 'Daily'),

        ('weekly', 'Weekly'),

        ('monthly', 'Monthly'),

        ('seasonal', 'Seasonal'),

        ('annual', 'Annual'),

        ('irregular', 'Irregular'),

    ]

   

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='events')

    name_en = models.CharField(max_length=200)

    name_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Name")

    type = models.CharField(max_length=30, choices=TYPE_CHOICES, default='other')

    description_en = models.TextField()

    description_vi = models.TextField(blank=True, verbose_name="Vietnamese Description")

   

    # TripAdvisor-style ratings

    rating = models.DecimalField(max_digits=3, decimal_places=2, default=0.0,

                               validators=[MinValueValidator(0), MaxValueValidator(5)])

    review_count = models.PositiveIntegerField(default=0)

   

    # Event scheduling

    start_date = models.DateField(blank=True, null=True)

    end_date = models.DateField(blank=True, null=True)

    start_time = models.TimeField(blank=True, null=True)

    end_time = models.TimeField(blank=True, null=True)

    frequency = models.CharField(max_length=20, choices=FREQUENCY_CHOICES, default='one_time')

    duration = models.CharField(max_length=100, blank=True, help_text="e.g., 2 hours, 3 days")

   

    # Location details

    venue_name_en = models.CharField(max_length=200, blank=True)

    venue_name_vi = models.CharField(max_length=200, blank=True)

    address_en = models.CharField(max_length=300, blank=True)

    address_vi = models.CharField(max_length=300, blank=True)

   

    # Contact and booking

    website = models.URLField(blank=True)

    phone = models.CharField(max_length=50, blank=True)

    email = models.EmailField(blank=True)

    booking_required = models.BooleanField(default=False)

    booking_url = models.URLField(blank=True)

   

    # Pricing

    is_free = models.BooleanField(default=False)

    price_min = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    price_max = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)

    currency = models.CharField(max_length=10, blank=True)

    price_details_en = models.TextField(blank=True)

    price_details_vi = models.TextField(blank=True)

   

    # Event features

    age_restrictions = models.CharField(max_length=100, blank=True)

    dress_code = models.CharField(max_length=100, blank=True)

    languages = models.CharField(max_length=200, blank=True, help_text="Languages supported")

    accessibility_info_en = models.TextField(blank=True)

    accessibility_info_vi = models.TextField(blank=True)

   

    # Event amenities

    food_available = models.BooleanField(default=False)

    drinks_available = models.BooleanField(default=False)

    parking_available = models.BooleanField(default=False)

    photography_allowed = models.BooleanField(default=True)

    wheelchair_accessible = models.BooleanField(default=False)

   

    # Weather dependency

    indoor_event = models.BooleanField(default=False)

    weather_dependent = models.BooleanField(default=False)

    rain_alternative_en = models.TextField(blank=True)

    rain_alternative_vi = models.TextField(blank=True)

   

    # Media

    image = models.ImageField(upload_to='events/', blank=True, null=True)

    gallery_images = models.TextField(blank=True, help_text="JSON array of image URLs")

   

    # Event highlights

    highlights_en = models.TextField(blank=True)

    highlights_vi = models.TextField(blank=True)

    what_to_expect_en = models.TextField(blank=True)

    what_to_expect_vi = models.TextField(blank=True)

    what_to_bring_en = models.TextField(blank=True)

    what_to_bring_vi = models.TextField(blank=True)

   

    # Popularity indicators

    is_featured = models.BooleanField(default=False)

    is_popular = models.BooleanField(default=False)

    traveler_choice = models.BooleanField(default=False)

    sold_out = models.BooleanField(default=False)

   

    # Administrative

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        ordering = ['-is_featured', '-start_date', '-rating', 'name_en']

        verbose_name = "Event"

        verbose_name_plural = "Events"

 

    def __str__(self):

        return self.name_en

 

    def get_absolute_url(self):

        """Return the URL to the destination detail page, scrolled to this event."""

        return self.destination.get_absolute_url() + f"#event-{self.id}"

 

    @property

    def is_upcoming(self):

        """Check if event is upcoming"""

        if self.start_date:

            from django.utils import timezone

            return self.start_date >= timezone.now().date()

        return False

 

    @property

    def is_ongoing(self):

        """Check if event is currently ongoing"""

        if self.start_date and self.end_date:

            from django.utils import timezone

            today = timezone.now().date()

            return self.start_date <= today <= self.end_date

        return False

 

7

class SafetyTip(models.Model):

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='safety_tips')

    tip_en = models.TextField()

    tip_vi = models.TextField(blank=True, verbose_name="Vietnamese Safety Tip")

 

    def __str__(self):

        return self.tip_en[:50]

 

8

class UserReview(models.Model):

    """User reviews for destinations and related items"""

    REVIEW_TYPE_CHOICES = [

        ('destination', 'Destination'),

        ('attraction', 'Attraction'),

        ('restaurant', 'Restaurant'),

        ('accommodation', 'Accommodation'),

        ('event', 'Event'),

    ]

   

    TRAVELER_TYPE_CHOICES = [

        ('family', 'Family'),

        ('couple', 'Couple'),

        ('solo', 'Solo Traveler'),

        ('business', 'Business'),

        ('friends', 'Friends'),

        ('other', 'Other'),

    ]

   

    VISIT_TYPE_CHOICES = [

        ('first_time', 'First Time'),

        ('repeat', 'Repeat Visitor'),

    ]

   

    # Review target (polymorphic relationship)

    review_type = models.CharField(max_length=20, choices=REVIEW_TYPE_CHOICES)

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, null=True, blank=True, related_name='user_reviews')

    attraction = models.ForeignKey(WhatToSee, on_delete=models.CASCADE, null=True, blank=True, related_name='user_reviews')

    restaurant = models.ForeignKey(WhereToEat, on_delete=models.CASCADE, null=True, blank=True, related_name='user_reviews')

    accommodation = models.ForeignKey(WhereToStay, on_delete=models.CASCADE, null=True, blank=True, related_name='user_reviews')

    event = models.ForeignKey(Event, on_delete=models.CASCADE, null=True, blank=True, related_name='user_reviews')

   

    # Reviewer information

    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='destination_reviews')

    reviewer_name = models.CharField(max_length=100)

    reviewer_location = models.CharField(max_length=100, blank=True)

    traveler_type = models.CharField(max_length=20, choices=TRAVELER_TYPE_CHOICES, blank=True)

    visit_type = models.CharField(max_length=20, choices=VISIT_TYPE_CHOICES, blank=True)

   

    # Review content

    title = models.CharField(max_length=200)

    review_text = models.TextField()

   

    # Ratings

    overall_rating = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])

   

    # Specific ratings (for different review types)

    service_rating = models.PositiveIntegerField(null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(5)])

    value_rating = models.PositiveIntegerField(null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(5)])

    location_rating = models.PositiveIntegerField(null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(5)])

    cleanliness_rating = models.PositiveIntegerField(null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(5)])

   

    # Visit details

    visit_date = models.DateField(null=True, blank=True)

    stay_duration = models.CharField(max_length=50, blank=True, help_text="e.g., 3 days, 1 week")

   

    # Review metadata

    helpful_votes = models.PositiveIntegerField(default=0)

    total_votes = models.PositiveIntegerField(default=0)

    is_verified = models.BooleanField(default=False)

    is_featured = models.BooleanField(default=False)

   

    # Administrative

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

    is_published = models.BooleanField(default=True)

   

    class Meta:

        ordering = ['-created_at']

        unique_together = [['user', 'review_type', 'destination'], ['user', 'review_type', 'attraction'],

                          ['user', 'review_type', 'restaurant'], ['user', 'review_type', 'accommodation'],

                          ['user', 'review_type', 'event']]

        verbose_name = "User Review"

        verbose_name_plural = "User Reviews"

 

    def __str__(self):

        return f"{self.title} by {self.reviewer_name}"

 

    @property

    def helpful_percentage(self):

        """Calculate helpful vote percentage"""

        if self.total_votes > 0:

            return round((self.helpful_votes / self.total_votes) * 100, 1)

        return 0

 

9

class TravelTip(models.Model):

    """Travel tips and insider advice for destinations"""

    TIP_CATEGORY_CHOICES = [

        ('general', 'General Tips'),

        ('transportation', 'Transportation'),

        ('accommodation', 'Accommodation'),

        ('food', 'Food & Dining'),

        ('shopping', 'Shopping'),

        ('culture', 'Culture & Customs'),

        ('safety', 'Safety'),

        ('money', 'Money & Costs'),

        ('weather', 'Weather & Climate'),

        ('activities', 'Activities'),

        ('photography', 'Photography'),

        ('health', 'Health & Medical'),

        ('packing', 'Packing'),

        ('communication', 'Communication'),

        ('etiquette', 'Local Etiquette'),

    ]

   

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='travel_tips')

    category = models.CharField(max_length=30, choices=TIP_CATEGORY_CHOICES)

    title_en = models.CharField(max_length=200)

    title_vi = models.CharField(max_length=200, blank=True)

    tip_en = models.TextField()

    tip_vi = models.TextField(blank=True)

   

    # Tip metadata

    is_insider_tip = models.BooleanField(default=False)

    is_featured = models.BooleanField(default=False)

    helpful_votes = models.PositiveIntegerField(default=0)

   

    # Author information

    author = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)

    author_name = models.CharField(max_length=100, blank=True)

   

    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)

    updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)

   

    class Meta:

        ordering = ['-is_featured', '-helpful_votes', '-created_at']

        verbose_name = "Travel Tip"

        verbose_name_plural = "Travel Tips"

 

    def __str__(self):

        return f"{self.title_en} ({self.get_category_display()})"

 

10

class UsefulPhrase(models.Model):

    destination = models.ForeignKey(Destination, on_delete=models.CASCADE, related_name='phrases')

    phrase_en = models.CharField(max_length=200, default='', verbose_name="English Phrase")

    phrase_vi = models.CharField(max_length=200, default='', verbose_name="Vietnamese Phrase")

    translation = models.CharField(max_length=200, blank=True, verbose_name="Pronunciation Guide")

    note_en = models.CharField(max_length=200, blank=True, verbose_name="English Note")

    note_vi = models.CharField(max_length=200, blank=True, verbose_name="Vietnamese Note")

 

    def __str__(self):

        return f"{self.phrase_en} - {self.phrase_vi}"

 

Attached Files

0 files found.

You are viewing this article in public mode. Some features may be limited.