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 %} +
+
+
+
+
+
+ Fortschritts-Update für {{ order.character_name }} +
+
+
+
+ {% csrf_token %} + + {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %} + {{ error }} + {% endfor %} +
+ {% endif %} + +
+ + {{ form.stage }} + {% if form.stage.errors %} +
+ {% for error in form.stage.errors %} + {{ error }} + {% endfor %} +
+ {% endif %} +
+ +
+ + {{ form.description }} + {% if form.description.errors %} +
+ {% for error in form.description.errors %} + {{ error }} + {% endfor %} +
+ {% endif %} +
+ +
+ + {{ form.image }} + {% if form.image.errors %} +
+ {% for error in form.image.errors %} + {{ error }} + {% endfor %} +
+ {% endif %} +
+ +
+
+ {{ form.completed }} + +
+ {% if form.completed.errors %} +
+ {% for error in form.completed.errors %} + {{ error }} + {% endfor %} +
+ {% endif %} +
+ +
+ + Zurück + + +
+
+
+
+
+
+
+>>>>>>> 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.name }}

+

{{ item.product.price }} €

+
+
+ {% csrf_token %} + + +
+
+ {% csrf_token %} + +
+
+
+
+ {% endfor %} +
+
+ Gesamtsumme: + {{ cart.total_price }} € + Weiter zur Kasse +
+{% else %} +

Dein Warenkorb ist leer.

+{% endif %} + + +{% if related_products %} +
+
+

💡 Das könnte dir auch gefallen

+

Weitere Fursuits in deinem Stil

+
+ +
+ {% for product in related_products %} +
+ {% if product.image %} + {{ product.name }} + {% else %} +
+ 🐾 +
+ {% endif %} + +
+
+

{{ product.name }}

+

{{ product.get_fursuit_type_display }}

+
+
+ +
+

{{ 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

+
+
+
+ + +
Bitte gib deine vollständige Adresse ein 🐾
+
+
+ + +
Stadt nicht vergessen!
+
+
+ + +
Postleitzahl bitte 🐾
+
+
+ + +
Wähle deine bevorzugte Zahlungsart
+
+ +
+
+

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 %} +
+ +
+
+

📞 Kontakt

+

Wir sind für dich da! Lass uns gemeinsam deinen perfekten Fursuit planen.

+
+
+ +
+
+ +
+

🐾 Erreiche uns

+
+
+
+
📧
+
E-Mail
+

info@kasico-art.de

+ Nachricht schreiben +
+
+
+
+
📱
+
Telefon
+

+49 123 456789

+ Anrufen +
+
+
+
+
🕒
+
Geschäftszeiten
+

Mo-Fr: 9:00 - 18:00 Uhr
+ Sa: 10:00 - 14:00 Uhr

+ 🐺 Sonntags geschlossen +
+
+
+
+ + +
+

✍️ Nachricht schreiben

+

Erzähl uns von deinem Traum-Fursuit!

+ +
+ {% csrf_token %} + + {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %} + {{ error }} + {% endfor %} +
+ {% endif %} + +
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+
+ + + +
+
+
+ + + + +>>>>>>> 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 +
+
+
+ +
+ +
+
+
+
+

Bestellprozess

+

So wird dein Fursuit Realität

+
+ +
+
+
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 +
+
+ + +
+
+ + +
+
+
+

🎭 Character Anfrage

+

Erzähl uns von deinem Traum-Fursuit

+
+ +
+ {% csrf_token %} + + {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %} + {{ error }} + {% endfor %} +
+ {% endif %} + + +
+

🐺 Character Informationen

+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+

🎨 Design Präferenzen

+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+

✨ Zusätzliche Details

+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ +
+ +
+ 📁 + Bilder hierher ziehen oder klicken zum Auswählen +
+
+
+
+
+
+
+ + +
+
+
+
+
+ +
+ +
+
+
+
+
+
+ + + + +>>>>>>> 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 %} +
+
+ +
+
+
+
Anfrage-Details
+
+
+
    +
  • + 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 %} +
+
+ + +
+
+
Character-Details
+
+
+
Beschreibung
+

{{ order.character_description }}

+ + {% if order.reference_images %} +
Referenzbilder
+ Referenzbild + {% endif %} + +
Farbwünsche
+

{{ order.color_preferences }}

+ + {% if order.special_requests %} +
Besondere Wünsche
+

{{ order.special_requests }}

