* Vietnamese translation incomplete

Business Directory Models explain

Business Directory Models explain (English fallback)

Aug. 16, 2025

Posted by admin

Nhom

Notes

 

The Business Directory models provide a comprehensive foundation for managing travel-related businesses in Vietnam, with full multilingual support, subscription management, customer reviews, and business verification systems suitable for a professional travel platform .

 

 

I'll provide a comprehensive explanation of the Business Directory models and their fields, showing how they work together to create a complete business listing platform for the DichVuHowTo travel website.

🏢 Business Directory Models Overview

The business directory system consists of four main models that work together to create a comprehensive business listing platform with multilingual support, reviews, and business verification.

 

 

 

Listing

🏪 BusinessListing Model - Core Business Information

📋 Basic Business Information

business_name = models.CharField(max_length=200)

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

slug = models.SlugField(max_length=200, unique=True)

category = models.ForeignKey(BusinessCategory, on_delete=models.CASCADE, related_name='businesses')

owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='businesses')

Core Identity:

  • business_name: Official business name in English
  • business_name_vi: Local Vietnamese business name
  • slug: SEO-friendly URL identifier
  • category: Links to BusinessCategory for organization
  • owner: User who created/manages the listing

Example Usage:

saigon_hotel = BusinessListing.objects.create(

    business_name="Saigon Grand Hotel",

    business_name_vi="Khách sạn Sài Gòn Grand",

    slug="saigon-grand-hotel",

    category=luxury_hotels_category,

    owner=hotel_manager_user

)

📝 Description Fields

description = models.TextField()

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

short_description = models.CharField(max_length=300, help_text="Brief description for listings")

short_description_vi = models.CharField(max_length=300, blank=True, verbose_name="Vietnamese Short Description")

Content Strategy:

  • description: Full business description for detail pages
  • short_description: Excerpt for listing cards and search results
  • _vi versions: Complete Vietnamese translations for local market

Template Usage:

<!-- Listing card -->

<div class="business-card">

    <h3>{{ business.get_display_name:request.LANGUAGE_CODE }}</h3>

    <p>{{ business.get_display_short_description:request.LANGUAGE_CODE }}</p>

</div>

<!-- Detail page -->

<div class="business-detail">

    {{ business.get_display_description:request.LANGUAGE_CODE|safe }}

</div>

📞 Contact Information

phone = models.CharField(max_length=20)

email = models.EmailField()

website = models.URLField(blank=True)

Customer Communication:

  • phone: Primary contact number (clickable on mobile)
  • email: Business email for inquiries
  • website: Official business website

Template Implementation:

<div class="contact-info">

    <a href="tel:{{ business.phone }}" class="btn btn-primary">

        <i class="fas fa-phone"></i> Call Now

    </a>

    <a href="mailto:{{ business.email }}" class="btn btn-secondary">

        <i class="fas fa-envelope"></i> Email

    </a>

    {% if business.website %}

        <a href="{{ business.website }}" target="_blank" class="btn btn-info">

            <i class="fas fa-globe"></i> Website

        </a>

    {% endif %}

</div>

📍 Location & Mapping

street_address = models.CharField(max_length=200)

city = models.CharField(max_length=100)

state = models.CharField(max_length=100)

postal_code = models.CharField(max_length=20)

country = models.CharField(max_length=100, default='Vietnam')

latitude = models.DecimalField(max_digits=10, decimal_places=8, null=True, blank=True)

longitude = models.DecimalField(max_digits=11, decimal_places=8, null=True, blank=True)

Geographic Features:

  • Full Address: Complete address breakdown for search and display
  • GPS Coordinates: Enable map integration and location-based search
  • Default Country: Optimized for Vietnam-based businesses

Map Integration Example:

def get_full_address(self):

    """Get formatted full address"""

    parts = [self.street_address, self.city, self.state, self.postal_code, self.country]

    return ', '.join(filter(None, parts))

# Template usage with Google Maps

<div id="map" data-lat="{{ business.latitude }}" data-lng="{{ business.longitude }}">

    <p>{{ business.get_full_address }}</p>

</div>

🕒 Business Hours System

monday_hours = models.CharField(max_length=50, blank=True, help_text="e.g., 9:00 AM - 5:00 PM")

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

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

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

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

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

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

Operating Hours Management:

  • Flexible Format: Free-text allows various hour formats
  • Daily Granularity: Different hours for each day of week
  • Special Cases: Can handle "Closed", "24 Hours", "By Appointment"

Template Display:

<div class="business-hours">

    <h4>Business Hours</h4>

    <ul class="list-unstyled">

        <li><strong>Monday:</strong> {{ business.monday_hours|default:"Closed" }}</li>

        <li><strong>Tuesday:</strong> {{ business.tuesday_hours|default:"Closed" }}</li>

        <!-- ... other days ... -->

    </ul>

</div>

📱 Social Media Integration

facebook_url = models.URLField(blank=True)

twitter_url = models.URLField(blank=True)

instagram_url = models.URLField(blank=True)

linkedin_url = models.URLField(blank=True)

Social Presence:

  • Customer Engagement: Links to business social profiles
  • Trust Building: Social proof for business legitimacy
  • Marketing Integration: Cross-platform promotion

