from django.contrib.auth.models import AbstractUser from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models from django.db.models import Avg class User(AbstractUser): """ Custom user model with phone-based authentication. Phone number is unique and serves as the primary identifier. """ phone = models.CharField(max_length=15, unique=True, db_index=True) name = models.CharField(max_length=255) # Username is not used but required by AbstractUser # We'll make it optional by setting blank=True username = models.CharField(max_length=150, blank=True, null=True) USERNAME_FIELD = 'phone' REQUIRED_FIELDS = ['name'] class Meta: db_table = 'users' verbose_name = 'User' verbose_name_plural = 'Users' def __str__(self): return f"{self.name} ({self.phone})" class Place(models.Model): """ Represents a place that can be reviewed. Unique constraint on (name, address) ensures no duplicates. """ name = models.CharField(max_length=255, db_index=True) address = models.TextField() created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'places' unique_together = [['name', 'address']] verbose_name = 'Place' verbose_name_plural = 'Places' indexes = [ models.Index(fields=['name']), ] def __str__(self): return f"{self.name} - {self.address[:50]}" @property def average_rating(self): """Calculate average rating from all reviews.""" avg = self.reviews.aggregate(avg_rating=Avg('rating'))['avg_rating'] return round(avg, 2) if avg is not None else None class Review(models.Model): """ Review left by a user for a place. Rating must be between 1-5. One review per user per place. """ user = models.ForeignKey( User, on_delete=models.CASCADE, related_name='reviews' ) place = models.ForeignKey( Place, on_delete=models.CASCADE, related_name='reviews' ) rating = models.IntegerField( validators=[MinValueValidator(1), MaxValueValidator(5)] ) text = models.TextField() created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'reviews' unique_together = [['user', 'place']] verbose_name = 'Review' verbose_name_plural = 'Reviews' ordering = ['-created_at'] indexes = [ models.Index(fields=['-created_at']), ] def __str__(self): return f"{self.user.name} - {self.place.name} ({self.rating}/5)"