* Vietnamese translation incomplete

Cruise models

Cruise models (English fallback)

Aug. 17, 2025

Posted by admin

Nhom

Notes

 

from django.db import models

from django.urls import reverse

from django.contrib.auth import get_user_model

from django.utils.translation import gettext_lazy as _

from decimal import Decimal

from datetime import datetime, timedelta

from apps.geo.models import Location

 

User = get_user_model()

 

Booking

class CruiseBooking(models.Model):

    """Cruise bookings made through the platform"""

    STATUS_CHOICES = [

        ('pending', 'Pending'),

        ('confirmed', 'Confirmed'),

        ('cancelled', 'Cancelled'),

        ('completed', 'Completed'),

    ]

 

    booking_number = models.CharField(max_length=20, unique=True)

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

    cruise = models.ForeignKey(Cruise, on_delete=models.CASCADE, related_name='bookings')

    cabin_category = models.ForeignKey(CabinCategory, on_delete=models.CASCADE)

   

    # Guest information

    primary_guest_name = models.CharField(max_length=200)

    primary_guest_email = models.EmailField()

    primary_guest_phone = models.CharField(max_length=20)

    number_of_guests = models.PositiveIntegerField(default=2)

    special_requests = models.TextField(blank=True)

   

    # Pricing

    base_price = models.DecimalField(max_digits=10, decimal_places=2)

    taxes_and_fees = models.DecimalField(max_digits=10, decimal_places=2)

    total_price = models.DecimalField(max_digits=10, decimal_places=2)

    commission_rate = models.DecimalField(max_digits=5, decimal_places=2, default=10)

    commission_amount = models.DecimalField(max_digits=10, decimal_places=2)

   

    # Status and dates

    status = models.CharField(max_length=15, choices=STATUS_CHOICES, default='pending')

    booking_date = models.DateTimeField(auto_now_add=True)

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

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

   

    # Payment tracking

    deposit_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    deposit_paid = models.BooleanField(default=False)

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

    final_payment_made = models.BooleanField(default=False)

   

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

 

    class Meta:

        ordering = ['-created_at']

 

    def save(self, *args, **kwargs):

        if not self.booking_number:

            # Generate unique booking number

            import uuid

            self.booking_number = f"CR{uuid.uuid4().hex[:8].upper()}"

        super().save(*args, **kwargs)

 

    def __str__(self):

        return f"Booking {self.booking_number} - {self.cruise}"

 

Cruise Line

class CruiseLine(models.Model):

    """Major cruise companies like Royal Caribbean, Norwegian, etc."""

    name = models.CharField(max_length=100, verbose_name=_("Name"))

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

    code = models.CharField(max_length=10, unique=True, help_text=_("Short code like 'RCL', 'NCL'"), verbose_name=_("Code"))

    description = models.TextField(blank=True, verbose_name=_("Description"))

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

    logo = models.ImageField(upload_to='cruise_lines/', blank=True, null=True, verbose_name=_("Logo"))

    website = models.URLField(blank=True, verbose_name=_("Website"))

    star_rating = models.DecimalField(max_digits=2, decimal_places=1, default=3.0,

                                    help_text=_("1-5 star rating"), verbose_name=_("Star Rating"))

    is_active = models.BooleanField(default=True, verbose_name=_("Is Active"))

    owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='cruise_lines', verbose_name=_("Owner"))

    created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))

    updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))

 

    class Meta:

        ordering = ['name']

        verbose_name = _("Cruise Line")

        verbose_name_plural = _("Cruise Lines")

 

    def __str__(self):

        return self.name

 

    @property

    def ship_count(self):

        return self.ships.filter(is_active=True).count()

 

 

Cruise Line Cruise