Feature & Amenities Flags

accepts_credit_cards = models.BooleanField(default=False)

parking_available = models.BooleanField(default=False)

wheelchair_accessible = models.BooleanField(default=False)

wifi_available = models.BooleanField(default=False)

outdoor_seating = models.BooleanField(default=False)

delivery_available = models.BooleanField(default=False)

Customer Decision Factors:

  • Accessibility: Wheelchair access for inclusive tourism
  • Payment Options: Credit card acceptance for international visitors
  • Convenience Features: WiFi, parking, delivery options
  • Seating Options: Outdoor seating for restaurants/cafes

Filter & Search Integration:

# Find restaurants with WiFi and outdoor seating

restaurants_with_amenities = BusinessListing.objects.filter(

    category__slug='restaurants',

    wifi_available=True,

    outdoor_seating=True,

    status='approved'

)

💼 Subscription & Status Management

SUBSCRIPTION_CHOICES = [

    ('free', 'Free Listing'),

    ('basic', 'Basic Listing'),

    ('premium', 'Premium Listing'),

    ('featured', 'Featured Listing'),

]

STATUS_CHOICES = [

    ('draft', 'Draft'),

    ('pending', 'Pending Approval'),

    ('approved', 'Approved'),

    ('rejected', 'Rejected'),

    ('suspended', 'Suspended'),

]

subscription_type = models.CharField(max_length=20, choices=SUBSCRIPTION_CHOICES, default='free')

status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')

is_verified = models.BooleanField(default=False)

is_featured = models.BooleanField(default=False)

Business Monetization:

  • Subscription Tiers: Different feature levels (photos, priority, etc.)
  • Approval Workflow: Quality control for listings
  • Verification System: Trust indicators for customers
  • Featured Promotion: Premium visibility options

Business Logic:

# Get featured businesses for homepage

featured_businesses = BusinessListing.objects.filter(

    status='approved',

    is_featured=True,

    subscription_type__in=['premium', 'featured']

).order_by('-created_at')

# Subscription-based features

def can_upload_multiple_photos(self):

    return self.subscription_type in ['premium', 'featured']

def max_photos_allowed(self):

    photo_limits = {

        'free': 3,

        'basic': 10,

        'premium': 25,

        'featured': 50

    }

    return photo_limits.get(self.subscription_type, 3)

🔍 SEO & Marketing Fields

meta_title = models.CharField(max_length=60, blank=True)

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

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

Search Engine Optimization:

  • Custom Titles: Override default page titles for SEO
  • Meta Descriptions: Search result snippets
  • Keywords: SEO targeting and internal search

Template SEO Integration:

<head>

    <title>{{ business.meta_title|default:business.business_name }} - DichVuHowTo</title>

    <meta name="description" content="{{ business.meta_description|default:business.short_description }}">

    <meta name="keywords" content="{{ business.keywords }}">

</head>

📊 Analytics & Performance Tracking

views_count = models.PositiveIntegerField(default=0)

phone_clicks = models.PositiveIntegerField(default=0)

website_clicks = models.PositiveIntegerField(default=0)

Business Intelligence:

  • views_count: Page view tracking for popularity
  • phone_clicks: Phone number click tracking
  • website_clicks: External website click tracking

Analytics Implementation:

def increment_views(self):

    """Increment view counter"""

    self.views_count += 1

    self.save(update_fields=['views_count'])

def track_phone_click(self):

    """Track phone number clicks"""

    self.phone_clicks += 1

    self.save(update_fields=['phone_clicks'])

# In views

def business_detail(request, slug):

    business = get_object_or_404(BusinessListing, slug=slug)

    business.increment_views()  # Track page view

    return render(request, 'business_detail.html', {'business': business})

Subscription Management

created_at = models.DateTimeField(auto_now_add=True)

updated_at = models.DateTimeField(auto_now=True)

featured_until = models.DateTimeField(null=True, blank=True)

subscription_expires = models.DateTimeField(null=True, blank=True)

Temporal Business Logic:

  • Lifecycle Tracking: Creation and modification dates
  • Feature Expiration: Time-limited featured listings
  • Subscription Billing: Automatic subscription management

Subscription Management:

from django.utils import timezone

def is_subscription_active(self):

    """Check if subscription is still active"""

    if not self.subscription_expires:

        return True  # Lifetime subscription

    return timezone.now() < self.subscription_expires

def is_currently_featured(self):

    """Check if business is currently featured"""

    if not self.is_featured or not self.featured_until:

        return self.is_featured

    return timezone.now() < self.featured_until

# Automated tasks

def expire_featured_listings():

    """Celery task to expire featured listings"""

    expired_listings = BusinessListing.objects.filter(

        is_featured=True,

        featured_until__lt=timezone.now()

    )

    expired_listings.update(is_featured=False)

 

 

Listing Category

📂 BusinessCategory Model - Hierarchical Organization

🔤 Basic Information Fields

name = models.CharField(max_length=100, unique=True)

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

slug = models.SlugField(max_length=100, unique=True)

description = models.TextField(blank=True)

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