+ {% endif %} +
+
+
+ + +
+
+
+
Fortschritt
+ {% if user.is_staff %} + + Update hinzufügen + + {% endif %} +
+
+ {% if progress_updates %} +
+ {% for update in progress_updates %} +
+
+
+
+ {{ update.get_stage_display }} + {% if update.completed %} + + {% endif %} +
+

{{ update.description }}

+ {% if update.image %} + Fortschrittsbild + {% 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 }}
  • +
+
+
+ + + + +
+
+
+
+
+>>>>>>> 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
+
+
+
+
+ + + + + + {% if recent_orders %} +
+
+

📦 Letzte Bestellungen

+ + Alle anzeigen + +
+ +
+ {% for order in recent_orders %} +
+
+

Bestellung #{{ order.id }}

+ + {{ order.get_status_display }} + +
+
+

{{ order.created_at|date:"d.m.Y H:i" }}

+

{{ order.total_amount }}€

+
+
+ + 👁️ Details + + {% if order.status == 'pending' %} + + {% endif %} +
+
+ {% endfor %} +
+
+ {% endif %} + + + {% if custom_orders %} +
+
+

🎨 Custom Orders

+ + Alle anzeigen + +
+ +
+ {% for order in custom_orders %} +
+
+

Custom Order #{{ order.id }}

+ + {{ order.get_status_display }} + +
+
+

{{ order.created_at|date:"d.m.Y" }}

+

{{ order.description|truncatewords:10 }}

+
+
+ + 👁️ Details + + {% if order.status == 'in_progress' %} + + {% endif %} +
+
+ {% endfor %} +
+
+ {% endif %} + + +
+
+

👤 Mein Profil

+ + Bearbeiten + +
+ +
+
+

📝 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 %} +
+
+

🔔 Benachrichtigungen

+
+ +
+ {% for notification in notifications %} +
+
+ {% if notification.type == 'success' %}✅ + {% elif notification.type == 'error' %}❌ + {% elif notification.type == 'warning' %}⚠️ + {% else %}ℹ️{% endif %} +
+
+

{{ notification.title }}

+

{{ notification.message }}

+
+
+ {% endfor %} +
+
+ {% endif %} + + +
+
+

🆘 Brauchst du Hilfe?

+
+ +
+
+
📞
+

Kontakt

+

Hast du Fragen? Wir helfen dir gerne weiter!

+ + Kontakt aufnehmen + +
+ +
+
+

FAQ

+

Häufig gestellte Fragen und Antworten

+ + FAQ durchsuchen + +
+ +
+
📋
+

Bestellungen

+

Verfolge deine Bestellungen und Custom Orders

+ + Bestellungen anzeigen + +
+
+
+
+ + + + +>>>>>>> 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

+
+ + {% for category in categories %} + + {% endfor %} +
+
+ + +
+

💡 Antworten finden

+
+ {% for faq in faqs %} +
+

+ +

+
+
+
+ {{ faq.answer|linebreaks }} +
+
+ War diese Antwort hilfreich? + + +
+
+
+
+ {% empty %} +
+
🦊
+

Keine FAQ-Einträge verfügbar

+

Wir arbeiten daran, die häufigsten Fragen zu beantworten. Schau bald wieder vorbei!

+
+ {% endfor %} +
+
+ + + +
+
+
+ + + + +>>>>>>> 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 %} + + + + + + + + +>>>>>>> 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 %} +
+ {{ product.name }} +
+ +
+ × + +
+ {% endif %} + +

{{ product.description }}

+ +
+
+
Preis
+
{{ product.price }} €
+
+
+
Verfügbarkeit
+
{{ product.stock }} Stück
+
+
+
Hinzugefügt am
+
{{ product.created|date:"d.m.Y" }}
+
+
+
Bewertung
+
+ {% with avg_rating=product.average_rating %} + {% if avg_rating > 0 %} + {{ avg_rating|floatformat:1 }} von 5 Sternen + {% else %} + Noch keine Bewertungen + {% endif %} + {% endwith %} +
+
+
+ +
+
+ {% csrf_token %} + + +
+
+
+
+
+ + +
+
+
+

Bewertungen

+ {% if user.is_authenticated and not user_has_reviewed %} + + {% endif %} +
+
+ {% 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 %} + +{% 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 %} +
+
+
+

🐾 Unsere Fursuits

+

Entdecke unsere handgefertigten Fursuits und Accessoires

+
+
Neu
+
+ + +
+

🔍 Filter & Suche

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + 🗑️ Filter zurücksetzen + +
+
+
+ + +
+ {% for product in products %} +
+ {% if product.image %} + {{ product.name }} + {% else %} +
+ 🐾 +
+ {% endif %} + +
+
+