class Cruise(models.Model):

    """Specific cruise departures"""

    STATUS_CHOICES = [

        ('active', 'Active'),

        ('sold_out', 'Sold Out'),

        ('cancelled', 'Cancelled'),

        ('completed', 'Completed'),

    ]

 

    DEAL_TYPES = [

        ('early_bird', 'Early Bird'),

        ('last_minute', 'Last Minute'),

        ('group', 'Group Discount'),

        ('senior', 'Senior Discount'),

        ('military', 'Military Discount'),

        ('repeat', 'Repeat Guest'),

        ('two_for_one', 'Two for One'),

    ]

 

    ship = models.ForeignKey(Ship, on_delete=models.CASCADE, related_name='cruises')

    itinerary = models.ForeignKey(CruiseItinerary, on_delete=models.CASCADE, related_name='cruises')

    departure_date = models.DateField()

    return_date = models.DateField()

    status = models.CharField(max_length=15, choices=STATUS_CHOICES, default='active')

   

    # Pricing and deals

    is_deal = models.BooleanField(default=False)

    deal_type = models.CharField(max_length=15, choices=DEAL_TYPES, blank=True)

    deal_discount_percent = models.DecimalField(max_digits=5, decimal_places=2, default=0)

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

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

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

   

    # Additional offerings

    airfare_included = models.BooleanField(default=False)

    shore_excursions_included = models.BooleanField(default=False)

    wifi_included = models.BooleanField(default=False)

    drinks_package_included = models.BooleanField(default=False)

    gratuities_included = models.BooleanField(default=False)

   

    # Internal tracking

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

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

 

    class Meta:

        verbose_name = _("Cruise")

        verbose_name_plural = _("Cruises")

        ordering = ['departure_date']

 

    def __str__(self):

        return f"{self.ship.name} - {self.departure_date}"

 

    @property

    def days_until_departure(self):

        if self.departure_date is None:

            return None

        from datetime import date

        return (self.departure_date - date.today()).days

 

    @property

    def is_last_minute(self):

        days = self.days_until_departure

        if days is None:

            return False

        return days <= 90

 

    def get_absolute_url(self):

        return reverse('cruise:detail', kwargs={'pk': self.pk})

 

 

Cruise Line Cruise Image

class CruiseImage(models.Model):

    """Images for cruises, ships, and itineraries"""

    IMAGE_TYPES = [

        ('ship_exterior', 'Ship Exterior'),

        ('ship_interior', 'Ship Interior'),

        ('cabin', 'Cabin'),

        ('dining', 'Dining'),

        ('entertainment', 'Entertainment'),

        ('destination', 'Destination'),

        ('amenity', 'Amenity'),

    ]

 

    cruise = models.ForeignKey(Cruise, on_delete=models.CASCADE, related_name='images', null=True, blank=True)

    ship = models.ForeignKey(Ship, on_delete=models.CASCADE, related_name='images', null=True, blank=True)

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

    image_type = models.CharField(max_length=20, choices=IMAGE_TYPES)

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

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

    is_featured = models.BooleanField(default=False)

    order = models.PositiveIntegerField(default=0)

 

    class Meta:

        ordering = ['order']

 

    def __str__(self):

        if self.cruise:

            return f"{self.cruise} - {self.image_type}"

        elif self.ship:

            return f"{self.ship} - {self.image_type}"

        return f"Image {self.pk}"

 

 

Cruise Line Cruise itinerary

class CruiseItinerary(models.Model):

    """Cruise routes and destinations"""

    ITINERARY_TYPES = [

        ('round_trip', 'Round Trip'),

        ('one_way', 'One Way'),

        ('open_jaw', 'Open Jaw'),

    ]

 

    name = models.CharField(max_length=200)

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

    cruise_line = models.ForeignKey(CruiseLine, on_delete=models.CASCADE, related_name='itineraries')

    duration_days = models.PositiveIntegerField()

    itinerary_type = models.CharField(max_length=15, choices=ITINERARY_TYPES, default='round_trip')

    departure_port = models.ForeignKey(Location, on_delete=models.CASCADE, related_name='departure_cruises')

    arrival_port = models.ForeignKey(Location, on_delete=models.CASCADE, related_name='arrival_cruises')

    description = models.TextField(blank=True)

    description_vi = models.TextField(blank=True)

    highlights = models.TextField(blank=True, help_text="Key highlights of this itinerary")

    highlights_vi = models.TextField(blank=True)

    is_active = models.BooleanField(default=True)

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

 

    class Meta:

        ordering = ['duration_days', 'name']

        verbose_name_plural = 'Cruise Itineraries'

 

    def __str__(self):

        return f"{self.duration_days}-day {self.name}"

 

    @property

    def port_count(self):

        return self.ports.count()

 

 

 

Cruise Line Cruise itinerary port