Purpose: Creates bilingual category structure for business organization

  • name: English category name (e.g., "Restaurants", "Hotels", "Tour Operators")
  • name_vi: Vietnamese equivalent (e.g., "Nhà hàng", "Khách sạn", "Công ty du lịch")
  • slug: URL-friendly identifier for SEO and routing
  • description/description_vi: Detailed category explanations in both languages

Real-World Examples:

# Tourism category structure

tourism_services = BusinessCategory.objects.create(

    name="Tourism Services",

    name_vi="Dịch vụ du lịch",

    slug="tourism-services",

    description="Companies providing travel and tourism services",

    description_vi="Các công ty cung cấp dịch vụ du lịch và nghỉ dưỡng"

)

hotels = BusinessCategory.objects.create(

    name="Hotels & Accommodation",

    name_vi="Khách sạn & Chỗ ở",

    slug="hotels-accommodation",

    parent=tourism_services,

    description="Hotels, hostels, and accommodation providers"

)

🎨 Visual & Organization Fields

icon = models.CharField(max_length=50, default='fas fa-building')

parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='subcategories')

is_active = models.BooleanField(default=True)

sort_order = models.PositiveIntegerField(default=0)

Purpose: Visual presentation and hierarchical organization

  • icon: FontAwesome icon class for UI display
  • parent: Self-referencing FK for category trees (unlimited nesting)
  • is_active: Hide/show categories without deletion
  • sort_order: Manual ordering for editorial control

Category Hierarchy Example:

Tourism Services (fas fa-plane)

── Hotels & Accommodation (fas fa-bed)

│   ── Luxury Hotels (fas fa-crown)

│   ── Budget Hotels (fas fa-wallet)

│   └── Hostels (fas fa-users)

── Restaurants (fas fa-utensils)

│   ── Vietnamese Cuisine (fas fa-leaf)

│   ── International Food (fas fa-globe)

│   └── Street Food (fas fa-shopping-cart)

└── Transportation (fas fa-car)

    ── Car Rental (fas fa-car-side)

    ── Motorbike Rental (fas fa-motorcycle)

    └── Tour Buses (fas fa-bus)

🌍 Translation Management System

translation_status = models.CharField(max_length=20, choices=TRANSLATION_STATUS_CHOICES, default='pending')

translated_by = models.CharField(max_length=100, blank=True, help_text="Name of translator")

translated_at = models.DateTimeField(null=True, blank=True, help_text="When translation was completed")

Translation Workflow:

  1. pending: Category created in English, needs Vietnamese translation
  2. translated: Vietnamese content added, awaiting review
  3. reviewed: Translation verified by native speaker

Business Logic Methods:

def get_display_name(self, language='en'):

    """Smart language switching"""

    if language == 'vi' and self.name_vi:

        return self.name_vi

    return self.name

def get_business_count(self):

    """Get count of approved businesses in this category"""

    return self.businesses.filter(status='approved').count()

 

Listing Claim

🏢 BusinessClaim Model - Ownership Verification

📋 Claim Process

business = models.ForeignKey(BusinessListing, on_delete=models.CASCADE, related_name='claims')

claimant = models.ForeignKey(User, on_delete=models.CASCADE, related_name='business_claims')

claim_reason = models.TextField(help_text="Reason for claiming this business")

verification_document = models.FileField(upload_to='claim_documents/', blank=True)

status = models.CharField(max_length=20, choices=[...], default='pending')

Ownership Verification:

  • Claim Process: Users can claim business ownership
  • Documentation: Upload business documents for verification
  • Admin Review: Claims require administrative approval
  • Status Tracking: Track claim review progress

Claim Workflow:

def approve_claim(self, admin_user):

    """Approve business claim and transfer ownership"""

    if self.status == 'pending':

        # Transfer ownership

        old_owner = self.business.owner

        self.business.owner = self.claimant

        self.business.is_verified = True

        self.business.save()

        

        # Update claim status

        self.status = 'approved'

        self.reviewed_at = timezone.now()

        self.save()

        

        # Send notifications

        send_claim_approved_email(self.claimant, self.business)

        send_ownership_transferred_email(old_owner, self.business)

# Template for claim submission

<form method="post" enctype="multipart/form-data">

    <h3>Claim Business Ownership</h3>

    <div class="form-group">

        <label>Reason for Claim</label>

        <textarea name="claim_reason" required></textarea>

    </div>

    <div class="form-group">

        <label>Verification Document</label>

        <input type="file" name="verification_document">

        <small>Upload business license or registration document</small>

    </div>

    <button type="submit">Submit Claim</button>

</form>

 

🎯 Real-World Integration Scenarios

🔍 Advanced Business Search