{{ product.name }}

+

{{ product.get_fursuit_type_display }} • {{ product.get_style_display }}

+
+ {% if product.featured %} +
Featured
+ {% 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 %} +
+
+ {% if message.tags == 'success' %}✅ + {% elif message.tags == 'error' %}❌ + {% elif message.tags == 'warning' %}⚠️ + {% else %}ℹ️{% endif %} +
+
+
{{ message.tags|title }}
+
{{ message }}
+
+
+{% 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 %} +
+
+
+
+ +
+ Kasico Art & Design Logo +

🐾 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 %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% if field.errors %} +
+ {% for error in field.errors %} + {{ error }} + {% endfor %} +
+ {% endif %} +
+ {% endfor %} + +
+ +
+
+ +
+

Bereits ein Konto? Jetzt anmelden

+

Passwort vergessen?

+
+
+
+
+
+ + + +{% 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 %} +
+
+ +
+
+
+

+ + {% trans "Shopping Cart" %} +

+
+ + {% if cart_items %} + {% for item in cart_items %} +
+
+ +
+ {{ item.product.name }} +
+ + +
+
{{ 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 }} € +
+
+
+ + +
+ +
+
+
+
+ {% endfor %} + + +
+
+
+
{% trans "Cart Summary" %}
+
+ {% trans "Subtotal" %}: + {{ cart_subtotal }} € +
+
+ {% trans "Shipping" %}: + {{ shipping_cost }} € +
+ {% if discount_amount %} +
+ {% trans "Discount" %}: + -{{ discount_amount }} € +
+ {% endif %} +
+
+ {% trans "Total" %}: + {{ cart_total }} € +
+
+ +
+
{% trans "Shipping Options" %}
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ {% else %} + +
+
+ +

{% trans "Your cart is empty" %}

+

{% trans "Looks like you haven't added any items to your cart yet." %}

+ + {% trans "Continue Shopping" %} + +
+
+ {% endif %} +
+
+ + +
+ {% if cart_items %} +
+
+
{% trans "Order Summary" %}
+ + +
+ {% for item in cart_items|slice:":3" %} +
+ {{ item.product.name }} +
+
{{ 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 %} +
+
+
+ {{ product.name }} + {% 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 %} +
+ + +
+ +
+ + +>>>>>>> 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 %} + + +
+
+ + + {% if form.first_name.errors %} +
+ {{ form.first_name.errors }} +
+ {% endif %} +
+ +
+ + + {% if form.last_name.errors %} +
+ {{ form.last_name.errors }} +
+ {% endif %} +
+ +
+ + + {% if form.email.errors %} +
+ {{ form.email.errors }} +
+ {% endif %} +
+ +
+ + + {% if form.address.errors %} +
+ {{ form.address.errors }} +
+ {% endif %} +
+ +
+ + + {% if form.city.errors %} +
+ {{ form.city.errors }} +
+ {% endif %} +
+ +
+ + + {% if form.zip.errors %} +
+ {{ form.zip.errors }} +
+ {% endif %} +
+ +
+ + + {% if form.country.errors %} +
+ {{ form.country.errors }} +
+ {% endif %} +
+
+ +
+ +
+ +
+
+
+
+ + + {% elif step == 'payment' %} +
+
+

{% trans "Payment Method" %}

+
+ {% csrf_token %} + + + +
+ +
+
+
+ +
+
+ +
PayPal
+

+ {% trans "Fast and secure payment with PayPal" %} +

+
+
+
+ + +
+
+
+ +
+
+ +
{% trans "Credit Card" %}
+

+ {% trans "Pay with Visa, Mastercard, or American Express" %} +

+
+
+
+ + +
+
+
+ +
+
+ +
{% trans "Bank Transfer" %}
+

+ {% trans "Pay via bank transfer" %} +

+
+
+
+
+ + {% if form.payment_method.errors %} +
+ {{ form.payment_method.errors }} +
+ {% endif %} + +
+ +
+ + + {% trans "Back to Shipping" %} + + +
+
+
+
+ + + {% 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" %} + + +
+
+
+
+ {% 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 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 %} +

+
+ +
+ + {% 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 %} + {{ product.name }} + {% 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 %} +
+ +
+ + {% trans "View Order Details" %} + +
+ +>>>>>>> 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" %} + + + + + +>>>>>>> 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 "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 %} +
+ {% if product.image %} + {{ product.name }} + {% 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 %} +
+
+ +
+ + {% trans "View Order Details" %} + +
+ + + +>>>>>>> 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" %} + + + + + +>>>>>>> 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 "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" %}