class ItineraryPort(models.Model):

    """Ports visited during a cruise itinerary"""

    itinerary = models.ForeignKey(CruiseItinerary, on_delete=models.CASCADE, related_name='ports')

    port = models.ForeignKey(Location, on_delete=models.CASCADE, related_name='itinerary_visits')

    day_number = models.PositiveIntegerField()

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

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

    is_sea_day = models.BooleanField(default=False)

    notes = models.TextField(blank=True, help_text="Special notes about this port visit")

    notes_vi = models.TextField(blank=True)

 

    class Meta:

        ordering = ['itinerary', 'day_number']

        constraints = [

            models.UniqueConstraint(fields=['itinerary', 'day_number'], name='unique_itinerary_day')

        ]

 

    def __str__(self):

        if self.is_sea_day:

            return f"Day {self.day_number}: Sea Day"

        return f"Day {self.day_number}: {self.port.name}"

 

 

Cruise Line Cruise Price

class CruisePrice(models.Model):

    """Pricing for different cabin categories on specific cruises"""

    cruise = models.ForeignKey(Cruise, on_delete=models.CASCADE, related_name='prices')

    cabin_category = models.ForeignKey(CabinCategory, on_delete=models.CASCADE)

    price_per_person = models.DecimalField(max_digits=10, decimal_places=2)

    single_supplement = models.DecimalField(max_digits=10, decimal_places=2, default=0)

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

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

   

    # Availability

    total_cabins = models.PositiveIntegerField(default=0)

    available_cabins = models.PositiveIntegerField(default=0)

   

    # Taxes and fees

    port_charges = models.DecimalField(max_digits=8, decimal_places=2, default=0)

    government_fees = models.DecimalField(max_digits=8, decimal_places=2, default=0)

   

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

   

    class Meta:

        constraints = [

            models.UniqueConstraint(fields=['cruise', 'cabin_category'], name='unique_cruise_cabin_category')

        ]

        ordering = ['cruise', 'cabin_category__cabin_type']

 

    def __str__(self):

        return f"{self.cruise} - {self.cabin_category.name}: ${self.price_per_person}"

 

    @property

    def total_price_per_person(self):

        price = self.price_per_person or 0

        port_charges = self.port_charges or 0

        government_fees = self.government_fees or 0

        return price + port_charges + government_fees

 

    @property

    def discounted_price(self):

        price = self.price_per_person or 0

        if self.cruise.is_deal and self.cruise.deal_discount_percent > 0:

            discount = (price * self.cruise.deal_discount_percent) / 100

            return price - discount

        return price

 

    @property

    def savings_amount(self):

        if self.cruise.is_deal:

            price = self.price_per_person or 0

            return price - self.discounted_price

        return Decimal('0.00')

 

    @property

    def is_available(self):

        return self.available_cabins > 0

 

Cruise Line Cruise Review

class CruiseReview(models.Model):

    """Customer reviews for cruises"""

    RATING_CHOICES = [(i, str(i)) for i in range(1, 6)]

 

    cruise = models.ForeignKey(Cruise, on_delete=models.CASCADE, related_name='reviews')

    user = models.ForeignKey(User, on_delete=models.CASCADE)

    overall_rating = models.IntegerField(choices=RATING_CHOICES)

    ship_rating = models.IntegerField(choices=RATING_CHOICES)

    service_rating = models.IntegerField(choices=RATING_CHOICES)

    dining_rating = models.IntegerField(choices=RATING_CHOICES)

    entertainment_rating = models.IntegerField(choices=RATING_CHOICES)

    value_rating = models.IntegerField(choices=RATING_CHOICES)

   

    title = models.CharField(max_length=200)

    review_text = models.TextField()

    pros = models.TextField(blank=True)

    cons = models.TextField(blank=True)

   

    travel_date = models.DateField()

    cabin_category = models.ForeignKey(CabinCategory, on_delete=models.SET_NULL, null=True, blank=True)

    traveler_type = models.CharField(max_length=50, blank=True, help_text="e.g., Couple, Family, Solo")

   

    is_verified = models.BooleanField(default=False)

    helpful_votes = models.PositiveIntegerField(default=0)

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

 

    class Meta:

        ordering = ['-created_at']

        constraints = [

            models.UniqueConstraint(fields=['cruise', 'user'], name='unique_cruise_user_review')

        ]

 

    def __str__(self):

        return f"{self.cruise} - {self.title} ({self.overall_rating}/5)"

 

 

Cruise Line Cruise Wishlist