def search_businesses(query, category=None, city=None, amenities=None):

    """Comprehensive business search with filters"""

    businesses = BusinessListing.objects.filter(status='approved')

    

    # Text search

    if query:

        businesses = businesses.filter(

            Q(business_name__icontains=query) |

            Q(business_name_vi__icontains=query) |

            Q(description__icontains=query) |

            Q(description_vi__icontains=query)

        )

    

    # Category filter

    if category:

        businesses = businesses.filter(category=category)

    

    # Location filter

    if city:

        businesses = businesses.filter(city__icontains=city)

    

    # Amenity filters

    if amenities:

        for amenity in amenities:

            if amenity == 'wifi':

                businesses = businesses.filter(wifi_available=True)

            elif amenity == 'parking':

                businesses = businesses.filter(parking_available=True)

            elif amenity == 'accessible':

                businesses = businesses.filter(wheelchair_accessible=True)

    

    return businesses.select_related('category').prefetch_related('images', 'reviews')

📊 Business Dashboard Analytics

def get_business_analytics(business):

    """Generate analytics for business owner dashboard"""

    return {

        'total_views': business.views_count,

        'phone_clicks': business.phone_clicks,

        'website_clicks': business.website_clicks,

        'average_rating': business.calculate_average_rating(),

        'total_reviews': business.reviews.count(),

        'verified_reviews': business.reviews.filter(is_verified=True).count(),

        'subscription_status': business.is_subscription_active(),

        'featured_until': business.featured_until,

        'images_count': business.images.count(),

        'max_images': business.max_photos_allowed(),

        'monthly_views': get_monthly_views(business),

        'competitor_comparison': get_competitor_metrics(business)

    }

🌟 Featured Business Rotation

def get_featured_businesses_for_homepage():

    """Get featured businesses with smart rotation"""

    # Get currently featured businesses

    featured = BusinessListing.objects.filter(

        status='approved',

        is_featured=True,

        subscription_type__in=['premium', 'featured']

    ).select_related('category').prefetch_related('images')

    

    # Apply smart rotation based on subscription level

    premium_businesses = featured.filter(subscription_type='premium')

    featured_businesses = featured.filter(subscription_type='featured')

    

    # Feature rotation: 60% featured, 40% premium

    import random

    final_list = []

    

    if featured_businesses.exists():

        final_list.extend(random.sample(list(featured_businesses), min(6, len(featured_businesses))))

    

    if premium_businesses.exists():

        final_list.extend(random.sample(list(premium_businesses), min(4, len(premium_businesses))))

    

    return final_list[:10]  # Return top 10 for homepage

 

Listing Image

📸 BusinessImage Model - Visual Content

🖼️ Image Management

business = models.ForeignKey(BusinessListing, on_delete=models.CASCADE, related_name='images')

image = models.ImageField(upload_to='business_images/')

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

is_primary = models.BooleanField(default=False)

sort_order = models.PositiveIntegerField(default=0)

uploaded_at = models.DateTimeField(auto_now_add=True)

Visual Presentation:

  • Multiple Images: Each business can have multiple photos
  • Primary Image: Main photo for listings and cards
  • Captions: Descriptive text for accessibility and SEO
  • Manual Ordering: Control photo sequence in galleries

Image Management Logic:

def set_as_primary(self):

    """Set this image as primary and unset others"""

    # First, unset all other primary images for this business

    BusinessImage.objects.filter(

        business=self.business,

        is_primary=True

    ).update(is_primary=False)

    

    # Set this image as primary

    self.is_primary = True

    self.save()

# Template usage

{% for image in business.images.all %}

    <div class="gallery-item {% if image.is_primary %}primary{% endif %}">

        <img src="{{ image.image.url }}" alt="{{ image.caption }}">

        {% if image.caption %}

            <div class="caption">{{ image.caption }}</div>

        {% endif %}

    </div>

{% endfor %}

 

Listing Review

 BusinessReview Model - Customer Feedback

📝 Review System

business = models.ForeignKey(BusinessListing, on_delete=models.CASCADE, related_name='reviews')

reviewer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='business_reviews')

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

title = models.CharField(max_length=200)

review_text = models.TextField()

is_verified = models.BooleanField(default=False)

Review Features:

  • Star Rating: 1-5 star rating system
  • Detailed Reviews: Title and full review text
  • Verification System: Verified vs. unverified reviews
  • User Accountability: Links reviews to user accounts

Review Analytics:

def calculate_average_rating(self):

    """Calculate average rating for business"""

    reviews = self.reviews.all()

    if reviews:

        total_rating = sum(review.rating for review in reviews)

        return round(total_rating / len(reviews), 1)

    return 0

def get_rating_distribution(self):

    """Get distribution of ratings (1-5 stars)"""

    from django.db.models import Count

    return self.reviews.values('rating').annotate(

        count=Count('rating')

    ).order_by('rating')

# Template display

<div class="rating-summary">

    <div class="average-rating">

        {{ business.calculate_average_rating }} / 5 stars

        ({{ business.reviews.count }} review{{ business.reviews.count|pluralize }})

    </div>

    

    {% for rating_data in business.get_rating_distribution %}

        <div class="rating-bar">

            {{ rating_data.rating }} stars: {{ rating_data.count }} review{{ rating_data.count|pluralize }}

        </div>

    {% endfor %}

</div>

🔒 Review Integrity

class Meta:

    unique_together = ['business', 'reviewer']

    ordering = ['-created_at']

Anti-Spam Measures:

  • One Review Per User: Prevents review spam
  • Chronological Ordering: Shows newest reviews first