+
+ {{ 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 %} + {{ product.name }} + {% endif %} +
+

{{ product.name }}

+ {% if product.product_type == 'fursuit' %} + {% trans "Fursuit" %} + {% else %} + {% trans "Printed Item" %} + {% endif %} +
+
+ {% endfor %} +
+
+ +
+ + {% trans "Track Your Shipment" %} + +
+ + + +>>>>>>> 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 %} +
+ +
+ Kasico Art & Design Logo +

Unsere Fursuit Galerie

+

Entdecken Sie unsere handgefertigten Kreationen und lassen Sie sich inspirieren

+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+
+
+ + + + + + {% 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 %} +
+ + {{ gallery.name }} + +
+ {% endif %} + + + {% if gallery.images.all|length > 1 %} + + {% endif %} +
+ +
+
+
+
+

{{ gallery.name }}

+ +
+ +
+ {% 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" %}

+
+ + +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+ + + {% if galleries %} + + + + {% 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 %} + +
+
+
+ Kasico Art & Design Logo +
+
+

Willkommen bei Kasico Art & Design

+

Wo Ihre Fursuit-Träume Realität werden

+ + Custom Order starten + + + Galerie ansehen + + + Kontakt + +
+
+
+ + +
+

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

+
+
+
+ Kasico Art & Design +
+
+
+
+

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 %} +
+

+ +

+
+
+ {{ faq.answer }} +
+
+
+ {% endfor %} +
+
+ + +
+
+

Haben Sie Fragen?

+

Wir sind für Sie da! Kontaktieren Sie uns für individuelle Beratung und Support.

+ + Kontaktieren Sie uns + +
+
+>>>>>>> 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 %} +
+
+
+
+ +
+ Kasico Art & Design Logo +

🐾 {% 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 }} + {% if form.username.errors %} +
+ {% for error in form.username.errors %} + {{ error }} + {% endfor %} +
+ {% endif %} +
+ +
+ + {{ form.password }} + {% if form.password.errors %} +
+ {% for error in form.password.errors %} + {{ error }} + {% endfor %} +
+ {% endif %} +
+ +
+ +
+
+ +
+

{% trans "Don't have an account?" %} + {% trans "Register here" %} +

+

{% trans "Forgot your password?" %}

+
+
+
+
+
+ + + +{% 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 %} +
+
+

{% trans "My Orders" %}

+ + + {% trans "Continue Shopping" %} + +
+ + {% 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 %} + {{ product.name }} + {% 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 }} € +

+
+ + +
+ +
+
+ + +
+
+ {% for update in order.progress_updates.all %} +
+
+
{{ update.title }}
+
+ {{ update.created_at|date:"d.m.Y H:i" }} +
+

{{ update.description }}