class CruiseWishlist(models.Model):

    """User wishlist for cruises"""

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

    cruise = models.ForeignKey(Cruise, on_delete=models.CASCADE, related_name='wishlisted_by')

    created_at = models.DateTimeField(auto_now_add=True)

 

    class Meta:

        constraints = [

            models.UniqueConstraint(fields=['user', 'cruise'], name='unique_user_cruise_wishlist')

        ]

        ordering = ['-created_at']

 

    def __str__(self):

        return f"{self.user.username} - {self.cruise}"

 

Cruise Line ship

class Ship(models.Model):

    """Individual cruise ships"""

    SHIP_CATEGORIES = [

        ('luxury', 'Luxury'),

        ('premium', 'Premium'),

        ('contemporary', 'Contemporary'),

        ('budget', 'Budget'),

        ('expedition', 'Expedition'),

        ('river', 'River Cruise'),

    ]

 

    name = models.CharField(max_length=100)

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

    cruise_line = models.ForeignKey(CruiseLine, on_delete=models.CASCADE, related_name='ships')

    category = models.CharField(max_length=20, choices=SHIP_CATEGORIES, default='contemporary')

    year_built = models.PositiveIntegerField()

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

    capacity = models.PositiveIntegerField(help_text="Maximum passenger capacity")

    crew_size = models.PositiveIntegerField(help_text="Number of crew members")

    gross_tonnage = models.PositiveIntegerField()

    length = models.DecimalField(max_digits=6, decimal_places=2, help_text="Length in meters")

    beam = models.DecimalField(max_digits=5, decimal_places=2, help_text="Width in meters")

    decks = models.PositiveIntegerField()

    description = models.TextField(blank=True)

    description_vi = models.TextField(blank=True)

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

    is_active = models.BooleanField(default=True)

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

 

    class Meta:

        verbose_name = _("Cruise Ship")

        verbose_name_plural = _("Cruise Ships")

        ordering = ['cruise_line__name', 'name']

 

    def __str__(self):

        return f"{self.cruise_line.name} - {self.name}"

 

    @property

    def passenger_to_crew_ratio(self):

        if self.crew_size and self.capacity and self.crew_size > 0:

            return round(self.capacity / self.crew_size, 1)

        return 0

 

 

Ship Amenity

class ShipAmenity(models.Model):

    """Ship facilities and amenities"""

    AMENITY_CATEGORIES = [

        ('dining', 'Dining'),

        ('entertainment', 'Entertainment'),

        ('activities', 'Activities'),

        ('spa', 'Spa & Wellness'),

        ('sports', 'Sports & Recreation'),

        ('kids', 'Kids & Family'),

        ('shopping', 'Shopping'),

        ('accessibility', 'Accessibility'),

    ]

 

    name = models.CharField(max_length=100)

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

    category = models.CharField(max_length=20, choices=AMENITY_CATEGORIES)

    description = models.TextField(blank=True)

    description_vi = models.TextField(blank=True)

    icon = models.ImageField(upload_to='amenities/', blank=True, null=True)

    ships = models.ManyToManyField(Ship, related_name='amenities', blank=True)

 

    class Meta:

        ordering = ['category', 'name']

        verbose_name_plural = 'Ship Amenities'

 

    def __str__(self):

        return self.name

 

 

Ship Camin

class CabinCategory(models.Model):

    """Different types of cabins available on ships"""

    CABIN_TYPES = [

        ('interior', 'Interior'),

        ('ocean_view', 'Ocean View'),

        ('balcony', 'Balcony'),

        ('suite', 'Suite'),

    ]

 

    ship = models.ForeignKey(Ship, on_delete=models.CASCADE, related_name='cabin_categories')

    name = models.CharField(max_length=100)

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

    cabin_type = models.CharField(max_length=15, choices=CABIN_TYPES)

    deck_location = models.CharField(max_length=100, blank=True, help_text="Which decks this category is on")

    size_sqft = models.PositiveIntegerField(help_text="Size in square feet")

    max_occupancy = models.PositiveIntegerField(default=2)

    description = models.TextField(blank=True)

    description_vi = models.TextField(blank=True)

    amenities = models.TextField(blank=True, help_text="List of cabin amenities")

    amenities_vi = models.TextField(blank=True)

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

    is_active = models.BooleanField(default=True)

 

    class Meta:

        ordering = ['ship', 'cabin_type', 'name']

        verbose_name_plural = 'Cabin Categories'

 

    def __str__(self):

        return f"{self.ship.name} - {self.name}"

 

 

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