Verification Flag: Distinguishes verified customer reviews

 

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

Nhom

Notes

 

The Business Directory models provide a comprehensive foundation for managing travel-related businesses in Vietnam, with full multilingual support, subscription management, customer reviews, and business verification systems suitable for a professional travel platform .

 

 

I'll provide a comprehensive explanation of the Business Directory models and their fields, showing how they work together to create a complete business listing platform for the DichVuHowTo travel website.

🏢 Business Directory Models Overview

The business directory system consists of four main models that work together to create a comprehensive business listing platform with multilingual support, reviews, and business verification.

 

 

 

Listing

🏪 BusinessListing Model - Core Business Information

📋 Basic Business Information

business_name = models.CharField(max_length=200)

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

slug = models.SlugField(max_length=200, unique=True)

category = models.ForeignKey(BusinessCategory, on_delete=models.CASCADE, related_name='businesses')

owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='businesses')

Core Identity:

  • business_name: Official business name in English
  • business_name_vi: Local Vietnamese business name
  • slug: SEO-friendly URL identifier
  • category: Links to BusinessCategory for organization
  • owner: User who created/manages the listing

Example Usage:

saigon_hotel = BusinessListing.objects.create(

    business_name="Saigon Grand Hotel",

    business_name_vi="Khách sạn Sài Gòn Grand",

    slug="saigon-grand-hotel",

    category=luxury_hotels_category,

    owner=hotel_manager_user

)

📝 Description Fields

description = models.TextField()

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

short_description = models.CharField(max_length=300, help_text="Brief description for listings")

short_description_vi = models.CharField(max_length=300, blank=True, verbose_name="Vietnamese Short Description")

Content Strategy:

  • description: Full business description for detail pages
  • short_description: Excerpt for listing cards and search results
  • _vi versions: Complete Vietnamese translations for local market

Template Usage:

<!-- Listing card -->

<div class="business-card">

    <h3>{{ business.get_display_name:request.LANGUAGE_CODE }}</h3>

    <p>{{ business.get_display_short_description:request.LANGUAGE_CODE }}</p>

</div>

<!-- Detail page -->

<div class="business-detail">

    {{ business.get_display_description:request.LANGUAGE_CODE|safe }}

</div>

📞 Contact Information

phone = models.CharField(max_length=20)

email = models.EmailField()

website = models.URLField(blank=True)

Customer Communication:

  • phone: Primary contact number (clickable on mobile)
  • email: Business email for inquiries
  • website: Official business website

Template Implementation:

<div class="contact-info">

    <a href="tel:{{ business.phone }}" class="btn btn-primary">

        <i class="fas fa-phone"></i> Call Now

    </a>

    <a href="mailto:{{ business.email }}" class="btn btn-secondary">

        <i class="fas fa-envelope"></i> Email

    </a>

    {% if business.website %}

        <a href="{{ business.website }}" target="_blank" class="btn btn-info">

            <i class="fas fa-globe"></i> Website

        </a>

    {% endif %}

</div>

📍 Location & Mapping

street_address = models.CharField(max_length=200)

city = models.CharField(max_length=100)

state = models.CharField(max_length=100)

postal_code = models.CharField(max_length=20)

country = models.CharField(max_length=100, default='Vietnam')

latitude = models.DecimalField(max_digits=10, decimal_places=8, null=True, blank=True)

longitude = models.DecimalField(max_digits=11, decimal_places=8, null=True, blank=True)

Geographic Features:

  • Full Address: Complete address breakdown for search and display
  • GPS Coordinates: Enable map integration and location-based search
  • Default Country: Optimized for Vietnam-based businesses

Map Integration Example:

def get_full_address(self):

    """Get formatted full address"""

    parts = [self.street_address, self.city, self.state, self.postal_code, self.country]

    return ', '.join(filter(None, parts))

# Template usage with Google Maps

<div id="map" data-lat="{{ business.latitude }}" data-lng="{{ business.longitude }}">

    <p>{{ business.get_full_address }}</p>

</div>

🕒 Business Hours System

monday_hours = models.CharField(max_length=50, blank=True, help_text="e.g., 9:00 AM - 5:00 PM")

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

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

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

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

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

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

Operating Hours Management:

  • Flexible Format: Free-text allows various hour formats
  • Daily Granularity: Different hours for each day of week
  • Special Cases: Can handle "Closed", "24 Hours", "By Appointment"

Template Display:

<div class="business-hours">

    <h4>Business Hours</h4>

    <ul class="list-unstyled">

        <li><strong>Monday:</strong> {{ business.monday_hours|default:"Closed" }}</li>

        <li><strong>Tuesday:</strong> {{ business.tuesday_hours|default:"Closed" }}</li>

        <!-- ... other days ... -->

    </ul>

</div>

📱 Social Media Integration

facebook_url = models.URLField(blank=True)

twitter_url = models.URLField(blank=True)

instagram_url = models.URLField(blank=True)

linkedin_url = models.URLField(blank=True)

Social Presence:

  • Customer Engagement: Links to business social profiles
  • Trust Building: Social proof for business legitimacy
  • Marketing Integration: Cross-platform promotion

Feature & Amenities Flags

