diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..dd1f7a3
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,72 @@
+# Git
+.git
+.gitignore
+
+# Python
+__pycache__
+*.pyc
+*.pyo
+*.pyd
+.Python
+env
+pip-log.txt
+pip-delete-this-directory.txt
+.tox
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.log
+.git
+.mypy_cache
+.pytest_cache
+.hypothesis
+
+# Django
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Media files (will be mounted as volume)
+media/
+
+# Static files (will be collected)
+staticfiles/
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# OS
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Docker
+Dockerfile
+docker-compose.yml
+.dockerignore
+
+# Documentation
+README.md
+docs/
+*.md
+
+# Testing
+.pytest_cache/
+.coverage
+htmlcov/
+
+# Temporary files
+*.tmp
+*.temp
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index ba1571f..d1428f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Django
*.log
*.pot
@@ -150,11 +151,66 @@ nosetests.xml
coverage.xml
*.cover
.hypothesis/
+=======
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
# Translations
*.mo
*.pot
+<<<<<<< HEAD
# Scrapy stuff:
.scrapy
@@ -203,6 +259,8 @@ dmypy.json
# Pyre type checker
.pyre/
+=======
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
# Django stuff:
*.log
local_settings.py
@@ -225,11 +283,34 @@ target/
# Jupyter Notebook
.ipynb_checkpoints
+<<<<<<< HEAD
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
+=======
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
# SageMath parsed files
*.sage.py
@@ -261,7 +342,36 @@ dmypy.json
# Pyre type checker
.pyre/
+<<<<<<< HEAD
# Project specific
uploads/
temp/
-cache/
\ No newline at end of file
+cache/
+=======
+# Django static files
+staticfiles/
+
+# Media files (uploads)
+media/
+
+# Database files
+*.db
+*.sqlite3
+
+# IDE files
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# OS files
+.DS_Store
+Thumbs.db
+
+# Log files
+*.log
+
+# Environment variables
+.env.local
+.env.production
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/django_shop/settings.py b/django_shop/settings.py
index 78454e5..6d8cb69 100644
--- a/django_shop/settings.py
+++ b/django_shop/settings.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
import os
from pathlib import Path
from dotenv import load_dotenv
@@ -18,4 +19,26 @@ ADMINS = [
]
# Lagerbestand-Einstellungen
+=======
+import os
+from pathlib import Path
+from dotenv import load_dotenv
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+# Lade .env Datei
+load_dotenv(BASE_DIR / '.env')
+
+# E-Mail-Einstellungen (temporär Console-Backend)
+EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+DEFAULT_FROM_EMAIL = 'Fursuit Shop '
+
+# Admin-E-Mail-Empfänger
+ADMINS = [
+ ('Shop Admin', 'admin@fursuitshop.com'),
+]
+
+# Lagerbestand-Einstellungen
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
LOW_STOCK_THRESHOLD = 5 # Schwellenwert für niedrigen Lagerbestand
\ No newline at end of file
diff --git a/docs/email_system.md b/docs/email_system.md
index 4821e51..3333378 100644
--- a/docs/email_system.md
+++ b/docs/email_system.md
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# E-Mail-System Dokumentation
## Übersicht
@@ -152,4 +153,160 @@ shop/templates/shop/emails/
- CSS-Styles in Template-Header
- Einheitliche Farbcodes und Abstände
- Bootstrap-kompatible Klassen
+=======
+# E-Mail-System Dokumentation
+
+## Übersicht
+
+Das E-Mail-System des Fursuit Shops versendet automatisch Benachrichtigungen an Kunden und Administratoren bei verschiedenen Shop-Ereignissen. Das System ist mehrsprachig (DE/EN) und verwendet responsive HTML-Templates mit Text-Alternativen.
+
+## Konfiguration
+
+### E-Mail-Einstellungen
+
+Die E-Mail-Konfiguration erfolgt über Umgebungsvariablen in der `.env`-Datei:
+
+```env
+EMAIL_HOST=smtp.gmail.com
+EMAIL_PORT=587
+EMAIL_USE_TLS=True
+EMAIL_HOST_USER=your-email@gmail.com
+EMAIL_HOST_PASSWORD=your-app-specific-password
+DEFAULT_FROM_EMAIL=Fursuit Shop
+```
+
+### Admin-Benachrichtigungen
+
+Administratoren werden in `settings.py` konfiguriert:
+
+```python
+ADMINS = [
+ ('Shop Admin', 'admin@fursuitshop.com'),
+]
+```
+
+### Lagerbestand-Schwellenwert
+
+```python
+LOW_STOCK_THRESHOLD = 5 # Benachrichtigung bei ≤ 5 Artikeln
+```
+
+## E-Mail-Typen
+
+### 1. Kundenbenachrichtigungen
+
+#### Bestellbestätigung
+- Gesendet nach erfolgreicher Zahlung
+- Enthält Bestelldetails, Produkte und Preise
+- Template: `order_confirmation.html/txt`
+
+#### Status-Updates
+- Gesendet bei Statusänderungen der Bestellung
+- Kann Fortschrittsbilder und Beschreibungen enthalten
+- Template: `order_status_update.html/txt`
+
+#### Versandbestätigung
+- Gesendet wenn Bestellung versendet wurde
+- Enthält Tracking-Nummer und Versanddetails
+- Template: `shipping_confirmation.html/txt`
+
+### 2. Admin-Benachrichtigungen
+
+#### Neue Bestellung
+- Bei jeder neuen Bestellung
+- Spezielle Markierung für Fursuit-Bestellungen
+- Template: `admin_notification.html/txt`
+
+#### Zahlungsfehler
+- Bei fehlgeschlagenen Zahlungen
+- Enthält detaillierte Fehlerinformationen
+- Template: `admin_notification.html/txt`
+
+#### Custom Design
+- Bei Bestellungen mit Custom Designs
+- Enthält Design-Dateien und Notizen
+- Template: `admin_notification.html/txt`
+
+#### Niedriger Lagerbestand
+- Bei Unterschreitung des Schwellenwerts
+- Enthält Produktdetails und aktuellen Bestand
+- Template: `low_stock_notification.html/txt`
+
+## Signal-Handler
+
+Das System verwendet Django-Signals für automatische Benachrichtigungen:
+
+### Order-Signals
+```python
+@receiver(post_save, sender=Order)
+def handle_order_notifications(sender, instance, created, **kwargs):
+ # Sendet Benachrichtigungen bei neuen Bestellungen und Statusänderungen
+```
+
+### Payment-Signals
+```python
+@receiver(post_save, sender=PaymentError)
+def handle_payment_error(sender, instance, created, **kwargs):
+ # Benachrichtigt Admins über Zahlungsfehler
+```
+
+### Product-Signals
+```python
+@receiver(pre_save, sender=Product)
+def check_stock_level(sender, instance, **kwargs):
+ # Überprüft Lagerbestand und sendet Warnungen
+```
+
+## E-Mail-Templates
+
+Alle E-Mail-Templates:
+- Sind vollständig responsive
+- Unterstützen HTML und Text-Alternativen
+- Sind mehrsprachig (DE/EN)
+- Verwenden einheitliches Branding
+
+### Template-Struktur
+```
+shop/templates/shop/emails/
+├── order_confirmation.html
+├── order_confirmation.txt
+├── order_status_update.html
+├── order_status_update.txt
+├── shipping_confirmation.html
+├── shipping_confirmation.txt
+├── admin_notification.html
+├── admin_notification.txt
+├── low_stock_notification.html
+└── low_stock_notification.txt
+```
+
+## Sicherheit
+
+- TLS-Verschlüsselung für E-Mail-Versand
+- Keine sensiblen Daten in E-Mails
+- Sichere Links zu Admin-Bereich
+- App-spezifische Passwörter für SMTP
+
+## Fehlerbehandlung
+
+- Robuste Exception-Handling
+- Logging von Versandfehlern
+- Vermeidung von Doppel-Benachrichtigungen
+- Fallback auf Text-Version bei HTML-Problemen
+
+## Wartung
+
+### Neue E-Mail-Typen hinzufügen
+
+1. Templates erstellen (HTML und Text)
+2. E-Mail-Funktion in `emails.py` hinzufügen
+3. Signal-Handler in `signals.py` registrieren
+4. Übersetzungen in `.po`-Dateien hinzufügen
+
+### Template-Anpassung
+
+- CSS-Styles in Template-Header
+- Einheitliche Farbcodes und Abstände
+- Bootstrap-kompatible Klassen
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
- Responsive Breakpoints
\ No newline at end of file
diff --git a/init_data.py b/init_data.py
index 9e22031..4aca65a 100644
--- a/init_data.py
+++ b/init_data.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
import sqlite3
def add_test_data():
@@ -28,4 +29,36 @@ def add_test_data():
print("Testdaten wurden erfolgreich hinzugefügt!")
if __name__ == "__main__":
+=======
+import sqlite3
+
+def add_test_data():
+ conn = sqlite3.connect('shop.db')
+ cursor = conn.cursor()
+
+ # Lösche vorhandene Daten
+ cursor.execute("DELETE FROM products")
+
+ # Testprodukte
+ products = [
+ ("Gaming Maus", "Hochwertige Gaming-Maus mit RGB-Beleuchtung", 49.99, 10),
+ ("Mechanische Tastatur", "Mechanische Gaming-Tastatur mit blauen Switches", 89.99, 5),
+ ("Gaming Headset", "7.1 Surround Sound Gaming Headset", 79.99, 8),
+ ("Mousepad XL", "Extra großes Gaming-Mousepad", 19.99, 15),
+ ("Webcam HD", "1080p Webcam für Streaming", 59.99, 3)
+ ]
+
+ # Füge Produkte ein
+ cursor.executemany(
+ "INSERT INTO products (name, description, price, stock) VALUES (?, ?, ?, ?)",
+ products
+ )
+
+ # Speichere Änderungen
+ conn.commit()
+ conn.close()
+ print("Testdaten wurden erfolgreich hinzugefügt!")
+
+if __name__ == "__main__":
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
add_test_data()
\ No newline at end of file
diff --git a/init_db.py b/init_db.py
index 6a5b1dd..5d2f845 100644
--- a/init_db.py
+++ b/init_db.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
import sqlite3
from contextlib import contextmanager
@@ -40,4 +41,48 @@ def init_db():
if __name__ == "__main__":
init_db()
+=======
+import sqlite3
+from contextlib import contextmanager
+
+@contextmanager
+def get_db():
+ conn = sqlite3.connect('shop.db')
+ try:
+ yield conn
+ finally:
+ conn.close()
+
+def init_db():
+ with get_db() as conn:
+ # Tabelle erstellen
+ conn.execute("""
+ CREATE TABLE IF NOT EXISTS products (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ description TEXT,
+ price REAL NOT NULL,
+ stock INTEGER NOT NULL
+ )
+ """)
+
+ # Beispieldaten einfügen
+ products = [
+ ("Laptop", "Leistungsstarker Laptop für Arbeit und Gaming", 999.99, 5),
+ ("Smartphone", "Neuestes Modell mit Top-Kamera", 699.99, 10),
+ ("Kopfhörer", "Kabellose Kopfhörer mit Noise-Cancelling", 199.99, 15),
+ ("Tablet", "Perfekt für Unterhaltung und Produktivität", 449.99, 8),
+ ("Smartwatch", "Fitness-Tracking und Benachrichtigungen", 299.99, 12)
+ ]
+
+ conn.execute("DELETE FROM products") # Alte Daten löschen
+ conn.executemany(
+ "INSERT INTO products (name, description, price, stock) VALUES (?, ?, ?, ?)",
+ products
+ )
+ conn.commit()
+
+if __name__ == "__main__":
+ init_db()
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
print("Datenbank wurde erfolgreich initialisiert!")
\ No newline at end of file
diff --git a/manage.py b/manage.py
index 4073cee..eeaf2e6 100644
--- a/manage.py
+++ b/manage.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
@@ -20,3 +21,27 @@ def main():
if __name__ == '__main__':
main()
+=======
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+ """Run administrative tasks."""
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webshop.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/admin.py b/products/admin.py
index 622bb25..bbd972a 100644
--- a/products/admin.py
+++ b/products/admin.py
@@ -1,2 +1,7 @@
+<<<<<<< HEAD
# Diese Datei ist jetzt leer, da alle Admin-Konfigurationen in webshop/admin.py zentral verwaltet werden
# Die Admin-Konfigurationen wurden in die neue zentrale Admin-Site verschoben
+=======
+# Diese Datei ist jetzt leer, da alle Admin-Konfigurationen in webshop/admin.py zentral verwaltet werden
+# Die Admin-Konfigurationen wurden in die neue zentrale Admin-Site verschoben
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/apps.py b/products/apps.py
index 145a2ac..361a5de 100644
--- a/products/apps.py
+++ b/products/apps.py
@@ -1,6 +1,15 @@
+<<<<<<< HEAD
from django.apps import AppConfig
class ProductsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'products'
+=======
+from django.apps import AppConfig
+
+
+class ProductsConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'products'
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/forms.py b/products/forms.py
index 9f9a8d7..e781ec1 100644
--- a/products/forms.py
+++ b/products/forms.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
from django import forms
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordChangeForm
from django.contrib.auth.models import User
@@ -216,4 +217,224 @@ class OrderProgressForm(forms.ModelForm):
'description': 'Beschreiben Sie detailliert, was in diesem Schritt gemacht wurde.',
'image': 'Fügen Sie ein Foto hinzu, um den Fortschritt zu dokumentieren.',
'completed': 'Markieren Sie diesen Schritt als abgeschlossen, wenn er fertig ist.'
+=======
+from django import forms
+from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordChangeForm
+from django.contrib.auth.models import User
+from .models import Order, Product, Review, UserProfile, ContactMessage, CustomOrder, OrderProgress
+
+class OrderForm(forms.ModelForm):
+ class Meta:
+ model = Order
+ fields = ['full_name', 'email', 'address', 'phone']
+ widgets = {
+ 'full_name': forms.TextInput(attrs={'class': 'form-control'}),
+ 'email': forms.EmailInput(attrs={'class': 'form-control'}),
+ 'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
+ 'phone': forms.TextInput(attrs={'class': 'form-control'}),
+ }
+ labels = {
+ 'full_name': 'Vollständiger Name',
+ 'email': 'E-Mail-Adresse',
+ 'address': 'Lieferadresse',
+ 'phone': 'Telefonnummer',
+ }
+
+class CustomUserCreationForm(UserCreationForm):
+ email = forms.EmailField(required=True, widget=forms.EmailInput(attrs={'class': 'form-control'}))
+
+ class Meta:
+ model = User
+ fields = ['username', 'email', 'password1', 'password2']
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ for field in self.fields:
+ self.fields[field].widget.attrs['class'] = 'form-control'
+ self.fields['username'].label = 'Benutzername'
+ self.fields['password1'].label = 'Passwort'
+ self.fields['password2'].label = 'Passwort bestätigen'
+
+class CustomAuthenticationForm(AuthenticationForm):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ for field in self.fields:
+ self.fields[field].widget.attrs['class'] = 'form-control'
+ self.fields['username'].label = 'Benutzername'
+ self.fields['password'].label = 'Passwort'
+
+class ReviewForm(forms.ModelForm):
+ class Meta:
+ model = Review
+ fields = ['rating', 'comment']
+ widgets = {
+ 'rating': forms.Select(attrs={'class': 'form-control'}),
+ 'comment': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
+ }
+ labels = {
+ 'rating': 'Bewertung',
+ 'comment': 'Kommentar',
+ }
+
+class UserProfileForm(forms.ModelForm):
+ first_name = forms.CharField(max_length=30, required=False, label='Vorname')
+ last_name = forms.CharField(max_length=30, required=False, label='Nachname')
+ email = forms.EmailField(required=True, label='E-Mail-Adresse')
+
+ class Meta:
+ model = UserProfile
+ fields = ['phone', 'address', 'default_shipping_address', 'newsletter']
+ labels = {
+ 'phone': 'Telefonnummer',
+ 'address': 'Adresse',
+ 'default_shipping_address': 'Standard-Lieferadresse',
+ 'newsletter': 'Newsletter abonnieren',
+ }
+ widgets = {
+ 'address': forms.Textarea(attrs={'rows': 3}),
+ 'default_shipping_address': forms.Textarea(attrs={'rows': 3}),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if self.instance.user:
+ self.fields['first_name'].initial = self.instance.user.first_name
+ self.fields['last_name'].initial = self.instance.user.last_name
+ self.fields['email'].initial = self.instance.user.email
+ for field in self.fields:
+ self.fields[field].widget.attrs['class'] = 'form-control'
+
+ def save(self, commit=True):
+ profile = super().save(commit=False)
+ if commit:
+ user = profile.user
+ user.first_name = self.cleaned_data['first_name']
+ user.last_name = self.cleaned_data['last_name']
+ user.email = self.cleaned_data['email']
+ user.save()
+ profile.save()
+ return profile
+
+class ContactForm(forms.ModelForm):
+ class Meta:
+ model = ContactMessage
+ fields = ['name', 'email', 'category', 'order_number', 'subject', 'message']
+ widgets = {
+ 'name': forms.TextInput(attrs={'class': 'form-control'}),
+ 'email': forms.EmailInput(attrs={'class': 'form-control'}),
+ 'category': forms.Select(attrs={'class': 'form-control'}),
+ 'order_number': forms.TextInput(attrs={'class': 'form-control'}),
+ 'subject': forms.TextInput(attrs={'class': 'form-control'}),
+ 'message': forms.Textarea(attrs={'class': 'form-control', 'rows': 5}),
+ }
+ labels = {
+ 'name': 'Name',
+ 'email': 'E-Mail-Adresse',
+ 'category': 'Kategorie',
+ 'order_number': 'Bestellnummer (optional)',
+ 'subject': 'Betreff',
+ 'message': 'Ihre Nachricht',
+ }
+
+ def __init__(self, *args, user=None, **kwargs):
+ super().__init__(*args, **kwargs)
+ if user and user.is_authenticated:
+ self.fields['name'].initial = user.get_full_name() or user.username
+ self.fields['email'].initial = user.email
+
+class CustomOrderForm(forms.ModelForm):
+ class Meta:
+ model = CustomOrder
+ fields = [
+ 'fursuit_type', 'style', 'character_name',
+ 'character_description', 'reference_images',
+ 'special_requests', 'measurements',
+ 'color_preferences', 'budget_range',
+ 'deadline_request'
+ ]
+ widgets = {
+ 'fursuit_type': forms.Select(attrs={'class': 'form-select'}),
+ 'style': forms.Select(attrs={'class': 'form-select'}),
+ 'character_name': forms.TextInput(attrs={'class': 'form-control'}),
+ 'character_description': forms.Textarea(attrs={
+ 'class': 'form-control',
+ 'rows': 4,
+ 'placeholder': 'Beschreiben Sie Ihren Character so detailliert wie möglich...'
+ }),
+ 'special_requests': forms.Textarea(attrs={
+ 'class': 'form-control',
+ 'rows': 3,
+ 'placeholder': 'Besondere Wünsche oder Anforderungen...'
+ }),
+ 'measurements': forms.Textarea(attrs={
+ 'class': 'form-control',
+ 'rows': 4,
+ 'placeholder': 'Bitte geben Sie alle relevanten Maße an (Kopfumfang, Körpergröße, etc.)'
+ }),
+ 'color_preferences': forms.Textarea(attrs={
+ 'class': 'form-control',
+ 'rows': 3,
+ 'placeholder': 'Beschreiben Sie die gewünschten Farben und Farbkombinationen...'
+ }),
+ 'budget_range': forms.TextInput(attrs={
+ 'class': 'form-control',
+ 'placeholder': 'z.B. 2000-3000€'
+ }),
+ 'deadline_request': forms.DateInput(attrs={
+ 'class': 'form-control',
+ 'type': 'date'
+ }),
+ 'reference_images': forms.FileInput(attrs={
+ 'class': 'form-control',
+ 'accept': 'image/*'
+ })
+ }
+ labels = {
+ 'fursuit_type': 'Art des Fursuits',
+ 'style': 'Gewünschter Stil',
+ 'character_name': 'Name des Characters',
+ 'character_description': 'Beschreibung des Characters',
+ 'reference_images': 'Referenzbilder',
+ 'special_requests': 'Besondere Wünsche',
+ 'measurements': 'Maße',
+ 'color_preferences': 'Farbwünsche',
+ 'budget_range': 'Budget-Rahmen',
+ 'deadline_request': 'Gewünschter Fertigstellungstermin'
+ }
+ help_texts = {
+ 'character_description': 'Je detaillierter die Beschreibung, desto besser können wir Ihre Vision umsetzen.',
+ 'reference_images': 'Laden Sie bis zu 5 Referenzbilder hoch (max. 5MB pro Bild)',
+ 'measurements': 'Genaue Maße sind wichtig für eine perfekte Passform.',
+ 'budget_range': 'Geben Sie einen Bereich an, in dem Sie sich preislich bewegen möchten.',
+ 'deadline_request': 'Optional: Wenn Sie einen bestimmten Termin im Auge haben.'
+ }
+
+class OrderProgressForm(forms.ModelForm):
+ class Meta:
+ model = OrderProgress
+ fields = ['stage', 'description', 'image', 'completed']
+ widgets = {
+ 'stage': forms.Select(attrs={'class': 'form-select'}),
+ 'description': forms.Textarea(attrs={
+ 'class': 'form-control',
+ 'rows': 3,
+ 'placeholder': 'Beschreiben Sie den aktuellen Fortschritt...'
+ }),
+ 'image': forms.FileInput(attrs={
+ 'class': 'form-control',
+ 'accept': 'image/*'
+ }),
+ 'completed': forms.CheckboxInput(attrs={'class': 'form-check-input'})
+ }
+ labels = {
+ 'stage': 'Arbeitsschritt',
+ 'description': 'Beschreibung des Fortschritts',
+ 'image': 'Foto des Fortschritts',
+ 'completed': 'Abgeschlossen'
+ }
+ help_texts = {
+ 'description': 'Beschreiben Sie detailliert, was in diesem Schritt gemacht wurde.',
+ 'image': 'Fügen Sie ein Foto hinzu, um den Fortschritt zu dokumentieren.',
+ 'completed': 'Markieren Sie diesen Schritt als abgeschlossen, wenn er fertig ist.'
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
}
\ No newline at end of file
diff --git a/products/migrations/0001_initial.py b/products/migrations/0001_initial.py
index ad0da7a..0a4dc74 100644
--- a/products/migrations/0001_initial.py
+++ b/products/migrations/0001_initial.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-29 14:00
from django.db import migrations, models
@@ -27,3 +28,34 @@ class Migration(migrations.Migration):
},
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-29 14:00
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Product',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200)),
+ ('description', models.TextField()),
+ ('price', models.DecimalField(decimal_places=2, max_digits=10)),
+ ('stock', models.IntegerField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ options={
+ 'ordering': ['-created_at'],
+ },
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0002_cart_cartitem.py b/products/migrations/0002_cart_cartitem.py
index 559c153..b0d1f93 100644
--- a/products/migrations/0002_cart_cartitem.py
+++ b/products/migrations/0002_cart_cartitem.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-29 14:12
import django.db.models.deletion
@@ -37,3 +38,44 @@ class Migration(migrations.Migration):
},
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-29 14:12
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0001_initial'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Cart',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('session_id', models.CharField(blank=True, max_length=100, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='CartItem',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('quantity', models.PositiveIntegerField(default=1)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='products.cart')),
+ ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product')),
+ ],
+ options={
+ 'unique_together': {('cart', 'product')},
+ },
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0003_product_category_product_featured_product_image_and_more.py b/products/migrations/0003_product_category_product_featured_product_image_and_more.py
index 48647ef..06112c4 100644
--- a/products/migrations/0003_product_category_product_featured_product_image_and_more.py
+++ b/products/migrations/0003_product_category_product_featured_product_image_and_more.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-29 14:19
import django.db.models.deletion
@@ -58,3 +59,65 @@ class Migration(migrations.Migration):
],
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-29 14:19
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0002_cart_cartitem'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='product',
+ name='category',
+ field=models.CharField(blank=True, max_length=100, null=True),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='featured',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='image',
+ field=models.ImageField(blank=True, null=True, upload_to='products/'),
+ ),
+ migrations.CreateModel(
+ name='Order',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('full_name', models.CharField(max_length=200)),
+ ('email', models.EmailField(max_length=254)),
+ ('address', models.TextField()),
+ ('phone', models.CharField(max_length=20)),
+ ('total_amount', models.DecimalField(decimal_places=2, max_digits=10)),
+ ('status', models.CharField(choices=[('pending', 'Ausstehend'), ('processing', 'In Bearbeitung'), ('shipped', 'Versendet'), ('delivered', 'Geliefert'), ('cancelled', 'Storniert')], default='pending', max_length=20)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'ordering': ['-created_at'],
+ },
+ ),
+ migrations.CreateModel(
+ name='OrderItem',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('product_name', models.CharField(max_length=200)),
+ ('price', models.DecimalField(decimal_places=2, max_digits=10)),
+ ('quantity', models.PositiveIntegerField()),
+ ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='products.order')),
+ ('product', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='products.product')),
+ ],
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0004_review.py b/products/migrations/0004_review.py
index 182aa43..4c61978 100644
--- a/products/migrations/0004_review.py
+++ b/products/migrations/0004_review.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-29 14:25
import django.core.validators
@@ -31,3 +32,38 @@ class Migration(migrations.Migration):
},
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-29 14:25
+
+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 = [
+ ('products', '0003_product_category_product_featured_product_image_and_more'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Review',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('rating', models.IntegerField(choices=[(1, '1 - Sehr schlecht'), (2, '2 - Schlecht'), (3, '3 - Okay'), (4, '4 - Gut'), (5, '5 - Sehr gut')], validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)])),
+ ('comment', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='products.product')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'ordering': ['-created_at'],
+ 'unique_together': {('product', 'user')},
+ },
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0005_userprofile_wishlist.py b/products/migrations/0005_userprofile_wishlist.py
index dd51941..9f22228 100644
--- a/products/migrations/0005_userprofile_wishlist.py
+++ b/products/migrations/0005_userprofile_wishlist.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-29 14:50
import django.db.models.deletion
@@ -36,3 +37,43 @@ class Migration(migrations.Migration):
],
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-29 14:50
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0004_review'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='UserProfile',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('phone', models.CharField(blank=True, max_length=20)),
+ ('address', models.TextField(blank=True)),
+ ('default_shipping_address', models.TextField(blank=True)),
+ ('newsletter', models.BooleanField(default=False)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Wishlist',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('products', models.ManyToManyField(to='products.product')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0006_faq_contactmessage.py b/products/migrations/0006_faq_contactmessage.py
index 04b0927..ca91534 100644
--- a/products/migrations/0006_faq_contactmessage.py
+++ b/products/migrations/0006_faq_contactmessage.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-29 14:57
import django.db.models.deletion
@@ -52,3 +53,59 @@ class Migration(migrations.Migration):
},
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-29 14:57
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0005_userprofile_wishlist'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='FAQ',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('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_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ options={
+ 'verbose_name': 'FAQ',
+ 'verbose_name_plural': 'FAQs',
+ 'ordering': ['category', 'order'],
+ },
+ ),
+ migrations.CreateModel(
+ name='ContactMessage',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100)),
+ ('email', models.EmailField(max_length=254)),
+ ('order_number', models.CharField(blank=True, max_length=50, null=True)),
+ ('category', models.CharField(choices=[('general', 'Allgemeine Anfrage'), ('order', 'Bestellung'), ('return', 'Rückgabe/Umtausch'), ('complaint', 'Beschwerde'), ('technical', 'Technische Frage')], max_length=20)),
+ ('subject', models.CharField(max_length=200)),
+ ('message', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('status', models.CharField(choices=[('new', 'Neu'), ('in_progress', 'In Bearbeitung'), ('resolved', 'Erledigt'), ('closed', 'Geschlossen')], default='new', max_length=20)),
+ ('staff_notes', models.TextField(blank=True)),
+ ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name': 'Kontaktanfrage',
+ 'verbose_name_plural': 'Kontaktanfragen',
+ 'ordering': ['-created_at'],
+ },
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0007_product_extras_description_product_fursuit_type_and_more.py b/products/migrations/0007_product_extras_description_product_fursuit_type_and_more.py
index 6239df1..3cf2f03 100644
--- a/products/migrations/0007_product_extras_description_product_fursuit_type_and_more.py
+++ b/products/migrations/0007_product_extras_description_product_fursuit_type_and_more.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-29 15:01
import django.db.models.deletion
@@ -85,3 +86,92 @@ class Migration(migrations.Migration):
},
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-29 15:01
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0006_faq_contactmessage'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='product',
+ name='extras_description',
+ field=models.TextField(blank=True),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='fursuit_type',
+ field=models.CharField(choices=[('fullsuit', 'Fullsuit'), ('partial', 'Partial'), ('head', 'Kopf'), ('paws', 'Pfoten'), ('tail', 'Schwanz'), ('other', 'Sonstiges')], default='head', max_length=20),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='includes_extras',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='is_custom_order',
+ field=models.BooleanField(default=True),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='production_time_weeks',
+ field=models.IntegerField(default=8),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='style',
+ field=models.CharField(choices=[('toony', 'Toony'), ('semi_realistic', 'Semi-Realistisch'), ('realistic', 'Realistisch')], default='toony', max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='stock',
+ field=models.IntegerField(default=1),
+ ),
+ migrations.CreateModel(
+ name='CustomOrder',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('fursuit_type', models.CharField(choices=[('fullsuit', 'Fullsuit'), ('partial', 'Partial'), ('head', 'Kopf'), ('paws', 'Pfoten'), ('tail', 'Schwanz'), ('other', 'Sonstiges')], max_length=20)),
+ ('style', models.CharField(choices=[('toony', 'Toony'), ('semi_realistic', 'Semi-Realistisch'), ('realistic', 'Realistisch')], max_length=20)),
+ ('character_name', models.CharField(max_length=100)),
+ ('character_description', models.TextField()),
+ ('reference_images', models.FileField(blank=True, null=True, upload_to='references/')),
+ ('special_requests', models.TextField(blank=True)),
+ ('measurements', models.TextField()),
+ ('color_preferences', models.TextField()),
+ ('budget_range', models.CharField(max_length=100)),
+ ('deadline_request', models.DateField(blank=True, null=True)),
+ ('status', models.CharField(choices=[('pending', 'Anfrage eingegangen'), ('quoted', 'Angebot erstellt'), ('approved', 'Angebot akzeptiert'), ('in_progress', 'In Arbeit'), ('ready', 'Fertig zur Abholung'), ('shipped', 'Versendet'), ('completed', 'Abgeschlossen'), ('cancelled', 'Storniert')], default='pending', max_length=20)),
+ ('quoted_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='OrderProgress',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('stage', models.CharField(choices=[('design', 'Design & Planung'), ('base', 'Grundform'), ('fur', 'Fell'), ('details', 'Details'), ('electronics', 'Elektronik (optional)'), ('finishing', 'Finishing')], max_length=20)),
+ ('description', models.TextField()),
+ ('image', models.ImageField(blank=True, null=True, upload_to='progress/')),
+ ('completed', models.BooleanField(default=False)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('custom_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='progress_updates', to='products.customorder')),
+ ],
+ options={
+ 'ordering': ['created_at'],
+ },
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0008_alter_contactmessage_options_and_more.py b/products/migrations/0008_alter_contactmessage_options_and_more.py
index 08c3486..a68075f 100644
--- a/products/migrations/0008_alter_contactmessage_options_and_more.py
+++ b/products/migrations/0008_alter_contactmessage_options_and_more.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-29 18:29
import django.db.models.deletion
@@ -143,3 +144,150 @@ class Migration(migrations.Migration):
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='product_orders', to=settings.AUTH_USER_MODEL),
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-29 18:29
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0007_product_extras_description_product_fursuit_type_and_more'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='contactmessage',
+ options={'ordering': ['-created'], 'verbose_name': 'Kontaktanfrage', 'verbose_name_plural': 'Kontaktanfragen'},
+ ),
+ migrations.AlterModelOptions(
+ name='customorder',
+ options={'ordering': ['-created']},
+ ),
+ migrations.AlterModelOptions(
+ name='order',
+ options={'ordering': ['-created']},
+ ),
+ migrations.AlterModelOptions(
+ name='orderprogress',
+ options={'ordering': ['created']},
+ ),
+ migrations.AlterModelOptions(
+ name='product',
+ options={'ordering': ['-created']},
+ ),
+ migrations.AlterModelOptions(
+ name='review',
+ options={'ordering': ['-created']},
+ ),
+ migrations.RenameField(
+ model_name='cart',
+ old_name='created_at',
+ new_name='created',
+ ),
+ migrations.RenameField(
+ model_name='cart',
+ old_name='updated_at',
+ new_name='updated',
+ ),
+ migrations.RenameField(
+ model_name='cartitem',
+ old_name='created_at',
+ new_name='created',
+ ),
+ migrations.RenameField(
+ model_name='contactmessage',
+ old_name='created_at',
+ new_name='created',
+ ),
+ migrations.RenameField(
+ model_name='customorder',
+ old_name='created_at',
+ new_name='created',
+ ),
+ migrations.RenameField(
+ model_name='customorder',
+ old_name='updated_at',
+ new_name='updated',
+ ),
+ migrations.RenameField(
+ model_name='faq',
+ old_name='created_at',
+ new_name='created',
+ ),
+ migrations.RenameField(
+ model_name='faq',
+ old_name='updated_at',
+ new_name='updated',
+ ),
+ migrations.RenameField(
+ model_name='order',
+ old_name='created_at',
+ new_name='created',
+ ),
+ migrations.RenameField(
+ model_name='order',
+ old_name='updated_at',
+ new_name='updated',
+ ),
+ migrations.RenameField(
+ model_name='orderprogress',
+ old_name='created_at',
+ new_name='created',
+ ),
+ migrations.RenameField(
+ model_name='product',
+ old_name='created_at',
+ new_name='created',
+ ),
+ migrations.RenameField(
+ model_name='product',
+ old_name='updated_at',
+ new_name='updated',
+ ),
+ migrations.RenameField(
+ model_name='review',
+ old_name='created_at',
+ new_name='created',
+ ),
+ migrations.RenameField(
+ model_name='review',
+ old_name='updated_at',
+ new_name='updated',
+ ),
+ migrations.RenameField(
+ model_name='userprofile',
+ old_name='created_at',
+ new_name='created',
+ ),
+ migrations.RenameField(
+ model_name='userprofile',
+ old_name='updated_at',
+ new_name='updated',
+ ),
+ migrations.RenameField(
+ model_name='wishlist',
+ old_name='created_at',
+ new_name='created',
+ ),
+ migrations.AddField(
+ model_name='orderprogress',
+ name='updated',
+ field=models.DateTimeField(auto_now=True),
+ ),
+ migrations.AlterField(
+ model_name='cart',
+ name='user',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='product_carts', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AlterField(
+ model_name='order',
+ name='user',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='product_orders', to=settings.AUTH_USER_MODEL),
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0009_remove_product_category_and_more.py b/products/migrations/0009_remove_product_category_and_more.py
index 421c44f..0a7bea2 100644
--- a/products/migrations/0009_remove_product_category_and_more.py
+++ b/products/migrations/0009_remove_product_category_and_more.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-30 07:31
import django.core.validators
@@ -145,3 +146,152 @@ class Migration(migrations.Migration):
index=models.Index(fields=['style'], name='products_pr_style_de3c68_idx'),
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-30 07:31
+
+import django.core.validators
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0008_alter_contactmessage_options_and_more'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='product',
+ name='category',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='extras_description',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='featured',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='includes_extras',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='production_time_weeks',
+ ),
+ migrations.RemoveField(
+ model_name='review',
+ name='updated',
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='payment_date',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='payment_method',
+ field=models.CharField(blank=True, choices=[('card', 'Kreditkarte'), ('sepa', 'SEPA-Lastschrift'), ('giropay', 'Giropay'), ('sofort', 'Sofort'), ('bancontact', 'Bancontact')], max_length=20, null=True),
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='payment_status',
+ field=models.CharField(choices=[('pending', 'Ausstehend'), ('processing', 'Wird bearbeitet'), ('paid', 'Bezahlt'), ('failed', 'Fehlgeschlagen'), ('refunded', 'Zurückerstattet')], default='pending', max_length=20),
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='stripe_payment_intent_id',
+ field=models.CharField(blank=True, max_length=100, null=True),
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='stripe_payment_method_id',
+ field=models.CharField(blank=True, max_length=100, null=True),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='is_featured',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='wishlist_users',
+ field=models.ManyToManyField(blank=True, related_name='wishlist_products', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AlterField(
+ model_name='customorder',
+ name='fursuit_type',
+ field=models.CharField(choices=[('partial', 'Partial'), ('fullsuit', 'Fullsuit'), ('head', 'Head Only'), ('paws', 'Paws'), ('tail', 'Tail'), ('other', 'Other')], max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='customorder',
+ name='style',
+ field=models.CharField(choices=[('toony', 'Toony'), ('semi_realistic', 'Semi-Realistic'), ('realistic', 'Realistic'), ('anime', 'Anime'), ('chibi', 'Chibi')], max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='fursuit_type',
+ field=models.CharField(choices=[('partial', 'Partial'), ('fullsuit', 'Fullsuit'), ('head', 'Head Only'), ('paws', 'Paws'), ('tail', 'Tail'), ('other', 'Other')], default='other', max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='is_custom_order',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='price',
+ field=models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0)]),
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='stock',
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='style',
+ field=models.CharField(choices=[('toony', 'Toony'), ('semi_realistic', 'Semi-Realistic'), ('realistic', 'Realistic'), ('anime', 'Anime'), ('chibi', 'Chibi')], default='toony', max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='review',
+ name='rating',
+ field=models.IntegerField(validators=[django.core.validators.MinValueValidator(1)]),
+ ),
+ migrations.AddIndex(
+ model_name='order',
+ index=models.Index(fields=['status'], name='products_or_status_bd22a2_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='order',
+ index=models.Index(fields=['payment_status'], name='products_or_payment_0d94df_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='order',
+ index=models.Index(fields=['created'], name='products_or_created_a2e72d_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='product',
+ index=models.Index(fields=['name'], name='products_pr_name_9ff0a3_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='product',
+ index=models.Index(fields=['price'], name='products_pr_price_9b1a5f_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='product',
+ index=models.Index(fields=['created'], name='products_pr_created_9a1943_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='product',
+ index=models.Index(fields=['fursuit_type'], name='products_pr_fursuit_fde435_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='product',
+ index=models.Index(fields=['style'], name='products_pr_style_de3c68_idx'),
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0010_galleryimage.py b/products/migrations/0010_galleryimage.py
index 7435d92..397bf43 100644
--- a/products/migrations/0010_galleryimage.py
+++ b/products/migrations/0010_galleryimage.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-30 07:49
from django.db import migrations, models
@@ -30,3 +31,37 @@ class Migration(migrations.Migration):
},
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-30 07:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0009_remove_product_category_and_more'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='GalleryImage',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('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(choices=[('partial', 'Partial'), ('fullsuit', 'Fullsuit'), ('head', 'Head Only'), ('paws', 'Paws'), ('tail', 'Tail'), ('other', 'Other')], max_length=20, verbose_name='Fursuit-Typ')),
+ ('style', models.CharField(choices=[('toony', 'Toony'), ('semi_realistic', 'Semi-Realistic'), ('realistic', 'Realistic'), ('anime', 'Anime'), ('chibi', 'Chibi')], max_length=20, verbose_name='Stil')),
+ ('created', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am')),
+ ('is_featured', models.BooleanField(default=False, verbose_name='Hervorgehoben')),
+ ('order', models.IntegerField(default=0, verbose_name='Reihenfolge')),
+ ],
+ options={
+ 'verbose_name': 'Galeriebild',
+ 'verbose_name_plural': 'Galeriebilder',
+ 'ordering': ['order', '-created'],
+ },
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0011_alter_galleryimage_fursuit_type_and_more.py b/products/migrations/0011_alter_galleryimage_fursuit_type_and_more.py
index 1d82f12..071d71c 100644
--- a/products/migrations/0011_alter_galleryimage_fursuit_type_and_more.py
+++ b/products/migrations/0011_alter_galleryimage_fursuit_type_and_more.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-30 08:24
from django.db import migrations, models
@@ -21,3 +22,28 @@ class Migration(migrations.Migration):
field=models.CharField(choices=[('toony', 'Toony'), ('semi', 'Semi-Realistic'), ('real', 'Realistic'), ('anime', 'Anime')], default='toony', max_length=20, verbose_name='Stil'),
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-30 08:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0010_galleryimage'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='galleryimage',
+ name='fursuit_type',
+ field=models.CharField(choices=[('full', 'Fullsuit'), ('partial', 'Partial'), ('head', 'Head Only'), ('other', 'Other')], default='full', max_length=20, verbose_name='Fursuit-Typ'),
+ ),
+ migrations.AlterField(
+ model_name='galleryimage',
+ name='style',
+ field=models.CharField(choices=[('toony', 'Toony'), ('semi', 'Semi-Realistic'), ('real', 'Realistic'), ('anime', 'Anime')], default='toony', max_length=20, verbose_name='Stil'),
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0012_category_product_category.py b/products/migrations/0012_category_product_category.py
index c7cc491..5c100b3 100644
--- a/products/migrations/0012_category_product_category.py
+++ b/products/migrations/0012_category_product_category.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-30 11:51
import django.db.models.deletion
@@ -33,3 +34,40 @@ class Migration(migrations.Migration):
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='products.category', verbose_name='Kategorie'),
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-30 11:51
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0011_alter_galleryimage_fursuit_type_and_more'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Category',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100, verbose_name='Name')),
+ ('slug', models.SlugField(unique=True, verbose_name='URL-Slug')),
+ ('description', models.TextField(blank=True, verbose_name='Beschreibung')),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('updated', models.DateTimeField(auto_now=True)),
+ ],
+ options={
+ 'verbose_name': 'Kategorie',
+ 'verbose_name_plural': 'Kategorien',
+ 'ordering': ['name'],
+ },
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='category',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='products.category', verbose_name='Kategorie'),
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/migrations/0013_alter_product_category_delete_category.py b/products/migrations/0013_alter_product_category_delete_category.py
index d9ed66c..f67c159 100644
--- a/products/migrations/0013_alter_product_category_delete_category.py
+++ b/products/migrations/0013_alter_product_category_delete_category.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-30 11:54
import django.db.models.deletion
@@ -21,3 +22,28 @@ class Migration(migrations.Migration):
name='Category',
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-30 11:54
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0012_category_product_category'),
+ ('shop', '0003_contactmessage'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='product',
+ name='category',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='product_items', to='shop.category', verbose_name='Kategorie'),
+ ),
+ migrations.DeleteModel(
+ name='Category',
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/models.py b/products/models.py
index 405cd58..3ac4e37 100644
--- a/products/models.py
+++ b/products/models.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator, MaxValueValidator
@@ -426,3 +427,421 @@ class Payment(models.Model):
def get_success_url(self):
return f'/payment/success/{self.order.id}/'
+=======
+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}/'
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/serializers.py b/products/serializers.py
index 7bf787d..bf4fb94 100644
--- a/products/serializers.py
+++ b/products/serializers.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
from rest_framework import serializers
from .models import Product, Category, Review, Wishlist, GalleryImage, CustomOrder, Payment
from django.contrib.auth.models import User
@@ -211,4 +212,219 @@ class GalleryImageDetailSerializer(serializers.ModelSerializer):
'id', 'product', 'image', 'alt_text', 'title', 'description',
'is_featured', 'fursuit_type', 'style', 'created_at'
]
+=======
+from rest_framework import serializers
+from .models import Product, Category, Review, Wishlist, GalleryImage, CustomOrder, Payment
+from django.contrib.auth.models import User
+
+
+class UserSerializer(serializers.ModelSerializer):
+ """Serializer für User-Model"""
+ class Meta:
+ model = User
+ fields = ['id', 'username', 'email', 'first_name', 'last_name']
+ read_only_fields = ['id']
+
+
+class CategorySerializer(serializers.ModelSerializer):
+ """Serializer für Category-Model"""
+ product_count = serializers.SerializerMethodField()
+
+ class Meta:
+ model = Category
+ fields = ['id', 'name', 'slug', 'description', 'image', 'product_count']
+
+ def get_product_count(self, obj):
+ return obj.products.count()
+
+
+class ReviewSerializer(serializers.ModelSerializer):
+ """Serializer für Review-Model"""
+ user = UserSerializer(read_only=True)
+ user_name = serializers.CharField(source='user.username', read_only=True)
+
+ class Meta:
+ model = Review
+ fields = ['id', 'product', 'user', 'user_name', 'rating', 'comment', 'created_at']
+ read_only_fields = ['id', 'created_at']
+
+ def create(self, validated_data):
+ validated_data['user'] = self.context['request'].user
+ return super().create(validated_data)
+
+
+class GalleryImageSerializer(serializers.ModelSerializer):
+ """Serializer für GalleryImage-Model"""
+ class Meta:
+ model = GalleryImage
+ fields = ['id', 'product', 'image', 'alt_text', 'is_featured', 'created_at']
+ read_only_fields = ['id', 'created_at']
+
+
+class ProductSerializer(serializers.ModelSerializer):
+ """Basis-Serializer für Product-Model"""
+ category = CategorySerializer(read_only=True)
+ category_id = serializers.IntegerField(write_only=True, required=False)
+ reviews = ReviewSerializer(many=True, read_only=True)
+ gallery_images = GalleryImageSerializer(many=True, read_only=True)
+ average_rating = serializers.FloatField(read_only=True)
+ review_count = serializers.IntegerField(read_only=True)
+ is_in_wishlist = serializers.SerializerMethodField()
+
+ class Meta:
+ model = Product
+ fields = [
+ 'id', 'name', 'slug', 'description', 'base_price', 'sale_price',
+ 'on_sale', 'stock', 'product_type', 'fursuit_type', 'style',
+ 'is_custom_order', 'is_featured', 'category', 'category_id',
+ 'image', 'gallery_images', 'reviews', 'average_rating',
+ 'review_count', 'is_in_wishlist', 'created_at', 'updated_at'
+ ]
+ read_only_fields = ['id', 'slug', 'created_at', 'updated_at']
+
+ def get_is_in_wishlist(self, obj):
+ user = self.context['request'].user
+ if user.is_authenticated:
+ return obj.wishlists.filter(user=user).exists()
+ return False
+
+ def create(self, validated_data):
+ category_id = validated_data.pop('category_id', None)
+ if category_id:
+ validated_data['category'] = Category.objects.get(id=category_id)
+ return super().create(validated_data)
+
+
+class ProductListSerializer(serializers.ModelSerializer):
+ """Vereinfachter Serializer für Produktlisten"""
+ category = CategorySerializer(read_only=True)
+ average_rating = serializers.FloatField(read_only=True)
+ review_count = serializers.IntegerField(read_only=True)
+ is_in_wishlist = serializers.SerializerMethodField()
+
+ class Meta:
+ model = Product
+ fields = [
+ 'id', 'name', 'slug', 'description', 'base_price', 'sale_price',
+ 'on_sale', 'stock', 'product_type', 'fursuit_type', 'style',
+ 'is_custom_order', 'is_featured', 'category', 'image',
+ 'average_rating', 'review_count', 'is_in_wishlist'
+ ]
+
+ def get_is_in_wishlist(self, obj):
+ user = self.context['request'].user
+ if user.is_authenticated:
+ return obj.wishlists.filter(user=user).exists()
+ return False
+
+
+class ProductDetailSerializer(ProductSerializer):
+ """Detaillierter Serializer für Produktdetails"""
+ related_products = ProductListSerializer(many=True, read_only=True)
+
+ class Meta(ProductSerializer.Meta):
+ fields = ProductSerializer.Meta.fields + ['related_products']
+
+
+class WishlistSerializer(serializers.ModelSerializer):
+ """Serializer für Wishlist-Model"""
+ products = ProductListSerializer(many=True, read_only=True)
+ product_count = serializers.SerializerMethodField()
+
+ class Meta:
+ model = Wishlist
+ fields = ['id', 'user', 'products', 'product_count', 'created_at']
+ read_only_fields = ['id', 'created_at']
+
+ def get_product_count(self, obj):
+ return obj.products.count()
+
+
+class CustomOrderSerializer(serializers.ModelSerializer):
+ """Serializer für CustomOrder-Model"""
+ user = UserSerializer(read_only=True)
+ status_display = serializers.CharField(source='get_status_display', read_only=True)
+
+ class Meta:
+ model = CustomOrder
+ fields = [
+ 'id', 'user', 'title', 'description', 'fursuit_type', 'style',
+ 'size', 'color_preferences', 'special_requirements', 'budget',
+ 'status', 'status_display', 'created_at', 'updated_at'
+ ]
+ read_only_fields = ['id', 'created_at', 'updated_at']
+
+ def create(self, validated_data):
+ validated_data['user'] = self.context['request'].user
+ return super().create(validated_data)
+
+
+class PaymentSerializer(serializers.ModelSerializer):
+ """Serializer für Payment-Model"""
+ user = UserSerializer(read_only=True)
+ status_display = serializers.CharField(source='get_status_display', read_only=True)
+
+ class Meta:
+ model = Payment
+ fields = [
+ 'id', 'user', 'order', 'amount', 'payment_method', 'status',
+ 'status_display', 'transaction_id', 'created_at'
+ ]
+ read_only_fields = ['id', 'created_at']
+
+
+class CartItemSerializer(serializers.Serializer):
+ """Serializer für Warenkorb-Items"""
+ product_id = serializers.IntegerField()
+ quantity = serializers.IntegerField(min_value=1)
+ product = ProductListSerializer(read_only=True)
+ total_price = serializers.DecimalField(max_digits=10, decimal_places=2, read_only=True)
+
+
+class CartSerializer(serializers.Serializer):
+ """Serializer für Warenkorb"""
+ items = CartItemSerializer(many=True)
+ subtotal = serializers.DecimalField(max_digits=10, decimal_places=2)
+ shipping_cost = serializers.DecimalField(max_digits=10, decimal_places=2)
+ total = serializers.DecimalField(max_digits=10, decimal_places=2)
+ item_count = serializers.IntegerField()
+
+
+class SearchFilterSerializer(serializers.Serializer):
+ """Serializer für Such- und Filter-Parameter"""
+ query = serializers.CharField(required=False, allow_blank=True)
+ category = serializers.CharField(required=False, allow_blank=True)
+ fursuit_type = serializers.CharField(required=False, allow_blank=True)
+ style = serializers.CharField(required=False, allow_blank=True)
+ min_price = serializers.DecimalField(max_digits=10, decimal_places=2, required=False)
+ max_price = serializers.DecimalField(max_digits=10, decimal_places=2, required=False)
+ on_sale = serializers.BooleanField(required=False)
+ is_featured = serializers.BooleanField(required=False)
+ sort_by = serializers.CharField(required=False, default='newest')
+ page = serializers.IntegerField(required=False, default=1)
+ page_size = serializers.IntegerField(required=False, default=12)
+
+
+class ProductStatsSerializer(serializers.Serializer):
+ """Serializer für Produkt-Statistiken"""
+ total_products = serializers.IntegerField()
+ featured_products = serializers.IntegerField()
+ on_sale_products = serializers.IntegerField()
+ low_stock_products = serializers.IntegerField()
+ categories_count = serializers.IntegerField()
+ average_rating = serializers.FloatField()
+ total_reviews = serializers.IntegerField()
+
+
+class GalleryImageDetailSerializer(serializers.ModelSerializer):
+ """Detaillierter Serializer für Galerie-Bilder"""
+ product = ProductListSerializer(read_only=True)
+
+ class Meta:
+ model = GalleryImage
+ fields = [
+ 'id', 'product', 'image', 'alt_text', 'title', 'description',
+ 'is_featured', 'fursuit_type', 'style', 'created_at'
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
read_only_fields = ['id', 'created_at']
\ No newline at end of file
diff --git a/products/templates/base.html b/products/templates/base.html
index 2b9312c..afc96b3 100644
--- a/products/templates/base.html
+++ b/products/templates/base.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends "base.html" %}
{% block extra_head %}
@@ -12,4 +13,20 @@
{# Der eigentliche Seiteninhalt kommt in den content-Block #}
{% block content %}
{{ block.super }}
+=======
+{% extends "base.html" %}
+
+{% block extra_head %}
+
+
+
+
+
+ {{ block.super }}
+{% endblock %}
+
+{# Der eigentliche Seiteninhalt kommt in den content-Block #}
+{% block content %}
+ {{ block.super }}
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/add_progress.html b/products/templates/products/add_progress.html
index df80110..86f8a66 100644
--- a/products/templates/products/add_progress.html
+++ b/products/templates/products/add_progress.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% block title %}Fortschritt hinzufügen - {{ block.super }}{% endblock %}
@@ -96,4 +97,104 @@
+=======
+{% extends 'base.html' %}
+
+{% block title %}Fortschritt hinzufügen - {{ block.super }}{% endblock %}
+
+{% block content %}
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/cart_detail.html b/products/templates/products/cart_detail.html
index 80166c8..5d83126 100644
--- a/products/templates/products/cart_detail.html
+++ b/products/templates/products/cart_detail.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% load static %}
@@ -173,4 +174,181 @@ function addToCart(productId) {
});
}
+=======
+{% extends 'base.html' %}
+{% load static %}
+
+{% block title %}Warenkorb{% endblock %}
+
+{% block content %}
+Warenkorb
+{% if cart.items %}
+
+ {% for item in cart.items %}
+
+
+
+
{{ item.product.name }}
+
{{ item.product.price }} €
+
+
+
+
+
+
+ {% endfor %}
+
+
+{% else %}
+Dein Warenkorb ist leer.
+{% endif %}
+
+
+{% if related_products %}
+
+
+
+
+ {% for product in related_products %}
+
+ {% if product.image %}
+
+ {% else %}
+
+ 🐾
+
+ {% endif %}
+
+
+
+
+
{{ product.description|truncatewords:10 }}
+
{{ product.price }}€
+
+
+
+
+ {% endfor %}
+
+
+{% endif %}
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/checkout.html b/products/templates/products/checkout.html
index f3f2d06..bfcd9ec 100644
--- a/products/templates/products/checkout.html
+++ b/products/templates/products/checkout.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% block title %}Checkout{% endblock %}
{% block content %}
@@ -40,4 +41,48 @@
Gesamtsumme: {{ cart.total_price|floatformat:2 }} €
+=======
+{% extends 'base.html' %}
+{% block title %}Checkout{% endblock %}
+{% block content %}
+Checkout
+
+
+
+
Deine Bestellung
+
+ {% for item in cart.items %}
+ {{ item.product.name }} × {{ item.quantity }} {{ item.product.price|floatformat:2 }} €
+ {% endfor %}
+
+
Gesamtsumme: {{ cart.total_price|floatformat:2 }} €
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/contact.html b/products/templates/products/contact.html
index fa9221e..1ec4173 100644
--- a/products/templates/products/contact.html
+++ b/products/templates/products/contact.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% load static %}
@@ -452,4 +453,460 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
+=======
+{% extends 'base.html' %}
+{% load static %}
+
+{% block title %}Kontakt - {{ block.super }}{% endblock %}
+
+{% block content %}
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/contact_success.html b/products/templates/products/contact_success.html
index 7152fa8..6c51780 100644
--- a/products/templates/products/contact_success.html
+++ b/products/templates/products/contact_success.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% block title %}Nachricht gesendet - {{ block.super }}{% endblock %}
@@ -30,4 +31,38 @@
+=======
+{% extends 'base.html' %}
+
+{% block title %}Nachricht gesendet - {{ block.super }}{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
Vielen Dank für Ihre Nachricht!
+
+ Wir haben Ihre Anfrage erhalten und werden uns schnellstmöglich bei Ihnen melden.
+
+
+ Unsere durchschnittliche Antwortzeit beträgt 24-48 Stunden an Werktagen.
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/custom_order.html b/products/templates/products/custom_order.html
index 719d497..327dd36 100644
--- a/products/templates/products/custom_order.html
+++ b/products/templates/products/custom_order.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% load static %}
@@ -732,4 +733,740 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
+=======
+{% extends 'base.html' %}
+{% load static %}
+
+{% block title %}Custom Fursuit Bestellung - {{ block.super }}{% endblock %}
+
+{% block content %}
+
+
+
+
+
🎨 Custom Fursuit Anfrage
+
Lass deinen Traum-Fursuit Realität werden!
+
+ ⚡ 8-12 Wochen Produktionszeit
+ 💰 30% Anzahlung
+ 🎯 Individuelles Design
+
+
+
+
+
+
+
+
+
+
+
+
+
1
+
+
📝 Formular ausfüllen
+
Beschreibe deinen Character und deine Wünsche
+
+
+
+
2
+
+
💰 Angebot erhalten
+
Wir erstellen ein individuelles Angebot für dich
+
+
+
+
3
+
+
💳 Anzahlung leisten
+
30% Anzahlung sichert deinen Auftrag
+
+
+
+
4
+
+
🎨 Design-Abstimmung
+
Gemeinsam entwickeln wir das perfekte Design
+
+
+
+
5
+
+
🛠️ Produktion & Updates
+
Regelmäßige Updates während der Produktion
+
+
+
+
6
+
+
📦 Fertigstellung & Versand
+
Dein Fursuit kommt sicher zu dir
+
+
+
+
+
+
⚠️ Wichtige Hinweise
+
+ ⏰
+ Produktionszeit: 8-12 Wochen
+
+
+ 💳
+ Anzahlung: 30% des Gesamtpreises
+
+
+ 📏
+ Genaue Maße sind erforderlich
+
+
+ 🎨
+ Referenzbilder sind sehr hilfreich
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/custom_order_detail.html b/products/templates/products/custom_order_detail.html
index d2b38d6..79bc16d 100644
--- a/products/templates/products/custom_order_detail.html
+++ b/products/templates/products/custom_order_detail.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% block title %}Custom Order #{{ order.id }} - {{ block.super }}{% endblock %}
@@ -176,4 +177,184 @@
.badge.bg-completed { background-color: #28a745; }
.badge.bg-cancelled { background-color: #dc3545; }
+=======
+{% extends 'base.html' %}
+
+{% block title %}Custom Order #{{ order.id }} - {{ block.super }}{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+ Status:
+
+ {{ order.get_status_display }}
+
+
+
+ Character:
+ {{ order.character_name }}
+
+
+ Typ:
+ {{ order.get_fursuit_type_display }}
+
+
+ Stil:
+ {{ order.get_style_display }}
+
+
+ Budget:
+ {{ order.budget_range }}
+
+ {% if order.deadline_request %}
+
+ Gewünschter Termin:
+ {{ order.deadline_request }}
+
+ {% endif %}
+
+ Bestelldatum:
+ {{ order.created|date:"d.m.Y" }}
+
+
+
+ {% if order.quoted_price %}
+
+
Angebot
+
{{ order.quoted_price }} €
+
+ {% endif %}
+
+
+
+
+
+
+
+
Beschreibung
+
{{ order.character_description }}
+
+ {% if order.reference_images %}
+
Referenzbilder
+
+ {% endif %}
+
+
Farbwünsche
+
{{ order.color_preferences }}
+
+ {% if order.special_requests %}
+
Besondere Wünsche
+
{{ order.special_requests }}
+ {% endif %}
+
+
+
+
+
+
+
+
+
+ {% if progress_updates %}
+
+ {% for update in progress_updates %}
+
+
+
+
+ {{ update.get_stage_display }}
+ {% if update.completed %}
+
+ {% endif %}
+
+
{{ update.description }}
+ {% if update.image %}
+
+ {% endif %}
+
+ {{ update.created|date:"d.m.Y H:i" }}
+
+
+
+ {% endfor %}
+
+ {% else %}
+
+
+
Noch keine Fortschritts-Updates vorhanden.
+
+ {% endif %}
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/custom_order_success.html b/products/templates/products/custom_order_success.html
index 7d6af24..61c028d 100644
--- a/products/templates/products/custom_order_success.html
+++ b/products/templates/products/custom_order_success.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% block title %}Anfrage erfolgreich - {{ block.super }}{% endblock %}
@@ -45,4 +46,53 @@
+=======
+{% extends 'base.html' %}
+
+{% block title %}Anfrage erfolgreich - {{ block.super }}{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
Ihre Fursuit-Anfrage wurde erfolgreich übermittelt!
+
+ Vielen Dank für Ihr Interesse an einem Custom Fursuit.
+
+
+
+
+
Ihre Anfrage-Details
+
+ Anfrage-ID: #{{ order.id }}
+ Character: {{ order.character_name }}
+ Typ: {{ order.get_fursuit_type_display }}
+ Stil: {{ order.get_style_display }}
+ Status: {{ order.get_status_display }}
+
+
+
+
+
+
+ Wir werden Ihre Anfrage prüfen und uns innerhalb von 2-3 Werktagen mit einem detaillierten Angebot bei Ihnen melden.
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/dashboard.html b/products/templates/products/dashboard.html
index 7b62731..c8499a0 100644
--- a/products/templates/products/dashboard.html
+++ b/products/templates/products/dashboard.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% load static %}
@@ -842,4 +843,850 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
+=======
+{% extends 'base.html' %}
+{% load static %}
+
+{% block title %}Dashboard - Kasico Fursuit Shop{% endblock %}
+
+{% block content %}
+
+
+
+
+
🎛️ Mein Dashboard
+
Willkommen zurück, {{ user.username }}! 🐺
+
+ 📅 {{ user.date_joined|date:"d.m.Y" }}
+ ⭐ Mitglied seit {{ user.date_joined|timesince }}
+
+
+
+
+
+
+
📊 Übersicht
+
+
+
📦
+
+
{{ total_orders }}
+
Bestellungen
+
+
+
+
+
💳
+
+
{{ total_spent }}€
+
Ausgegeben
+
+
+
+
+
❤️
+
+
{{ wishlist_count }}
+
Wunschliste
+
+
+
+
+
⭐
+
+
{{ reviews_count }}
+
Bewertungen
+
+
+
+
+
+
+
+
⚡ Schnelle Aktionen
+
+
+
+
+ {% if recent_orders %}
+
+
+
+
+ {% for order in recent_orders %}
+
+
+
+
{{ order.created_at|date:"d.m.Y H:i" }}
+
{{ order.total_amount }}€
+
+
+
+ 👁️ Details
+
+ {% if order.status == 'pending' %}
+
+ 💳 Bezahlen
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+
+ {% if custom_orders %}
+
+
+
+
+ {% for order in custom_orders %}
+
+
+
+
{{ order.created_at|date:"d.m.Y" }}
+
{{ order.description|truncatewords:10 }}
+
+
+
+ 👁️ Details
+
+ {% if order.status == 'in_progress' %}
+
+ 📊 Fortschritt
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+
+
+
+
+
+
+
📝 Persönliche Daten
+
+
+ Name:
+ {{ user.first_name }} {{ user.last_name }}
+
+
+ E-Mail:
+ {{ user.email }}
+
+
+ Mitglied seit:
+ {{ user.date_joined|date:"d.m.Y" }}
+
+
+
+
+
+
📍 Adressen
+
+ {% if shipping_addresses %}
+
+ Versandadressen:
+ {{ shipping_addresses.count }}
+
+ {% else %}
+
+ Versandadressen:
+ Keine gespeichert
+
+ {% endif %}
+
+
+
+
+
+
+ {% if notifications %}
+
+
+
+
+ {% for notification in notifications %}
+
+
+ {% if notification.type == 'success' %}✅
+ {% elif notification.type == 'error' %}❌
+ {% elif notification.type == 'warning' %}⚠️
+ {% else %}ℹ️{% endif %}
+
+
+
{{ notification.title }}
+
{{ notification.message }}
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/faq.html b/products/templates/products/faq.html
index 8aa7397..2972379 100644
--- a/products/templates/products/faq.html
+++ b/products/templates/products/faq.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% load static %}
@@ -526,4 +527,534 @@ function markHelpful(button, isHelpful) {
console.log(`FAQ marked as ${isHelpful ? 'helpful' : 'not helpful'}`);
}
+=======
+{% extends 'base.html' %}
+{% load static %}
+
+{% block title %}FAQ - {{ block.super }}{% endblock %}
+
+{% block content %}
+
+
+
+
+
❓ Häufige Fragen
+
Finde schnell Antworten auf die wichtigsten Fragen rund um Fursuits
+
+ 📚 {{ faqs.count }} Fragen
+ 📂 {{ categories|length }} Kategorien
+ 🐺 Furry-Expertise
+
+
+
+
+
+
+
+
+
📂 Kategorien durchsuchen
+
+
+ 🌟 Alle Fragen
+
+ {% for category in categories %}
+
+ {% if category == "Bestellung" %}🛒
+ {% elif category == "Versand" %}📦
+ {% elif category == "Fursuit" %}🐺
+ {% elif category == "Zahlung" %}💳
+ {% elif category == "Support" %}🛠️
+ {% else %}❓{% endif %}
+ {{ category }}
+
+ {% endfor %}
+
+
+
+
+
+
💡 Antworten finden
+
+ {% for faq in faqs %}
+
+
+
+
+
+ {{ faq.answer|linebreaks }}
+
+
+ War diese Antwort hilfreich?
+
+ 👍 Ja
+
+
+ 👎 Nein
+
+
+
+
+
+ {% empty %}
+
+
🦊
+
Keine FAQ-Einträge verfügbar
+
Wir arbeiten daran, die häufigsten Fragen zu beantworten. Schau bald wieder vorbei!
+
+ {% endfor %}
+
+
+
+
+
+
+
🤔 Keine Antwort gefunden?
+
Wir helfen dir gerne persönlich weiter!
+
+
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/gallery.html b/products/templates/products/gallery.html
index 6b3b222..192841a 100644
--- a/products/templates/products/gallery.html
+++ b/products/templates/products/gallery.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends "shop/base.html" %}
{% load i18n %}
{% load static %}
@@ -529,4 +530,537 @@ document.getElementById('lightbox').addEventListener('click', function(event) {
}
});
+=======
+{% extends "shop/base.html" %}
+{% load i18n %}
+{% load static %}
+{% load extras %}
+
+{% block title %}Galerie - Kasico Art & Design{% endblock %}
+
+{% block content %}
+
+
+
+
+
🎨 Furry Galerie
+
Entdecke unsere einzigartigen Fursuit-Kreationen
+
+ 📸 {{ images.count }} Bilder
+ 🎭 {{ fursuit_types|length }} Typen
+ 🎨 {{ fursuit_styles|length }} Styles
+
+
+
+
+
+
+
+
+
+
+
+ 🐺 Fursuit-Typ
+
+ 🎭 Alle Typen
+ {% for type_code, type_name in fursuit_types %}
+
+ {{ type_name }}
+
+ {% endfor %}
+
+
+
+
+
+ 🎨 Style
+
+ 🌈 Alle Styles
+ {% for style_code, style_name in fursuit_styles %}
+
+ {{ style_name }}
+
+ {% endfor %}
+
+
+
+
+
+ 📅 Sortierung
+
+
+ ⭐ Neueste zuerst
+
+
+ 📅 Älteste zuerst
+
+
+
+
+
+
+
+ 🗑️ Filter zurücksetzen
+
+
+
+
+
+
+
+ {% for image in images %}
+
+
+
+
+ {% if image.is_featured %}
+
+ ⭐ Featured
+
+ {% endif %}
+
+
+ 🔍 Vergrößern
+
+
+
+
+
{{ image.title }}
+
{{ image.description }}
+
+
+ 🐺 {{ image.get_fursuit_type_display }}
+
+
+ 🎨 {{ image.get_style_display }}
+
+
+
+
+
+ {% empty %}
+
+
+
🦊
+
Keine Bilder gefunden
+
Versuche andere Filter-Einstellungen oder schaue später wieder vorbei!
+
+ 🔄 Alle Filter zurücksetzen
+
+
+
+ {% endfor %}
+
+
+
+
+
+
+
×
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/order_confirmation.html b/products/templates/products/order_confirmation.html
index 54fc2fd..d5c57cf 100644
--- a/products/templates/products/order_confirmation.html
+++ b/products/templates/products/order_confirmation.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% block title %}Bestellung erfolgreich{% endblock %}
@@ -18,4 +19,26 @@
Zurück zur Startseite
+=======
+{% extends 'base.html' %}
+
+{% block title %}Bestellung erfolgreich{% endblock %}
+
+{% block content %}
+
+
🎉
+
Danke für deine Bestellung!
+
Wir haben deine Bestellung erhalten und unsere Furry-Freunde machen sich sofort an die Arbeit 🐾
+
+
Deine Bestellung:
+
+ {% for item in order.items.all %}
+ {{ item.product_name }} × {{ item.quantity }} {{ item.price|floatformat:2 }} €
+ {% endfor %}
+
+
Gesamtsumme: {{ order.total_amount|floatformat:2 }} €
+
+
Zurück zur Startseite
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/product_detail.html b/products/templates/products/product_detail.html
index 031d9fa..1026a0e 100644
--- a/products/templates/products/product_detail.html
+++ b/products/templates/products/product_detail.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% block title %}{{ product.name }} - {{ block.super }}{% endblock %}
@@ -549,4 +550,557 @@ document.querySelectorAll('.share-btn').forEach(btn => {
});
});
+=======
+{% extends 'base.html' %}
+
+{% block title %}{{ product.name }} - {{ block.super }}{% endblock %}
+
+{% block extra_css %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
{{ product.name }}
+
+ {% if product.image %}
+
+
+
+
+
+
×
+
+
+ {% endif %}
+
+
{{ product.description }}
+
+
+
+
+
+ {% csrf_token %}
+
+ In den Warenkorb
+
+
+
+
+
+
+
+
+
+
+
+ {% if product.reviews.all %}
+ {% for review in product.reviews.all %}
+
+
+
{{ review.user.username }}
+ {{ review.created|date:"d.m.Y" }}
+
+
+ {% for i in "12345"|make_list %}
+ {% if forloop.counter <= review.rating %}
+
+ {% else %}
+
+ {% endif %}
+ {% endfor %}
+
+
{{ review.comment }}
+
+ {% if not forloop.last %}
{% endif %}
+ {% endfor %}
+ {% else %}
+
Noch keine Bewertungen vorhanden.
+ {% endif %}
+
+
+
+
+
+
+
+
+{% if user.is_authenticated and not user_has_reviewed %}
+
+
+
+
+
+
+ {% csrf_token %}
+ {% for field in review_form %}
+
+
{{ field.label }}
+ {{ field }}
+ {% if field.errors %}
+
+ {% for error in field.errors %}
+ {{ error }}
+ {% endfor %}
+
+ {% endif %}
+
+ {% endfor %}
+
+
+
+
+
+
+{% endif %}
+{% endblock %}
+
+{% block extra_js %}
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/product_list.html b/products/templates/products/product_list.html
index 6f164b5..2704004 100644
--- a/products/templates/products/product_list.html
+++ b/products/templates/products/product_list.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% load static %}
@@ -193,4 +194,201 @@ window.addEventListener('DOMContentLoaded', function() {
});
});
+=======
+{% extends 'base.html' %}
+{% load static %}
+
+{% block title %}Produkte - Kasico Fursuit Shop{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
🔍 Filter & Suche
+
+
+
+ Fursuit Typ
+
+ Alle Typen
+ Partial
+ Fullsuit
+ Head Only
+ Paws
+ Tail
+
+
+
+ Stil
+
+ Alle Stile
+ Toony
+ Semi-Realistic
+ Realistic
+ Anime
+ Chibi
+
+
+
+ Preisbereich
+
+ Alle Preise
+ 0 - 500€
+ 500 - 1000€
+ 1000 - 2000€
+ 2000€+
+
+
+
+ Sortierung
+
+ Neueste zuerst
+ Preis: Niedrig zu Hoch
+ Preis: Hoch zu Niedrig
+ Name A-Z
+
+
+
+
+
+
+
+
+
+ {% for product in products %}
+
+ {% if product.image %}
+
+ {% else %}
+
+ 🐾
+
+ {% endif %}
+
+
+
+
+
{{ product.description|truncatewords:20 }}
+
{{ product.price }}€
+
+
+
+
+ {% empty %}
+
+
🐾
+
Keine Produkte gefunden
+
Versuche andere Filter-Einstellungen oder schaue später wieder vorbei!
+
+ Alle Produkte anzeigen
+
+
+ {% endfor %}
+
+
+
+ {% if products.has_other_pages %}
+
+
+ {% if products.has_previous %}
+
+ ← Zurück
+
+ {% endif %}
+
+ {% for num in products.paginator.page_range %}
+ {% if products.number == num %}
+
{{ num }}
+ {% elif num > products.number|add:'-3' and num < products.number|add:'3' %}
+
{{ num }}
+ {% endif %}
+ {% endfor %}
+
+ {% if products.has_next %}
+
+ Weiter →
+
+ {% endif %}
+
+
+ {% endif %}
+
+
+
+{% if messages %}
+{% for message in messages %}
+
+{% endfor %}
+{% endif %}
+
+✔️ Zum Warenkorb hinzugefügt!
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/profile.html b/products/templates/products/profile.html
index 759bbdc..9e3c909 100644
--- a/products/templates/products/profile.html
+++ b/products/templates/products/profile.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% load i18n %}
@@ -25,4 +26,33 @@
Wunschliste ansehen
Logout
+=======
+{% extends 'base.html' %}
+{% load i18n %}
+
+{% block title %}Profil{% endblock %}
+
+{% block content %}
+
+
Willkommen, {{ user.username }}!
+
+
E-Mail: {{ user.email }}
+
Mitglied seit: {{ user.date_joined|date:"d.m.Y" }}
+
+
+
Bestellungen
+ {% if orders %}
+
+ {% for order in orders %}
+ Bestellung #{{ order.id }} vom {{ order.created|date:"d.m.Y" }} – {{ order.total_amount|floatformat:2 }} €
+ {% endfor %}
+
+ {% else %}
+
Du hast noch keine Bestellungen.
+ {% endif %}
+
+
Wunschliste ansehen
+
Logout
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/templates/registration/register.html b/products/templates/registration/register.html
index 38ff303..c8a7562 100644
--- a/products/templates/registration/register.html
+++ b/products/templates/registration/register.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'base.html' %}
{% load static %}
@@ -221,4 +222,229 @@ a:hover {
});
{% endblock %}
+=======
+{% extends 'base.html' %}
+{% load static %}
+
+{% block title %}Registrieren - {{ block.super }}{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
🐾 Registrieren
+
Werde Teil der Furry-Community!
+
+
+
+ {% csrf_token %}
+
+ {% if form.non_field_errors %}
+
+ {% for error in form.non_field_errors %}
+ {{ error }}
+ {% endfor %}
+
+ {% endif %}
+
+ {% for field in form %}
+
+ {% endfor %}
+
+
+
+ 🐾 Registrieren
+
+
+
+
+
+
+
+
+
+
+
+
+{% block extra_js %}
+
+{% endblock %}
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/products/tests.py b/products/tests.py
index 7ce503c..95edf74 100644
--- a/products/tests.py
+++ b/products/tests.py
@@ -1,3 +1,9 @@
+<<<<<<< HEAD
from django.test import TestCase
# Create your tests here.
+=======
+from django.test import TestCase
+
+# Create your tests here.
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/products/views.py b/products/views.py
index 787c5d4..6fe52ba 100644
--- a/products/views.py
+++ b/products/views.py
@@ -18,6 +18,7 @@ from django.utils import timezone
from django.core.mail import send_mail
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
import json
+<<<<<<< HEAD
# import paypalrestsdk # Temporär auskommentiert
# from payments import get_payment_model, RedirectNeeded # Temporär auskommentiert
# from paypal.standard.ipn.models import PayPalIPN # Temporär auskommentiert
@@ -29,6 +30,14 @@ from django.core.exceptions import ValidationError
from django.http import Http404
logger = logging.getLogger(__name__)
+=======
+import paypalrestsdk
+from payments import get_payment_model, RedirectNeeded
+from paypal.standard.ipn.models import PayPalIPN
+from django.urls import reverse
+from django.http import HttpResponse
+from paypal.standard.forms import PayPalPaymentsForm
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
# Create your views here.
@@ -39,6 +48,7 @@ class ProductListView(ListView):
paginate_by = 12
def get_queryset(self):
+<<<<<<< HEAD
try:
queryset = Product.objects.all().annotate(
average_rating=Avg('reviews__rating'),
@@ -130,6 +140,108 @@ class ProductListView(ListView):
except Exception as e:
logger.error(f"Error in ProductListView.get_context_data: {e}")
return super().get_context_data(**kwargs)
+=======
+ queryset = Product.objects.all().annotate(
+ average_rating=Avg('reviews__rating'),
+ review_count=Count('reviews')
+ ).select_related('category').prefetch_related('reviews')
+
+ # Erweiterte Suchfilter
+ search_query = self.request.GET.get('search')
+ if search_query:
+ # Verbesserte Suche mit Q-Objekten
+ search_terms = search_query.split()
+ q_objects = Q()
+ for term in search_terms:
+ q_objects |= (
+ Q(name__icontains=term) |
+ Q(description__icontains=term) |
+ Q(fursuit_type__icontains=term) |
+ Q(style__icontains=term) |
+ Q(category__name__icontains=term)
+ )
+ queryset = queryset.filter(q_objects)
+
+ # Preisbereich Filter mit Validierung
+ min_price = self.request.GET.get('min_price')
+ max_price = self.request.GET.get('max_price')
+ if min_price and min_price.isdigit():
+ queryset = queryset.filter(price__gte=float(min_price))
+ if max_price and max_price.isdigit():
+ queryset = queryset.filter(price__lte=float(max_price))
+
+ # Fursuit-Typ Filter
+ fursuit_type = self.request.GET.get('fursuit_type')
+ if fursuit_type:
+ queryset = queryset.filter(fursuit_type=fursuit_type)
+
+ # Style Filter
+ style = self.request.GET.get('style')
+ if style:
+ queryset = queryset.filter(style=style)
+
+ # Kategorie Filter
+ category = self.request.GET.get('category')
+ if category:
+ queryset = queryset.filter(category__slug=category)
+
+ # Verfügbarkeit Filter
+ availability = self.request.GET.get('availability')
+ if availability == 'in_stock':
+ queryset = queryset.filter(stock__gt=0)
+ elif availability == 'low_stock':
+ queryset = queryset.filter(stock__lte=5, stock__gt=0)
+ elif availability == 'out_of_stock':
+ queryset = queryset.filter(stock=0)
+
+ # Sortierung mit verbesserter Performance
+ sort = self.request.GET.get('sort', '-created')
+ if sort == 'price':
+ queryset = queryset.order_by('price')
+ elif sort == '-price':
+ queryset = queryset.order_by('-price')
+ elif sort == 'name':
+ queryset = queryset.order_by('name')
+ elif sort == '-name':
+ queryset = queryset.order_by('-name')
+ elif sort == '-rating':
+ queryset = queryset.order_by('-average_rating')
+ elif sort == 'popularity':
+ queryset = queryset.order_by('-review_count')
+ else: # Default: Neueste zuerst
+ queryset = queryset.order_by('-created')
+
+ return queryset.distinct()
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+
+ # Kategorien für Filter mit Anzahl
+ context['categories'] = Category.objects.annotate(
+ product_count=Count('product_items')
+ ).filter(product_count__gt=0)
+ context['selected_category'] = self.request.GET.get('category')
+
+ # Fursuit-Typen und Styles für Filter
+ context['fursuit_types'] = Product.FURSUIT_TYPE_CHOICES
+ context['fursuit_styles'] = Product.STYLE_CHOICES
+
+ # Filter-Werte
+ context['min_price'] = self.request.GET.get('min_price')
+ context['max_price'] = self.request.GET.get('max_price')
+ context['selected_type'] = self.request.GET.get('fursuit_type')
+ context['selected_style'] = self.request.GET.get('style')
+ context['search_query'] = self.request.GET.get('search')
+ context['sort'] = self.request.GET.get('sort', '-created')
+ context['availability'] = self.request.GET.get('availability')
+
+ # Statistiken für Filter
+ context['total_products'] = Product.objects.count()
+ context['in_stock_count'] = Product.objects.filter(stock__gt=0).count()
+ context['low_stock_count'] = Product.objects.filter(stock__lte=5, stock__gt=0).count()
+
+ return context
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
class ProductDetailView(DetailView):
model = Product
diff --git a/shop/apps.py b/shop/apps.py
index f451ee7..38c0942 100644
--- a/shop/apps.py
+++ b/shop/apps.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
@@ -12,3 +13,19 @@ class ShopConfig(AppConfig):
Importiert und registriert die Signal-Handler
"""
import shop.signals
+=======
+from django.apps import AppConfig
+from django.utils.translation import gettext_lazy as _
+
+
+class ShopConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'shop'
+ verbose_name = _('Shop')
+
+ def ready(self):
+ """
+ Importiert und registriert die Signal-Handler
+ """
+ import shop.signals
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/shop/asgi.py b/shop/asgi.py
index 32d6ad5..118fce0 100644
--- a/shop/asgi.py
+++ b/shop/asgi.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
"""
ASGI config for shop project.
@@ -14,3 +15,21 @@ from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shop.settings')
application = get_asgi_application()
+=======
+"""
+ASGI config for shop project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shop.settings')
+
+application = get_asgi_application()
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/shop/emails.py b/shop/emails.py
index 0287297..5418d81 100644
--- a/shop/emails.py
+++ b/shop/emails.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.translation import gettext as _
@@ -203,4 +204,211 @@ def send_low_stock_notification(request, product):
admin_emails
)
msg.attach_alternative(html_content, "text/html")
+=======
+from django.core.mail import EmailMultiAlternatives
+from django.template.loader import render_to_string
+from django.utils.translation import gettext as _
+from django.conf import settings
+from django.urls import reverse
+from django.contrib.sites.shortcuts import get_current_site
+
+def send_order_confirmation(request, order):
+ """
+ Sendet eine Bestellbestätigungs-E-Mail an den Kunden
+ """
+ context = {
+ 'order': order,
+ 'logo_url': f"{settings.STATIC_URL}images/logo.png",
+ 'order_url': request.build_absolute_uri(
+ reverse('shop:my_orders')
+ )
+ }
+
+ # HTML-Version
+ html_content = render_to_string(
+ 'shop/emails/order_confirmation.html',
+ context
+ )
+
+ # Text-Version
+ text_content = render_to_string(
+ 'shop/emails/order_confirmation.txt',
+ context
+ )
+
+ subject = _('Order Confirmation - Order #{}').format(order.id)
+ from_email = settings.DEFAULT_FROM_EMAIL
+ to_email = order.shipping_address.email
+
+ msg = EmailMultiAlternatives(
+ subject,
+ text_content,
+ from_email,
+ [to_email]
+ )
+ msg.attach_alternative(html_content, "text/html")
+ msg.send()
+
+def send_order_status_update(request, order, update=None):
+ """
+ Sendet eine E-Mail über Statusänderungen der Bestellung
+ """
+ context = {
+ 'order': order,
+ 'update': update,
+ 'logo_url': f"{settings.STATIC_URL}images/logo.png",
+ 'order_url': request.build_absolute_uri(
+ reverse('shop:my_orders')
+ )
+ }
+
+ # HTML-Version
+ html_content = render_to_string(
+ 'shop/emails/order_status_update.html',
+ context
+ )
+
+ # Text-Version
+ text_content = render_to_string(
+ 'shop/emails/order_status_update.txt',
+ context
+ )
+
+ subject = _('Order Status Update - Order #{}').format(order.id)
+ from_email = settings.DEFAULT_FROM_EMAIL
+ to_email = order.shipping_address.email
+
+ msg = EmailMultiAlternatives(
+ subject,
+ text_content,
+ from_email,
+ [to_email]
+ )
+ msg.attach_alternative(html_content, "text/html")
+ msg.send()
+
+def send_shipping_confirmation(request, order):
+ """
+ Sendet eine Versandbestätigungs-E-Mail mit Tracking-Nummer
+ """
+ context = {
+ 'order': order,
+ 'logo_url': f"{settings.STATIC_URL}images/logo.png",
+ 'order_url': request.build_absolute_uri(
+ reverse('shop:my_orders')
+ )
+ }
+
+ # HTML-Version
+ html_content = render_to_string(
+ 'shop/emails/shipping_confirmation.html',
+ context
+ )
+
+ # Text-Version
+ text_content = render_to_string(
+ 'shop/emails/shipping_confirmation.txt',
+ context
+ )
+
+ subject = _('Your Order Has Been Shipped - Order #{}').format(order.id)
+ from_email = settings.DEFAULT_FROM_EMAIL
+ to_email = order.shipping_address.email
+
+ msg = EmailMultiAlternatives(
+ subject,
+ text_content,
+ from_email,
+ [to_email]
+ )
+ msg.attach_alternative(html_content, "text/html")
+ msg.send()
+
+def send_admin_notification(request, order, notification_type, extra_context=None):
+ """
+ Sendet eine Benachrichtigung an den Shop-Administrator
+ """
+ context = {
+ 'order': order,
+ 'notification_type': notification_type,
+ 'logo_url': f"{settings.STATIC_URL}images/logo.png",
+ 'order_url': request.build_absolute_uri(
+ reverse('admin:shop_order_change', args=[order.id])
+ )
+ }
+
+ if extra_context:
+ context.update(extra_context)
+
+ # HTML-Version
+ html_content = render_to_string(
+ 'shop/emails/admin_notification.html',
+ context
+ )
+
+ # Text-Version
+ text_content = render_to_string(
+ 'shop/emails/admin_notification.txt',
+ context
+ )
+
+ # Betreff basierend auf Benachrichtigungstyp
+ subjects = {
+ 'new_order': _('New Order Received - Order #{}'),
+ 'payment_failed': _('Payment Failed - Order #{}'),
+ 'custom_design': _('New Custom Design Order #{}'),
+ 'fursuit_order': _('New Fursuit Order #{}'),
+ }
+
+ subject = subjects.get(notification_type, _('Order Notification - Order #{}')).format(order.id)
+
+ # E-Mail an alle konfigurierten Admin-E-Mail-Adressen senden
+ admin_emails = [email for name, email in settings.ADMINS]
+
+ if admin_emails:
+ msg = EmailMultiAlternatives(
+ subject,
+ text_content,
+ settings.DEFAULT_FROM_EMAIL,
+ admin_emails
+ )
+ msg.attach_alternative(html_content, "text/html")
+ msg.send()
+
+def send_low_stock_notification(request, product):
+ """
+ Benachrichtigt den Admin über niedrigen Lagerbestand
+ """
+ context = {
+ 'product': product,
+ 'logo_url': f"{settings.STATIC_URL}images/logo.png",
+ 'product_url': request.build_absolute_uri(
+ reverse('admin:shop_product_change', args=[product.id])
+ )
+ }
+
+ # HTML-Version
+ html_content = render_to_string(
+ 'shop/emails/low_stock_notification.html',
+ context
+ )
+
+ # Text-Version
+ text_content = render_to_string(
+ 'shop/emails/low_stock_notification.txt',
+ context
+ )
+
+ subject = _('Low Stock Alert - {}').format(product.name)
+ admin_emails = [email for name, email in settings.ADMINS]
+
+ if admin_emails:
+ msg = EmailMultiAlternatives(
+ subject,
+ text_content,
+ settings.DEFAULT_FROM_EMAIL,
+ admin_emails
+ )
+ msg.attach_alternative(html_content, "text/html")
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
msg.send()
\ No newline at end of file
diff --git a/shop/forms.py b/shop/forms.py
index 66565f7..583c234 100644
--- a/shop/forms.py
+++ b/shop/forms.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
from django import forms
from django.utils.translation import gettext_lazy as _
from .models import ShippingAddress, Order
@@ -20,4 +21,28 @@ class PaymentMethodForm(forms.Form):
error_messages={
'required': _('Please select a payment method.')
}
+=======
+from django import forms
+from django.utils.translation import gettext_lazy as _
+from .models import ShippingAddress, Order
+
+class ShippingAddressForm(forms.ModelForm):
+ class Meta:
+ model = ShippingAddress
+ fields = ['first_name', 'last_name', 'email', 'address', 'city', 'zip', 'country']
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ for field in self.fields.values():
+ field.widget.attrs['class'] = 'form-control'
+
+class PaymentMethodForm(forms.Form):
+ payment_method = forms.ChoiceField(
+ choices=Order.PAYMENT_METHODS,
+ widget=forms.HiddenInput(),
+ required=True,
+ error_messages={
+ 'required': _('Please select a payment method.')
+ }
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
)
\ No newline at end of file
diff --git a/shop/management/commands/test_email.py b/shop/management/commands/test_email.py
index 5c48bfb..eaae7cd 100644
--- a/shop/management/commands/test_email.py
+++ b/shop/management/commands/test_email.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
from django.core.management.base import BaseCommand
from django.core.mail import send_mail, get_connection
from django.conf import settings
@@ -107,4 +108,115 @@ class Command(BaseCommand):
except Exception as e:
self.stdout.write(self.style.ERROR(f'✗ Lagerbestand-Warnung fehlgeschlagen: {str(e)}'))
+=======
+from django.core.management.base import BaseCommand
+from django.core.mail import send_mail, get_connection
+from django.conf import settings
+from django.template.loader import render_to_string
+from shop.models import Order, Product
+from shop.emails import (
+ send_order_confirmation,
+ send_order_status_update,
+ send_shipping_confirmation,
+ send_admin_notification,
+ send_low_stock_notification
+)
+
+class Command(BaseCommand):
+ help = 'Testet das E-Mail-System mit verschiedenen E-Mail-Typen'
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '--email',
+ type=str,
+ help='Test-E-Mail-Adresse',
+ )
+ parser.add_argument(
+ '--type',
+ type=str,
+ choices=['all', 'order', 'status', 'shipping', 'admin', 'stock'],
+ default='all',
+ help='Art der Test-E-Mail',
+ )
+
+ def handle(self, *args, **options):
+ test_email = options['email']
+ if not test_email:
+ self.stdout.write(self.style.ERROR('Bitte geben Sie eine Test-E-Mail-Adresse an mit --email=ihre@email.de'))
+ return
+
+ email_type = options['type']
+
+ self.stdout.write('Starte E-Mail-Test...')
+
+ try:
+ # Debug-Informationen ausgeben
+ self.stdout.write(f'Backend: {settings.EMAIL_BACKEND}')
+ self.stdout.write(f'Host: {settings.EMAIL_HOST}')
+ self.stdout.write(f'Port: {settings.EMAIL_PORT}')
+ self.stdout.write(f'TLS: {settings.EMAIL_USE_TLS}')
+ self.stdout.write(f'Benutzer: {settings.EMAIL_HOST_USER}')
+ self.stdout.write('Passwort: ***versteckt***')
+
+ # Basis-Test
+ send_mail(
+ 'Test E-Mail',
+ 'Dies ist eine Test-E-Mail vom Fursuit Shop.',
+ settings.DEFAULT_FROM_EMAIL,
+ [test_email],
+ fail_silently=False,
+ )
+ self.stdout.write(self.style.SUCCESS('✓ Basis-E-Mail erfolgreich gesendet'))
+
+ except Exception as e:
+ self.stdout.write(self.style.ERROR(f'✗ Basis-E-Mail fehlgeschlagen: {str(e)}'))
+ return
+
+ if email_type in ['all', 'order']:
+ try:
+ # Test-Bestellung erstellen
+ order = Order.objects.first()
+ if order:
+ send_order_confirmation(None, order)
+ self.stdout.write(self.style.SUCCESS('✓ Bestellbestätigung gesendet'))
+ except Exception as e:
+ self.stdout.write(self.style.ERROR(f'✗ Bestellbestätigung fehlgeschlagen: {str(e)}'))
+
+ if email_type in ['all', 'status']:
+ try:
+ order = Order.objects.first()
+ if order:
+ send_order_status_update(None, order)
+ self.stdout.write(self.style.SUCCESS('✓ Status-Update gesendet'))
+ except Exception as e:
+ self.stdout.write(self.style.ERROR(f'✗ Status-Update fehlgeschlagen: {str(e)}'))
+
+ if email_type in ['all', 'shipping']:
+ try:
+ order = Order.objects.filter(tracking_number__isnull=False).first()
+ if order:
+ send_shipping_confirmation(None, order)
+ self.stdout.write(self.style.SUCCESS('✓ Versandbestätigung gesendet'))
+ except Exception as e:
+ self.stdout.write(self.style.ERROR(f'✗ Versandbestätigung fehlgeschlagen: {str(e)}'))
+
+ if email_type in ['all', 'admin']:
+ try:
+ order = Order.objects.first()
+ if order:
+ send_admin_notification(None, order, 'new_order')
+ self.stdout.write(self.style.SUCCESS('✓ Admin-Benachrichtigung gesendet'))
+ except Exception as e:
+ self.stdout.write(self.style.ERROR(f'✗ Admin-Benachrichtigung fehlgeschlagen: {str(e)}'))
+
+ if email_type in ['all', 'stock']:
+ try:
+ product = Product.objects.first()
+ if product:
+ send_low_stock_notification(None, product)
+ self.stdout.write(self.style.SUCCESS('✓ Lagerbestand-Warnung gesendet'))
+ except Exception as e:
+ self.stdout.write(self.style.ERROR(f'✗ Lagerbestand-Warnung fehlgeschlagen: {str(e)}'))
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
self.stdout.write(self.style.SUCCESS('\nE-Mail-Test abgeschlossen!'))
\ No newline at end of file
diff --git a/shop/migrations/0001_initial.py b/shop/migrations/0001_initial.py
index 8f14795..a9f3e3f 100644
--- a/shop/migrations/0001_initial.py
+++ b/shop/migrations/0001_initial.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-29 10:19
import django.db.models.deletion
@@ -136,3 +137,143 @@ class Migration(migrations.Migration):
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.product'),
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-29 10:19
+
+import django.db.models.deletion
+from decimal import Decimal
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Category',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200, verbose_name='Name')),
+ ('name_en', models.CharField(max_length=200, verbose_name='Name (English)')),
+ ('slug', models.SlugField(unique=True)),
+ ],
+ options={
+ 'verbose_name': 'Category',
+ 'verbose_name_plural': 'Categories',
+ },
+ ),
+ migrations.CreateModel(
+ name='DesignTemplate',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200, verbose_name='Name')),
+ ('name_en', models.CharField(max_length=200, verbose_name='Name (English)')),
+ ('description', models.TextField(verbose_name='Description')),
+ ('description_en', models.TextField(verbose_name='Description (English)')),
+ ('image', models.ImageField(upload_to='designs/')),
+ ('model_3d', models.FileField(blank=True, null=True, upload_to='3d_models/', verbose_name='3D Model')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='FursuitGallery',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200, verbose_name='Name')),
+ ('slug', models.SlugField(unique=True)),
+ ('description', models.TextField(verbose_name='Description')),
+ ('description_en', models.TextField(verbose_name='Description (English)')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ],
+ options={
+ 'verbose_name': 'Fursuit Gallery',
+ 'verbose_name_plural': 'Fursuit Galleries',
+ },
+ ),
+ migrations.CreateModel(
+ name='CustomerDesign',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200, verbose_name='Design Name')),
+ ('design_file', models.FileField(upload_to='customer_designs/')),
+ ('notes', models.TextField(blank=True, verbose_name='Notes')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='GalleryImage',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('image', models.ImageField(upload_to='gallery/')),
+ ('title', models.CharField(blank=True, max_length=200, verbose_name='Title')),
+ ('description', models.TextField(blank=True, verbose_name='Description')),
+ ('order', models.PositiveIntegerField(default=0)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('gallery', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='shop.fursuitgallery')),
+ ],
+ options={
+ 'ordering': ['order', 'created_at'],
+ },
+ ),
+ migrations.CreateModel(
+ name='Order',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('status', models.CharField(choices=[('pending', 'Pending'), ('confirmed', 'Confirmed'), ('in_progress', 'In Progress'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='pending', max_length=20)),
+ ('payment_method', models.CharField(choices=[('paypal', 'PayPal'), ('credit_card', 'Credit Card'), ('bank_transfer', 'Bank Transfer')], max_length=20)),
+ ('special_instructions', models.TextField(blank=True)),
+ ('measurements', models.JSONField(blank=True, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('total_price', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10)),
+ ('customer_design', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='shop.customerdesign')),
+ ('design_template', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='shop.designtemplate')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='OrderProgress',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=200, verbose_name='Title')),
+ ('description', models.TextField(verbose_name='Description')),
+ ('image', models.ImageField(blank=True, null=True, upload_to='progress/')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='progress_updates', to='shop.order')),
+ ],
+ options={
+ 'ordering': ['-created_at'],
+ },
+ ),
+ migrations.CreateModel(
+ name='Product',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('product_type', models.CharField(choices=[('fursuit', 'Fursuit'), ('printed', 'Printed Item')], default='printed', max_length=10, verbose_name='Product Type')),
+ ('name', models.CharField(max_length=200, verbose_name='Name')),
+ ('name_en', models.CharField(max_length=200, verbose_name='Name (English)')),
+ ('description', models.TextField(verbose_name='Description')),
+ ('description_en', models.TextField(verbose_name='Description (English)')),
+ ('base_price', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='Base Price')),
+ ('image', models.ImageField(upload_to='products/')),
+ ('model_3d', models.FileField(blank=True, null=True, upload_to='3d_models/', verbose_name='3D Model')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.category')),
+ ('gallery', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='shop.fursuitgallery')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='product',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.product'),
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/shop/migrations/0002_producttype_alter_category_options_and_more.py b/shop/migrations/0002_producttype_alter_category_options_and_more.py
index fa6fc3a..882173d 100644
--- a/shop/migrations/0002_producttype_alter_category_options_and_more.py
+++ b/shop/migrations/0002_producttype_alter_category_options_and_more.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-29 12:47
import django.core.validators
@@ -275,3 +276,282 @@ class Migration(migrations.Migration):
},
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-29 12:47
+
+import django.core.validators
+import django.db.models.deletion
+import django.utils.timezone
+from decimal import Decimal
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('shop', '0001_initial'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ProductType',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100, verbose_name='Name')),
+ ('has_sizes', models.BooleanField(default=False, verbose_name='Hat Größen')),
+ ('has_colors', models.BooleanField(default=False, verbose_name='Hat Farben')),
+ ('has_custom_design', models.BooleanField(default=False, verbose_name='Erlaubt Custom Design')),
+ ('requires_measurements', models.BooleanField(default=False, verbose_name='Benötigt Maße')),
+ ],
+ options={
+ 'verbose_name': 'Produkttyp',
+ 'verbose_name_plural': 'Produkttypen',
+ },
+ ),
+ migrations.AlterModelOptions(
+ name='category',
+ options={'ordering': ['name'], 'verbose_name': 'Kategorie', 'verbose_name_plural': 'Kategorien'},
+ ),
+ migrations.AlterModelOptions(
+ name='product',
+ options={'ordering': ['-created'], 'verbose_name': 'Produkt', 'verbose_name_plural': 'Produkte'},
+ ),
+ migrations.RemoveField(
+ model_name='category',
+ name='name_en',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='base_price',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='created_at',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='description_en',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='gallery',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='model_3d',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='name_en',
+ ),
+ migrations.RemoveField(
+ model_name='product',
+ name='updated_at',
+ ),
+ migrations.AddField(
+ model_name='category',
+ name='description',
+ field=models.TextField(blank=True, verbose_name='Beschreibung'),
+ ),
+ migrations.AddField(
+ model_name='category',
+ name='image',
+ field=models.ImageField(blank=True, upload_to='categories/', verbose_name='Bild'),
+ ),
+ migrations.AddField(
+ model_name='category',
+ name='parent',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='shop.category', verbose_name='Übergeordnete Kategorie'),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='available',
+ field=models.BooleanField(default=True, verbose_name='Verfügbar'),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='created',
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Erstellt'),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='price',
+ field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='Preis'),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='slug',
+ field=models.SlugField(default='', max_length=200, unique=True, verbose_name='Slug'),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='stock',
+ field=models.PositiveIntegerField(default=0, verbose_name='Lagerbestand'),
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='updated',
+ field=models.DateTimeField(auto_now=True, verbose_name='Aktualisiert'),
+ ),
+ migrations.AlterField(
+ model_name='category',
+ name='name',
+ field=models.CharField(max_length=100, verbose_name='Name'),
+ ),
+ migrations.AlterField(
+ model_name='category',
+ name='slug',
+ field=models.SlugField(max_length=100, unique=True, verbose_name='Slug'),
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='category',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='shop.category', verbose_name='Kategorie'),
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='description',
+ field=models.TextField(verbose_name='Beschreibung'),
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='image',
+ field=models.ImageField(upload_to='products/', verbose_name='Hauptbild'),
+ ),
+ migrations.CreateModel(
+ name='Cart',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='CartItem',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('quantity', models.PositiveIntegerField(default=1)),
+ ('size', models.CharField(blank=True, max_length=20, null=True)),
+ ('notes', models.TextField(blank=True, null=True)),
+ ('custom_design', models.ImageField(blank=True, null=True, upload_to='custom_designs/')),
+ ('added_at', models.DateTimeField(auto_now_add=True)),
+ ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='shop.cart')),
+ ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.product')),
+ ],
+ options={
+ 'ordering': ['-added_at'],
+ },
+ ),
+ migrations.CreateModel(
+ name='CustomDesign',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('design_file', models.FileField(upload_to='designs/', verbose_name='Design-Datei')),
+ ('notes', models.TextField(verbose_name='Anmerkungen')),
+ ('created', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Erstellt')),
+ ('status', models.CharField(choices=[('pending', 'Ausstehend'), ('approved', 'Genehmigt'), ('rejected', 'Abgelehnt'), ('in_progress', 'In Bearbeitung'), ('completed', 'Abgeschlossen')], default='pending', max_length=20, verbose_name='Status')),
+ ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Kunde')),
+ ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='custom_designs', to='shop.product', verbose_name='Produkt')),
+ ],
+ options={
+ 'verbose_name': 'Custom Design',
+ 'verbose_name_plural': 'Custom Designs',
+ 'ordering': ['-created'],
+ },
+ ),
+ migrations.CreateModel(
+ name='PaymentError',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('error_code', models.CharField(max_length=100, verbose_name='Error Code')),
+ ('error_message', models.TextField(verbose_name='Error Message')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payment_errors', to='shop.order')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='PayPalPayment',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('payment_id', models.CharField(max_length=100, verbose_name='PayPal Payment ID')),
+ ('payer_id', models.CharField(max_length=100, verbose_name='PayPal Payer ID')),
+ ('status', models.CharField(choices=[('pending', 'Pending'), ('completed', 'Completed'), ('failed', 'Failed'), ('refunded', 'Refunded')], max_length=20, verbose_name='Payment Status')),
+ ('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Amount')),
+ ('currency', models.CharField(default='EUR', max_length=3, verbose_name='Currency')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('order', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='paypal_payment', to='shop.order')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ProductImage',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('image', models.ImageField(upload_to='products/', verbose_name='Bild')),
+ ('alt_text', models.CharField(blank=True, max_length=200, verbose_name='Alternativer Text')),
+ ('is_feature', models.BooleanField(default=False, verbose_name='Ist Hauptbild')),
+ ('created', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Erstellt')),
+ ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='shop.product', verbose_name='Produkt')),
+ ],
+ options={
+ 'verbose_name': 'Produktbild',
+ 'verbose_name_plural': 'Produktbilder',
+ 'ordering': ['-is_feature', '-created'],
+ },
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='product_type',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.producttype', verbose_name='Produkttyp'),
+ ),
+ migrations.CreateModel(
+ name='ShippingAddress',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('first_name', models.CharField(max_length=100, verbose_name='First Name')),
+ ('last_name', models.CharField(max_length=100, verbose_name='Last Name')),
+ ('email', models.EmailField(max_length=254, verbose_name='Email')),
+ ('address', models.CharField(max_length=200, verbose_name='Address')),
+ ('city', models.CharField(max_length=100, verbose_name='City')),
+ ('zip', models.CharField(max_length=10, verbose_name='ZIP Code')),
+ ('country', models.CharField(choices=[('DE', 'Deutschland'), ('AT', 'Österreich'), ('CH', 'Schweiz')], max_length=2, verbose_name='Country')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Checkout',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('payment_method', models.CharField(choices=[('paypal', 'PayPal'), ('credit_card', 'Credit Card'), ('bank_transfer', 'Bank Transfer')], max_length=20, null=True, verbose_name='Payment Method')),
+ ('status', models.CharField(choices=[('address', 'Shipping Address'), ('payment', 'Payment Method'), ('confirm', 'Confirmation'), ('completed', 'Completed')], default='address', max_length=20, verbose_name='Status')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('cart', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='shop.cart')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ('shipping_address', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='shop.shippingaddress')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ProductVariant',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('size', models.CharField(blank=True, choices=[('XS', 'Extra Small'), ('S', 'Small'), ('M', 'Medium'), ('L', 'Large'), ('XL', 'Extra Large'), ('XXL', '2X Large'), ('XXXL', '3X Large')], max_length=10, verbose_name='Größe')),
+ ('color', models.CharField(blank=True, max_length=50, verbose_name='Farbe')),
+ ('sku', models.CharField(max_length=50, unique=True, verbose_name='Artikelnummer')),
+ ('price_adjustment', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Preisanpassung')),
+ ('stock', models.PositiveIntegerField(default=0, verbose_name='Lagerbestand')),
+ ('image', models.ImageField(blank=True, upload_to='variants/', verbose_name='Variantenbild')),
+ ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variants', to='shop.product', verbose_name='Produkt')),
+ ],
+ options={
+ 'verbose_name': 'Produktvariante',
+ 'verbose_name_plural': 'Produktvarianten',
+ 'unique_together': {('product', 'size', 'color')},
+ },
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/shop/migrations/0003_contactmessage.py b/shop/migrations/0003_contactmessage.py
index 60a63ee..55f1c0d 100644
--- a/shop/migrations/0003_contactmessage.py
+++ b/shop/migrations/0003_contactmessage.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
# Generated by Django 5.2.1 on 2025-05-30 10:18
from django.db import migrations, models
@@ -27,3 +28,34 @@ class Migration(migrations.Migration):
},
),
]
+=======
+# Generated by Django 5.2.1 on 2025-05-30 10:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('shop', '0002_producttype_alter_category_options_and_more'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ContactMessage',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100, verbose_name='Name')),
+ ('email', models.EmailField(max_length=254, verbose_name='E-Mail')),
+ ('subject', models.CharField(max_length=200, verbose_name='Betreff')),
+ ('message', models.TextField(verbose_name='Nachricht')),
+ ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am')),
+ ],
+ options={
+ 'verbose_name': 'Kontaktnachricht',
+ 'verbose_name_plural': 'Kontaktnachrichten',
+ 'ordering': ['-created_at'],
+ },
+ ),
+ ]
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/shop/models.py b/shop/models.py
index be8eca7..23b95e7 100644
--- a/shop/models.py
+++ b/shop/models.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User
@@ -363,3 +364,370 @@ class ContactMessage(models.Model):
def __str__(self):
return f"{self.subject} - {self.name}"
+=======
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+from django.contrib.auth.models import User
+from django.utils.text import slugify
+from decimal import Decimal
+from django.core.validators import MinValueValidator
+from django.utils import timezone
+
+# Create your models here.
+
+class FursuitGallery(models.Model):
+ name = models.CharField(_('Name'), max_length=200)
+ slug = models.SlugField(unique=True)
+ description = models.TextField(_('Description'))
+ description_en = models.TextField(_('Description (English)'))
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ def save(self, *args, **kwargs):
+ if not self.slug:
+ self.slug = slugify(self.name)
+ super().save(*args, **kwargs)
+
+ class Meta:
+ verbose_name = _('Fursuit Gallery')
+ verbose_name_plural = _('Fursuit Galleries')
+
+ def __str__(self):
+ return self.name
+
+class GalleryImage(models.Model):
+ gallery = models.ForeignKey(FursuitGallery, on_delete=models.CASCADE, related_name='images')
+ image = models.ImageField(upload_to='gallery/')
+ title = models.CharField(_('Title'), max_length=200, blank=True)
+ description = models.TextField(_('Description'), blank=True)
+ order = models.PositiveIntegerField(default=0)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ ordering = ['order', 'created_at']
+
+class Category(models.Model):
+ name = models.CharField(_('Name'), max_length=100)
+ slug = models.SlugField(_('Slug'), max_length=100, unique=True)
+ description = models.TextField(_('Beschreibung'), blank=True)
+ image = models.ImageField(_('Bild'), upload_to='categories/', blank=True)
+ parent = models.ForeignKey('self', verbose_name=_('Übergeordnete Kategorie'),
+ on_delete=models.CASCADE, null=True, blank=True,
+ related_name='children')
+
+ class Meta:
+ verbose_name = _('Kategorie')
+ verbose_name_plural = _('Kategorien')
+ ordering = ['name']
+
+ def __str__(self):
+ return self.name
+
+ def save(self, *args, **kwargs):
+ if not self.slug:
+ self.slug = slugify(self.name)
+ super().save(*args, **kwargs)
+
+class ProductType(models.Model):
+ name = models.CharField(_('Name'), max_length=100)
+ has_sizes = models.BooleanField(_('Hat Größen'), default=False)
+ has_colors = models.BooleanField(_('Hat Farben'), default=False)
+ has_custom_design = models.BooleanField(_('Erlaubt Custom Design'), default=False)
+ requires_measurements = models.BooleanField(_('Benötigt Maße'), default=False)
+
+ class Meta:
+ verbose_name = _('Produkttyp')
+ verbose_name_plural = _('Produkttypen')
+
+ def __str__(self):
+ return self.name
+
+class Product(models.Model):
+ category = models.ForeignKey(Category, verbose_name=_('Kategorie'),
+ on_delete=models.CASCADE, related_name='products')
+ product_type = models.ForeignKey(ProductType, verbose_name=_('Produkttyp'),
+ on_delete=models.CASCADE)
+ name = models.CharField(_('Name'), max_length=200)
+ slug = models.SlugField(_('Slug'), max_length=200, unique=True, default='')
+ description = models.TextField(_('Beschreibung'))
+ price = models.DecimalField(_('Preis'), max_digits=10, decimal_places=2,
+ validators=[MinValueValidator(Decimal('0.01'))],
+ default=Decimal('0.00'))
+ stock = models.PositiveIntegerField(_('Lagerbestand'), default=0)
+ available = models.BooleanField(_('Verfügbar'), default=True)
+ created = models.DateTimeField(_('Erstellt'), default=timezone.now)
+ updated = models.DateTimeField(_('Aktualisiert'), auto_now=True)
+ image = models.ImageField(_('Hauptbild'), upload_to='products/')
+
+ class Meta:
+ verbose_name = _('Produkt')
+ verbose_name_plural = _('Produkte')
+ ordering = ['-created']
+
+ def __str__(self):
+ return self.name
+
+ def save(self, *args, **kwargs):
+ if not self.slug:
+ self.slug = slugify(self.name)
+ super().save(*args, **kwargs)
+
+class ProductImage(models.Model):
+ product = models.ForeignKey(Product, verbose_name=_('Produkt'),
+ on_delete=models.CASCADE, related_name='images')
+ image = models.ImageField(_('Bild'), upload_to='products/')
+ alt_text = models.CharField(_('Alternativer Text'), max_length=200, blank=True)
+ is_feature = models.BooleanField(_('Ist Hauptbild'), default=False)
+ created = models.DateTimeField(_('Erstellt'), default=timezone.now)
+
+ class Meta:
+ verbose_name = _('Produktbild')
+ verbose_name_plural = _('Produktbilder')
+ ordering = ['-is_feature', '-created']
+
+class ProductVariant(models.Model):
+ SIZE_CHOICES = [
+ ('XS', 'Extra Small'),
+ ('S', 'Small'),
+ ('M', 'Medium'),
+ ('L', 'Large'),
+ ('XL', 'Extra Large'),
+ ('XXL', '2X Large'),
+ ('XXXL', '3X Large'),
+ ]
+
+ product = models.ForeignKey(Product, verbose_name=_('Produkt'),
+ on_delete=models.CASCADE, related_name='variants')
+ size = models.CharField(_('Größe'), max_length=10, choices=SIZE_CHOICES, blank=True)
+ color = models.CharField(_('Farbe'), max_length=50, blank=True)
+ sku = models.CharField(_('Artikelnummer'), max_length=50, unique=True)
+ price_adjustment = models.DecimalField(_('Preisanpassung'),
+ max_digits=10, decimal_places=2, default=0)
+ stock = models.PositiveIntegerField(_('Lagerbestand'), default=0)
+ image = models.ImageField(_('Variantenbild'), upload_to='variants/', blank=True)
+
+ class Meta:
+ verbose_name = _('Produktvariante')
+ verbose_name_plural = _('Produktvarianten')
+ unique_together = ['product', 'size', 'color']
+
+ def __str__(self):
+ variant_parts = []
+ if self.size:
+ variant_parts.append(f"Größe: {self.size}")
+ if self.color:
+ variant_parts.append(f"Farbe: {self.color}")
+ return f"{self.product.name} ({', '.join(variant_parts)})"
+
+class CustomDesign(models.Model):
+ STATUS_CHOICES = [
+ ('pending', _('Ausstehend')),
+ ('approved', _('Genehmigt')),
+ ('rejected', _('Abgelehnt')),
+ ('in_progress', _('In Bearbeitung')),
+ ('completed', _('Abgeschlossen')),
+ ]
+
+ product = models.ForeignKey(Product, verbose_name=_('Produkt'),
+ on_delete=models.CASCADE, related_name='custom_designs')
+ customer = models.ForeignKey('auth.User', verbose_name=_('Kunde'),
+ on_delete=models.CASCADE)
+ design_file = models.FileField(_('Design-Datei'), upload_to='designs/')
+ notes = models.TextField(_('Anmerkungen'))
+ created = models.DateTimeField(_('Erstellt'), default=timezone.now)
+ status = models.CharField(_('Status'), max_length=20, choices=STATUS_CHOICES, default='pending')
+
+ class Meta:
+ verbose_name = _('Custom Design')
+ verbose_name_plural = _('Custom Designs')
+ ordering = ['-created']
+
+ def __str__(self):
+ return f"Custom Design für {self.product.name} von {self.customer.username}"
+
+class DesignTemplate(models.Model):
+ name = models.CharField(_('Name'), max_length=200)
+ name_en = models.CharField(_('Name (English)'), max_length=200)
+ description = models.TextField(_('Description'))
+ description_en = models.TextField(_('Description (English)'))
+ image = models.ImageField(upload_to='designs/')
+ model_3d = models.FileField(_('3D Model'), upload_to='3d_models/', null=True, blank=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return self.name
+
+class CustomerDesign(models.Model):
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ name = models.CharField(_('Design Name'), max_length=200)
+ design_file = models.FileField(upload_to='customer_designs/')
+ notes = models.TextField(_('Notes'), blank=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return f"{self.user.username} - {self.name}"
+
+class Order(models.Model):
+ STATUS_CHOICES = [
+ ('pending', _('Pending')),
+ ('confirmed', _('Confirmed')),
+ ('in_progress', _('In Progress')),
+ ('completed', _('Completed')),
+ ('cancelled', _('Cancelled')),
+ ]
+
+ PAYMENT_METHODS = [
+ ('paypal', 'PayPal'),
+ ('credit_card', _('Credit Card')),
+ ('bank_transfer', _('Bank Transfer')),
+ ]
+
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ product = models.ForeignKey(Product, on_delete=models.CASCADE)
+ status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
+ payment_method = models.CharField(max_length=20, choices=PAYMENT_METHODS)
+ customer_design = models.ForeignKey(CustomerDesign, on_delete=models.SET_NULL, null=True, blank=True)
+ design_template = models.ForeignKey(DesignTemplate, on_delete=models.SET_NULL, null=True, blank=True)
+ special_instructions = models.TextField(blank=True)
+
+ # Nur für Fursuits
+ measurements = models.JSONField(null=True, blank=True)
+
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+ total_price = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'))
+
+ def __str__(self):
+ return f"Order {self.id} - {self.product.name}"
+
+class OrderProgress(models.Model):
+ order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='progress_updates')
+ title = models.CharField(_('Title'), max_length=200)
+ description = models.TextField(_('Description'))
+ image = models.ImageField(upload_to='progress/', null=True, blank=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ ordering = ['-created_at']
+
+ def __str__(self):
+ return f"Progress update for Order {self.order.id}"
+
+class Cart(models.Model):
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = 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} - {self.user.username}"
+
+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)
+ size = models.CharField(max_length=20, blank=True, null=True)
+ notes = models.TextField(blank=True, null=True)
+ custom_design = models.ImageField(upload_to='custom_designs/', blank=True, null=True)
+ added_at = models.DateTimeField(auto_now_add=True)
+
+ def get_subtotal(self):
+ return self.product.base_price * self.quantity
+
+ class Meta:
+ ordering = ['-added_at']
+
+ def __str__(self):
+ return f"{self.quantity}x {self.product.name} in Cart #{self.cart.id}"
+
+class ShippingAddress(models.Model):
+ COUNTRY_CHOICES = [
+ ('DE', 'Deutschland'),
+ ('AT', 'Österreich'),
+ ('CH', 'Schweiz'),
+ ]
+
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ first_name = models.CharField(_('First Name'), max_length=100)
+ last_name = models.CharField(_('Last Name'), max_length=100)
+ email = models.EmailField(_('Email'))
+ address = models.CharField(_('Address'), max_length=200)
+ city = models.CharField(_('City'), max_length=100)
+ zip = models.CharField(_('ZIP Code'), max_length=10)
+ country = models.CharField(_('Country'), max_length=2, choices=COUNTRY_CHOICES)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return f"{self.first_name} {self.last_name}, {self.city}"
+
+ def get_full_address(self):
+ return f"{self.address}, {self.zip} {self.city}, {self.get_country_display()}"
+
+class Checkout(models.Model):
+ STATUS_CHOICES = [
+ ('address', _('Shipping Address')),
+ ('payment', _('Payment Method')),
+ ('confirm', _('Confirmation')),
+ ('completed', _('Completed')),
+ ]
+
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ cart = models.OneToOneField(Cart, on_delete=models.CASCADE)
+ shipping_address = models.ForeignKey(ShippingAddress, on_delete=models.SET_NULL, null=True)
+ payment_method = models.CharField(_('Payment Method'), max_length=20, choices=Order.PAYMENT_METHODS, null=True)
+ status = models.CharField(_('Status'), max_length=20, choices=STATUS_CHOICES, default='address')
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ def __str__(self):
+ return f"Checkout #{self.id} - {self.user.username}"
+
+ def get_total(self):
+ cart_total = self.cart.get_total()
+ if cart_total < 200:
+ return cart_total + Decimal('5.99')
+ return cart_total
+
+class PayPalPayment(models.Model):
+ order = models.OneToOneField(Order, on_delete=models.CASCADE, related_name='paypal_payment')
+ payment_id = models.CharField(_('PayPal Payment ID'), max_length=100)
+ payer_id = models.CharField(_('PayPal Payer ID'), max_length=100)
+ status = models.CharField(_('Payment Status'), max_length=20, choices=[
+ ('pending', _('Pending')),
+ ('completed', _('Completed')),
+ ('failed', _('Failed')),
+ ('refunded', _('Refunded')),
+ ])
+ amount = models.DecimalField(_('Amount'), max_digits=10, decimal_places=2)
+ currency = models.CharField(_('Currency'), max_length=3, default='EUR')
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ def __str__(self):
+ return f"PayPal Payment {self.payment_id} for Order #{self.order.id}"
+
+class PaymentError(models.Model):
+ order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='payment_errors')
+ error_code = models.CharField(_('Error Code'), max_length=100)
+ error_message = models.TextField(_('Error Message'))
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return f"Payment Error for Order #{self.order.id}: {self.error_code}"
+
+class ContactMessage(models.Model):
+ name = models.CharField(_('Name'), max_length=100)
+ email = models.EmailField(_('E-Mail'))
+ subject = models.CharField(_('Betreff'), max_length=200)
+ message = models.TextField(_('Nachricht'))
+ created_at = models.DateTimeField(_('Erstellt am'), auto_now_add=True)
+
+ class Meta:
+ verbose_name = _('Kontaktnachricht')
+ verbose_name_plural = _('Kontaktnachrichten')
+ ordering = ['-created_at']
+
+ def __str__(self):
+ return f"{self.subject} - {self.name}"
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/shop/settings.py b/shop/settings.py
index efb58c1..b97fb14 100644
--- a/shop/settings.py
+++ b/shop/settings.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
"""
Django settings for shop project.
@@ -141,3 +142,148 @@ LOGOUT_REDIRECT_URL = 'product_list'
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 'PAGE_SIZE': 10
# }
+=======
+"""
+Django settings for shop project.
+
+Generated by 'django-admin startproject' using Django 5.2.1.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/5.2/ref/settings/
+"""
+
+from pathlib import Path
+import os
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-8m^(a*6xx46y=v*z8j*s*f=hup!+cyzghx8e9f^eugbg1)o!1s'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ # 'rest_framework', # Temporär auskommentiert
+ 'products',
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'shop.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'shop.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR / 'db.sqlite3',
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/5.2/topics/i18n/
+
+LANGUAGE_CODE = 'de'
+
+TIME_ZONE = 'Europe/Berlin'
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/5.2/howto/static-files/
+
+STATIC_URL = 'static/'
+STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
+
+# Media files
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+# Authentication
+LOGIN_REDIRECT_URL = 'product_list'
+LOGOUT_REDIRECT_URL = 'product_list'
+
+# REST Framework - Temporär auskommentiert
+# REST_FRAMEWORK = {
+# 'DEFAULT_PERMISSION_CLASSES': [
+# 'rest_framework.permissions.IsAuthenticatedOrReadOnly',
+# ],
+# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
+# 'PAGE_SIZE': 10
+# }
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/shop/signals.py b/shop/signals.py
index 5104f87..3fb2200 100644
--- a/shop/signals.py
+++ b/shop/signals.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.conf import settings
@@ -66,4 +67,74 @@ def check_stock_level(sender, instance, **kwargs):
send_low_stock_notification(None, instance)
except ObjectDoesNotExist:
+=======
+from django.db.models.signals import post_save, pre_save
+from django.dispatch import receiver
+from django.conf import settings
+from django.contrib.sites.shortcuts import get_current_site
+from django.core.exceptions import ObjectDoesNotExist
+from .models import Order, Product, PaymentError
+from .emails import (
+ send_order_confirmation,
+ send_order_status_update,
+ send_shipping_confirmation,
+ send_admin_notification,
+ send_low_stock_notification
+)
+
+@receiver(post_save, sender=Order)
+def handle_order_notifications(sender, instance, created, **kwargs):
+ """
+ Sendet E-Mail-Benachrichtigungen basierend auf Bestellstatus
+ """
+ if created:
+ # Neue Bestellung - Admin benachrichtigen
+ notification_type = 'fursuit_order' if any(p.product_type == 'fursuit' for p in instance.products.all()) else 'new_order'
+ if hasattr(instance, 'customer_design') and instance.customer_design:
+ notification_type = 'custom_design'
+
+ send_admin_notification(None, instance, notification_type)
+
+ else:
+ # Status-Änderung
+ try:
+ old_instance = Order.objects.get(id=instance.id)
+ if old_instance.status != instance.status:
+ # Status hat sich geändert - Kunde benachrichtigen
+ send_order_status_update(None, instance)
+
+ # Bei Versand zusätzlich Versandbestätigung senden
+ if instance.status == 'completed' and instance.tracking_number:
+ send_shipping_confirmation(None, instance)
+ except ObjectDoesNotExist:
+ pass
+
+@receiver(post_save, sender=PaymentError)
+def handle_payment_error(sender, instance, created, **kwargs):
+ """
+ Benachrichtigt den Admin über Zahlungsfehler
+ """
+ if created:
+ send_admin_notification(None, instance.order, 'payment_failed', {
+ 'payment_error': instance
+ })
+
+@receiver(pre_save, sender=Product)
+def check_stock_level(sender, instance, **kwargs):
+ """
+ Überprüft den Lagerbestand und sendet Benachrichtigungen bei niedrigem Stand
+ """
+ if not instance.pk: # Neues Produkt
+ return
+
+ try:
+ old_instance = Product.objects.get(pk=instance.pk)
+
+ # Wenn der Lagerbestand unter den Schwellenwert fällt
+ if (old_instance.stock > settings.LOW_STOCK_THRESHOLD and
+ instance.stock <= settings.LOW_STOCK_THRESHOLD):
+ send_low_stock_notification(None, instance)
+
+ except ObjectDoesNotExist:
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
pass
\ No newline at end of file
diff --git a/shop/templates/shop/base.html b/shop/templates/shop/base.html
index 72ac470..078116e 100644
--- a/shop/templates/shop/base.html
+++ b/shop/templates/shop/base.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends "base.html" %}
{% load static %}
@@ -12,4 +13,20 @@
{% block content %}
{{ block.super }}
+=======
+{% extends "base.html" %}
+{% load static %}
+
+{% block extra_head %}
+
+
+
+
+
+ {{ block.super }}
+{% endblock %}
+
+{% block content %}
+ {{ block.super }}
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/cart.html b/shop/templates/shop/cart.html
index 208d25c..5dee604 100644
--- a/shop/templates/shop/cart.html
+++ b/shop/templates/shop/cart.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends "shop/base.html" %}
{% load i18n %}
{% load static %}
@@ -459,4 +460,467 @@ function addToCart(productId) {
});
}
+=======
+{% extends "shop/base.html" %}
+{% load i18n %}
+{% load static %}
+
+{% block title %}{% trans "Shopping Cart" %} - Fursuit Shop{% endblock %}
+
+{% block extra_css %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+ {% if cart_items %}
+ {% for item in cart_items %}
+
+
+
+
+
+
+
+
+
+
{{ item.product.name }}
+
{{ item.product.description|truncatewords:15 }}
+
+
+ {% if item.product.product_type == 'fursuit' %}
+
+ Fursuit
+
+ {% else %}
+
+ Printed Item
+
+ {% endif %}
+
+ {% if item.product.is_custom_order %}
+
+ Custom Order
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+ {{ item.quantity }}
+
+
+
+
+
+
+
+
+ {% if item.product.on_sale %}
+ {{ item.product.sale_price }} €
+ {{ item.product.base_price }} €
+ {% else %}
+ {{ item.product.base_price }} €
+ {% endif %}
+
+
+ Total: {{ item.total_price }} €
+
+
+
+
+
+
+
+ {% trans "Remove" %}
+
+
+
+
+
+ {% endfor %}
+
+
+
+
+
+
{% trans "Cart Summary" %}
+
+ {% trans "Subtotal" %}:
+ {{ cart_subtotal }} €
+
+
+ {% trans "Shipping" %}:
+ {{ shipping_cost }} €
+
+ {% if discount_amount %}
+
+ {% trans "Discount" %}:
+ -{{ discount_amount }} €
+
+ {% endif %}
+
+
+ {% trans "Total" %}:
+ {{ cart_total }} €
+
+
+
+
+
+
+ {% else %}
+
+
+ {% endif %}
+
+
+
+
+
+ {% if cart_items %}
+
+
+
{% trans "Order Summary" %}
+
+
+
+ {% for item in cart_items|slice:":3" %}
+
+
+
+
{{ item.product.name }}
+
Qty: {{ item.quantity }}
+
+
+
{{ item.total_price }} €
+
+
+ {% endfor %}
+
+ {% if cart_items|length > 3 %}
+
+ +{{ cart_items|length|add:"-3" }} more items
+
+ {% endif %}
+
+
+
+
+
+ {% trans "Total" %}
+ {{ cart_total }} €
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{% trans "Secure checkout with SSL encryption" %}
+
+
+
+ {% endif %}
+
+
+
+
+ {% if related_products %}
+
+
{% trans "You might also like" %}
+
+ {% for product in related_products %}
+
+
+
+
+ {% if product.on_sale %}
+
+ Sale!
+
+ {% endif %}
+
+
+
{{ product.name }}
+
{{ product.description|truncatewords:10 }}
+
+
+ {% if product.on_sale %}
+ {{ product.sale_price }} €
+ {{ product.base_price }} €
+ {% else %}
+ {{ product.base_price }} €
+ {% endif %}
+
+
+
+
+
+
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+
+
+
+
+
+
+ Warenkorb wurde aktualisiert!
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/checkout.html b/shop/templates/shop/checkout.html
index 6fd8a39..d5b8605 100644
--- a/shop/templates/shop/checkout.html
+++ b/shop/templates/shop/checkout.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends "shop/base.html" %}
{% load i18n %}
@@ -465,4 +466,473 @@ function selectPayment(method) {
}
{% endif %}
+=======
+{% extends "shop/base.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "Checkout" %} - Fursuit Shop{% endblock %}
+
+{% block extra_css %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
1
+
{% trans "Shipping" %}
+
+
+
2
+
{% trans "Payment" %}
+
+
+
3
+
{% trans "Confirm" %}
+
+
+
+
+
+
+ {% if step == 'address' %}
+
+
+
{% trans "Shipping Address" %}
+
+ {% csrf_token %}
+
+
+
+
+
{% trans "First Name" %}
+
+ {% if form.first_name.errors %}
+
+ {{ form.first_name.errors }}
+
+ {% endif %}
+
+
+
+
{% trans "Last Name" %}
+
+ {% if form.last_name.errors %}
+
+ {{ form.last_name.errors }}
+
+ {% endif %}
+
+
+
+
{% trans "Email" %}
+
+ {% if form.email.errors %}
+
+ {{ form.email.errors }}
+
+ {% endif %}
+
+
+
+
{% trans "Street Address" %}
+
+ {% if form.address.errors %}
+
+ {{ form.address.errors }}
+
+ {% endif %}
+
+
+
+
{% trans "City" %}
+
+ {% if form.city.errors %}
+
+ {{ form.city.errors }}
+
+ {% endif %}
+
+
+
+
{% trans "ZIP Code" %}
+
+ {% if form.zip.errors %}
+
+ {{ form.zip.errors }}
+
+ {% endif %}
+
+
+
+
{% trans "Country" %}
+
+ {% trans "Choose..." %}
+ Deutschland
+ Österreich
+ Schweiz
+
+ {% if form.country.errors %}
+
+ {{ form.country.errors }}
+
+ {% endif %}
+
+
+
+
+
+
+
+ {% trans "Continue to Payment" %}
+
+
+
+
+
+
+
+ {% elif step == 'payment' %}
+
+
+
{% trans "Payment Method" %}
+
+ {% csrf_token %}
+
+
+
+
+
+ {% if form.payment_method.errors %}
+
+ {{ form.payment_method.errors }}
+
+ {% endif %}
+
+
+
+
+
+
+ {% trans "Back to Shipping" %}
+
+
+ {% trans "Continue to Confirmation" %}
+
+
+
+
+
+
+
+ {% elif step == 'confirm' %}
+
+
+
{% trans "Order Confirmation" %}
+
+
+
+
{% trans "Shipping Address" %}
+
{{ order.shipping_address.first_name }} {{ order.shipping_address.last_name }}
+
{{ order.shipping_address.address }}
+
{{ order.shipping_address.zip }} {{ order.shipping_address.city }}
+
{{ order.shipping_address.get_country_display }}
+
+
+
+
+
{% trans "Payment Method" %}
+
+ {% if order.payment_method == 'paypal' %}
+ PayPal
+ {% elif order.payment_method == 'credit_card' %}
+ {% trans "Credit Card" %}
+ {% else %}
+ {% trans "Bank Transfer" %}
+ {% endif %}
+
+
+
+
+
+
{% trans "Order Items" %}
+ {% for item in cart.items.all %}
+
+
+ {{ item.quantity }}x
+ {{ item.product.name }}
+ {% if item.size %}
+ ({{ item.size }})
+ {% endif %}
+
+
{{ item.get_subtotal }} €
+
+ {% endfor %}
+
+
+
+
+
+
+
+ {% trans "Subtotal" %}
+ {{ cart.get_total }} €
+
+ {% if cart.get_total < 200 %}
+
+ {% trans "Shipping" %}
+ 5.99 €
+
+ {% else %}
+
+ {% trans "Shipping" %}
+ {% trans "FREE" %}
+
+ {% endif %}
+
+
+ {% trans "Total" %}
+
+ {% if cart.get_total < 200 %}
+ {{ cart.get_total|add:"5.99" }} €
+ {% else %}
+ {{ cart.get_total }} €
+ {% endif %}
+
+
+
+
+
+ {% csrf_token %}
+
+
+
+
+
+ {% trans "Back to Payment" %}
+
+
+
+ {% trans "Place Order" %}
+
+
+
+
+
+ {% endif %}
+
+
+
+
+
+
+
{% trans "Order Summary" %}
+
+ {% for item in cart.items.all %}
+
+
+ {{ item.quantity }}x
+ {{ item.product.name }}
+
+
{{ item.get_subtotal }} €
+
+ {% endfor %}
+
+
+
+
+ {% trans "Subtotal" %}
+ {{ cart.get_total }} €
+
+ {% if cart.get_total < 200 %}
+
+ {% trans "Shipping" %}
+ 5.99 €
+
+ {% else %}
+
+ {% trans "Shipping" %}
+ {% trans "FREE" %}
+
+ {% endif %}
+
+
+ {% trans "Total" %}
+
+ {% if cart.get_total < 200 %}
+ {{ cart.get_total|add:"5.99" }} €
+ {% else %}
+ {{ cart.get_total }} €
+ {% endif %}
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+{% if step == 'payment' %}
+
+{% endif %}
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/emails/admin_notification.html b/shop/templates/shop/emails/admin_notification.html
index d63c058..4916991 100644
--- a/shop/templates/shop/emails/admin_notification.html
+++ b/shop/templates/shop/emails/admin_notification.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
@@ -209,4 +210,217 @@
+=======
+
+
+
+
+
+
+
+
+
+
+
+ {% if notification_type == 'new_order' %}
+ {% trans "New Order" %}
+ {% elif notification_type == 'payment_failed' %}
+ {% trans "Payment Failed" %}
+ {% elif notification_type == 'custom_design' %}
+ {% trans "Custom Design" %}
+ {% elif notification_type == 'fursuit_order' %}
+ {% trans "Fursuit Order" %}
+ {% endif %}
+
+
+
{% trans "Order" %} #{{ order.id }}
+
+
+
{% trans "Customer Information" %}
+
+ {% trans "Name" %}:
+ {{ order.shipping_address.first_name }} {{ order.shipping_address.last_name }}
+
+
+ {% trans "Email" %}:
+ {{ order.shipping_address.email }}
+
+
+ {% trans "Address" %}:
+ {{ order.shipping_address.address }}
+ {{ order.shipping_address.zip }} {{ order.shipping_address.city }}
+ {{ order.shipping_address.get_country_display }}
+
+
+
+
{% trans "Ordered Items" %}
+ {% for product in order.products.all %}
+
+ {% if product.image %}
+
+ {% endif %}
+
+
{{ product.name }}
+ {% if product.product_type == 'fursuit' %}
+ {% trans "Fursuit" %}
+ {% else %}
+ {% trans "Printed Item" %}
+ {% endif %}
+
+
+ {{ product.base_price }} €
+
+
+ {% endfor %}
+
+
+ {% trans "Total" %}: {{ order.total_price }} €
+
+
+ {% if notification_type == 'payment_failed' and payment_error %}
+
+
{% trans "Payment Error Details" %}
+
{% trans "Error Code" %}: {{ payment_error.error_code }}
+
{% trans "Error Message" %}: {{ payment_error.error_message }}
+
+ {% endif %}
+
+ {% if notification_type == 'custom_design' and order.customer_design %}
+
+
{% trans "Custom Design Details" %}
+
{% trans "Design Name" %}: {{ order.customer_design.name }}
+ {% if order.customer_design.notes %}
+
{% trans "Notes" %}: {{ order.customer_design.notes }}
+ {% endif %}
+ {% if order.customer_design.design_file %}
+
{% trans "Design File" %}:
+
+ {% trans "Download Design File" %}
+
+
+ {% endif %}
+
+ {% endif %}
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
\ No newline at end of file
diff --git a/shop/templates/shop/emails/admin_notification.txt b/shop/templates/shop/emails/admin_notification.txt
index d57191b..fdaa12f 100644
--- a/shop/templates/shop/emails/admin_notification.txt
+++ b/shop/templates/shop/emails/admin_notification.txt
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% load i18n %}
{% if notification_type == 'new_order' %}
@@ -48,4 +49,56 @@
{% endif %}
{% endif %}
+=======
+{% load i18n %}
+
+{% if notification_type == 'new_order' %}
+{% trans "New Order Received" %}
+{% elif notification_type == 'payment_failed' %}
+{% trans "Payment Failed" %}
+{% elif notification_type == 'custom_design' %}
+{% trans "New Custom Design Order" %}
+{% elif notification_type == 'fursuit_order' %}
+{% trans "New Fursuit Order" %}
+{% else %}
+{% trans "Order Notification" %}
+{% endif %}
+
+{% trans "Order" %} #{{ order.id }}
+
+{% trans "Customer Information" %}:
+{% trans "Name" %}: {{ order.shipping_address.first_name }} {{ order.shipping_address.last_name }}
+{% trans "Email" %}: {{ order.shipping_address.email }}
+
+{% trans "Address" %}:
+{{ order.shipping_address.address }}
+{{ order.shipping_address.zip }} {{ order.shipping_address.city }}
+{{ order.shipping_address.get_country_display }}
+
+{% trans "Ordered Items" %}:
+{% for product in order.products.all %}
+- {{ product.name }} ({% if product.product_type == 'fursuit' %}{% trans "Fursuit" %}{% else %}{% trans "Printed Item" %}{% endif %})
+ {{ product.base_price }} €
+{% endfor %}
+
+{% trans "Total" %}: {{ order.total_price }} €
+
+{% if notification_type == 'payment_failed' and payment_error %}
+{% trans "Payment Error Details" %}:
+{% trans "Error Code" %}: {{ payment_error.error_code }}
+{% trans "Error Message" %}: {{ payment_error.error_message }}
+{% endif %}
+
+{% if notification_type == 'custom_design' and order.customer_design %}
+{% trans "Custom Design Details" %}:
+{% trans "Design Name" %}: {{ order.customer_design.name }}
+{% if order.customer_design.notes %}
+{% trans "Notes" %}: {{ order.customer_design.notes }}
+{% endif %}
+{% if order.customer_design.design_file %}
+{% trans "Design File" %}: {{ order.customer_design.design_file.url }}
+{% endif %}
+{% endif %}
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% trans "View Order Details" %}: {{ order_url }}
\ No newline at end of file
diff --git a/shop/templates/shop/emails/low_stock_notification.html b/shop/templates/shop/emails/low_stock_notification.html
index 561e923..6c15535 100644
--- a/shop/templates/shop/emails/low_stock_notification.html
+++ b/shop/templates/shop/emails/low_stock_notification.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
@@ -296,4 +297,304 @@
+=======
+
+
+
+
+
+ {% trans "Low Stock Alert" %}
+
+
+
+
+
+
+
+ {% trans "Warning" %}:
+ {% trans "The following product is running low on stock and needs attention." %}
+
+
+
+ {% if product.image %}
+
+ {% endif %}
+
+
{{ product.name }}
+
+ {% if product.product_type == 'fursuit' %}
+
{% trans "Fursuit" %}
+ {% else %}
+
{% trans "Printed Item" %}
+ {% endif %}
+
+
+ {% trans "Current Stock" %}: {{ product.stock }}
+
+
+
+
{% trans "SKU" %}: {{ product.sku }}
+
{% trans "Base Price" %}: {{ product.base_price }} €
+ {% if product.category %}
+
{% trans "Category" %}: {{ product.category.name }}
+ {% endif %}
+
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
\ No newline at end of file
diff --git a/shop/templates/shop/emails/low_stock_notification.txt b/shop/templates/shop/emails/low_stock_notification.txt
index 3ee98b1..86ac18e 100644
--- a/shop/templates/shop/emails/low_stock_notification.txt
+++ b/shop/templates/shop/emails/low_stock_notification.txt
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% load i18n %}
{% trans "Low Stock Alert" %}
@@ -12,4 +13,20 @@
{% trans "Current Stock" %}: {{ product.stock }}
+=======
+{% load i18n %}
+
+{% trans "Low Stock Alert" %}
+
+{% trans "Warning" %}: {% trans "The following product is running low on stock and needs attention." %}
+
+{% trans "Product Details" %}:
+{% trans "Name" %}: {{ product.name }}
+{% trans "Type" %}: {% if product.product_type == 'fursuit' %}{% trans "Fursuit" %}{% else %}{% trans "Printed Item" %}{% endif %}
+{% trans "SKU" %}: {{ product.sku }}
+{% trans "Base Price" %}: {{ product.base_price }} €
+
+{% trans "Current Stock" %}: {{ product.stock }}
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% trans "Manage this product at" %}: {{ product_url }}
\ No newline at end of file
diff --git a/shop/templates/shop/emails/order_confirmation.html b/shop/templates/shop/emails/order_confirmation.html
index 0c01d42..967a4f9 100644
--- a/shop/templates/shop/emails/order_confirmation.html
+++ b/shop/templates/shop/emails/order_confirmation.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
@@ -179,4 +180,187 @@
+=======
+
+
+
+
+
+
+
+
+
+
+
{% trans "Order Details" %}
+
{% trans "Order Number" %}: #{{ order.id }}
+
{% trans "Order Date" %}: {{ order.created_at|date:"d.m.Y" }}
+
+ {% trans "Payment Method" %}:
+ {% if order.payment_method == 'paypal' %}
+ PayPal
+ {% elif order.payment_method == 'credit_card' %}
+ {% trans "Credit Card" %}
+ {% else %}
+ {% trans "Bank Transfer" %}
+ {% endif %}
+
+
+
{% trans "Shipping Address" %}
+
+ {{ order.shipping_address.first_name }} {{ order.shipping_address.last_name }}
+ {{ order.shipping_address.address }}
+ {{ order.shipping_address.zip }} {{ order.shipping_address.city }}
+ {{ order.shipping_address.get_country_display }}
+
+
+
{% trans "Ordered Items" %}
+ {% for product in order.products.all %}
+
+ {% if product.image %}
+
+ {% endif %}
+
+
{{ product.name }}
+ {% if product.product_type == 'fursuit' %}
+ {% trans "Fursuit" %}
+ {% else %}
+ {% trans "Printed Item" %}
+ {% endif %}
+
+
+ {{ product.base_price }} €
+
+
+ {% endfor %}
+
+
+
+ {% trans "Subtotal" %}:
+ {{ order.total_price }} €
+
+ {% if order.total_price < 200 %}
+
+ {% trans "Shipping" %}:
+ 5.99 €
+
+
+ {% trans "Total" %}:
+ {{ order.total_price|add:"5.99" }} €
+
+ {% else %}
+
+ {% trans "Shipping" %}:
+ {% trans "FREE" %}
+
+
+ {% trans "Total" %}:
+ {{ order.total_price }} €
+
+ {% endif %}
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
\ No newline at end of file
diff --git a/shop/templates/shop/emails/order_confirmation.txt b/shop/templates/shop/emails/order_confirmation.txt
index 65c7ba4..7754718 100644
--- a/shop/templates/shop/emails/order_confirmation.txt
+++ b/shop/templates/shop/emails/order_confirmation.txt
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% load i18n %}
{% trans "Thank You for Your Order!" %}
@@ -35,4 +36,43 @@
{% trans "If you have any questions about your order, please contact our support team." %}
support@fursuitshop.com
+=======
+{% load i18n %}
+
+{% trans "Thank You for Your Order!" %}
+
+{% trans "Your order has been confirmed and is being processed." %}
+
+{% trans "Order Details" %}:
+{% trans "Order Number" %}: #{{ order.id }}
+{% trans "Order Date" %}: {{ order.created_at|date:"d.m.Y" }}
+{% trans "Payment Method" %}: {% if order.payment_method == 'paypal' %}PayPal{% elif order.payment_method == 'credit_card' %}{% trans "Credit Card" %}{% else %}{% trans "Bank Transfer" %}{% endif %}
+
+{% trans "Shipping Address" %}:
+{{ order.shipping_address.first_name }} {{ order.shipping_address.last_name }}
+{{ order.shipping_address.address }}
+{{ order.shipping_address.zip }} {{ order.shipping_address.city }}
+{{ order.shipping_address.get_country_display }}
+
+{% trans "Ordered Items" %}:
+{% for product in order.products.all %}
+- {{ product.name }} ({% if product.product_type == 'fursuit' %}{% trans "Fursuit" %}{% else %}{% trans "Printed Item" %}{% endif %})
+ {{ product.base_price }} €
+{% endfor %}
+
+{% trans "Subtotal" %}: {{ order.total_price }} €
+{% if order.total_price < 200 %}
+{% trans "Shipping" %}: 5.99 €
+{% trans "Total" %}: {{ order.total_price|add:"5.99" }} €
+{% else %}
+{% trans "Shipping" %}: {% trans "FREE" %}
+{% trans "Total" %}: {{ order.total_price }} €
+{% endif %}
+
+{% trans "You can view your order details at" %}: {{ order_url }}
+
+{% trans "If you have any questions about your order, please contact our support team." %}
+support@fursuitshop.com
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
© {% now "Y" %} Fursuit Shop. {% trans "All rights reserved." %}
\ No newline at end of file
diff --git a/shop/templates/shop/emails/order_status_update.html b/shop/templates/shop/emails/order_status_update.html
index a6193a6..3817932 100644
--- a/shop/templates/shop/emails/order_status_update.html
+++ b/shop/templates/shop/emails/order_status_update.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
@@ -254,4 +255,262 @@
+=======
+
+
+
+
+
+ {% trans "Order Status Update" %}
+
+
+
+
+
+
+
+
{% trans "Order" %} #{{ order.id }}
+
+
+ {% trans "New Status" %}:
+
+ {{ order.get_status_display }}
+
+
+
+ {% if update %}
+
{{ update.title }}
+
{{ update.description }}
+ {% if update.image %}
+
+ {% endif %}
+ {% endif %}
+
+ {% if order.status == 'confirmed' %}
+
{% trans "Your order has been confirmed and will be processed soon." %}
+ {% elif order.status == 'in_progress' %}
+
{% trans "We are currently working on your order." %}
+ {% elif order.status == 'completed' %}
+
{% trans "Your order has been completed and will be shipped soon." %}
+ {% if order.tracking_number %}
+
+ {% trans "Tracking Number" %}:
+ {{ order.tracking_number }}
+
+ {% endif %}
+ {% endif %}
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
\ No newline at end of file
diff --git a/shop/templates/shop/emails/order_status_update.txt b/shop/templates/shop/emails/order_status_update.txt
index f60d9b6..1f822e8 100644
--- a/shop/templates/shop/emails/order_status_update.txt
+++ b/shop/templates/shop/emails/order_status_update.txt
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% load i18n %}
{% trans "Order Status Update" %}
@@ -29,4 +30,37 @@
{% trans "If you have any questions about your order, please contact our support team." %}
support@fursuitshop.com
+=======
+{% load i18n %}
+
+{% trans "Order Status Update" %}
+
+{% trans "Your order has been updated." %}
+
+{% trans "Order" %} #{{ order.id }}
+
+{% trans "New Status" %}: {{ order.get_status_display }}
+
+{% if update %}
+{{ update.title }}
+{{ update.description }}
+{% endif %}
+
+{% if order.status == 'confirmed' %}
+{% trans "Your order has been confirmed and will be processed soon." %}
+{% elif order.status == 'in_progress' %}
+{% trans "We are currently working on your order." %}
+{% elif order.status == 'completed' %}
+{% trans "Your order has been completed and will be shipped soon." %}
+{% if order.tracking_number %}
+{% trans "Tracking Number" %}: {{ order.tracking_number }}
+{% endif %}
+{% endif %}
+
+{% trans "You can view your order details at" %}: {{ order_url }}
+
+{% trans "If you have any questions about your order, please contact our support team." %}
+support@fursuitshop.com
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
© {% now "Y" %} Fursuit Shop. {% trans "All rights reserved." %}
\ No newline at end of file
diff --git a/shop/templates/shop/emails/shipping_confirmation.html b/shop/templates/shop/emails/shipping_confirmation.html
index a347094..16ee0df 100644
--- a/shop/templates/shop/emails/shipping_confirmation.html
+++ b/shop/templates/shop/emails/shipping_confirmation.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
@@ -164,4 +165,172 @@
+=======
+
+
+
+
+
+
+
+
+
+
+
{% trans "Order" %} #{{ order.id }}
+
+
+
{% trans "Tracking Information" %}
+
+ {{ order.tracking_number }}
+
+
+ {% trans "Use this number to track your shipment" %}
+
+
+
+
{% trans "Shipping Address" %}
+
+ {{ order.shipping_address.first_name }} {{ order.shipping_address.last_name }}
+ {{ order.shipping_address.address }}
+ {{ order.shipping_address.zip }} {{ order.shipping_address.city }}
+ {{ order.shipping_address.get_country_display }}
+
+
+
+
{% trans "Shipped Items" %}
+ {% for product in order.products.all %}
+
+ {% if product.image %}
+
+ {% endif %}
+
+
{{ product.name }}
+ {% if product.product_type == 'fursuit' %}
+ {% trans "Fursuit" %}
+ {% else %}
+ {% trans "Printed Item" %}
+ {% endif %}
+
+
+ {% endfor %}
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
\ No newline at end of file
diff --git a/shop/templates/shop/emails/shipping_confirmation.txt b/shop/templates/shop/emails/shipping_confirmation.txt
index 4de6e03..52f921a 100644
--- a/shop/templates/shop/emails/shipping_confirmation.txt
+++ b/shop/templates/shop/emails/shipping_confirmation.txt
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% load i18n %}
{% trans "Your Order Has Been Shipped!" %}
@@ -25,4 +26,33 @@
{% trans "If you have any questions about your shipment, please contact our support team." %}
support@fursuitshop.com
+=======
+{% load i18n %}
+
+{% trans "Your Order Has Been Shipped!" %}
+
+{% trans "Great news! Your order has been shipped and is on its way to you." %}
+
+{% trans "Order" %} #{{ order.id }}
+
+{% trans "Tracking Information" %}:
+{% trans "Tracking Number" %}: {{ order.tracking_number }}
+
+{% trans "Shipping Address" %}:
+{{ order.shipping_address.first_name }} {{ order.shipping_address.last_name }}
+{{ order.shipping_address.address }}
+{{ order.shipping_address.zip }} {{ order.shipping_address.city }}
+{{ order.shipping_address.get_country_display }}
+
+{% trans "Ordered Items" %}:
+{% for product in order.products.all %}
+- {{ product.name }} ({% if product.product_type == 'fursuit' %}{% trans "Fursuit" %}{% else %}{% trans "Printed Item" %}{% endif %})
+{% endfor %}
+
+{% trans "You can track your shipment and view your order details at" %}: {{ order_url }}
+
+{% trans "If you have any questions about your shipment, please contact our support team." %}
+support@fursuitshop.com
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
© {% now "Y" %} Fursuit Shop. {% trans "All rights reserved." %}
\ No newline at end of file
diff --git a/shop/templates/shop/gallery.html b/shop/templates/shop/gallery.html
index 532fdd3..6617c72 100644
--- a/shop/templates/shop/gallery.html
+++ b/shop/templates/shop/gallery.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'shop/base.html' %}
{% load static %}
@@ -330,4 +331,338 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
+=======
+{% extends 'shop/base.html' %}
+{% load static %}
+
+{% block title %}Galerie - Kasico Art & Design{% endblock %}
+
+{% block content %}
+
+
+
+
+
Unsere Fursuit Galerie
+
Entdecken Sie unsere handgefertigten Kreationen und lassen Sie sich inspirieren
+
+
+
+
+
+
+ Kategorie
+
+ Alle Kategorien
+ {% for category in categories %}
+
+ {{ category.name }}
+
+ {% endfor %}
+
+
+
+ Stil
+
+ Alle Stile
+ {% for style in styles %}
+
+ {{ style.name }}
+
+ {% endfor %}
+
+
+
+
+ Filter anwenden
+
+
+
+
+
+
+
+ {% for item in gallery_items %}
+
+
+
+
+
+
+
+
+
+
{{ item.title }}
+
{{ item.description|truncatewords:20 }}
+
+ {% for tag in item.tags.all %}
+ {{ tag.name }}
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ item.title }}
+
{{ item.description }}
+
+
+ {% if item.tags.all %}
+
+
Tags:
+
+ {% for tag in item.tags.all %}
+ {{ tag.name }}
+ {% endfor %}
+
+
+ {% endif %}
+
+
+
+
+
+
+
+ Teilen
+
+
+ Herunterladen
+
+
+
+
+
+
+
+
+ {% empty %}
+
+
+
+
+
Keine Bilder gefunden
+
Versuchen Sie andere Filter oder schauen Sie später wieder vorbei.
+
+
+
+ {% endfor %}
+
+
+
+ {% if gallery_items.has_other_pages %}
+
+
+
+ {% endif %}
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/gallery_detail.html b/shop/templates/shop/gallery_detail.html
index baf771b..86c5264 100644
--- a/shop/templates/shop/gallery_detail.html
+++ b/shop/templates/shop/gallery_detail.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends "shop/base.html" %}
{% load i18n %}
@@ -296,4 +297,304 @@ function toggleLike(button) {
});
}
+=======
+{% extends "shop/base.html" %}
+{% load i18n %}
+
+{% block title %}{{ gallery.name }} - {% trans "Gallery" %} - Fursuit Shop{% endblock %}
+
+{% block extra_css %}
+
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+ {% if gallery.images.first %}
+
+ {% endif %}
+
+
+ {% if gallery.images.all|length > 1 %}
+
+ {% for image in gallery.images.all %}
+ {% if not forloop.first %}
+
+
+
+ {% endif %}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
+
+
+
{{ gallery.name }}
+
+
+ {{ gallery.likes }}
+
+
+
+
+ {% if gallery.fursuit_type == 'fullsuit' %}
+ {% trans "Fullsuit" %}
+ {% elif gallery.fursuit_type == 'partial' %}
+ {% trans "Partial Suit" %}
+ {% else %}
+ {% trans "Head Only" %}
+ {% endif %}
+
+ {% if gallery.style == 'toony' %}
+ {% trans "Toony" %}
+ {% elif gallery.style == 'realistic' %}
+ {% trans "Realistic" %}
+ {% else %}
+ {% trans "Semi-Realistic" %}
+ {% endif %}
+
+
+
{{ gallery.description }}
+
+
+
+
+ {% if gallery.features %}
+
+
+
{% trans "Features" %}
+
+ {% for feature in gallery.features %}
+ {{ feature }}
+ {% endfor %}
+
+
+
+ {% endif %}
+
+ {% if gallery.materials %}
+
+
+
{% trans "Materials" %}
+
+ {% for material in gallery.materials %}
+ {{ material }}
+ {% endfor %}
+
+
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/gallery_list.html b/shop/templates/shop/gallery_list.html
index f4d6419..4044726 100644
--- a/shop/templates/shop/gallery_list.html
+++ b/shop/templates/shop/gallery_list.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'shop/base.html' %}
{% load static %}
{% load i18n %}
@@ -295,4 +296,303 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
+=======
+{% extends 'shop/base.html' %}
+{% load static %}
+{% load i18n %}
+
+{% block title %}{% trans "Gallery" %} - Fursuit Shop{% endblock %}
+
+{% block extra_css %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
{% trans "Fursuit Gallery" %}
+
{% trans "Discover our handcrafted creations and get inspired" %}
+
+
+
+
+
+
+ {% trans "Fursuit Type" %}
+
+ {% trans "All Types" %}
+
+ {% trans "Fullsuit" %}
+
+
+ {% trans "Partial Suit" %}
+
+
+ {% trans "Head Only" %}
+
+
+
+
+
+ {% trans "Style" %}
+
+ {% trans "All Styles" %}
+
+ {% trans "Toony" %}
+
+
+ {% trans "Realistic" %}
+
+
+ {% trans "Semi-Realistic" %}
+
+
+
+
+
+ {% trans "Sort By" %}
+
+
+ {% trans "Newest First" %}
+
+
+ {% trans "Oldest First" %}
+
+
+ {% trans "Name A-Z" %}
+
+
+ {% trans "Most Liked" %}
+
+
+
+
+
+
+ {% trans "Apply Filters" %}
+
+
+
+
+
+
+ {% if galleries %}
+
+ {% for gallery in galleries %}
+
+ {% endfor %}
+
+
+
+ {% if galleries.has_other_pages %}
+
+
+
+ {% endif %}
+ {% else %}
+
+
+
+
{% trans "No galleries found" %}
+
{% trans "Try adjusting your filters or check back later for new content." %}
+
+
+ {% endif %}
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/home.html b/shop/templates/shop/home.html
index 2441aaa..e06f382 100644
--- a/shop/templates/shop/home.html
+++ b/shop/templates/shop/home.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends "shop/base.html" %}
{% load i18n %}
{% load static %}
@@ -157,4 +158,165 @@
+=======
+{% extends "shop/base.html" %}
+{% load i18n %}
+{% load static %}
+
+{% block title %}Willkommen bei Kasico Art & Design - Fursuit Shop{% endblock %}
+
+{% block content %}
+
+
+
+
+
+ Unsere Dienstleistungen
+
+
+
+
+
+
+
Custom Design
+
Ihr einzigartiger Charakter, zum Leben erweckt mit höchster Handwerkskunst und Liebe zum Detail.
+
+ Individuelle Konzeption
+ 3D-Modellierung
+ Maßanfertigung
+
+
+
+
+
+
+
+
+
Reparaturen
+
Professionelle Pflege und Reparatur für Ihren bestehenden Fursuit.
+
+ Fell-Erneuerung
+ Strukturreparaturen
+ Reinigung
+
+
+
+
+
+
+
+
+
Fotoshootings
+
Professionelle Fotografie-Sessions für Ihren Fursuit.
+
+ Studio-Aufnahmen
+ Outdoor-Shootings
+ Event-Dokumentation
+
+
+
+
+
+
+
+
+
+
Custom Orders
+
+
+
+
+
+
+
+
+
Ihr Traum-Fursuit wartet auf Sie
+
+
+
+ Kostenlose Designberatung
+
+
+
+ Detaillierte 3D-Visualisierung
+
+
+
+ Regelmäßige Fortschrittsberichte
+
+
+
+ Höchste Materialqualität
+
+
+
+ Maßgeschneiderte Passform
+
+
+
+ Anfrage senden
+
+
+
+
+
+
+
+
+
+ Häufig gestellte Fragen
+
+ {% for faq in faqs %}
+
+ {% endfor %}
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/login.html b/shop/templates/shop/login.html
index 5bcd65d..3428a15 100644
--- a/shop/templates/shop/login.html
+++ b/shop/templates/shop/login.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends "shop/base.html" %}
{% load i18n %}
{% load static %}
@@ -151,4 +152,159 @@ a:hover {
});
{% endblock %}
+=======
+{% extends "shop/base.html" %}
+{% load i18n %}
+{% load static %}
+
+{% block title %}{% trans "Login" %} - Kasico Art & Design{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
🐾 {% trans "Login" %}
+
Willkommen zurück in der Furry-Community!
+
+
+
+ {% csrf_token %}
+
+
+ {% if form.non_field_errors %}
+
+ {% for error in form.non_field_errors %}
+ {{ error }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+ {{ form.username.label }}
+
+ {{ form.username }}
+ {% if form.username.errors %}
+
+ {% for error in form.username.errors %}
+ {{ error }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
+ {{ form.password.label }}
+
+ {{ form.password }}
+ {% if form.password.errors %}
+
+ {% for error in form.password.errors %}
+ {{ error }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
+ 🐾 {% trans "Login" %}
+
+
+
+
+
+
+
+
+
+
+
+
+{% block extra_js %}
+
+{% endblock %}
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/my_orders.html b/shop/templates/shop/my_orders.html
index ca7fe12..10d942f 100644
--- a/shop/templates/shop/my_orders.html
+++ b/shop/templates/shop/my_orders.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends "shop/base.html" %}
{% load i18n %}
@@ -205,4 +206,213 @@
{% endif %}
+=======
+{% extends "shop/base.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "My Orders" %} - Fursuit Shop{% endblock %}
+
+{% block extra_css %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+ {% if orders %}
+
+ {% for order in orders %}
+
+
+
+
+
+
+
+ {% trans "Order" %} #{{ order.id }}
+
+
+
+ {{ order.get_status_display }}
+
+
+
+ {{ order.created_at|date:"d.m.Y" }}
+
+
+
+
+
+
{% trans "Order Items" %}
+ {% for product in order.products.all %}
+
+ {% if product.image %}
+
+ {% endif %}
+
+ {{ product.name }}
+ {% if product.product_type == 'fursuit' %}
+ {% trans "Fursuit" %}
+ {% else %}
+ {% trans "Printed Item" %}
+ {% endif %}
+
+
+ {% endfor %}
+
+
+
+
+
{% trans "Payment" %}
+
+ {% if order.payment_method == 'paypal' %}
+ PayPal
+ {% elif order.payment_method == 'credit_card' %}
+ {% trans "Credit Card" %}
+ {% else %}
+ {% trans "Bank Transfer" %}
+ {% endif %}
+
+
+ {{ order.total_price }} €
+
+
+
+
+
+
+
+ {% trans "Show Progress" %}
+
+
+
+
+
+
+
+ {% for update in order.progress_updates.all %}
+
+
+
{{ update.title }}
+
+ {{ update.created_at|date:"d.m.Y H:i" }}
+
+
{{ update.description }}
+ {% if update.image %}
+
+ {% endif %}
+
+ {% endfor %}
+
+
+
+
+
+ {% endfor %}
+
+ {% else %}
+
+
+
{% trans "No orders yet" %}
+
+ {% trans "You haven't placed any orders yet." %}
+
+
+
+ {% trans "Start Shopping" %}
+
+
+ {% endif %}
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/order_success.html b/shop/templates/shop/order_success.html
index 0f8d0bd..ad23db1 100644
--- a/shop/templates/shop/order_success.html
+++ b/shop/templates/shop/order_success.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends "shop/base.html" %}
{% load i18n %}
@@ -126,4 +127,134 @@
+=======
+{% extends "shop/base.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "Order Successful" %} - Fursuit Shop{% endblock %}
+
+{% block content %}
+
+
+
+
+
{% trans "Thank You for Your Order!" %}
+
+
+ {% trans "Your payment was successful and your order has been confirmed." %}
+
+ {% trans "We'll send you an email with your order details shortly." %}
+
+
+
+
+
+
+
+
{% trans "Order Details" %}
+
+
+
+
+ {% trans "Order Number" %}:
+ #{{ order.id }}
+
+
+ {% trans "Order Date" %}:
+ {{ order.created_at|date:"d.m.Y" }}
+
+
+ {% trans "Payment Method" %}:
+ {% if order.payment_method == 'paypal' %}
+ PayPal
+ {% elif order.payment_method == 'credit_card' %}
+ {% trans "Credit Card" %}
+ {% else %}
+ {% trans "Bank Transfer" %}
+ {% endif %}
+
+
+
+
+ {% trans "Shipping To" %}:
+
+
+ {{ order.shipping_address.first_name }} {{ order.shipping_address.last_name }}
+ {{ order.shipping_address.address }}
+ {{ order.shipping_address.zip }} {{ order.shipping_address.city }}
+ {{ order.shipping_address.get_country_display }}
+
+
+
+
+
{% trans "Ordered Items" %}
+ {% for product in order.products.all %}
+
+ {% if product.image %}
+
+ {% endif %}
+
+
{{ product.name }}
+
+ {% if product.product_type == 'fursuit' %}
+ {% trans "Fursuit" %}
+ {% else %}
+ {% trans "Printed Item" %}
+ {% endif %}
+
+
+
+ {{ product.base_price }} €
+
+
+ {% endfor %}
+
+
+
+
+
+ {% trans "Subtotal" %}:
+ {{ order.total_price }} €
+
+ {% if order.total_price < 200 %}
+
+ {% trans "Shipping" %}:
+ 5.99 €
+
+
+ {% trans "Total" %}:
+ {{ order.total_price|add:"5.99" }} €
+
+ {% else %}
+
+ {% trans "Shipping" %}:
+ {% trans "FREE" %}
+
+
+ {% trans "Total" %}:
+ {{ order.total_price }} €
+
+ {% endif %}
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/password_reset.html b/shop/templates/shop/password_reset.html
index a1b2b9c..f2c7b92 100644
--- a/shop/templates/shop/password_reset.html
+++ b/shop/templates/shop/password_reset.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'shop/base.html' %}
{% load i18n %}
{% load static %}
@@ -450,4 +451,458 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
+=======
+{% extends 'shop/base.html' %}
+{% load i18n %}
+{% load static %}
+
+{% block title %}{% trans "Reset Password" %} - Kasico Art & Design{% endblock %}
+
+{% block content %}
+
+
+
+
🔐
+
{% trans "Passwort zurücksetzen" %}
+
{% trans "Keine Sorge! Wir helfen dir dabei, dein Passwort wiederherzustellen." %}
+
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/password_reset_complete.html b/shop/templates/shop/password_reset_complete.html
index f7d5ff5..9ba830d 100644
--- a/shop/templates/shop/password_reset_complete.html
+++ b/shop/templates/shop/password_reset_complete.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'shop/base.html' %}
{% load i18n %}
{% load static %}
@@ -469,4 +470,477 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
+=======
+{% extends 'shop/base.html' %}
+{% load i18n %}
+{% load static %}
+
+{% block title %}{% trans "Password Reset Complete" %} - Kasico Art & Design{% endblock %}
+
+{% block content %}
+
+
+
+
🎉
+
{% trans "Passwort erfolgreich geändert!" %}
+
{% trans "Dein neues Passwort wurde gespeichert und du kannst dich jetzt anmelden." %}
+
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/password_reset_confirm.html b/shop/templates/shop/password_reset_confirm.html
index b7532e5..bc251a1 100644
--- a/shop/templates/shop/password_reset_confirm.html
+++ b/shop/templates/shop/password_reset_confirm.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'shop/base.html' %}
{% load i18n %}
{% load static %}
@@ -654,4 +655,662 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
+=======
+{% extends 'shop/base.html' %}
+{% load i18n %}
+{% load static %}
+
+{% block title %}{% trans "Set New Password" %} - Kasico Art & Design{% endblock %}
+
+{% block content %}
+
+
+
+
🔑
+
{% trans "Neues Passwort setzen" %}
+
{% trans "Wähle ein sicheres neues Passwort für dein Konto." %}
+
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/password_reset_done.html b/shop/templates/shop/password_reset_done.html
index 2a096d5..56fe6a1 100644
--- a/shop/templates/shop/password_reset_done.html
+++ b/shop/templates/shop/password_reset_done.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'shop/base.html' %}
{% load i18n %}
{% load static %}
@@ -328,4 +329,336 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
+=======
+{% extends 'shop/base.html' %}
+{% load i18n %}
+{% load static %}
+
+{% block title %}{% trans "Password Reset Email Sent" %} - Kasico Art & Design{% endblock %}
+
+{% block content %}
+
+
+
+
📧
+
{% trans "E-Mail gesendet!" %}
+
{% trans "Wir haben dir eine E-Mail mit Anweisungen zum Zurücksetzen deines Passworts gesendet." %}
+
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/password_reset_email.html b/shop/templates/shop/password_reset_email.html
index 95ca2c4..ef71f05 100644
--- a/shop/templates/shop/password_reset_email.html
+++ b/shop/templates/shop/password_reset_email.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at Kasico Art & Design.{% endblocktrans %}
@@ -12,4 +13,20 @@
{% blocktrans %}The Kasico Art & Design Team{% endblocktrans %}
+=======
+{% load i18n %}{% autoescape off %}
+{% blocktrans %}You're receiving this email because you requested a password reset for your user account at Kasico Art & Design.{% endblocktrans %}
+
+{% trans "Please go to the following page and choose a new password:" %}
+{% block reset_link %}
+{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
+{% endblock %}
+
+{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
+
+{% trans "Thanks for using our site!" %}
+
+{% blocktrans %}The Kasico Art & Design Team{% endblocktrans %}
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endautoescape %}
\ No newline at end of file
diff --git a/shop/templates/shop/password_reset_subject.txt b/shop/templates/shop/password_reset_subject.txt
index ad709b9..82eb19b 100644
--- a/shop/templates/shop/password_reset_subject.txt
+++ b/shop/templates/shop/password_reset_subject.txt
@@ -1,3 +1,8 @@
+<<<<<<< HEAD
{% load i18n %}{% autoescape off %}
{% blocktrans %}Password reset on Kasico Art & Design{% endblocktrans %}
+=======
+{% load i18n %}{% autoescape off %}
+{% blocktrans %}Password reset on Kasico Art & Design{% endblocktrans %}
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endautoescape %}
\ No newline at end of file
diff --git a/shop/templates/shop/payment_failed.html b/shop/templates/shop/payment_failed.html
index 0cbab84..82007c1 100644
--- a/shop/templates/shop/payment_failed.html
+++ b/shop/templates/shop/payment_failed.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends "shop/base.html" %}
{% load i18n %}
@@ -38,4 +39,46 @@
+=======
+{% extends "shop/base.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "Payment Failed" %} - Fursuit Shop{% endblock %}
+
+{% block content %}
+
+
+
+
+
{% trans "Payment Failed" %}
+
+
+ {% trans "We're sorry, but there was a problem processing your payment." %}
+
+ {% trans "Please try again or choose a different payment method." %}
+
+
+ {% if order.payment_errors.exists %}
+
+
{% trans "Error Details" %}:
+ {% for error in order.payment_errors.all|slice:"-1:" %}
+
{{ error.error_message }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/product_detail.html b/shop/templates/shop/product_detail.html
index a96fd81..b7b5e67 100644
--- a/shop/templates/shop/product_detail.html
+++ b/shop/templates/shop/product_detail.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'shop/base.html' %}
{% load static %}
@@ -345,4 +346,353 @@ function shareProduct() {
}
}
+=======
+{% extends 'shop/base.html' %}
+{% load static %}
+
+{% block title %}{{ product.name }} - Fursuit Shop{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+ {% if product.on_sale %}
+
+ Sale!
+
+ {% endif %}
+
+
+
+ {% if product.gallery.all %}
+
+
+
+
+ {% for image in product.gallery.all %}
+
+
+
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
+
+
+
+
{{ product.name }}
+
+
+
+ {% if product.on_sale %}
+
+ {{ product.sale_price }} €
+ {{ product.base_price }} €
+ -{{ product.discount_percentage }}%
+
+ {% else %}
+
{{ product.base_price }} €
+ {% endif %}
+
+
+
+
+ {% if product.product_type == 'fursuit' %}
+
+ Fursuit
+
+ {% else %}
+
+ Printed Item
+
+ {% endif %}
+
+ {% if product.is_custom_order %}
+
+ Custom Order
+
+ {% endif %}
+
+
+
+
+
Beschreibung
+
{{ product.description }}
+
+
+
+
+ {% if product.stock > 0 %}
+
+
+ Verfügbar ({{ product.stock }} auf Lager)
+
+ {% else %}
+
+
+ Nicht verfügbar
+
+ {% endif %}
+
+
+
+
+ {% if product.stock > 0 %}
+
+
+ In den Warenkorb
+
+ {% else %}
+
+
+ Nicht verfügbar
+
+ {% endif %}
+
+
+
+
+ Zur Wunschliste
+
+
+
+ Teilen
+
+
+
+
+
+
+
+
+
+
+
+
+ {% if similar_products %}
+
+
Ähnliche Produkte
+
+ {% for similar_product in similar_products %}
+
+
+
+
+ {% if similar_product.on_sale %}
+
+ Sale!
+
+ {% endif %}
+
+
+
{{ similar_product.name }}
+
{{ similar_product.description|truncatewords:10 }}
+
+
+ {% if similar_product.on_sale %}
+ {{ similar_product.sale_price }} €
+ {{ similar_product.base_price }} €
+ {% else %}
+ {{ similar_product.base_price }} €
+ {% endif %}
+
+
+
+
+
+
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/product_list.html b/shop/templates/shop/product_list.html
index 551f157..a4614e4 100644
--- a/shop/templates/shop/product_list.html
+++ b/shop/templates/shop/product_list.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'shop/base.html' %}
{% load static %}
{% load i18n %}
@@ -417,4 +418,425 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
+=======
+{% extends 'shop/base.html' %}
+{% load static %}
+{% load i18n %}
+
+{% block title %}{% trans "Shop" %} - Fursuit Shop{% endblock %}
+
+{% block extra_css %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
{% trans "Fursuit Shop" %}
+
{% trans "Discover our handcrafted fursuits and accessories" %}
+
+
+
+
+
+
+ {% trans "Category" %}
+
+ {% trans "All Categories" %}
+ {% for category in categories %}
+
+ {{ category.name }}
+
+ {% endfor %}
+
+
+
+
+ {% trans "Fursuit Type" %}
+
+ {% trans "All Types" %}
+
+ {% trans "Fullsuit" %}
+
+
+ {% trans "Partial Suit" %}
+
+
+ {% trans "Head Only" %}
+
+
+
+
+
+ {% trans "Sort By" %}
+
+
+ {% trans "Newest First" %}
+
+
+ {% trans "Price: Low to High" %}
+
+
+ {% trans "Price: High to Low" %}
+
+
+ {% trans "Name A-Z" %}
+
+
+
+
+
+
+ {% trans "Apply Filters" %}
+
+
+
+
+
+
+ {% if products %}
+
+ {% for product in products %}
+
+
+
+
+ {% if product.image %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if product.on_sale %}
+
+ Sale!
+
+ {% endif %}
+
+ {% if product.is_custom_order %}
+
+ Custom
+
+ {% endif %}
+
+
+
+
+
+
+
+
{{ product.name }}
+
{{ product.description|truncatewords:15 }}
+
+
+
+ {% if product.product_type == 'fursuit' %}
+
+ Fursuit
+
+ {% else %}
+
+ Printed Item
+
+ {% endif %}
+
+ {% if product.fursuit_type %}
+
+ {{ product.get_fursuit_type_display }}
+
+ {% endif %}
+
+
+
+
+
+ {% if product.on_sale %}
+ {{ product.sale_price }} €
+ {{ product.base_price }} €
+ {% else %}
+ {{ product.base_price }} €
+ {% endif %}
+
+
+
+
+ {% trans "Add to Cart" %}
+
+
+
+
+
+
+ {% endfor %}
+
+
+
+ {% if products.has_other_pages %}
+
+
+
+ {% endif %}
+ {% else %}
+
+
+ {% endif %}
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/templates/shop/register.html b/shop/templates/shop/register.html
index b55134a..c5b5114 100644
--- a/shop/templates/shop/register.html
+++ b/shop/templates/shop/register.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% extends 'shop/base.html' %}
{% load i18n %}
{% load static %}
@@ -139,4 +140,147 @@ a:hover {
margin-top: 0.25rem;
}
+=======
+{% extends 'shop/base.html' %}
+{% load i18n %}
+{% load static %}
+
+{% block content %}
+
+
+
+
+
+
+
+
{% trans "Create Account" %}
+
+
+
+ {% csrf_token %}
+
+ {% if form.errors %}
+
+ {% for field in form %}
+ {% for error in field.errors %}
+
{{ error }}
+ {% endfor %}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+ {% trans "Username" %}
+
+ {{ form.username }}
+ {% if form.username.help_text %}
+
{{ form.username.help_text }}
+ {% endif %}
+
+
+
+
+ {% trans "Password" %}
+
+ {{ form.password1 }}
+ {% if form.password1.help_text %}
+
{{ form.password1.help_text }}
+ {% endif %}
+
+
+
+
+ {% trans "Confirm Password" %}
+
+ {{ form.password2 }}
+ {% if form.password2.help_text %}
+
{{ form.password2.help_text }}
+ {% endif %}
+
+
+
+
+ {% trans "Register" %}
+
+
+
+
+
+
{% trans "Already have an account?" %}
+ {% trans "Login here" %}
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/shop/tests.py b/shop/tests.py
index 7ce503c..95edf74 100644
--- a/shop/tests.py
+++ b/shop/tests.py
@@ -1,3 +1,9 @@
+<<<<<<< HEAD
from django.test import TestCase
# Create your tests here.
+=======
+from django.test import TestCase
+
+# Create your tests here.
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/shop/urls.py b/shop/urls.py
index f9bcc9f..e48b1d3 100644
--- a/shop/urls.py
+++ b/shop/urls.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
"""
URL configuration for shop project.
@@ -34,3 +35,41 @@ urlpatterns = [
# path('api/', include(router.urls)), # Temporär auskommentiert
# path('api-auth/', include('rest_framework.urls')), # Temporär auskommentiert
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+=======
+"""
+URL configuration for shop project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/5.2/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+from django.urls import path, include
+from django.conf import settings
+from django.conf.urls.static import static
+from .views import home, contact
+# from rest_framework.routers import DefaultRouter # Temporär auskommentiert
+# from products.views import ProductViewSet, ReviewViewSet # Temporär auskommentiert
+
+# router = DefaultRouter() # Temporär auskommentiert
+# router.register(r'products', ProductViewSet) # Temporär auskommentiert
+# router.register(r'reviews', ReviewViewSet) # Temporär auskommentiert
+
+app_name = 'shop'
+
+urlpatterns = [
+ path('', home, name='home'),
+ path('contact/', contact, name='contact'),
+ path('accounts/', include('django.contrib.auth.urls')),
+ # path('api/', include(router.urls)), # Temporär auskommentiert
+ # path('api-auth/', include('rest_framework.urls')), # Temporär auskommentiert
+] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/shop/views.py b/shop/views.py
index f78f24b..7e0d438 100644
--- a/shop/views.py
+++ b/shop/views.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
from django.shortcuts import render, redirect
from django.contrib import messages
from django.utils.translation import gettext as _
@@ -44,4 +45,52 @@ def register(request):
return redirect('login')
else:
form = UserCreationForm()
+=======
+from django.shortcuts import render, redirect
+from django.contrib import messages
+from django.utils.translation import gettext as _
+from products.models import Product, GalleryImage
+from .models import ContactMessage
+from django.contrib.auth.forms import UserCreationForm
+
+def home(request):
+ featured_products = Product.objects.filter(is_featured=True)[:3]
+ latest_galleries = GalleryImage.objects.filter(is_featured=True)[:3]
+
+ return render(request, 'shop/home.html', {
+ 'featured_products': featured_products,
+ 'latest_galleries': latest_galleries,
+ })
+
+def contact(request):
+ if request.method == 'POST':
+ name = request.POST.get('name')
+ email = request.POST.get('email')
+ subject = request.POST.get('subject')
+ message = request.POST.get('message')
+
+ if name and email and subject and message:
+ ContactMessage.objects.create(
+ name=name,
+ email=email,
+ subject=subject,
+ message=message
+ )
+ messages.success(request, 'Ihre Nachricht wurde erfolgreich gesendet! Wir werden uns in Kürze bei Ihnen melden.')
+ return redirect('shop:contact')
+ else:
+ messages.error(request, 'Bitte füllen Sie alle Pflichtfelder aus.')
+
+ return render(request, 'shop/contact.html')
+
+def register(request):
+ if request.method == 'POST':
+ form = UserCreationForm(request.POST)
+ if form.is_valid():
+ form.save()
+ messages.success(request, _('Ihr Account wurde erfolgreich erstellt! Sie können sich jetzt anmelden.'))
+ return redirect('login')
+ else:
+ form = UserCreationForm()
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
return render(request, 'shop/register.html', {'form': form})
\ No newline at end of file
diff --git a/shop/wsgi.py b/shop/wsgi.py
index d56c3d9..92f073a 100644
--- a/shop/wsgi.py
+++ b/shop/wsgi.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
"""
WSGI config for shop project.
@@ -14,3 +15,21 @@ from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shop.settings')
application = get_wsgi_application()
+=======
+"""
+WSGI config for shop project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shop.settings')
+
+application = get_wsgi_application()
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/static/css/dashboard.css b/static/css/dashboard.css
index 92c1b88..2e028da 100644
--- a/static/css/dashboard.css
+++ b/static/css/dashboard.css
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
.progress-bar-custom {
height: 8px;
border-radius: 4px;
@@ -46,4 +47,54 @@
.modal-body {
max-height: calc(100vh - 210px);
overflow-y: auto;
+=======
+.progress-bar-custom {
+ height: 8px;
+ border-radius: 4px;
+}
+
+.status-badge {
+ font-size: 0.85rem;
+ padding: 0.35em 0.65em;
+}
+
+.stats-card {
+ transition: transform 0.2s;
+}
+
+.stats-card:hover {
+ transform: translateY(-5px);
+}
+
+/* Status Badge Colors */
+.badge.bg-pending { background-color: #ffc107; }
+.badge.bg-processing { background-color: #17a2b8; }
+.badge.bg-shipped { background-color: #28a745; }
+.badge.bg-delivered { background-color: #20c997; }
+.badge.bg-cancelled { background-color: #dc3545; }
+.badge.bg-quoted { background-color: #6610f2; }
+.badge.bg-approved { background-color: #198754; }
+.badge.bg-in_progress { background-color: #0d6efd; }
+.badge.bg-ready { background-color: #20c997; }
+
+/* Avatar Styling */
+.avatar-placeholder {
+ font-size: 24px;
+}
+
+/* Card Styling */
+.card-header {
+ background-color: #f8f9fa;
+}
+
+/* Table Styling */
+.table > :not(caption) > * > * {
+ padding: 0.75rem;
+}
+
+/* Modal Styling */
+.modal-body {
+ max-height: calc(100vh - 210px);
+ overflow-y: auto;
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
}
\ No newline at end of file
diff --git a/static/css/furry.css b/static/css/furry.css
index d494e1a..e6bc296 100644
--- a/static/css/furry.css
+++ b/static/css/furry.css
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
/* =============================================================================
MODERN FURRY DESIGN - KASICO ART & DESIGN
============================================================================= */
@@ -77,16 +78,42 @@ body {
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid var(--glass-border);
+=======
+body {
+ font-family: 'Baloo 2', Arial, sans-serif;
+ background: linear-gradient(135deg, #f8e1ff 0%, #e1f0ff 100%);
+ margin: 0;
+ color: #3a2d4d;
+ padding-top: 0;
+}
+.furry-header {
+ background: linear-gradient(90deg, #b36fff 0%, #ff6fd8 100%);
+ padding: 0.5rem 0;
+ box-shadow: 0 2px 8px rgba(179, 111, 255, 0.1);
+ position: sticky;
+ top: 0;
+ z-index: 1000;
+ transition: all 0.3s ease;
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
}
/* Sticky Navigation Scroll Effects */
.furry-header.scrolled {
background: rgba(179, 111, 255, 0.95);
+<<<<<<< HEAD
backdrop-filter: blur(25px);
-webkit-backdrop-filter: blur(25px);
box-shadow: var(--shadow-medium);
padding: 0.3rem 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+=======
+ backdrop-filter: blur(15px);
+ -webkit-backdrop-filter: blur(15px);
+ box-shadow: 0 4px 20px rgba(179, 111, 255, 0.2);
+ padding: 0.3rem 0;
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
}
.furry-header.scrolled .logo img {
@@ -111,6 +138,7 @@ body {
bottom: 0;
left: 0;
height: 3px;
+<<<<<<< HEAD
background: var(--secondary-gradient);
width: 0%;
transition: width 0.3s ease;
@@ -136,6 +164,29 @@ body {
.mobile-nav-toggle:hover {
background: rgba(255, 255, 255, 0.2);
transform: scale(1.05);
+=======
+ background: linear-gradient(90deg, #ff6fd8 0%, #3813c2 100%);
+ width: 0%;
+ transition: width 0.3s ease;
+ border-radius: 0 2px 2px 0;
+}
+
+/* Mobile Navigation */
+.mobile-nav-toggle {
+ display: none;
+ background: none;
+ border: none;
+ color: white;
+ font-size: 1.5rem;
+ cursor: pointer;
+ padding: 0.5rem;
+ border-radius: 8px;
+ transition: background 0.3s ease;
+}
+
+.mobile-nav-toggle:hover {
+ background: rgba(255, 255, 255, 0.1);
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
}
.mobile-nav-toggle .hamburger-line {
@@ -143,7 +194,11 @@ body {
height: 3px;
background: white;
margin: 5px 0;
+<<<<<<< HEAD
transition: var(--transition);
+=======
+ transition: all 0.3s ease;
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
border-radius: 2px;
}
@@ -159,19 +214,26 @@ body {
transform: rotate(-45deg) translate(7px, -6px);
}
+<<<<<<< HEAD
/* Enhanced Container */
+=======
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
}
+<<<<<<< HEAD
+=======
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
.nav-flex {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
+<<<<<<< HEAD
/* Enhanced Logo */
.logo img {
@@ -189,18 +251,37 @@ body {
}
/* Enhanced Navigation */
+=======
+.logo img {
+ height: 48px;
+ border-radius: 16px;
+ background: #fff;
+ padding: 4px;
+ transition: all 0.3s ease;
+}
+.logo img:hover {
+ transform: scale(1.05);
+ box-shadow: 0 4px 12px rgba(255, 255, 255, 0.3);
+}
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
nav {
display: flex;
align-items: center;
gap: 1rem;
}
+<<<<<<< HEAD
nav a {
color: var(--text-light);
+=======
+nav a {
+ color: #fff;
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
text-decoration: none;
margin: 0 1rem;
font-size: 1.1rem;
font-weight: 700;
+<<<<<<< HEAD
transition: var(--transition);
position: relative;
padding: 0.8rem 1.5rem;
@@ -225,12 +306,31 @@ nav a.active {
}
/* Enhanced Navigation Underline Effect */
+=======
+ transition: all 0.3s ease;
+ position: relative;
+ padding: 0.5rem 1rem;
+ border-radius: 20px;
+}
+nav a:hover {
+ color: #ffe6fa;
+ background: rgba(255, 255, 255, 0.1);
+ transform: translateY(-2px);
+}
+nav a.active {
+ background: rgba(255, 255, 255, 0.2);
+ color: #ffe6fa;
+}
+
+/* Navigation Underline Effect */
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
nav a::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
width: 0;
+<<<<<<< HEAD
height: 3px;
background: var(--text-light);
transition: var(--transition);
@@ -244,11 +344,23 @@ nav a:hover::after {
}
/* Enhanced Header Actions */
+=======
+ height: 2px;
+ background: #ffe6fa;
+ transition: all 0.3s ease;
+ transform: translateX(-50%);
+ border-radius: 1px;
+}
+nav a:hover::after {
+ width: 80%;
+}
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
.header-actions {
display: flex;
align-items: center;
gap: 1rem;
}
+<<<<<<< HEAD
.header-actions .btn {
margin-left: 1rem;
@@ -274,6 +386,333 @@ nav a:hover::after {
}
.furry-btn::before {
+=======
+.header-actions .btn {
+ margin-left: 1rem;
+ transition: all 0.3s ease;
+}
+.furry-btn {
+ background: linear-gradient(90deg, #ff6fd8 0%, #3813c2 100%);
+ color: #fff;
+ border: none;
+ border-radius: 32px;
+ padding: 0.7rem 2rem;
+ font-size: 1.1rem;
+ font-weight: 700;
+ box-shadow: 0 2px 8px rgba(255, 111, 216, 0.15);
+ cursor: pointer;
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+}
+.furry-btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
+ transition: left 0.5s ease;
+}
+.furry-btn:hover::before {
+ left: 100%;
+}
+.furry-btn:hover {
+ background: linear-gradient(90deg, #3813c2 0%, #ff6fd8 100%);
+ transform: translateY(-3px) scale(1.05);
+ box-shadow: 0 8px 25px rgba(255, 111, 216, 0.3);
+}
+.furry-btn:active {
+ transform: translateY(-1px) scale(1.02);
+}
+.furry-btn-outline {
+ background: rgba(255, 255, 255, 0.1);
+ color: #fff;
+ border: 2px solid rgba(255, 255, 255, 0.3);
+ border-radius: 32px;
+ padding: 0.7rem 2rem;
+ font-size: 1.1rem;
+ font-weight: 700;
+ margin-left: 1rem;
+ transition: all 0.3s ease;
+ backdrop-filter: blur(5px);
+ -webkit-backdrop-filter: blur(5px);
+}
+.furry-btn-outline:hover {
+ background: rgba(255, 255, 255, 0.2);
+ border-color: rgba(255, 255, 255, 0.5);
+ transform: translateY(-3px);
+ box-shadow: 0 8px 25px rgba(255, 255, 255, 0.2);
+}
+.furry-btn-secondary {
+ background: #fff;
+ color: #ff6fd8;
+ border: 2px solid #ff6fd8;
+ border-radius: 32px;
+ padding: 0.7rem 2rem;
+ font-size: 1.1rem;
+ font-weight: 700;
+ margin-left: 1rem;
+ transition: all 0.3s ease;
+}
+.furry-btn-secondary:hover {
+ background: #ff6fd8;
+ color: #fff;
+ transform: translateY(-3px);
+ box-shadow: 0 8px 25px rgba(255, 111, 216, 0.3);
+}
+.furry-main {
+ padding: 2rem 0 4rem 0;
+}
+.hero {
+ background: linear-gradient(135deg, #b36fff 0%, #ff6fd8 100%);
+ color: #fff;
+ border-radius: 32px;
+ margin: 2rem auto;
+ max-width: 900px;
+ box-shadow: 0 4px 32px rgba(179, 111, 255, 0.15);
+ padding: 3rem 2rem 2rem 2rem;
+ text-align: center;
+}
+.hero .brand {
+ color: #ffe6fa;
+ font-weight: 900;
+ letter-spacing: 2px;
+}
+.hero-actions {
+ margin-top: 2rem;
+}
+.services {
+ margin: 3rem auto;
+ max-width: 1100px;
+ text-align: center;
+}
+.service-cards {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 2rem;
+ margin-top: 2rem;
+}
+.service-card {
+ background: #fff;
+ border-radius: 24px;
+ box-shadow: 0 2px 16px rgba(179, 111, 255, 0.08);
+ padding: 2rem 1.5rem;
+ width: 300px;
+ transition: transform 0.2s, box-shadow 0.2s;
+}
+.service-card:hover {
+ transform: translateY(-8px) scale(1.03);
+ box-shadow: 0 8px 32px rgba(179, 111, 255, 0.18);
+}
+.service-card img {
+ width: 80px;
+ height: 80px;
+ object-fit: contain;
+ margin-bottom: 1rem;
+ border-radius: 16px;
+ background: #f8e1ff;
+ padding: 8px;
+}
+.gallery-preview {
+ margin: 3rem auto;
+ max-width: 1100px;
+ text-align: center;
+}
+.gallery-grid {
+ display: flex;
+ justify-content: center;
+ gap: 1.5rem;
+ margin: 2rem 0;
+}
+.gallery-grid img {
+ width: 180px;
+ height: 180px;
+ object-fit: cover;
+ border-radius: 24px;
+ box-shadow: 0 2px 16px rgba(179, 111, 255, 0.08);
+}
+.cta {
+ background: linear-gradient(90deg, #ff6fd8 0%, #b36fff 100%);
+ color: #fff;
+ border-radius: 32px;
+ margin: 3rem auto 0 auto;
+ max-width: 900px;
+ box-shadow: 0 4px 32px rgba(255, 111, 216, 0.15);
+ padding: 2rem 2rem 2.5rem 2rem;
+ text-align: center;
+}
+.furry-footer {
+ background: #b36fff;
+ color: #fff;
+ padding: 1.5rem 0;
+ text-align: center;
+ border-radius: 32px 32px 0 0;
+ margin-top: 3rem;
+}
+.footer-links a {
+ color: #ffe6fa;
+ margin: 0 0.5rem;
+ text-decoration: none;
+ font-weight: 700;
+}
+.footer-links a:hover {
+ text-decoration: underline;
+}
+@media (max-width: 900px) {
+ .service-cards, .gallery-grid {
+ flex-direction: column;
+ align-items: center;
+ }
+ .service-card, .gallery-grid img {
+ width: 90vw;
+ max-width: 350px;
+ }
+}
+
+/* Micro-Interaction: In den Warenkorb gelegt */
+.cart-added-feedback {
+ position: fixed;
+ top: 80px;
+ right: 40px;
+ background: #ff6fd8;
+ color: #fff;
+ border-radius: 32px;
+ padding: 1rem 2rem;
+ font-size: 1.2rem;
+ font-weight: 700;
+ box-shadow: 0 4px 24px rgba(255, 111, 216, 0.18);
+ z-index: 2000;
+ opacity: 0;
+ pointer-events: none;
+ transform: translateY(-30px) scale(0.95);
+ transition: opacity 0.3s, transform 0.3s;
+}
+.cart-added-feedback.show {
+ opacity: 1;
+ transform: translateY(0) scale(1.05);
+ animation: pop-furry 0.7s cubic-bezier(.68,-0.55,.27,1.55);
+}
+@keyframes pop-furry {
+ 0% { transform: translateY(-30px) scale(0.8); opacity: 0; }
+ 60% { transform: translateY(10px) scale(1.1); opacity: 1; }
+ 100% { transform: translateY(0) scale(1.05); opacity: 1; }
+}
+
+/* Furry-Style Lade-Spinner */
+.furry-spinner {
+ display: inline-block;
+ width: 60px;
+ height: 60px;
+ position: relative;
+}
+.furry-spinner div {
+ position: absolute;
+ border: 6px solid #ff6fd8;
+ opacity: 1;
+ border-radius: 50%;
+ animation: furry-spinner 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
+ border-color: #ff6fd8 transparent transparent transparent;
+}
+.furry-spinner div:nth-child(1) { animation-delay: -0.45s; }
+.furry-spinner div:nth-child(2) { animation-delay: -0.3s; }
+.furry-spinner div:nth-child(3) { animation-delay: -0.15s; }
+@keyframes furry-spinner {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+.furry-form .form-group {
+ margin-bottom: 1.5rem;
+}
+.furry-form label {
+ font-weight: 700;
+ color: #b36fff;
+ display: block;
+ margin-bottom: 0.3rem;
+}
+.furry-form input, .furry-form textarea {
+ width: 100%;
+ padding: 0.7rem 1rem;
+ border-radius: 16px;
+ border: 2px solid #e1f0ff;
+ font-size: 1.1rem;
+ transition: border-color 0.2s, box-shadow 0.2s;
+ outline: none;
+ background: #fff;
+ margin-bottom: 0.2rem;
+}
+.furry-form input:focus, .furry-form textarea:focus {
+ border-color: #b36fff;
+ box-shadow: 0 0 0 2px #ff6fd833;
+}
+.furry-form input.invalid, .furry-form textarea.invalid {
+ border-color: #ff6f91;
+ background: #fff0f6;
+}
+.furry-form input.valid, .furry-form textarea.valid {
+ border-color: #6fff91;
+ background: #f0fff6;
+}
+.furry-form .form-hint {
+ font-size: 0.95rem;
+ color: #ff6fd8;
+ margin-left: 0.2rem;
+ min-height: 1.2em;
+ transition: color 0.2s;
+}
+.furry-form input.valid ~ .form-hint, .furry-form textarea.valid ~ .form-hint {
+ color: #6fff91;
+}
+
+.furry-404 {
+ max-width: 500px;
+ margin: 4rem auto 0 auto;
+ background: #fff;
+ border-radius: 32px;
+ box-shadow: 0 4px 32px rgba(179, 111, 255, 0.12);
+ padding: 3rem 2rem 2.5rem 2rem;
+ text-align: center;
+ color: #b36fff;
+}
+.furry-404-emoji {
+ font-size: 4rem;
+ margin-bottom: 1rem;
+}
+.furry-404 h1 {
+ font-size: 4rem;
+ margin: 0.5rem 0 0.2rem 0;
+ color: #ff6fd8;
+ font-weight: 900;
+}
+.furry-404 h2 {
+ font-size: 1.6rem;
+ margin-bottom: 1rem;
+ color: #3a2d4d;
+}
+.furry-404 p {
+ font-size: 1.1rem;
+ margin-bottom: 2rem;
+ color: #3a2d4d;
+}
+
+.social-sharing {
+ margin: 2rem 0 1rem 0;
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+ background: linear-gradient(135deg, #f8e1ff 0%, #e1f0ff 100%);
+ padding: 1.5rem;
+ border-radius: 24px;
+ box-shadow: 0 4px 16px rgba(179, 111, 255, 0.08);
+ position: relative;
+ overflow: hidden;
+}
+
+.social-sharing::before {
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
content: '';
position: absolute;
top: 0;
@@ -284,6 +723,7 @@ nav a:hover::after {
transition: left 0.6s ease;
}
+<<<<<<< HEAD
.furry-btn:hover::before {
left: 100%;
}
@@ -600,10 +1040,51 @@ nav a:hover::after {
padding: 2rem;
text-align: center;
transition: var(--transition);
+=======
+.social-sharing:hover::before {
+ left: 100%;
+}
+
+.social-sharing span {
+ font-weight: 700;
+ color: #b36fff;
+ margin-right: 0.5rem;
+ font-size: 1.1rem;
+}
+
+.social-sharing .share-label {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-weight: 700;
+ color: #b36fff;
+ margin-right: 1rem;
+}
+
+.social-sharing .share-label::before {
+ content: '🌐';
+ font-size: 1.2rem;
+}
+
+.share-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ background: #fff;
+ box-shadow: 0 4px 12px rgba(179, 111, 255, 0.15);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ border: 2px solid transparent;
+ cursor: pointer;
+ text-decoration: none;
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
position: relative;
overflow: hidden;
}
+<<<<<<< HEAD
.feature-card::before {
content: '';
position: absolute;
@@ -645,10 +1126,207 @@ nav a:hover::after {
.testimonials {
padding: 4rem 0;
background: var(--glass-bg);
+=======
+.share-btn::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ background: rgba(255, 255, 255, 0.3);
+ border-radius: 50%;
+ transition: all 0.3s ease;
+ transform: translate(-50%, -50%);
+}
+
+.share-btn:hover::before {
+ width: 100%;
+ height: 100%;
+}
+
+.share-btn svg {
+ width: 24px;
+ height: 24px;
+ transition: transform 0.3s ease;
+ position: relative;
+ z-index: 1;
+}
+
+.share-btn:hover {
+ transform: translateY(-4px) scale(1.1);
+ box-shadow: 0 8px 25px rgba(179, 111, 255, 0.25);
+}
+
+.share-btn:hover svg {
+ transform: scale(1.1);
+}
+
+/* Social Platform Specific Colors */
+.share-btn.twitter {
+ background: linear-gradient(135deg, #1da1f2 0%, #0d8bd9 100%);
+ color: white;
+}
+
+.share-btn.twitter:hover {
+ background: linear-gradient(135deg, #0d8bd9 0%, #1da1f2 100%);
+ box-shadow: 0 8px 25px rgba(29, 161, 242, 0.4);
+}
+
+.share-btn.telegram {
+ background: linear-gradient(135deg, #0088cc 0%, #0077b3 100%);
+ color: white;
+}
+
+.share-btn.telegram:hover {
+ background: linear-gradient(135deg, #0077b3 0%, #0088cc 100%);
+ box-shadow: 0 8px 25px rgba(0, 136, 204, 0.4);
+}
+
+.share-btn.instagram {
+ background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%);
+ color: white;
+}
+
+.share-btn.instagram:hover {
+ background: linear-gradient(45deg, #bc1888 0%, #cc2366 25%, #dc2743 50%, #e6683c 75%, #f09433 100%);
+ box-shadow: 0 8px 25px rgba(220, 39, 67, 0.4);
+}
+
+.share-btn.facebook {
+ background: linear-gradient(135deg, #1877f2 0%, #166fe5 100%);
+ color: white;
+}
+
+.share-btn.facebook:hover {
+ background: linear-gradient(135deg, #166fe5 0%, #1877f2 100%);
+ box-shadow: 0 8px 25px rgba(24, 119, 242, 0.4);
+}
+
+.share-btn.whatsapp {
+ background: linear-gradient(135deg, #25d366 0%, #20ba5a 100%);
+ color: white;
+}
+
+.share-btn.whatsapp:hover {
+ background: linear-gradient(135deg, #20ba5a 0%, #25d366 100%);
+ box-shadow: 0 8px 25px rgba(37, 211, 102, 0.4);
+}
+
+.share-btn.pinterest {
+ background: linear-gradient(135deg, #e60023 0%, #cc001f 100%);
+ color: white;
+}
+
+.share-btn.pinterest:hover {
+ background: linear-gradient(135deg, #cc001f 0%, #e60023 100%);
+ box-shadow: 0 8px 25px rgba(230, 0, 35, 0.4);
+}
+
+.share-btn.linkedin {
+ background: linear-gradient(135deg, #0077b5 0%, #006097 100%);
+ color: white;
+}
+
+.share-btn.linkedin:hover {
+ background: linear-gradient(135deg, #006097 0%, #0077b5 100%);
+ box-shadow: 0 8px 25px rgba(0, 119, 181, 0.4);
+}
+
+.share-btn.email {
+ background: linear-gradient(135deg, #ff6fd8 0%, #b36fff 100%);
+ color: white;
+}
+
+.share-btn.email:hover {
+ background: linear-gradient(135deg, #b36fff 0%, #ff6fd8 100%);
+ box-shadow: 0 8px 25px rgba(255, 111, 216, 0.4);
+}
+
+.share-btn.copy {
+ background: linear-gradient(135deg, #6c757d 0%, #5a6268 100%);
+ color: white;
+}
+
+.share-btn.copy:hover {
+ background: linear-gradient(135deg, #5a6268 0%, #6c757d 100%);
+ box-shadow: 0 8px 25px rgba(108, 117, 125, 0.4);
+}
+
+/* Share Success Animation */
+.share-btn.copied {
+ animation: shareSuccess 0.6s cubic-bezier(0.68, -0.55, 0.27, 1.55);
+}
+
+@keyframes shareSuccess {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.2); }
+ 100% { transform: scale(1); }
+}
+
+/* Mobile Responsive Social Sharing */
+@media (max-width: 768px) {
+ .social-sharing {
+ flex-direction: column;
+ gap: 1rem;
+ padding: 1.5rem;
+ }
+
+ .social-sharing .share-label {
+ margin-right: 0;
+ margin-bottom: 0.5rem;
+ }
+
+ .share-btn {
+ width: 44px;
+ height: 44px;
+ }
+
+ .share-btn svg {
+ width: 20px;
+ height: 20px;
+ }
+}
+
+@media (max-width: 480px) {
+ .social-sharing {
+ padding: 1rem;
+ }
+
+ .share-btn {
+ width: 40px;
+ height: 40px;
+ }
+
+ .share-btn svg {
+ width: 18px;
+ height: 18px;
+ }
+}
+
+.newsletter-popup {
+ display: none;
+ position: fixed;
+ left: 50%;
+ bottom: 40px;
+ transform: translateX(-50%);
+ background: linear-gradient(135deg, #fff 0%, #f8e1ff 100%);
+ border-radius: 32px;
+ box-shadow: 0 8px 40px rgba(179, 111, 255, 0.25);
+ padding: 2.5rem 3rem 2rem 3rem;
+ z-index: 3000;
+ min-width: 380px;
+ max-width: 90vw;
+ text-align: center;
+ color: #b36fff;
+ animation: newsletterSlideIn 0.8s cubic-bezier(.68,-0.55,.27,1.55);
+ border: 2px solid rgba(179, 111, 255, 0.1);
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
+<<<<<<< HEAD
.testimonials h2 {
font-size: 2.5rem;
margin-bottom: 3rem;
@@ -745,17 +1423,86 @@ nav a:hover::after {
margin-bottom: 2rem;
color: var(--text-secondary);
line-height: 1.6;
+=======
+.newsletter-popup.show {
+ display: block;
+}
+
+.newsletter-popup.hide {
+ animation: newsletterSlideOut 0.6s cubic-bezier(.68,-0.55,.27,1.55) forwards;
+}
+
+@keyframes newsletterSlideIn {
+ 0% {
+ transform: translateX(-50%) translateY(100px) scale(0.8);
+ opacity: 0;
+ }
+ 100% {
+ transform: translateX(-50%) translateY(0) scale(1);
+ opacity: 1;
+ }
+}
+
+@keyframes newsletterSlideOut {
+ 0% {
+ transform: translateX(-50%) translateY(0) scale(1);
+ opacity: 1;
+ }
+ 100% {
+ transform: translateX(-50%) translateY(100px) scale(0.8);
+ opacity: 0;
+ }
+}
+
+.newsletter-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+}
+
+.newsletter-emoji {
+ font-size: 3rem;
+ margin-bottom: 0.5rem;
+ animation: bounce 2s infinite;
+}
+
+@keyframes bounce {
+ 0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
+ 40% { transform: translateY(-10px); }
+ 60% { transform: translateY(-5px); }
+}
+
+.newsletter-text {
+ font-size: 1.3rem;
+ font-weight: 700;
+ margin-bottom: 0.5rem;
+ color: #3a2d4d;
+}
+
+.newsletter-subtitle {
+ font-size: 1rem;
+ color: #666;
+ margin-bottom: 1.5rem;
+ line-height: 1.4;
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
}
.newsletter-form {
display: flex;
+<<<<<<< HEAD
gap: 1rem;
+=======
+ gap: 0.8rem;
+ width: 100%;
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.newsletter-form input[type="email"] {
+<<<<<<< HEAD
padding: 0.8rem 1.5rem;
border-radius: 25px;
border: 2px solid var(--glass-border);
@@ -769,10 +1516,27 @@ nav a:hover::after {
.newsletter-form input[type="email"]:focus {
border-color: var(--text-primary);
box-shadow: var(--shadow-soft);
+=======
+ padding: 0.8rem 1.2rem;
+ border-radius: 20px;
+ border: 2px solid #e1f0ff;
+ font-size: 1.1rem;
+ outline: none;
+ width: 220px;
+ transition: all 0.3s ease;
+ background: #fff;
+ box-shadow: 0 2px 8px rgba(179, 111, 255, 0.1);
+}
+
+.newsletter-form input[type="email"]:focus {
+ border-color: #b36fff;
+ box-shadow: 0 4px 16px rgba(179, 111, 255, 0.2);
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
transform: translateY(-2px);
}
.newsletter-form input[type="email"]::placeholder {
+<<<<<<< HEAD
color: var(--text-secondary);
}
@@ -798,18 +1562,524 @@ nav a:hover::after {
}
/* Enhanced Scroll to Top Button */
+=======
+ color: #999;
+ font-style: italic;
+}
+
+.newsletter-form button {
+ background: linear-gradient(135deg, #ff6fd8 0%, #b36fff 100%);
+ color: white;
+ border: none;
+ border-radius: 20px;
+ padding: 0.8rem 1.5rem;
+ font-size: 1.1rem;
+ font-weight: 700;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 16px rgba(255, 111, 216, 0.3);
+ position: relative;
+ overflow: hidden;
+}
+
+.newsletter-form button::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+ transition: left 0.5s ease;
+}
+
+.newsletter-form button:hover::before {
+ left: 100%;
+}
+
+.newsletter-form button:hover {
+ transform: translateY(-3px) scale(1.05);
+ box-shadow: 0 8px 25px rgba(255, 111, 216, 0.4);
+}
+
+.newsletter-form button:active {
+ transform: translateY(-1px) scale(1.02);
+}
+
+.newsletter-form button.loading {
+ pointer-events: none;
+ opacity: 0.7;
+}
+
+.newsletter-form button.loading::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 20px;
+ height: 20px;
+ margin: -10px 0 0 -10px;
+ border: 2px solid transparent;
+ border-top: 2px solid white;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+.newsletter-close {
+ position: absolute;
+ top: 15px;
+ right: 20px;
+ background: rgba(179, 111, 255, 0.1);
+ border: none;
+ border-radius: 50%;
+ width: 32px;
+ height: 32px;
+ font-size: 1.2rem;
+ color: #b36fff;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.newsletter-close:hover {
+ background: rgba(179, 111, 255, 0.2);
+ color: #ff6fd8;
+ transform: scale(1.1);
+}
+
+.newsletter-close:active {
+ transform: scale(0.95);
+}
+
+/* Newsletter Success State */
+.newsletter-success {
+ background: linear-gradient(135deg, #25d366 0%, #20ba5a 100%);
+ color: white;
+}
+
+.newsletter-success .newsletter-text {
+ color: white;
+}
+
+.newsletter-success .newsletter-subtitle {
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.newsletter-success .newsletter-emoji {
+ animation: confetti 0.6s ease;
+}
+
+@keyframes confetti {
+ 0% { transform: scale(0) rotate(0deg); }
+ 50% { transform: scale(1.2) rotate(180deg); }
+ 100% { transform: scale(1) rotate(360deg); }
+}
+
+/* Newsletter Error State */
+.newsletter-error {
+ background: linear-gradient(135deg, #ff6f91 0%, #e74c3c 100%);
+ color: white;
+}
+
+.newsletter-error .newsletter-text {
+ color: white;
+}
+
+.newsletter-error .newsletter-subtitle {
+ color: rgba(255, 255, 255, 0.9);
+}
+
+/* Newsletter Benefits */
+.newsletter-benefits {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ margin-top: 1rem;
+ font-size: 0.9rem;
+ color: #666;
+}
+
+.newsletter-benefits .benefit {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.newsletter-benefits .benefit::before {
+ content: '✨';
+ font-size: 1rem;
+}
+
+/* Mobile Responsive Newsletter */
+@media (max-width: 768px) {
+ .newsletter-popup {
+ left: 20px;
+ right: 20px;
+ transform: none;
+ min-width: auto;
+ padding: 2rem 1.5rem 1.5rem 1.5rem;
+ }
+
+ .newsletter-form {
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ .newsletter-form input[type="email"] {
+ width: 100%;
+ max-width: 280px;
+ }
+
+ .newsletter-form button {
+ width: 100%;
+ max-width: 280px;
+ }
+
+ .newsletter-emoji {
+ font-size: 2.5rem;
+ }
+
+ .newsletter-text {
+ font-size: 1.2rem;
+ }
+
+ .newsletter-subtitle {
+ font-size: 0.9rem;
+ }
+}
+
+@media (max-width: 480px) {
+ .newsletter-popup {
+ left: 10px;
+ right: 10px;
+ padding: 1.5rem 1rem 1rem 1rem;
+ }
+
+ .newsletter-emoji {
+ font-size: 2rem;
+ }
+
+ .newsletter-text {
+ font-size: 1.1rem;
+ }
+
+ .newsletter-subtitle {
+ font-size: 0.85rem;
+ }
+}
+
+.product-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 2rem;
+ justify-content: center;
+ margin: 2rem 0 3rem 0;
+}
+.product-card {
+ background: #fff;
+ border-radius: 24px;
+ box-shadow: 0 2px 16px rgba(179, 111, 255, 0.10);
+ padding: 1.5rem 1.2rem 1.2rem 1.2rem;
+ width: 270px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ transition: transform 0.2s, box-shadow 0.2s;
+ position: relative;
+}
+.product-card:hover {
+ transform: translateY(-8px) scale(1.03);
+ box-shadow: 0 8px 32px rgba(179, 111, 255, 0.18);
+}
+.product-image {
+ width: 180px;
+ height: 180px;
+ object-fit: cover;
+ border-radius: 16px;
+ margin-bottom: 1rem;
+ background: #f8e1ff;
+ box-shadow: 0 2px 8px rgba(179, 111, 255, 0.08);
+}
+.product-info {
+ text-align: center;
+}
+.product-info h3 {
+ margin: 0.5rem 0 0.2rem 0;
+ color: #b36fff;
+ font-size: 1.2rem;
+ font-weight: 700;
+}
+.product-price {
+ color: #ff6fd8;
+ font-size: 1.1rem;
+ font-weight: 700;
+ margin-bottom: 1rem;
+}
+@media (max-width: 900px) {
+ .product-list {
+ flex-direction: column;
+ align-items: center;
+ }
+ .product-card {
+ width: 90vw;
+ max-width: 350px;
+ }
+}
+
+.product-detail-card {
+ max-width: 700px;
+ margin: 3rem auto 2rem auto;
+ background: #fff;
+ border-radius: 32px;
+ box-shadow: 0 4px 32px rgba(179, 111, 255, 0.12);
+ padding: 2.5rem 2rem 2rem 2rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ color: #3a2d4d;
+}
+.product-detail-image {
+ width: 320px;
+ height: 320px;
+ object-fit: cover;
+ border-radius: 24px;
+ margin-bottom: 2rem;
+ background: #f8e1ff;
+ box-shadow: 0 2px 16px rgba(179, 111, 255, 0.10);
+}
+.product-detail-info {
+ width: 100%;
+ text-align: center;
+}
+.product-detail-info h2 {
+ color: #b36fff;
+ font-size: 2rem;
+ margin-bottom: 0.5rem;
+}
+.product-detail-price {
+ color: #ff6fd8;
+ font-size: 1.3rem;
+ font-weight: 700;
+ margin-bottom: 1rem;
+}
+.product-detail-desc {
+ font-size: 1.1rem;
+ margin-bottom: 1.5rem;
+}
+.product-detail-actions {
+ display: flex;
+ gap: 1rem;
+ justify-content: center;
+ margin-bottom: 1.5rem;
+}
+@media (max-width: 900px) {
+ .product-detail-card {
+ padding: 1.2rem 0.5rem;
+ }
+ .product-detail-image {
+ width: 90vw;
+ max-width: 320px;
+ height: 40vw;
+ max-height: 320px;
+ }
+}
+
+.cart-list {
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+ margin: 2rem 0 3rem 0;
+}
+.cart-item-card {
+ background: #fff;
+ border-radius: 24px;
+ box-shadow: 0 2px 16px rgba(179, 111, 255, 0.10);
+ padding: 1.5rem 1.2rem 1.2rem 1.2rem;
+ display: flex;
+ align-items: center;
+ gap: 2rem;
+ position: relative;
+}
+.cart-item-image {
+ width: 100px;
+ height: 100px;
+ object-fit: cover;
+ border-radius: 16px;
+ background: #f8e1ff;
+ box-shadow: 0 2px 8px rgba(179, 111, 255, 0.08);
+}
+.cart-item-info {
+ flex: 1;
+}
+.cart-item-info h3 {
+ margin: 0.5rem 0 0.2rem 0;
+ color: #b36fff;
+ font-size: 1.1rem;
+ font-weight: 700;
+}
+.cart-item-price {
+ color: #ff6fd8;
+ font-size: 1.1rem;
+ font-weight: 700;
+ margin-bottom: 0.7rem;
+}
+.cart-item-qty {
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+}
+.cart-qty-input {
+ width: 60px;
+ padding: 0.5rem 0.7rem;
+ border-radius: 12px;
+ border: 2px solid #e1f0ff;
+ font-size: 1rem;
+ margin-right: 0.5rem;
+}
+.cart-summary {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 1.5rem;
+ margin-top: 2rem;
+ font-size: 1.2rem;
+ font-weight: 700;
+ color: #b36fff;
+}
+.cart-total {
+ color: #ff6fd8;
+ font-size: 1.3rem;
+ font-weight: 900;
+}
+@media (max-width: 900px) {
+ .cart-item-card {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 1rem;
+ }
+ .cart-summary {
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 0.7rem;
+ }
+}
+
+.checkout-container {
+ max-width: 900px;
+ margin: 3rem auto 2rem auto;
+ background: #fff;
+ border-radius: 32px;
+ box-shadow: 0 4px 32px rgba(179, 111, 255, 0.12);
+ padding: 2.5rem 2rem 2rem 2rem;
+ display: flex;
+ gap: 3rem;
+ flex-wrap: wrap;
+}
+.checkout-form {
+ flex: 2;
+ min-width: 260px;
+}
+.checkout-summary {
+ flex: 1;
+ min-width: 220px;
+ background: #f8e1ff;
+ border-radius: 24px;
+ padding: 1.5rem 1rem;
+ box-shadow: 0 2px 8px rgba(179, 111, 255, 0.08);
+ margin-top: 1.5rem;
+ height: fit-content;
+}
+.checkout-summary h3 {
+ color: #b36fff;
+ font-size: 1.2rem;
+ margin-bottom: 1rem;
+}
+.checkout-summary ul {
+ list-style: none;
+ padding: 0;
+ margin: 0 0 1rem 0;
+}
+.checkout-summary li {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 0.5rem;
+ font-size: 1.05rem;
+}
+.checkout-total {
+ color: #ff6fd8;
+ font-size: 1.2rem;
+ font-weight: 900;
+ margin-top: 1rem;
+ text-align: right;
+}
+@media (max-width: 900px) {
+ .checkout-container {
+ flex-direction: column;
+ gap: 1.5rem;
+ padding: 1.2rem 0.5rem;
+ }
+ .checkout-summary {
+ margin-top: 0.5rem;
+ }
+}
+
+.profile-card {
+ max-width: 600px;
+ margin: 3rem auto 2rem auto;
+ background: #fff;
+ border-radius: 32px;
+ box-shadow: 0 4px 32px rgba(179, 111, 255, 0.12);
+ padding: 2.5rem 2rem 2rem 2rem;
+ text-align: center;
+ color: #3a2d4d;
+}
+.profile-info {
+ margin-bottom: 2rem;
+}
+.profile-orders {
+ margin-bottom: 2rem;
+}
+.profile-orders ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+.profile-orders li {
+ background: #f8e1ff;
+ border-radius: 16px;
+ margin-bottom: 0.7rem;
+ padding: 0.7rem 1rem;
+ color: #b36fff;
+ font-weight: 700;
+}
+
+/* Scroll to Top Button */
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
.scroll-to-top {
position: fixed;
bottom: 2rem;
right: 2rem;
+<<<<<<< HEAD
background: var(--secondary-gradient);
color: var(--text-light);
+=======
+ background: linear-gradient(135deg, #b36fff 0%, #ff6fd8 100%);
+ color: white;
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
border: none;
border-radius: 50%;
width: 60px;
height: 60px;
font-size: 1.5rem;
cursor: pointer;
+<<<<<<< HEAD
transition: var(--transition);
box-shadow: var(--shadow-medium);
backdrop-filter: blur(10px);
@@ -817,15 +2087,27 @@ nav a:hover::after {
opacity: 0;
transform: translateY(20px);
z-index: 1000;
+=======
+ box-shadow: 0 4px 20px rgba(179, 111, 255, 0.3);
+ transition: all 0.3s ease;
+ opacity: 0;
+ visibility: hidden;
+ z-index: 999;
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
}
.scroll-to-top.show {
opacity: 1;
+<<<<<<< HEAD
transform: translateY(0);
+=======
+ visibility: visible;
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
}
.scroll-to-top:hover {
transform: translateY(-5px) scale(1.1);
+<<<<<<< HEAD
box-shadow: var(--shadow-strong);
}
@@ -854,16 +2136,27 @@ nav a:hover::after {
}
/* Enhanced Mobile Responsiveness */
+=======
+ box-shadow: 0 8px 30px rgba(179, 111, 255, 0.4);
+}
+
+/* Mobile Responsive */
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
@media (max-width: 768px) {
.mobile-nav-toggle {
display: block;
}
+<<<<<<< HEAD
+=======
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
nav {
position: fixed;
top: 100%;
left: 0;
right: 0;
+<<<<<<< HEAD
background: var(--primary-gradient);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
@@ -888,10 +2181,33 @@ nav a:hover::after {
font-size: 1.2rem;
}
+=======
+ background: linear-gradient(90deg, #b36fff 0%, #ff6fd8 100%);
+ flex-direction: column;
+ padding: 2rem;
+ transform: translateY(-100%);
+ transition: transform 0.3s ease;
+ box-shadow: 0 4px 20px rgba(179, 111, 255, 0.2);
+ }
+
+ nav.active {
+ transform: translateY(0);
+ }
+
+ nav a {
+ margin: 0.5rem 0;
+ padding: 1rem;
+ width: 100%;
+ text-align: center;
+ border-radius: 15px;
+ }
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
.header-actions {
flex-direction: column;
gap: 0.5rem;
}
+<<<<<<< HEAD
.header-actions .btn {
margin: 0;
@@ -947,6 +2263,14 @@ nav a:hover::after {
max-width: 300px;
}
+=======
+
+ .header-actions .btn {
+ margin: 0;
+ width: 100%;
+ }
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
.scroll-to-top {
bottom: 1rem;
right: 1rem;
@@ -960,6 +2284,7 @@ nav a:hover::after {
.container {
padding: 0 1rem;
}
+<<<<<<< HEAD
.logo img {
height: 40px;
@@ -1007,4 +2332,14 @@ nav a:hover::after {
.newsletter-card p {
font-size: 1rem;
}
+=======
+
+ .logo img {
+ height: 40px;
+ }
+
+ .furry-header {
+ padding: 0.3rem 0;
+ }
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
}
\ No newline at end of file
diff --git a/static/css/products.css b/static/css/products.css
index 618ac0c..7c6dcdb 100644
--- a/static/css/products.css
+++ b/static/css/products.css
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
.product-card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
border: none;
@@ -189,4 +190,197 @@
white-space: nowrap;
padding-bottom: 10px;
}
+=======
+.product-card {
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
+ border: none;
+ border-radius: 15px;
+ overflow: hidden;
+}
+
+.product-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 12px 30px rgba(139, 92, 246, 0.15);
+}
+
+.product-card .card-img-top {
+ height: 250px;
+ object-fit: cover;
+}
+
+.product-badge {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ z-index: 2;
+}
+
+.product-type-badge {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 2;
+}
+
+.price-tag {
+ font-size: 1.25rem;
+ font-weight: bold;
+ color: #8B5CF6;
+}
+
+.stock-indicator {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ margin-right: 5px;
+}
+
+.stock-high {
+ background-color: #10B981;
+}
+
+.stock-medium {
+ background-color: #F59E0B;
+}
+
+.stock-low {
+ background-color: #EF4444;
+}
+
+.filter-section {
+ background: white;
+ border-radius: 15px;
+ padding: 1.5rem;
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
+ margin-bottom: 2rem;
+}
+
+.filter-section .form-label {
+ font-weight: 500;
+}
+
+.price-range-slider {
+ height: 5px;
+ position: relative;
+ background-color: #e1e9f6;
+ border-radius: 2px;
+}
+
+.price-range-slider .ui-slider-range {
+ height: 5px;
+ background-color: #0d6efd;
+ border-radius: 2px;
+}
+
+.price-range-slider .ui-slider-handle {
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ background-color: #0d6efd;
+ border: none;
+ top: -8px;
+}
+
+.rating-stars {
+ color: #F59E0B;
+}
+
+.rating-count {
+ color: #6c757d;
+ font-size: 0.9rem;
+}
+
+/* Sortieroptionen Styling */
+.sort-options .btn-outline-secondary {
+ border-radius: 20px;
+ margin: 0 5px;
+ padding: 5px 15px;
+}
+
+/* Kategoriefilter Styling */
+.category-filter .btn {
+ border-radius: 20px;
+ margin: 0 5px;
+ padding: 5px 15px;
+}
+
+/* Featured Products Section */
+.featured-section {
+ background: linear-gradient(to right, #f8f9fa, #e9ecef);
+ padding: 30px 0;
+ border-radius: 15px;
+ margin-bottom: 30px;
+}
+
+.featured-badge {
+ background: #dc3545;
+ color: white;
+ padding: 5px 10px;
+ border-radius: 20px;
+ font-size: 0.8rem;
+}
+
+/* Buttons */
+.btn-primary {
+ background: linear-gradient(135deg, #8B5CF6, #EC4899);
+ border: none;
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
+}
+
+.btn-primary:hover {
+ background: linear-gradient(135deg, #EC4899, #8B5CF6);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 15px rgba(139, 92, 246, 0.3);
+}
+
+.btn-outline-danger {
+ border-color: #EC4899;
+ color: #EC4899;
+}
+
+.btn-outline-danger:hover {
+ background-color: #EC4899;
+ border-color: #EC4899;
+ color: white;
+}
+
+/* Pagination */
+.pagination .page-link {
+ color: #8B5CF6;
+ border-color: #E5E7EB;
+}
+
+.pagination .page-item.active .page-link {
+ background-color: #8B5CF6;
+ border-color: #8B5CF6;
+ color: white;
+}
+
+.pagination .page-link:hover {
+ background-color: #F3E8FF;
+ border-color: #8B5CF6;
+ color: #8B5CF6;
+}
+
+/* Responsive Anpassungen */
+@media (max-width: 768px) {
+ .product-card .card-img-top {
+ height: 200px;
+ }
+
+ .filter-section {
+ padding: 1rem;
+ }
+
+ .sort-options {
+ margin-top: 15px;
+ }
+
+ .category-filter {
+ overflow-x: auto;
+ white-space: nowrap;
+ padding-bottom: 10px;
+ }
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
}
\ No newline at end of file
diff --git a/static/images/dog-user-icon.svg b/static/images/dog-user-icon.svg
index 598697e..4268e39 100644
--- a/static/images/dog-user-icon.svg
+++ b/static/images/dog-user-icon.svg
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
@@ -22,4 +23,30 @@
+=======
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
\ No newline at end of file
diff --git a/static/images/kasico-logo-simple.svg b/static/images/kasico-logo-simple.svg
index a5625e7..6dbe157 100644
--- a/static/images/kasico-logo-simple.svg
+++ b/static/images/kasico-logo-simple.svg
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
@@ -19,4 +20,27 @@
+=======
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
\ No newline at end of file
diff --git a/static/images/kasico-logo.svg b/static/images/kasico-logo.svg
index 7f2d076..65ce414 100644
--- a/static/images/kasico-logo.svg
+++ b/static/images/kasico-logo.svg
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
@@ -51,4 +52,59 @@
ART & DESIGN
+=======
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ KASICO
+
+
+ ART & DESIGN
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
\ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
index 4b94e5e..1ee7a00 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
{% load i18n %}
{% load static %}
@@ -335,4 +336,343 @@
});
+=======
+
+{% load i18n %}
+{% load static %}
+
+
+
+
+
+
+ {% include 'seo/meta_tags.html' with meta_tags=meta_tags %}
+
+
+ {% include 'seo/structured_data.html' with structured_data=structured_data %}
+
+
+
+
+
+ {% block title %}Kasico Fursuit Shop{% endblock %}
+
+
+ {% block extra_head %}{% endblock %}
+
+
+
+
+
+ {% block content %}{% endblock %}
+
+
+
+
+ 🚀
+
+
+
+
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
\ No newline at end of file
diff --git a/templates/home.html b/templates/home.html
index 484de9e..2af6d8b 100644
--- a/templates/home.html
+++ b/templates/home.html
@@ -1,6 +1,7 @@
{% extends 'base.html' %}
{% block title %}Willkommen bei Kasico Art & Design{% endblock %}
{% block content %}
+<<<<<<< HEAD
+=======
+
+
+
Willkommen bei Kasico Art & Design
+
Wo Ihre Fursuit-Träume Realität werden!
+
+
+
+
+ Unsere Dienstleistungen
+
+
+
+
Fursuit-Anfertigung
+
Individuelle Fursuits nach Ihren Wünschen – handgemacht und einzigartig.
+
+
+
+
Custom Orders
+
Von Accessoires bis Komplettanzug – alles ist möglich!
+
+
+
+
Design & Beratung
+
Gemeinsam entwickeln wir Ihr Wunschdesign – kreativ & professionell.
+
+
+
+
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
{% endblock %}
\ No newline at end of file
diff --git a/webshop/admin.py b/webshop/admin.py
index 96343f4..a4c9dfb 100644
--- a/webshop/admin.py
+++ b/webshop/admin.py
@@ -19,6 +19,7 @@ from shop.models import (
Category, ProductType, ProductImage, ProductVariant,
CustomDesign, PayPalPayment, PaymentError, Cart, CartItem
)
+<<<<<<< HEAD
# Temporär deaktiviert
# from chat.models import (
# ChatRoom, ChatMessage, UserOnlineStatus, QuickResponse, ChatAnalytics
@@ -35,6 +36,17 @@ from shop.models import (
# MobileCache, MobileSession
# )
# from paypal_integration.models import PayPalConfig
+=======
+from chat.models import (
+ ChatRoom, ChatMessage, UserOnlineStatus, QuickResponse, ChatAnalytics
+)
+from recommendations.models import (
+ UserBehavior, UserProfile as RecUserProfile, ProductSimilarity,
+ Recommendation, RecommendationModel, ABTest, RecommendationAnalytics
+)
+from mobile.models import MobileDevice, MobileSession
+from paypal_integration.models import PayPalConfig
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
# Custom Admin Site
@@ -267,6 +279,7 @@ class ContactMessageAdmin(admin.ModelAdmin):
mark_as_resolved.short_description = "Als erledigt markieren"
+<<<<<<< HEAD
# =============================================================================
# CHAT & COMMUNICATION (Temporär deaktiviert)
# =============================================================================
@@ -378,6 +391,115 @@ class ContactMessageAdmin(admin.ModelAdmin):
# 'classes': ('collapse',)
# }),
# )
+=======
+@admin.register(ChatRoom, site=admin_site)
+class ChatRoomAdmin(admin.ModelAdmin):
+ list_display = ('id', 'customer', 'admin', 'status', 'subject', 'last_message_at', 'message_count')
+ list_filter = ('status', 'created_at')
+ search_fields = ('customer__username', 'admin__username', 'subject')
+ ordering = ('-last_message_at',)
+ readonly_fields = ('id', 'created_at', 'last_message_at')
+
+ def message_count(self, obj):
+ return obj.messages.count()
+ message_count.short_description = 'Nachrichten'
+
+
+@admin.register(ChatMessage, site=admin_site)
+class ChatMessageAdmin(admin.ModelAdmin):
+ list_display = ('room', 'sender', 'message_type', 'content_preview', 'created_at')
+ list_filter = ('message_type', 'created_at')
+ search_fields = ('content', 'sender__username')
+ ordering = ('-created_at',)
+ readonly_fields = ('created_at',)
+
+ def content_preview(self, obj):
+ return obj.content[:50] + "..." if len(obj.content) > 50 else obj.content
+ content_preview.short_description = 'Inhalt'
+
+
+@admin.register(QuickResponse, site=admin_site)
+class QuickResponseAdmin(admin.ModelAdmin):
+ list_display = ('title', 'category', 'is_active', 'use_count', 'created_by')
+ list_filter = ('category', 'is_active', 'created_at')
+ search_fields = ('title', 'content')
+ ordering = ('category', 'title')
+
+
+# =============================================================================
+# ANALYTICS & ML
+# =============================================================================
+
+@admin.register(UserBehavior, site=admin_site)
+class UserBehaviorAdmin(admin.ModelAdmin):
+ list_display = ('user', 'behavior_type', 'product', 'created_at')
+ list_filter = ('behavior_type', 'created_at')
+ search_fields = ('user__username', 'product__name')
+ ordering = ('-created_at',)
+ readonly_fields = ('created_at',)
+
+
+@admin.register(Recommendation, site=admin_site)
+class RecommendationAdmin(admin.ModelAdmin):
+ list_display = ('user', 'product', 'recommendation_type', 'confidence_score', 'is_clicked', 'is_purchased')
+ list_filter = ('recommendation_type', 'is_clicked', 'is_purchased', 'created_at')
+ search_fields = ('user__username', 'product__name')
+ ordering = ('-created_at',)
+ readonly_fields = ('created_at',)
+
+
+@admin.register(RecommendationModel, site=admin_site)
+class RecommendationModelAdmin(admin.ModelAdmin):
+ list_display = ('model_name', 'model_type', 'model_version', 'accuracy_score', 'is_active', 'trained_at')
+ list_filter = ('model_type', 'is_active', 'trained_at')
+ search_fields = ('model_name',)
+ readonly_fields = ('created_at', 'trained_at')
+
+ fieldsets = (
+ ('Model Info', {
+ 'fields': ('model_type', 'model_name', 'model_version', 'model_file')
+ }),
+ ('Performance', {
+ 'fields': ('training_data_size', 'accuracy_score', 'precision_score', 'recall_score', 'f1_score')
+ }),
+ ('Status', {
+ 'fields': ('is_active', 'created_at', 'trained_at')
+ }),
+ )
+
+
+# =============================================================================
+# MOBILE & API
+# =============================================================================
+
+@admin.register(MobileDevice, site=admin_site)
+class MobileDeviceAdmin(admin.ModelAdmin):
+ list_display = ('user', 'device_type', 'device_name', 'is_active', 'last_seen')
+ list_filter = ('device_type', 'is_active', 'created_at')
+ search_fields = ('user__username', 'device_name', 'device_token')
+ ordering = ('-created_at',)
+ readonly_fields = ('created_at', 'last_seen')
+
+
+# =============================================================================
+# PAYMENT & INTEGRATION
+# =============================================================================
+
+@admin.register(PayPalConfig, site=admin_site)
+class PayPalConfigAdmin(admin.ModelAdmin):
+ list_display = ('get_mode', 'client_id', 'is_sandbox', 'updated_at')
+ readonly_fields = ('created_at', 'updated_at')
+
+ fieldsets = (
+ ('API Konfiguration', {
+ 'fields': ('client_id', 'client_secret', 'is_sandbox')
+ }),
+ ('Zeitstempel', {
+ 'fields': ('created_at', 'updated_at'),
+ 'classes': ('collapse',)
+ }),
+ )
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
# =============================================================================
@@ -468,6 +590,7 @@ admin_site.register(ProductType)
admin_site.register(ProductImage)
admin_site.register(ProductVariant)
admin_site.register(CustomDesign)
+<<<<<<< HEAD
# Temporär deaktiviert
# admin_site.register(PayPalPayment)
# admin_site.register(PaymentError)
@@ -476,4 +599,14 @@ admin_site.register(CustomDesign)
# admin_site.register(ProductSimilarity)
# admin_site.register(ABTest)
# admin_site.register(RecommendationAnalytics)
-# admin_site.register(MobileSession)
\ No newline at end of file
+# admin_site.register(MobileSession)
+=======
+admin_site.register(PayPalPayment)
+admin_site.register(PaymentError)
+admin_site.register(UserOnlineStatus)
+admin_site.register(ChatAnalytics)
+admin_site.register(ProductSimilarity)
+admin_site.register(ABTest)
+admin_site.register(RecommendationAnalytics)
+admin_site.register(MobileSession)
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/webshop/asgi.py b/webshop/asgi.py
index 4d75450..cb43175 100644
--- a/webshop/asgi.py
+++ b/webshop/asgi.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
"""
ASGI config for webshop project.
@@ -24,3 +25,31 @@ application = ProtocolTypeRouter({
)
),
})
+=======
+"""
+ASGI config for webshop project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
+"""
+
+import os
+from django.core.asgi import get_asgi_application
+from channels.routing import ProtocolTypeRouter, URLRouter
+from channels.auth import AuthMiddlewareStack
+from chat.routing import websocket_urlpatterns
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webshop.settings')
+
+# ASGI Application
+application = ProtocolTypeRouter({
+ "http": get_asgi_application(),
+ "websocket": AuthMiddlewareStack(
+ URLRouter(
+ websocket_urlpatterns
+ )
+ ),
+})
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/webshop/settings.py b/webshop/settings.py
index 19e5e85..2c7a2ff 100644
--- a/webshop/settings.py
+++ b/webshop/settings.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
"""
Django settings for webshop project.
@@ -462,3 +463,395 @@ LOGGING = {
},
}
+=======
+"""
+Django settings for webshop project.
+
+Generated by 'django-admin startproject' using Django 5.2.1.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/5.2/ref/settings/
+"""
+
+from pathlib import Path
+import os
+from dotenv import load_dotenv
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+# Lade .env Datei
+load_dotenv(BASE_DIR / '.env')
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-qddfdhpsm$=%o8p74xo8q9wbsa5^818(dzl4f&yrdcyn=050dt')
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0', '*']
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'django.contrib.sites',
+ 'shop.apps.ShopConfig',
+ 'products.apps.ProductsConfig',
+ 'paypal',
+ 'paypal.standard.ipn',
+ 'paypal_integration',
+ 'payments',
+ 'rest_framework',
+ 'rest_framework.authtoken',
+ 'django_filters',
+ 'corsheaders',
+ 'channels',
+ 'channels_redis',
+ 'chat',
+ 'auction',
+ # 'haystack', # Temporär deaktiviert
+ # 'search', # Temporär deaktiviert
+ 'mobile',
+ 'recommendations',
+]
+
+# Entferne den zusätzlichen staticfiles-Block
+if DEBUG:
+ INSTALLED_APPS += []
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'webshop.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [BASE_DIR / 'templates'],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.template.context_processors.static',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'webshop.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.postgresql',
+ 'NAME': os.getenv('POSTGRES_DB', 'fursuit_shop'),
+ 'USER': os.getenv('POSTGRES_USER', 'fursuit_user'),
+ 'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'fursuit_password'),
+ 'HOST': os.getenv('POSTGRES_HOST', 'db'),
+ 'PORT': os.getenv('POSTGRES_PORT', '5432'),
+ }
+}
+
+# Redis Configuration
+REDIS_URL = os.getenv('REDIS_URL', 'redis://redis:6379/0')
+
+# Channels Configuration
+CHANNEL_LAYERS = {
+ 'default': {
+ 'BACKEND': 'channels_redis.core.RedisChannelLayer',
+ 'CONFIG': {
+ "hosts": [REDIS_URL],
+ },
+ },
+}
+
+# Elasticsearch Configuration (temporär deaktiviert)
+# HAYSTACK_CONNECTIONS = {
+# 'default': {
+# 'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchBackend',
+# 'URL': os.getenv('ELASTICSEARCH_URL', 'http://elasticsearch:9200'),
+# 'INDEX_NAME': 'haystack',
+# },
+# }
+
+
+# Password validation
+# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/5.2/topics/i18n/
+
+LANGUAGE_CODE = 'de'
+LANGUAGES = [
+ ('de', 'Deutsch'),
+ ('en', 'English'),
+]
+
+TIME_ZONE = 'Europe/Berlin'
+USE_I18N = True
+USE_L10N = True
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/5.2/howto/static-files/
+
+STATIC_URL = '/static/'
+STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
+STATICFILES_DIRS = [
+ BASE_DIR / 'static',
+]
+
+# Media files (Uploads)
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
+
+# Stellen Sie sicher, dass der media-Ordner existiert
+if not os.path.exists(MEDIA_ROOT):
+ os.makedirs(MEDIA_ROOT)
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+# Stripe Einstellungen
+STRIPE_PUBLISHABLE_KEY = os.getenv('STRIPE_PUBLISHABLE_KEY', '')
+STRIPE_SECRET_KEY = os.getenv('STRIPE_SECRET_KEY', '')
+STRIPE_WEBHOOK_SECRET = os.getenv('STRIPE_WEBHOOK_SECRET', '')
+
+# E-Mail-Einstellungen (temporär Console-Backend)
+EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+DEFAULT_FROM_EMAIL = 'Fursuit Shop '
+
+# Admin-E-Mail-Empfänger
+ADMINS = [
+ ('Shop Admin', 'admin@fursuitshop.com'),
+]
+
+# Lagerbestand-Einstellungen
+LOW_STOCK_THRESHOLD = 5 # Schwellenwert für niedrigen Lagerbestand
+
+# Authentication Settings
+LOGIN_URL = 'login'
+LOGIN_REDIRECT_URL = 'products:product_list'
+LOGOUT_REDIRECT_URL = 'shop:home'
+
+SITE_URL = os.getenv('SITE_URL', 'http://127.0.0.1:8000')
+
+# PayPal Einstellungen
+PAYPAL_TEST = True
+PAYPAL_RECEIVER_EMAIL = 'sb-43wjt28371773@business.example.com'
+PAYPAL_CURRENCY_CODE = 'EUR'
+
+# PayPal URLs
+PAYPAL_BN = 'Fursuit_Shop'
+PAYPAL_RETURN_URL = f'{SITE_URL}/products/payment/success/'
+PAYPAL_CANCEL_URL = f'{SITE_URL}/products/payment/failed/'
+PAYPAL_NOTIFY_URL = f'{SITE_URL}/paypal/'
+
+# Sites Framework
+SITE_ID = 1
+
+# Payment settings
+PAYMENT_HOST = SITE_URL
+PAYMENT_USES_SSL = False # Set to True in production
+PAYMENT_MODEL = 'products.Payment'
+PAYMENT_VARIANTS = {
+ 'default': ('payments.dummy.DummyProvider', {}),
+ 'stripe': ('payments.stripe.StripeProvider', {
+ 'secret_key': STRIPE_SECRET_KEY,
+ 'public_key': STRIPE_PUBLISHABLE_KEY
+ })
+}
+
+# Caching Configuration
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
+ 'LOCATION': os.getenv('REDIS_URL', 'redis://redis:6379/0'),
+ }
+}
+
+# Cache time to live is 15 minutes
+CACHE_TTL = 60 * 15
+
+# Session configuration
+SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
+SESSION_CACHE_ALIAS = 'default'
+
+# Django REST Framework Settings
+REST_FRAMEWORK = {
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
+ 'rest_framework.authentication.SessionAuthentication',
+ 'rest_framework.authentication.TokenAuthentication',
+ ],
+ 'DEFAULT_PERMISSION_CLASSES': [
+ 'rest_framework.permissions.IsAuthenticatedOrReadOnly',
+ ],
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
+ 'PAGE_SIZE': 12,
+ 'DEFAULT_FILTER_BACKENDS': [
+ 'django_filters.rest_framework.DjangoFilterBackend',
+ 'rest_framework.filters.SearchFilter',
+ 'rest_framework.filters.OrderingFilter',
+ ],
+ 'DEFAULT_RENDERER_CLASSES': [
+ 'rest_framework.renderers.JSONRenderer',
+ 'rest_framework.renderers.BrowsableAPIRenderer',
+ ],
+ 'DEFAULT_PARSER_CLASSES': [
+ 'rest_framework.parsers.JSONParser',
+ 'rest_framework.parsers.FormParser',
+ 'rest_framework.parsers.MultiPartParser',
+ ],
+ 'DEFAULT_THROTTLE_CLASSES': [
+ 'rest_framework.throttling.AnonRateThrottle',
+ 'rest_framework.throttling.UserRateThrottle',
+ ],
+ 'DEFAULT_THROTTLE_RATES': {
+ 'anon': '100/hour',
+ 'user': '1000/hour',
+ },
+ 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
+ 'DEFAULT_VERSION': 'v1',
+ 'ALLOWED_VERSIONS': ['v1'],
+ 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
+}
+
+# CORS Settings für API
+CORS_ALLOWED_ORIGINS = [
+ "http://localhost:3000",
+ "http://127.0.0.1:3000",
+ "https://kasico.de",
+ "https://www.kasico.de",
+]
+
+CORS_ALLOW_CREDENTIALS = True
+
+CORS_ALLOWED_HEADERS = [
+ 'accept',
+ 'accept-encoding',
+ 'authorization',
+ 'content-type',
+ 'dnt',
+ 'origin',
+ 'user-agent',
+ 'x-csrftoken',
+ 'x-requested-with',
+]
+
+# API Rate Limiting
+REST_FRAMEWORK_THROTTLE_RATES = {
+ 'burst': '60/min',
+ 'sustained': '1000/day',
+}
+
+# Django Channels Configuration
+ASGI_APPLICATION = 'webshop.asgi.application'
+
+# Channel Layers für Redis
+CHANNEL_LAYERS = {
+ 'default': {
+ 'BACKEND': 'channels_redis.core.RedisChannelLayer',
+ 'CONFIG': {
+ "hosts": [os.getenv('REDIS_URL', 'redis://redis:6379/0')],
+ },
+ },
+}
+
+# Chat Settings
+CHAT_MESSAGE_TYPES = {
+ 'TEXT': 'text',
+ 'IMAGE': 'image',
+ 'FILE': 'file',
+ 'SYSTEM': 'system',
+}
+
+CHAT_USER_TYPES = {
+ 'CUSTOMER': 'customer',
+ 'ADMIN': 'admin',
+ 'SYSTEM': 'system',
+}
+
+# Chat Notifications
+CHAT_NOTIFICATION_SETTINGS = {
+ 'ENABLE_EMAIL_NOTIFICATIONS': True,
+ 'ENABLE_PUSH_NOTIFICATIONS': True,
+ 'ADMIN_EMAIL': 'admin@kasico.de',
+ 'NOTIFICATION_COOLDOWN': 300, # 5 Minuten
+}
+
+# Haystack / Elasticsearch Configuration
+HAYSTACK_CONNECTIONS = {
+ 'default': {
+ 'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchBackend',
+ 'URL': os.getenv('ELASTICSEARCH_URL', 'http://elasticsearch:9200'),
+ 'INDEX_NAME': 'haystack',
+ 'CONNECTION_ALIAS': 'default',
+ },
+}
+
+HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
+
+# Search Settings
+SEARCH_SETTINGS = {
+ 'ENABLE_FUZZY_SEARCH': True,
+ 'ENABLE_SUGGESTIONS': True,
+ 'ENABLE_FACETED_SEARCH': True,
+ 'MAX_SUGGESTIONS': 10,
+ 'MIN_SUGGESTION_LENGTH': 2,
+ 'SEARCH_RESULTS_PER_PAGE': 20,
+ 'ENABLE_SEARCH_ANALYTICS': True,
+ 'SEARCH_HIGHLIGHT': True,
+ 'SEARCH_SYNONYMS': {
+ 'fursuit': ['fursuit', 'kostüm', 'kostuem'],
+ 'partial': ['partial', 'teil', 'head'],
+ 'fullsuit': ['fullsuit', 'voll', 'komplett'],
+ 'custom': ['custom', 'individuell', 'maßgeschneidert'],
+ }
+}
+
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/webshop/urls.py b/webshop/urls.py
index 9e4b1db..7dcc19a 100644
--- a/webshop/urls.py
+++ b/webshop/urls.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
"""
URL configuration for webshop project.
@@ -69,3 +70,76 @@ urlpatterns = [
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+=======
+"""
+URL configuration for webshop project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/5.2/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path, include
+from django.conf import settings
+from django.conf.urls.static import static
+from django.contrib.auth import views as auth_views
+from shop import views as shop_views
+from products.forms import CustomAuthenticationForm
+from rest_framework.documentation import include_docs_urls
+from .admin import admin_site
+
+urlpatterns = [
+ path('admin/', admin_site.urls), # Neue Admin-Site
+ path('', include('shop.urls')),
+ path('products/', include('products.urls')),
+ path('paypal/', include('paypal_integration.urls')),
+ path('api/v1/', include('products.api_urls')),
+ path('api/docs/', include_docs_urls(title='Kasico API')),
+ path('register/', shop_views.register, name='register'),
+ path('login/', auth_views.LoginView.as_view(
+ template_name='shop/login.html',
+ authentication_form=CustomAuthenticationForm
+ ), name='login'),
+ path('logout/', auth_views.LogoutView.as_view(next_page='shop:home'), name='logout'),
+ path('password_change/', auth_views.PasswordChangeView.as_view(
+ template_name='products/password_change.html',
+ success_url='/password_change/done/'
+ ), name='password_change'),
+ path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(
+ template_name='products/password_change_done.html'
+ ), name='password_change_done'),
+ path('password_reset/', auth_views.PasswordResetView.as_view(
+ template_name='shop/password_reset.html',
+ email_template_name='shop/password_reset_email.html',
+ subject_template_name='shop/password_reset_subject.txt'
+ ), name='password_reset'),
+ path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(
+ template_name='shop/password_reset_done.html'
+ ), name='password_reset_done'),
+ path('reset///', auth_views.PasswordResetConfirmView.as_view(
+ template_name='shop/password_reset_confirm.html'
+ ), name='password_reset_confirm'),
+ path('reset/done/', auth_views.PasswordResetCompleteView.as_view(
+ template_name='shop/password_reset_complete.html'
+ ), name='password_reset_complete'),
+ path('i18n/', include('django.conf.urls.i18n')),
+ path('chat/', include('chat.urls')),
+ path('auction/', include('auction.urls')),
+ path('search/', include('search.urls')),
+ path('mobile/', include('mobile.urls')),
+ path('recommendations/', include('recommendations.urls')),
+]
+
+if settings.DEBUG:
+ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+ urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb
diff --git a/webshop/wsgi.py b/webshop/wsgi.py
index 34fe1ac..cc138fa 100644
--- a/webshop/wsgi.py
+++ b/webshop/wsgi.py
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
"""
WSGI config for webshop project.
@@ -14,3 +15,21 @@ from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webshop.settings')
application = get_wsgi_application()
+=======
+"""
+WSGI config for webshop project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webshop.settings')
+
+application = get_wsgi_application()
+>>>>>>> 5b9b867963eca600ed64b617dc2dc86c30dbd9cb