Initial commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from apps.salons.models import Review, Salon, SalonPhoto, Service, StaffProfile
|
||||
|
||||
admin.site.register(Salon)
|
||||
admin.site.register(SalonPhoto)
|
||||
admin.site.register(Service)
|
||||
admin.site.register(StaffProfile)
|
||||
admin.site.register(Review)
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SalonsConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.salons"
|
||||
@@ -0,0 +1,70 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Salon(models.Model):
|
||||
owner = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="owned_salons",
|
||||
)
|
||||
name = models.CharField(max_length=200)
|
||||
description = models.TextField(blank=True)
|
||||
address = models.CharField(max_length=255)
|
||||
city = models.CharField(max_length=100)
|
||||
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
|
||||
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
|
||||
phone_number = models.CharField(max_length=20, blank=True)
|
||||
email = models.EmailField(blank=True)
|
||||
website = models.URLField(blank=True)
|
||||
rating_avg = models.DecimalField(max_digits=3, decimal_places=2, default=0)
|
||||
rating_count = models.PositiveIntegerField(default=0)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class SalonPhoto(models.Model):
|
||||
salon = models.ForeignKey(Salon, on_delete=models.CASCADE, related_name="photos")
|
||||
image_url = models.URLField()
|
||||
alt_text = models.CharField(max_length=200, blank=True)
|
||||
sort_order = models.PositiveIntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.salon.name} photo"
|
||||
|
||||
|
||||
class Service(models.Model):
|
||||
salon = models.ForeignKey(Salon, on_delete=models.CASCADE, related_name="services")
|
||||
name = models.CharField(max_length=200)
|
||||
description = models.TextField(blank=True)
|
||||
duration_minutes = models.PositiveIntegerField()
|
||||
price_amount = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
currency = models.CharField(max_length=10, default=getattr(settings, "DEFAULT_CURRENCY", "SAR"))
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} - {self.salon.name}"
|
||||
|
||||
|
||||
class StaffProfile(models.Model):
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
salon = models.ForeignKey(Salon, on_delete=models.CASCADE, related_name="staff")
|
||||
title = models.CharField(max_length=200, blank=True)
|
||||
bio = models.TextField(blank=True)
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.email} - {self.salon.name}"
|
||||
|
||||
|
||||
class Review(models.Model):
|
||||
salon = models.ForeignKey(Salon, on_delete=models.CASCADE, related_name="reviews")
|
||||
customer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="reviews")
|
||||
rating = models.PositiveSmallIntegerField()
|
||||
comment = models.TextField(blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Review {self.rating} for {self.salon.name}"
|
||||
@@ -0,0 +1,71 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.salons.models import Review, Salon, SalonPhoto, Service, StaffProfile
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class SalonPhotoSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SalonPhoto
|
||||
fields = ["id", "image_url", "alt_text", "sort_order"]
|
||||
|
||||
|
||||
class ServiceSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Service
|
||||
fields = ["id", "name", "description", "duration_minutes", "price_amount", "currency", "is_active"]
|
||||
|
||||
|
||||
class StaffSerializer(serializers.ModelSerializer):
|
||||
name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = StaffProfile
|
||||
fields = ["id", "name", "title", "bio", "is_active"]
|
||||
|
||||
def get_name(self, obj):
|
||||
first = obj.user.first_name or ""
|
||||
last = obj.user.last_name or ""
|
||||
return (first + " " + last).strip() or obj.user.email
|
||||
|
||||
|
||||
class ReviewSerializer(serializers.ModelSerializer):
|
||||
customer_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Review
|
||||
fields = ["id", "rating", "comment", "created_at", "customer_name"]
|
||||
|
||||
def get_customer_name(self, obj):
|
||||
first = obj.customer.first_name or ""
|
||||
last = obj.customer.last_name or ""
|
||||
return (first + " " + last).strip() or obj.customer.email
|
||||
|
||||
|
||||
class SalonSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Salon
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"address",
|
||||
"city",
|
||||
"phone_number",
|
||||
"email",
|
||||
"website",
|
||||
"rating_avg",
|
||||
"rating_count",
|
||||
]
|
||||
|
||||
|
||||
class SalonDetailSerializer(SalonSerializer):
|
||||
photos = SalonPhotoSerializer(many=True, read_only=True)
|
||||
services = ServiceSerializer(many=True, read_only=True)
|
||||
staff = StaffSerializer(many=True, read_only=True)
|
||||
reviews = ReviewSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta(SalonSerializer.Meta):
|
||||
fields = SalonSerializer.Meta.fields + ["photos", "services", "staff", "reviews"]
|
||||
@@ -0,0 +1,14 @@
|
||||
from django.urls import include, path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from apps.salons.views import SalonReviewsView, SalonServicesView, SalonStaffView, SalonViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"", SalonViewSet, basename="salon")
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
path("<int:salon_id>/services/", SalonServicesView.as_view(), name="salon_services"),
|
||||
path("<int:salon_id>/staff/", SalonStaffView.as_view(), name="salon_staff"),
|
||||
path("<int:salon_id>/reviews/", SalonReviewsView.as_view(), name="salon_reviews"),
|
||||
]
|
||||
@@ -0,0 +1,57 @@
|
||||
from django.db.models import Q
|
||||
from rest_framework import generics, permissions, viewsets
|
||||
|
||||
from apps.salons.models import Review, Salon, Service, StaffProfile
|
||||
from apps.salons.serializers import (
|
||||
ReviewSerializer,
|
||||
SalonDetailSerializer,
|
||||
SalonSerializer,
|
||||
ServiceSerializer,
|
||||
StaffSerializer,
|
||||
)
|
||||
|
||||
|
||||
class SalonViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = Salon.objects.all()
|
||||
city = self.request.query_params.get("city")
|
||||
query = self.request.query_params.get("q")
|
||||
service = self.request.query_params.get("service")
|
||||
if city:
|
||||
queryset = queryset.filter(city__iexact=city)
|
||||
if query:
|
||||
queryset = queryset.filter(Q(name__icontains=query) | Q(description__icontains=query))
|
||||
if service:
|
||||
queryset = queryset.filter(services__name__icontains=service)
|
||||
return queryset.distinct()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == "retrieve":
|
||||
return SalonDetailSerializer
|
||||
return SalonSerializer
|
||||
|
||||
|
||||
class SalonServicesView(generics.ListAPIView):
|
||||
serializer_class = ServiceSerializer
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get_queryset(self):
|
||||
return Service.objects.filter(salon_id=self.kwargs["salon_id"], is_active=True)
|
||||
|
||||
|
||||
class SalonStaffView(generics.ListAPIView):
|
||||
serializer_class = StaffSerializer
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get_queryset(self):
|
||||
return StaffProfile.objects.filter(salon_id=self.kwargs["salon_id"], is_active=True)
|
||||
|
||||
|
||||
class SalonReviewsView(generics.ListAPIView):
|
||||
serializer_class = ReviewSerializer
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get_queryset(self):
|
||||
return Review.objects.filter(salon_id=self.kwargs["salon_id"]).order_by("-created_at")
|
||||
Reference in New Issue
Block a user