From 40f0ef8ea5656369a210701ae8e33f9fc3d8c074 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 8 May 2026 13:32:33 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20a=C3=B1adir=20modelo=20Review=20para=20?= =?UTF-8?q?valoraciones=20de=20productos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ntity_alter_orderitem_quantity_and_more.py | 49 +++++++++++++++++++ tienda/models.py | 40 +++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 tienda/migrations/0009_alter_cartitem_quantity_alter_orderitem_quantity_and_more.py diff --git a/tienda/migrations/0009_alter_cartitem_quantity_alter_orderitem_quantity_and_more.py b/tienda/migrations/0009_alter_cartitem_quantity_alter_orderitem_quantity_and_more.py new file mode 100644 index 0000000..694df1f --- /dev/null +++ b/tienda/migrations/0009_alter_cartitem_quantity_alter_orderitem_quantity_and_more.py @@ -0,0 +1,49 @@ +# Generated by Django 6.0.5 on 2026-05-08 11:32 + +import django.core.validators +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tienda', '0008_alter_product_briefdesc_alter_product_description'), + ] + + operations = [ + migrations.AlterField( + model_name='cartitem', + name='quantity', + field=models.PositiveIntegerField(default=1, validators=[django.core.validators.MaxValueValidator(9999)]), + ), + migrations.AlterField( + model_name='orderitem', + name='quantity', + field=models.PositiveIntegerField(default=1, validators=[django.core.validators.MaxValueValidator(9999)]), + ), + migrations.AlterField( + model_name='stockreservationitem', + name='quantity', + field=models.PositiveIntegerField(default=1, validators=[django.core.validators.MaxValueValidator(9999)]), + ), + migrations.CreateModel( + name='Review', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rating', models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(5)])), + ('title', models.CharField(default='', max_length=200)), + ('content', models.TextField(default='', max_length=2000)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('images', models.ManyToManyField(blank=True, related_name='product_reviews', to='tienda.image')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='tienda.product')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='product_reviews', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created_at'], + 'unique_together': {('product', 'user')}, + }, + ), + ] diff --git a/tienda/models.py b/tienda/models.py index 25d82be..bd61912 100644 --- a/tienda/models.py +++ b/tienda/models.py @@ -122,6 +122,27 @@ class Product(models.Model): "creator": self.creator.to_dict() if self.creator else None } + def has_user_purchased(self, user): + """Verifica si el usuario ha comprado este producto al menos una vez""" + if not user or not user.is_authenticated: + return False + return OrderItem.objects.filter( + order__buyer=user, + order__status=Order.STATUS_PAID, + product=self + ).exists() + + def get_average_rating(self): + """Retorna la nota media de las valoraciones""" + reviews = self.reviews.all() + if not reviews.exists(): + return 0 + return round(reviews.aggregate(models.Avg('rating'))['rating__avg'], 1) + + def get_reviews_count(self): + """Retorna el número total de valoraciones""" + return self.reviews.count() + class StockReservation(models.Model): STATUS_ACTIVE = "active" @@ -331,6 +352,25 @@ class SavedPaymentMethod(models.Model): super().save(*args, **kwargs) +class Review(models.Model): + """Valoraciones de productos por usuarios que han realizado una compra""" + product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='reviews') + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='product_reviews') + rating = models.PositiveIntegerField(validators=[MaxValueValidator(5)]) + title = models.CharField(max_length=200, default="") + content = models.TextField(max_length=2000, default="") + images = models.ManyToManyField(Image, related_name='product_reviews', blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + unique_together = ('product', 'user') + ordering = ['-created_at'] + + def __str__(self): + return f"Valoración de {self.user.username} en {self.product.name} ({self.rating}★)" + + class ShippingAddress(models.Model): """Direcciones de entrega de los usuarios""" user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='shipping_addresses')