Nhom

Notes

 

from django.db import models

from django.urls import reverse

from django.contrib.auth import get_user_model

from django.utils.translation import gettext_lazy as _

from decimal import Decimal

from datetime import datetime, timedelta

from apps.geo.models import Location

 

User = get_user_model()

 

Booking

class CruiseBooking(models.Model):

    """Cruise bookings made through the platform"""

    STATUS_CHOICES = [

        ('pending', 'Pending'),

        ('confirmed', 'Confirmed'),

        ('cancelled', 'Cancelled'),

        ('completed', 'Completed'),

    ]

 

    booking_number = models.CharField(max_length=20, unique=True)

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

    cruise = models.ForeignKey(Cruise, on_delete=models.CASCADE, related_name='bookings')

    cabin_category = models.ForeignKey(CabinCategory, on_delete=models.CASCADE)

   

    # Guest information

    primary_guest_name = models.CharField(max_length=200)

    primary_guest_email = models.EmailField()

    primary_guest_phone = models.CharField(max_length=20)

    number_of_guests = models.PositiveIntegerField(default=2)

    special_requests = models.TextField(blank=True)

   

    # Pricing

    base_price = models.DecimalField(max_digits=10, decimal_places=2)

    taxes_and_fees = models.DecimalField(max_digits=10, decimal_places=2)

    total_price = models.DecimalField(max_digits=10, decimal_places=2)

    commission_rate = models.DecimalField(max_digits=5, decimal_places=2, default=10)

    commission_amount = models.DecimalField(max_digits=10, decimal_places=2)

   

    # Status and dates

    status = models.CharField(max_length=15, choices=STATUS_CHOICES, default='pending')

    booking_date = models.DateTimeField(auto_now_add=True)

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

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

   

    # Payment tracking

    deposit_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    deposit_paid = models.BooleanField(default=False)

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

    final_payment_made = models.BooleanField(default=False)

   

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

 

    class Meta:

        ordering = ['-created_at']

 

    def save(self, *args, **kwargs):

        if not self.booking_number:

            # Generate unique booking number

            import uuid

            self.booking_number = f"CR{uuid.uuid4().hex[:8].upper()}"

        super().save(*args, **kwargs)

 

    def __str__(self):

        return f"Booking {self.booking_number} - {self.cruise}"

 

Cruise Line

class CruiseLine(models.Model):

    """Major cruise companies like Royal Caribbean, Norwegian, etc."""

    name = models.CharField(max_length=100, verbose_name=_("Name"))

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

    code = models.CharField(max_length=10, unique=True, help_text=_("Short code like 'RCL', 'NCL'"), verbose_name=_("Code"))

    description = models.TextField(blank=True, verbose_name=_("Description"))

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

    logo = models.ImageField(upload_to='cruise_lines/', blank=True, null=True, verbose_name=_("Logo"))

    website = models.URLField(blank=True, verbose_name=_("Website"))

    star_rating = models.DecimalField(max_digits=2, decimal_places=1, default=3.0,

                                    help_text=_("1-5 star rating"), verbose_name=_("Star Rating"))

    is_active = models.BooleanField(default=True, verbose_name=_("Is Active"))

    owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='cruise_lines', verbose_name=_("Owner"))

    created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))

    updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))

 

    class Meta:

        ordering = ['name']

        verbose_name = _("Cruise Line")

        verbose_name_plural = _("Cruise Lines")

 

    def __str__(self):

        return self.name

 

    @property

    def ship_count(self):

        return self.ships.filter(is_active=True).count()

 

 

Cruise Line Cruise