accepts_credit_cards = models.BooleanField(default=False)

parking_available = models.BooleanField(default=False)

wheelchair_accessible = models.BooleanField(default=False)

wifi_available = models.BooleanField(default=False)

outdoor_seating = models.BooleanField(default=False)

delivery_available = models.BooleanField(default=False)

Customer Decision Factors:

  • Accessibility: Wheelchair access for inclusive tourism
  • Payment Options: Credit card acceptance for international visitors
  • Convenience Features: WiFi, parking, delivery options
  • Seating Options: Outdoor seating for restaurants/cafes

Filter & Search Integration:

# Find restaurants with WiFi and outdoor seating

restaurants_with_amenities = BusinessListing.objects.filter(

    category__slug='restaurants',

    wifi_available=True,

    outdoor_seating=True,

    status='approved'

)

💼 Subscription & Status Management

SUBSCRIPTION_CHOICES = [

    ('free', 'Free Listing'),

    ('basic', 'Basic Listing'),

    ('premium', 'Premium Listing'),

    ('featured', 'Featured Listing'),

]

STATUS_CHOICES = [

    ('draft', 'Draft'),

    ('pending', 'Pending Approval'),

    ('approved', 'Approved'),

    ('rejected', 'Rejected'),

    ('suspended', 'Suspended'),

]

subscription_type = models.CharField(max_length=20, choices=SUBSCRIPTION_CHOICES, default='free')

status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')

is_verified = models.BooleanField(default=False)

is_featured = models.BooleanField(default=False)

Business Monetization:

  • Subscription Tiers: Different feature levels (photos, priority, etc.)
  • Approval Workflow: Quality control for listings
  • Verification System: Trust indicators for customers
  • Featured Promotion: Premium visibility options

Business Logic:

# Get featured businesses for homepage

featured_businesses = BusinessListing.objects.filter(

    status='approved',

    is_featured=True,

    subscription_type__in=['premium', 'featured']

).order_by('-created_at')

# Subscription-based features

def can_upload_multiple_photos(self):

    return self.subscription_type in ['premium', 'featured']

def max_photos_allowed(self):

    photo_limits = {

        'free': 3,

        'basic': 10,

        'premium': 25,

        'featured': 50

    }

    return photo_limits.get(self.subscription_type, 3)

🔍 SEO & Marketing Fields

meta_title = models.CharField(max_length=60, blank=True)

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

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

Search Engine Optimization:

  • Custom Titles: Override default page titles for SEO
  • Meta Descriptions: Search result snippets
  • Keywords: SEO targeting and internal search

Template SEO Integration:

<head>

    <title>{{ business.meta_title|default:business.business_name }} - DichVuHowTo</title>

    <meta name="description" content="{{ business.meta_description|default:business.short_description }}">

    <meta name="keywords" content="{{ business.keywords }}">

</head>

📊 Analytics & Performance Tracking

views_count = models.PositiveIntegerField(default=0)

phone_clicks = models.PositiveIntegerField(default=0)

website_clicks = models.PositiveIntegerField(default=0)

Business Intelligence:

  • views_count: Page view tracking for popularity
  • phone_clicks: Phone number click tracking
  • website_clicks: External website click tracking

Analytics Implementation:

def increment_views(self):

    """Increment view counter"""

    self.views_count += 1

    self.save(update_fields=['views_count'])

def track_phone_click(self):

    """Track phone number clicks"""

    self.phone_clicks += 1

    self.save(update_fields=['phone_clicks'])

# In views

def business_detail(request, slug):

    business = get_object_or_404(BusinessListing, slug=slug)

    business.increment_views()  # Track page view

    return render(request, 'business_detail.html', {'business': business})

Subscription Management

created_at = models.DateTimeField(auto_now_add=True)

updated_at = models.DateTimeField(auto_now=True)

featured_until = models.DateTimeField(null=True, blank=True)

subscription_expires = models.DateTimeField(null=True, blank=True)

Temporal Business Logic:

  • Lifecycle Tracking: Creation and modification dates
  • Feature Expiration: Time-limited featured listings
  • Subscription Billing: Automatic subscription management

Subscription Management:

from django.utils import timezone

def is_subscription_active(self):

    """Check if subscription is still active"""

    if not self.subscription_expires:

        return True  # Lifetime subscription

    return timezone.now() < self.subscription_expires

def is_currently_featured(self):

    """Check if business is currently featured"""

    if not self.is_featured or not self.featured_until:

        return self.is_featured

    return timezone.now() < self.featured_until

# Automated tasks

def expire_featured_listings():

    """Celery task to expire featured listings"""

    expired_listings = BusinessListing.objects.filter(

        is_featured=True,

        featured_until__lt=timezone.now()

    )

    expired_listings.update(is_featured=False)

 

 

Listing Category

📂 BusinessCategory Model - Hierarchical Organization

🔤 Basic Information Fields

name = models.CharField(max_length=100, unique=True)

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

slug = models.SlugField(max_length=100, unique=True)

description = models.TextField(blank=True)

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

