| 1 |
from django.db import models |
| 2 |
from django.core.validators import MinValueValidator, MaxValueValidator |
| 3 |
from django.utils import timezone |
| 4 |
|
| 5 |
|
| 6 |
class Restaurant(models.Model): |
| 7 |
"""A restaurant that might serve toast""" |
| 8 |
place_id = models.CharField(max_length=255, unique=True, db_index=True) |
| 9 |
name = models.CharField(max_length=255) |
| 10 |
address = models.TextField() |
| 11 |
latitude = models.FloatField() |
| 12 |
longitude = models.FloatField() |
| 13 |
created_at = models.DateTimeField(default=timezone.now) |
| 14 |
|
| 15 |
# Toast availability status |
| 16 |
has_toast = models.BooleanField(null=True, blank=True, help_text="Does this place serve toast?") |
| 17 |
|
| 18 |
# Cached rating fields (updated via signals) |
| 19 |
average_rating = models.FloatField(null=True, blank=True) |
| 20 |
total_ratings = models.IntegerField(default=0) |
| 21 |
|
| 22 |
class Meta: |
| 23 |
ordering = ['-created_at'] |
| 24 |
indexes = [ |
| 25 |
models.Index(fields=['latitude', 'longitude']), |
| 26 |
] |
| 27 |
|
| 28 |
def __str__(self): |
| 29 |
return self.name |
| 30 |
|
| 31 |
def update_rating_cache(self): |
| 32 |
"""Update the cached rating values""" |
| 33 |
ratings = self.ratings.all() |
| 34 |
if ratings.exists(): |
| 35 |
self.total_ratings = ratings.count() |
| 36 |
self.average_rating = ratings.aggregate( |
| 37 |
avg_rating=models.Avg('rating') |
| 38 |
)['avg_rating'] |
| 39 |
else: |
| 40 |
self.total_ratings = 0 |
| 41 |
self.average_rating = None |
| 42 |
self.save(update_fields=['average_rating', 'total_ratings']) |
| 43 |
|
| 44 |
|
| 45 |
class Rating(models.Model): |
| 46 |
"""A toast rating for a restaurant""" |
| 47 |
restaurant = models.ForeignKey( |
| 48 |
Restaurant, |
| 49 |
on_delete=models.CASCADE, |
| 50 |
related_name='ratings' |
| 51 |
) |
| 52 |
rating = models.IntegerField( |
| 53 |
validators=[MinValueValidator(1), MaxValueValidator(5)] |
| 54 |
) |
| 55 |
review = models.TextField() |
| 56 |
created_at = models.DateTimeField(default=timezone.now) |
| 57 |
|
| 58 |
class Meta: |
| 59 |
ordering = ['-created_at'] |
| 60 |
|
| 61 |
def __str__(self): |
| 62 |
return f"{self.restaurant.name} - {self.rating} stars" |
| 63 |
|
| 64 |
def clean(self): |
| 65 |
"""Validate that the review mentions toast""" |
| 66 |
from django.core.exceptions import ValidationError |
| 67 |
|
| 68 |
if self.review: |
| 69 |
toast_keywords = [ |
| 70 |
'toast', 'bread', 'butter', 'jam', 'marmalade', |
| 71 |
'french toast', 'avocado', 'sourdough', 'rye', |
| 72 |
'whole wheat', 'brioche', 'challah' |
| 73 |
] |
| 74 |
review_lower = self.review.lower() |
| 75 |
if not any(keyword in review_lower for keyword in toast_keywords): |
| 76 |
raise ValidationError( |
| 77 |
'Reviews must be about toast! Please mention the toast in your review.' |
| 78 |
) |
| 79 |
super().clean() |
| 80 |
|
| 81 |
def save(self, *args, **kwargs): |
| 82 |
self.full_clean() # Runs clean() method |
| 83 |
super().save(*args, **kwargs) |
| 84 |
# Update restaurant's cached ratings |
| 85 |
self.restaurant.update_rating_cache() |
| 86 |
|
| 87 |
def delete(self, *args, **kwargs): |
| 88 |
restaurant = self.restaurant |
| 89 |
super().delete(*args, **kwargs) |
| 90 |
# Update restaurant's cached ratings after deletion |
| 91 |
restaurant.update_rating_cache() |