class Cruise(models.Model):

    """Specific cruise departures"""

    STATUS_CHOICES = [

        ('active', 'Active'),

        ('sold_out', 'Sold Out'),

        ('cancelled', 'Cancelled'),

        ('completed', 'Completed'),

    ]

 

    DEAL_TYPES = [

        ('early_bird', 'Early Bird'),

        ('last_minute', 'Last Minute'),

        ('group', 'Group Discount'),

        ('senior', 'Senior Discount'),

        ('military', 'Military Discount'),

        ('repeat', 'Repeat Guest'),

        ('two_for_one', 'Two for One'),

    ]

 

    ship = models.ForeignKey(Ship, on_delete=models.CASCADE, related_name='cruises')

    itinerary = models.ForeignKey(CruiseItinerary, on_delete=models.CASCADE, related_name='cruises')

    departure_date = models.DateField()

    return_date = models.DateField()

    status = models.CharField(max_length=15, choices=STATUS_CHOICES, default='active')

   

    # Pricing and deals

    is_deal = models.BooleanField(default=False)

    deal_type = models.CharField(max_length=15, choices=DEAL_TYPES, blank=True)

    deal_discount_percent = models.DecimalField(max_digits=5, decimal_places=2, default=0)

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

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

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

   

    # Additional offerings

    airfare_included = models.BooleanField(default=False)

    shore_excursions_included = models.BooleanField(default=False)

    wifi_included = models.BooleanField(default=False)

    drinks_package_included = models.BooleanField(default=False)

    gratuities_included = models.BooleanField(default=False)

   

    # Internal tracking

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

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

 

    class Meta:

        verbose_name = _("Cruise")

        verbose_name_plural = _("Cruises")

        ordering = ['departure_date']

 

    def __str__(self):

        return f"{self.ship.name} - {self.departure_date}"

 

    @property

    def days_until_departure(self):

        if self.departure_date is None:

            return None

        from datetime import date

        return (self.departure_date - date.today()).days

 

    @property

    def is_last_minute(self):

        days = self.days_until_departure

        if days is None:

            return False

        return days <= 90

 

    def get_absolute_url(self):

        return reverse('cruise:detail', kwargs={'pk': self.pk})

 

 

Cruise Line Cruise Image

class CruiseImage(models.Model):

    """Images for cruises, ships, and itineraries"""

    IMAGE_TYPES = [

        ('ship_exterior', 'Ship Exterior'),

        ('ship_interior', 'Ship Interior'),

        ('cabin', 'Cabin'),

        ('dining', 'Dining'),

        ('entertainment', 'Entertainment'),

        ('destination', 'Destination'),

        ('amenity', 'Amenity'),

    ]

 

    cruise = models.ForeignKey(Cruise, on_delete=models.CASCADE, related_name='images', null=True, blank=True)

    ship = models.ForeignKey(Ship, on_delete=models.CASCADE, related_name='images', null=True, blank=True)

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

    image_type = models.CharField(max_length=20, choices=IMAGE_TYPES)

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

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

    is_featured = models.BooleanField(default=False)

    order = models.PositiveIntegerField(default=0)

 

    class Meta:

        ordering = ['order']

 

    def __str__(self):

        if self.cruise:

            return f"{self.cruise} - {self.image_type}"

        elif self.ship:

            return f"{self.ship} - {self.image_type}"

        return f"Image {self.pk}"

 

 

Cruise Line Cruise itinerary

class CruiseItinerary(models.Model):

    """Cruise routes and destinations"""

    ITINERARY_TYPES = [

        ('round_trip', 'Round Trip'),

        ('one_way', 'One Way'),

        ('open_jaw', 'Open Jaw'),

    ]

 

    name = models.CharField(max_length=200)

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

    cruise_line = models.ForeignKey(CruiseLine, on_delete=models.CASCADE, related_name='itineraries')

    duration_days = models.PositiveIntegerField()

    itinerary_type = models.CharField(max_length=15, choices=ITINERARY_TYPES, default='round_trip')

    departure_port = models.ForeignKey(Location, on_delete=models.CASCADE, related_name='departure_cruises')

    arrival_port = models.ForeignKey(Location, on_delete=models.CASCADE, related_name='arrival_cruises')

    description = models.TextField(blank=True)

    description_vi = models.TextField(blank=True)

    highlights = models.TextField(blank=True, help_text="Key highlights of this itinerary")

    highlights_vi = models.TextField(blank=True)

    is_active = models.BooleanField(default=True)

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

 

    class Meta:

        ordering = ['duration_days', 'name']

        verbose_name_plural = 'Cruise Itineraries'

 

    def __str__(self):

        return f"{self.duration_days}-day {self.name}"

 

    @property

    def port_count(self):

        return self.ports.count()

 

 

 

Cruise Line Cruise itinerary port