Purpose: Creates bilingual category structure for business organization

  • name: English category name (e.g., "Restaurants", "Hotels", "Tour Operators")
  • name_vi: Vietnamese equivalent (e.g., "Nhà hàng", "Khách sạn", "Công ty du lịch")
  • slug: URL-friendly identifier for SEO and routing
  • description/description_vi: Detailed category explanations in both languages

Real-World Examples:

# Tourism category structure

tourism_services = BusinessCategory.objects.create(

    name="Tourism Services",

    name_vi="Dịch vụ du lịch",

    slug="tourism-services",

    description="Companies providing travel and tourism services",

    description_vi="Các công ty cung cấp dịch vụ du lịch và nghỉ dưỡng"

)

hotels = BusinessCategory.objects.create(

    name="Hotels & Accommodation",

    name_vi="Khách sạn & Chỗ ở",

    slug="hotels-accommodation",

    parent=tourism_services,

    description="Hotels, hostels, and accommodation providers"

)

🎨 Visual & Organization Fields

icon = models.CharField(max_length=50, default='fas fa-building')

parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='subcategories')

is_active = models.BooleanField(default=True)

sort_order = models.PositiveIntegerField(default=0)

Purpose: Visual presentation and hierarchical organization

  • icon: FontAwesome icon class for UI display
  • parent: Self-referencing FK for category trees (unlimited nesting)
  • is_active: Hide/show categories without deletion
  • sort_order: Manual ordering for editorial control

Category Hierarchy Example:

Tourism Services (fas fa-plane)

── Hotels & Accommodation (fas fa-bed)

│   ── Luxury Hotels (fas fa-crown)

│   ── Budget Hotels (fas fa-wallet)

│   └── Hostels (fas fa-users)

── Restaurants (fas fa-utensils)

│   ── Vietnamese Cuisine (fas fa-leaf)

│   ── International Food (fas fa-globe)

│   └── Street Food (fas fa-shopping-cart)

└── Transportation (fas fa-car)

    ── Car Rental (fas fa-car-side)

    ── Motorbike Rental (fas fa-motorcycle)

    └── Tour Buses (fas fa-bus)

🌍 Translation Management System

translation_status = models.CharField(max_length=20, choices=TRANSLATION_STATUS_CHOICES, default='pending')

translated_by = models.CharField(max_length=100, blank=True, help_text="Name of translator")

translated_at = models.DateTimeField(null=True, blank=True, help_text="When translation was completed")

Translation Workflow:

  1. pending: Category created in English, needs Vietnamese translation
  2. translated: Vietnamese content added, awaiting review
  3. reviewed: Translation verified by native speaker

Business Logic Methods:

def get_display_name(self, language='en'):

    """Smart language switching"""

    if language == 'vi' and self.name_vi:

        return self.name_vi

    return self.name

def get_business_count(self):

    """Get count of approved businesses in this category"""

    return self.businesses.filter(status='approved').count()

 

Listing Claim

🏢 BusinessClaim Model - Ownership Verification

📋 Claim Process

business = models.ForeignKey(BusinessListing, on_delete=models.CASCADE, related_name='claims')

claimant = models.ForeignKey(User, on_delete=models.CASCADE, related_name='business_claims')

claim_reason = models.TextField(help_text="Reason for claiming this business")

verification_document = models.FileField(upload_to='claim_documents/', blank=True)

status = models.CharField(max_length=20, choices=[...], default='pending')

Ownership Verification:

  • Claim Process: Users can claim business ownership
  • Documentation: Upload business documents for verification
  • Admin Review: Claims require administrative approval
  • Status Tracking: Track claim review progress

Claim Workflow:

def approve_claim(self, admin_user):

    """Approve business claim and transfer ownership"""

    if self.status == 'pending':

        # Transfer ownership

        old_owner = self.business.owner

        self.business.owner = self.claimant

        self.business.is_verified = True

        self.business.save()

        

        # Update claim status

        self.status = 'approved'

        self.reviewed_at = timezone.now()

        self.save()

        

        # Send notifications

        send_claim_approved_email(self.claimant, self.business)

        send_ownership_transferred_email(old_owner, self.business)

# Template for claim submission

<form method="post" enctype="multipart/form-data">

    <h3>Claim Business Ownership</h3>

    <div class="form-group">

        <label>Reason for Claim</label>

        <textarea name="claim_reason" required></textarea>

    </div>

    <div class="form-group">

        <label>Verification Document</label>

        <input type="file" name="verification_document">

        <small>Upload business license or registration document</small>

    </div>

    <button type="submit">Submit Claim</button>

</form>

 

🎯 Real-World Integration Scenarios

🔍 Advanced Business Search

def search_businesses(query, category=None, city=None, amenities=None):

    """Comprehensive business search with filters"""

    businesses = BusinessListing.objects.filter(status='approved')

    

    # Text search

    if query:

        businesses = businesses.filter(

            Q(business_name__icontains=query) |

            Q(business_name_vi__icontains=query) |

            Q(description__icontains=query) |

            Q(description_vi__icontains=query)

        )

    

    # Category filter

    if category:

        businesses = businesses.filter(category=category)

    

    # Location filter

    if city:

        businesses = businesses.filter(city__icontains=city)

    

    # Amenity filters

    if amenities:

        for amenity in amenities:

            if amenity == 'wifi':

                businesses = businesses.filter(wifi_available=True)

            elif amenity == 'parking':

                businesses = businesses.filter(parking_available=True)

            elif amenity == 'accessible':

                businesses = businesses.filter(wheelchair_accessible=True)

    

    return businesses.select_related('category').prefetch_related('images', 'reviews')

