from django.db import models from django.contrib.auth.models import User from django.core.validators import MinValueValidator, MaxValueValidator from django.db.models.signals import post_save from django.dispatch import receiver from shop.models import Category # Import Category aus der shop App # Create your models here. class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) phone = models.CharField(max_length=20, blank=True) address = models.TextField(blank=True) default_shipping_address = models.TextField(blank=True) newsletter = models.BooleanField(default=False) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) def __str__(self): return f"Profil von {self.user.username}" @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): if created: UserProfile.objects.create(user=instance) @receiver(post_save, sender=User) def save_user_profile(sender, instance, **kwargs): instance.userprofile.save() class Product(models.Model): FURSUIT_TYPE_CHOICES = [ ('partial', 'Partial'), ('fullsuit', 'Fullsuit'), ('head', 'Head Only'), ('paws', 'Paws'), ('tail', 'Tail'), ('other', 'Other'), ] STYLE_CHOICES = [ ('toony', 'Toony'), ('semi_realistic', 'Semi-Realistic'), ('realistic', 'Realistic'), ('anime', 'Anime'), ('chibi', 'Chibi'), ] name = models.CharField(max_length=200) description = models.TextField() price = models.DecimalField(max_digits=10, decimal_places=2, validators=[MinValueValidator(0)]) stock = models.IntegerField(default=0) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) image = models.ImageField(upload_to='products/', null=True, blank=True) # Neue Felder fursuit_type = models.CharField(max_length=20, choices=FURSUIT_TYPE_CHOICES, default='other') style = models.CharField(max_length=20, choices=STYLE_CHOICES, default='toony') is_featured = models.BooleanField(default=False) is_custom_order = models.BooleanField(default=False) # Beziehungen category = models.ForeignKey('shop.Category', on_delete=models.SET_NULL, null=True, blank=True, related_name='product_items', verbose_name='Kategorie') wishlist_users = models.ManyToManyField(User, related_name='wishlist_products', blank=True) def __str__(self): return self.name def average_rating(self): if self.reviews.exists(): return self.reviews.aggregate(models.Avg('rating'))['rating__avg'] return 0 class Meta: ordering = ['-created'] indexes = [ models.Index(fields=['name']), models.Index(fields=['price']), models.Index(fields=['created']), models.Index(fields=['fursuit_type']), models.Index(fields=['style']), ] class Review(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='reviews') user = models.ForeignKey(User, on_delete=models.CASCADE) rating = models.IntegerField(validators=[MinValueValidator(1)]) comment = models.TextField() created = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('product', 'user') ordering = ['-created'] def __str__(self): return f'Review by {self.user.username} for {self.product.name}' class Cart(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name='product_carts') session_id = models.CharField(max_length=100, null=True, blank=True) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) def get_total(self): return sum(item.get_subtotal() for item in self.items.all()) def __str__(self): return f"Cart {self.id} - {'User: ' + self.user.username if self.user else 'Session: ' + self.session_id}" class CartItem(models.Model): cart = models.ForeignKey(Cart, related_name='items', on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.PositiveIntegerField(default=1) created = models.DateTimeField(auto_now_add=True) def get_subtotal(self): return self.product.price * self.quantity def __str__(self): return f"{self.quantity}x {self.product.name} in Cart {self.cart.id}" class Meta: unique_together = ('cart', 'product') class Order(models.Model): STATUS_CHOICES = [ ('pending', 'Ausstehend'), ('processing', 'In Bearbeitung'), ('shipped', 'Versendet'), ('delivered', 'Geliefert'), ('cancelled', 'Storniert'), ] PAYMENT_STATUS_CHOICES = [ ('pending', 'Ausstehend'), ('processing', 'Wird bearbeitet'), ('paid', 'Bezahlt'), ('failed', 'Fehlgeschlagen'), ('refunded', 'Zurückerstattet'), ] PAYMENT_METHOD_CHOICES = [ ('card', 'Kreditkarte'), ('sepa', 'SEPA-Lastschrift'), ('giropay', 'Giropay'), ('sofort', 'Sofort'), ('bancontact', 'Bancontact'), ] user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='product_orders') full_name = models.CharField(max_length=200) email = models.EmailField() address = models.TextField() phone = models.CharField(max_length=20) total_amount = models.DecimalField(max_digits=10, decimal_places=2) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) # Zahlungsinformationen payment_status = models.CharField(max_length=20, choices=PAYMENT_STATUS_CHOICES, default='pending') payment_method = models.CharField(max_length=20, choices=PAYMENT_METHOD_CHOICES, null=True, blank=True) stripe_payment_intent_id = models.CharField(max_length=100, null=True, blank=True) stripe_payment_method_id = models.CharField(max_length=100, null=True, blank=True) payment_date = models.DateTimeField(null=True, blank=True) def __str__(self): return f"Bestellung #{self.id} von {self.full_name}" class Meta: ordering = ['-created'] indexes = [ models.Index(fields=['status']), models.Index(fields=['payment_status']), models.Index(fields=['created']), ] def get_payment_status_display_class(self): status_classes = { 'pending': 'warning', 'processing': 'info', 'paid': 'success', 'failed': 'danger', 'refunded': 'secondary', } return status_classes.get(self.payment_status, 'secondary') def get_order_status_display_class(self): status_classes = { 'pending': 'warning', 'processing': 'info', 'shipped': 'primary', 'delivered': 'success', 'cancelled': 'danger', } return status_classes.get(self.status, 'secondary') class OrderItem(models.Model): order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True) product_name = models.CharField(max_length=200) price = models.DecimalField(max_digits=10, decimal_places=2) quantity = models.PositiveIntegerField() def get_subtotal(self): return self.price * self.quantity def __str__(self): return f"{self.quantity}x {self.product_name} in Bestellung #{self.order.id}" class Wishlist(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) products = models.ManyToManyField(Product) created = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Wunschliste von {self.user.username}" class FAQ(models.Model): question = models.CharField(max_length=255, verbose_name='Frage') answer = models.TextField(verbose_name='Antwort') category = models.CharField(max_length=100, verbose_name='Kategorie') order = models.IntegerField(default=0, verbose_name='Reihenfolge') created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) class Meta: ordering = ['category', 'order'] verbose_name = 'FAQ' verbose_name_plural = 'FAQs' def __str__(self): return self.question class ContactMessage(models.Model): CATEGORY_CHOICES = [ ('general', 'Allgemeine Anfrage'), ('order', 'Bestellung'), ('return', 'Rückgabe/Umtausch'), ('complaint', 'Beschwerde'), ('technical', 'Technische Frage'), ] name = models.CharField(max_length=100) email = models.EmailField() order_number = models.CharField(max_length=50, blank=True, null=True) category = models.CharField(max_length=20, choices=CATEGORY_CHOICES) subject = models.CharField(max_length=200) message = models.TextField() created = models.DateTimeField(auto_now_add=True) status = models.CharField( max_length=20, choices=[ ('new', 'Neu'), ('in_progress', 'In Bearbeitung'), ('resolved', 'Erledigt'), ('closed', 'Geschlossen'), ], default='new' ) user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) staff_notes = models.TextField(blank=True) class Meta: ordering = ['-created'] verbose_name = 'Kontaktanfrage' verbose_name_plural = 'Kontaktanfragen' def __str__(self): return f"{self.category} - {self.subject} ({self.created.strftime('%d.%m.%Y')})" class CustomOrder(models.Model): STATUS_CHOICES = [ ('pending', 'Anfrage eingegangen'), ('quoted', 'Angebot erstellt'), ('approved', 'Angebot akzeptiert'), ('in_progress', 'In Arbeit'), ('ready', 'Fertig zur Abholung'), ('shipped', 'Versendet'), ('completed', 'Abgeschlossen'), ('cancelled', 'Storniert'), ] user = models.ForeignKey(User, on_delete=models.CASCADE) fursuit_type = models.CharField(max_length=20, choices=Product.FURSUIT_TYPE_CHOICES) style = models.CharField(max_length=20, choices=Product.STYLE_CHOICES) character_name = models.CharField(max_length=100) character_description = models.TextField() reference_images = models.FileField(upload_to='references/', null=True, blank=True) special_requests = models.TextField(blank=True) measurements = models.TextField() color_preferences = models.TextField() budget_range = models.CharField(max_length=100) deadline_request = models.DateField(null=True, blank=True) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') quoted_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) def __str__(self): return f"Custom Order #{self.id} - {self.character_name}" class Meta: ordering = ['-created'] class OrderProgress(models.Model): PROGRESS_CHOICES = [ ('design', 'Design & Planung'), ('base', 'Grundform'), ('fur', 'Fell'), ('details', 'Details'), ('electronics', 'Elektronik (optional)'), ('finishing', 'Finishing'), ] custom_order = models.ForeignKey(CustomOrder, on_delete=models.CASCADE, related_name='progress_updates') stage = models.CharField(max_length=20, choices=PROGRESS_CHOICES) description = models.TextField() image = models.ImageField(upload_to='progress/', null=True, blank=True) completed = models.BooleanField(default=False) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) def __str__(self): return f"{self.custom_order.character_name} - {self.get_stage_display()}" class Meta: ordering = ['created'] class GalleryImage(models.Model): FURSUIT_TYPE_CHOICES = [ ('full', 'Fullsuit'), ('partial', 'Partial'), ('head', 'Head Only'), ('other', 'Other'), ] STYLE_CHOICES = [ ('toony', 'Toony'), ('semi', 'Semi-Realistic'), ('real', 'Realistic'), ('anime', 'Anime'), ] title = models.CharField(max_length=200, verbose_name='Titel') description = models.TextField(blank=True, verbose_name='Beschreibung') image = models.ImageField(upload_to='gallery/', verbose_name='Bild') fursuit_type = models.CharField( max_length=20, choices=FURSUIT_TYPE_CHOICES, default='full', verbose_name='Fursuit-Typ' ) style = models.CharField( max_length=20, choices=STYLE_CHOICES, default='toony', verbose_name='Stil' ) is_featured = models.BooleanField(default=False, verbose_name='Hervorgehoben') order = models.IntegerField(default=0, verbose_name='Reihenfolge') created = models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am') class Meta: verbose_name = 'Galeriebild' verbose_name_plural = 'Galeriebilder' ordering = ['order', '-created'] def __str__(self): return self.title class Payment(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE) variant = models.CharField(max_length=255) status = models.CharField(max_length=10) fraud_status = models.CharField(max_length=10, null=True, blank=True) fraud_message = models.TextField(null=True, blank=True) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) transaction_id = models.CharField(max_length=255, null=True, blank=True) currency = models.CharField(max_length=10) total = models.DecimalField(max_digits=9, decimal_places=2, default='0.0') delivery = models.DecimalField(max_digits=9, decimal_places=2, default='0.0') tax = models.DecimalField(max_digits=9, decimal_places=2, default='0.0') description = models.TextField(null=True, blank=True) billing_first_name = models.CharField(max_length=256, null=True, blank=True) billing_last_name = models.CharField(max_length=256, null=True, blank=True) billing_address_1 = models.CharField(max_length=256, null=True, blank=True) billing_address_2 = models.CharField(max_length=256, null=True, blank=True) billing_city = models.CharField(max_length=256, null=True, blank=True) billing_postcode = models.CharField(max_length=256, null=True, blank=True) billing_country_code = models.CharField(max_length=2, null=True, blank=True) billing_country_area = models.CharField(max_length=256, null=True, blank=True) billing_email = models.EmailField(null=True, blank=True) customer_ip_address = models.GenericIPAddressField(null=True, blank=True) extra_data = models.TextField(null=True, blank=True) message = models.TextField(null=True, blank=True) token = models.CharField(max_length=36, null=True, blank=True) captured_amount = models.DecimalField(max_digits=9, decimal_places=2, default='0.0') class Meta: ordering = ['-created'] def __str__(self): return f"Payment {self.id} for Order {self.order.id}" @property def attrs(self): return {} def get_failure_url(self): return f'/payment/failed/{self.order.id}/' def get_success_url(self): return f'/payment/success/{self.order.id}/'