class ItineraryPort(models.Model):

    """Ports visited during a cruise itinerary"""

    itinerary = models.ForeignKey(CruiseItinerary, on_delete=models.CASCADE, related_name='ports')

    port = models.ForeignKey(Location, on_delete=models.CASCADE, related_name='itinerary_visits')

    day_number = models.PositiveIntegerField()

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

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

    is_sea_day = models.BooleanField(default=False)

    notes = models.TextField(blank=True, help_text="Special notes about this port visit")

    notes_vi = models.TextField(blank=True)

 

    class Meta:

        ordering = ['itinerary', 'day_number']

        constraints = [

            models.UniqueConstraint(fields=['itinerary', 'day_number'], name='unique_itinerary_day')

        ]

 

    def __str__(self):

        if self.is_sea_day:

            return f"Day {self.day_number}: Sea Day"

        return f"Day {self.day_number}: {self.port.name}"

 

 

Cruise Line Cruise Price

class CruisePrice(models.Model):

    """Pricing for different cabin categories on specific cruises"""

    cruise = models.ForeignKey(Cruise, on_delete=models.CASCADE, related_name='prices')

    cabin_category = models.ForeignKey(CabinCategory, on_delete=models.CASCADE)

    price_per_person = models.DecimalField(max_digits=10, decimal_places=2)

    single_supplement = models.DecimalField(max_digits=10, decimal_places=2, default=0)

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

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

   

    # Availability

    total_cabins = models.PositiveIntegerField(default=0)

    available_cabins = models.PositiveIntegerField(default=0)

   

    # Taxes and fees

    port_charges = models.DecimalField(max_digits=8, decimal_places=2, default=0)

    government_fees = models.DecimalField(max_digits=8, decimal_places=2, default=0)

   

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

   

    class Meta:

        constraints = [

            models.UniqueConstraint(fields=['cruise', 'cabin_category'], name='unique_cruise_cabin_category')

        ]

        ordering = ['cruise', 'cabin_category__cabin_type']

 

    def __str__(self):

        return f"{self.cruise} - {self.cabin_category.name}: ${self.price_per_person}"

 

    @property

    def total_price_per_person(self):

        price = self.price_per_person or 0

        port_charges = self.port_charges or 0

        government_fees = self.government_fees or 0

        return price + port_charges + government_fees

 

    @property

    def discounted_price(self):

        price = self.price_per_person or 0

        if self.cruise.is_deal and self.cruise.deal_discount_percent > 0:

            discount = (price * self.cruise.deal_discount_percent) / 100

            return price - discount

        return price

 

    @property

    def savings_amount(self):

        if self.cruise.is_deal:

            price = self.price_per_person or 0

            return price - self.discounted_price

        return Decimal('0.00')

 

    @property

    def is_available(self):

        return self.available_cabins > 0

 

Cruise Line Cruise Review

class CruiseReview(models.Model):

    """Customer reviews for cruises"""

    RATING_CHOICES = [(i, str(i)) for i in range(1, 6)]

 

    cruise = models.ForeignKey(Cruise, on_delete=models.CASCADE, related_name='reviews')

    user = models.ForeignKey(User, on_delete=models.CASCADE)

    overall_rating = models.IntegerField(choices=RATING_CHOICES)

    ship_rating = models.IntegerField(choices=RATING_CHOICES)

    service_rating = models.IntegerField(choices=RATING_CHOICES)

    dining_rating = models.IntegerField(choices=RATING_CHOICES)

    entertainment_rating = models.IntegerField(choices=RATING_CHOICES)

    value_rating = models.IntegerField(choices=RATING_CHOICES)

   

    title = models.CharField(max_length=200)

    review_text = models.TextField()

    pros = models.TextField(blank=True)

    cons = models.TextField(blank=True)

   

    travel_date = models.DateField()

    cabin_category = models.ForeignKey(CabinCategory, on_delete=models.SET_NULL, null=True, blank=True)

    traveler_type = models.CharField(max_length=50, blank=True, help_text="e.g., Couple, Family, Solo")

   

    is_verified = models.BooleanField(default=False)

    helpful_votes = models.PositiveIntegerField(default=0)

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

 

    class Meta:

        ordering = ['-created_at']

        constraints = [

            models.UniqueConstraint(fields=['cruise', 'user'], name='unique_cruise_user_review')

        ]

 

    def __str__(self):

        return f"{self.cruise} - {self.title} ({self.overall_rating}/5)"

 

 

Cruise Line Cruise Wishlist