+ {% if update.image %} + {% trans 'Progress 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 %} + {{ product.name }} + {% 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." %}

+
+
+ +
+
+ +
+
📧
+

{% trans "E-Mail-Adresse eingeben" %}

+

+ {% trans "Gib deine E-Mail-Adresse ein und wir senden dir einen Link zum Zurücksetzen deines Passworts." %} +

+
+ +
+ {% csrf_token %} + + {% if form.errors %} +
+
⚠️
+
+

{% trans "Fehler aufgetreten" %}

+ {% for field in form %} + {% for error in field.errors %} +

{{ error }}

+ {% endfor %} + {% endfor %} +
+
+ {% endif %} + +
+ +
+ {{ form.email }} +
📧
+
+ {% if form.email.help_text %} +
{{ form.email.help_text }}
+ {% endif %} +
+ +
+ +
+
+ + +
+
+
+ + + + +>>>>>>> 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." %}

+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+

{% trans "Alles erledigt!" %}

+

+ {% trans "Dein Passwort wurde erfolgreich geändert. Du kannst dich jetzt mit deinem neuen Passwort anmelden." %} +

+ +
+

🔒 Sicherheitstipps:

+
    +
  • Teile dein Passwort niemals mit anderen
  • +
  • Verwende ein einzigartiges Passwort für jedes Konto
  • +
  • Ändere dein Passwort regelmäßig
  • +
  • Aktiviere die Zwei-Faktor-Authentifizierung
  • +
+
+ + +
+
+
+
+ + + + +>>>>>>> 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." %}

+
+
+ +
+
+ {% if validlink %} + +
+
+

{% trans "Sicheres Passwort erstellen" %}

+

+ {% trans "Dein neues Passwort sollte mindestens 8 Zeichen lang sein und Buchstaben, Zahlen und Sonderzeichen enthalten." %} +

+
+ +
+ {% csrf_token %} + + {% if form.errors %} +
+
⚠️
+
+

{% trans "Fehler aufgetreten" %}

+ {% for field in form %} + {% for error in field.errors %} +

{{ error }}

+ {% endfor %} + {% endfor %} +
+
+ {% endif %} + +
+ +
+ {{ form.new_password1 }} +
🔒
+ +
+
+
+
+
+
Passwort-Stärke
+
+ {% if form.new_password1.help_text %} +
{{ form.new_password1.help_text }}
+ {% endif %} +
+ +
+ +
+ {{ form.new_password2 }} +
🔐
+ +
+
+ + Passwörter werden verglichen... +
+ {% if form.new_password2.help_text %} +
{{ form.new_password2.help_text }}
+ {% endif %} +
+ +
+ +
+
+ + {% else %} + + + {% endif %} +
+
+
+ + + + +>>>>>>> 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." %}

+
+
+ +
+
+ +
+
+
+
+ +
+

{% trans "E-Mail ist unterwegs!" %}

+

+ {% trans "Wir haben dir eine E-Mail mit einem Link zum Zurücksetzen deines Passworts gesendet. Bitte überprüfe dein E-Mail-Postfach." %} +

+ + + + +
+
+
+
+ + + + +>>>>>>> 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 %} +
+
+ +
+
+ +
+ {{ product.name }} + + {% if product.on_sale %} +
+ Sale! +
+ {% endif %} +
+ + + {% if product.gallery.all %} +
+
+ Hauptbild +
+ {% for image in product.gallery.all %} +
+ {{ image.alt_text }} +
+ {% 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 %} + + {% else %} + + {% endif %} + +
+ + +
+
+ + +
+
+
+
+ +
Schneller Versand
+
+
+
+
+ +
Sichere Zahlung
+
+
+
+
+ +
30 Tage Rückgabe
+
+
+
+
+
+
+
+
+ + + {% if similar_products %} +
+

Ähnliche Produkte

+
+ {% for similar_product in similar_products %} +
+
+
+ {{ similar_product.name }} + {% 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" %}

+
+ + +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+ + + {% if products %} +
+ {% for product in products %} +
+
+
+ + {% if product.image %} + {{ product.name }} + {% else %} + Placeholder + {% 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 %} +
+ +
+ +
+
+
+
+
+ {% endfor %} +
+ + + {% if products.has_other_pages %} + + {% endif %} + {% else %} + +
+
+ +

{% trans "No products found" %}

+

{% trans "Try adjusting your filters or check back later for new products." %}

+ + {% trans "Clear Filters" %} + +
+
+ {% 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 %} +
+
+
+
+ +
+ Kasico Art & Design Logo +

{% trans "Create Account" %}

+
+ +
+ {% csrf_token %} + + {% if form.errors %} +
+ {% for field in form %} + {% for error in field.errors %} +

{{ error }}

+ {% endfor %} + {% endfor %} +
+ {% endif %} + +
+ + {{ form.username }} + {% if form.username.help_text %} +
{{ form.username.help_text }}
+ {% endif %} +
+ +
+ + {{ form.password1 }} + {% if form.password1.help_text %} +
{{ form.password1.help_text }}
+ {% endif %} +
+ +
+ + {{ form.password2 }} + {% if form.password2.help_text %} +
{{ form.password2.help_text }}
+ {% endif %} +
+ +
+ +
+
+ +
+

{% 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
@@ -144,4 +145,50 @@
+======= +
+
+

Willkommen bei Kasico Art & Design

+

Wo Ihre Fursuit-Träume Realität werden!

+ +
+
+
+

Unsere Dienstleistungen

+
+
+ Fursuit +

Fursuit-Anfertigung

+

Individuelle Fursuits nach Ihren Wünschen – handgemacht und einzigartig.

+
+
+ Custom +

Custom Orders

+

Von Accessoires bis Komplettanzug – alles ist möglich!

+
+
+ Design +

Design & Beratung

+

Gemeinsam entwickeln wir Ihr Wunschdesign – kreativ & professionell.

+
+
+
+ +
+

Bereit für Ihr Traumkostüm?

+ Jetzt Anfrage stellen +
+>>>>>>> 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