📊 Business Dashboard Analytics

def get_business_analytics(business):

    """Generate analytics for business owner dashboard"""

    return {

        'total_views': business.views_count,

        'phone_clicks': business.phone_clicks,

        'website_clicks': business.website_clicks,

        'average_rating': business.calculate_average_rating(),

        'total_reviews': business.reviews.count(),

        'verified_reviews': business.reviews.filter(is_verified=True).count(),

        'subscription_status': business.is_subscription_active(),

        'featured_until': business.featured_until,

        'images_count': business.images.count(),

        'max_images': business.max_photos_allowed(),

        'monthly_views': get_monthly_views(business),

        'competitor_comparison': get_competitor_metrics(business)

    }

🌟 Featured Business Rotation

def get_featured_businesses_for_homepage():

    """Get featured businesses with smart rotation"""

    # Get currently featured businesses

    featured = BusinessListing.objects.filter(

        status='approved',

        is_featured=True,

        subscription_type__in=['premium', 'featured']

    ).select_related('category').prefetch_related('images')

    

    # Apply smart rotation based on subscription level

    premium_businesses = featured.filter(subscription_type='premium')

    featured_businesses = featured.filter(subscription_type='featured')

    

    # Feature rotation: 60% featured, 40% premium

    import random

    final_list = []

    

    if featured_businesses.exists():

        final_list.extend(random.sample(list(featured_businesses), min(6, len(featured_businesses))))

    

    if premium_businesses.exists():

        final_list.extend(random.sample(list(premium_businesses), min(4, len(premium_businesses))))

    

    return final_list[:10]  # Return top 10 for homepage

 

Listing Image

📸 BusinessImage Model - Visual Content

🖼️ Image Management

business = models.ForeignKey(BusinessListing, on_delete=models.CASCADE, related_name='images')

image = models.ImageField(upload_to='business_images/')

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

is_primary = models.BooleanField(default=False)

sort_order = models.PositiveIntegerField(default=0)

uploaded_at = models.DateTimeField(auto_now_add=True)

Visual Presentation:

  • Multiple Images: Each business can have multiple photos
  • Primary Image: Main photo for listings and cards
  • Captions: Descriptive text for accessibility and SEO
  • Manual Ordering: Control photo sequence in galleries

Image Management Logic:

def set_as_primary(self):

    """Set this image as primary and unset others"""

    # First, unset all other primary images for this business

    BusinessImage.objects.filter(

        business=self.business,

        is_primary=True

    ).update(is_primary=False)

    

    # Set this image as primary

    self.is_primary = True

    self.save()

# Template usage

{% for image in business.images.all %}

    <div class="gallery-item {% if image.is_primary %}primary{% endif %}">

        <img src="{{ image.image.url }}" alt="{{ image.caption }}">

        {% if image.caption %}

            <div class="caption">{{ image.caption }}</div>

        {% endif %}

    </div>

{% endfor %}

 

Listing Review

 BusinessReview Model - Customer Feedback

📝 Review System

business = models.ForeignKey(BusinessListing, on_delete=models.CASCADE, related_name='reviews')

reviewer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='business_reviews')

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

title = models.CharField(max_length=200)

review_text = models.TextField()

is_verified = models.BooleanField(default=False)

Review Features:

  • Star Rating: 1-5 star rating system
  • Detailed Reviews: Title and full review text
  • Verification System: Verified vs. unverified reviews
  • User Accountability: Links reviews to user accounts

Review Analytics:

def calculate_average_rating(self):

    """Calculate average rating for business"""

    reviews = self.reviews.all()

    if reviews:

        total_rating = sum(review.rating for review in reviews)

        return round(total_rating / len(reviews), 1)

    return 0

def get_rating_distribution(self):

    """Get distribution of ratings (1-5 stars)"""

    from django.db.models import Count

    return self.reviews.values('rating').annotate(

        count=Count('rating')

    ).order_by('rating')

# Template display

<div class="rating-summary">

    <div class="average-rating">

        {{ business.calculate_average_rating }} / 5 stars

        ({{ business.reviews.count }} review{{ business.reviews.count|pluralize }})

    </div>

    

    {% for rating_data in business.get_rating_distribution %}

        <div class="rating-bar">

            {{ rating_data.rating }} stars: {{ rating_data.count }} review{{ rating_data.count|pluralize }}

        </div>

    {% endfor %}

</div>

🔒 Review Integrity

class Meta:

    unique_together = ['business', 'reviewer']

    ordering = ['-created_at']

Anti-Spam Measures:

  • One Review Per User: Prevents review spam
  • Chronological Ordering: Shows newest reviews first

Verification Flag: Distinguishes verified customer reviews

 

Attached Files

0 files found.

You are viewing this article in public mode. Some features may be limited.