class CruiseWishlist(models.Model):

    """User wishlist for cruises"""

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

    cruise = models.ForeignKey(Cruise, on_delete=models.CASCADE, related_name='wishlisted_by')

    created_at = models.DateTimeField(auto_now_add=True)

 

    class Meta:

        constraints = [

            models.UniqueConstraint(fields=['user', 'cruise'], name='unique_user_cruise_wishlist')

        ]

        ordering = ['-created_at']

 

    def __str__(self):

        return f"{self.user.username} - {self.cruise}"

 

Cruise Line ship

class Ship(models.Model):

    """Individual cruise ships"""

    SHIP_CATEGORIES = [

        ('luxury', 'Luxury'),

        ('premium', 'Premium'),

        ('contemporary', 'Contemporary'),

        ('budget', 'Budget'),

        ('expedition', 'Expedition'),

        ('river', 'River Cruise'),

    ]

 

    name = models.CharField(max_length=100)

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

    cruise_line = models.ForeignKey(CruiseLine, on_delete=models.CASCADE, related_name='ships')

    category = models.CharField(max_length=20, choices=SHIP_CATEGORIES, default='contemporary')

    year_built = models.PositiveIntegerField()

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

    capacity = models.PositiveIntegerField(help_text="Maximum passenger capacity")

    crew_size = models.PositiveIntegerField(help_text="Number of crew members")

    gross_tonnage = models.PositiveIntegerField()

    length = models.DecimalField(max_digits=6, decimal_places=2, help_text="Length in meters")

    beam = models.DecimalField(max_digits=5, decimal_places=2, help_text="Width in meters")

    decks = models.PositiveIntegerField()

    description = models.TextField(blank=True)

    description_vi = models.TextField(blank=True)

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

    is_active = models.BooleanField(default=True)

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

 

    class Meta:

        verbose_name = _("Cruise Ship")

        verbose_name_plural = _("Cruise Ships")

        ordering = ['cruise_line__name', 'name']

 

    def __str__(self):

        return f"{self.cruise_line.name} - {self.name}"

 

    @property

    def passenger_to_crew_ratio(self):

        if self.crew_size and self.capacity and self.crew_size > 0:

            return round(self.capacity / self.crew_size, 1)

        return 0

 

 

Ship Amenity

class ShipAmenity(models.Model):

    """Ship facilities and amenities"""

    AMENITY_CATEGORIES = [

        ('dining', 'Dining'),

        ('entertainment', 'Entertainment'),

        ('activities', 'Activities'),

        ('spa', 'Spa & Wellness'),

        ('sports', 'Sports & Recreation'),

        ('kids', 'Kids & Family'),

        ('shopping', 'Shopping'),

        ('accessibility', 'Accessibility'),

    ]

 

    name = models.CharField(max_length=100)

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

    category = models.CharField(max_length=20, choices=AMENITY_CATEGORIES)

    description = models.TextField(blank=True)

    description_vi = models.TextField(blank=True)

    icon = models.ImageField(upload_to='amenities/', blank=True, null=True)

    ships = models.ManyToManyField(Ship, related_name='amenities', blank=True)

 

    class Meta:

        ordering = ['category', 'name']

        verbose_name_plural = 'Ship Amenities'

 

    def __str__(self):

        return self.name

 

 

Ship Camin

class CabinCategory(models.Model):

    """Different types of cabins available on ships"""

    CABIN_TYPES = [

        ('interior', 'Interior'),

        ('ocean_view', 'Ocean View'),

        ('balcony', 'Balcony'),

        ('suite', 'Suite'),

    ]

 

    ship = models.ForeignKey(Ship, on_delete=models.CASCADE, related_name='cabin_categories')

    name = models.CharField(max_length=100)

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

    cabin_type = models.CharField(max_length=15, choices=CABIN_TYPES)

    deck_location = models.CharField(max_length=100, blank=True, help_text="Which decks this category is on")

    size_sqft = models.PositiveIntegerField(help_text="Size in square feet")

    max_occupancy = models.PositiveIntegerField(default=2)

    description = models.TextField(blank=True)

    description_vi = models.TextField(blank=True)

    amenities = models.TextField(blank=True, help_text="List of cabin amenities")

    amenities_vi = models.TextField(blank=True)

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

    is_active = models.BooleanField(default=True)

 

    class Meta:

        ordering = ['ship', 'cabin_type', 'name']

        verbose_name_plural = 'Cabin Categories'

 

    def __str__(self):

        return f"{self.ship.name} - {self.name}"

 

 

Attached Files

0 files found.

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