diff --git a/config/__pycache__/__init__.cpython-311.pyc b/config/__pycache__/__init__.cpython-311.pyc index 423a636..b3c24ac 100644 Binary files a/config/__pycache__/__init__.cpython-311.pyc and b/config/__pycache__/__init__.cpython-311.pyc differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 96bce55..a2442ba 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 0b85e94..cda247e 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc index 9c49e09..8baf432 100644 Binary files a/config/__pycache__/wsgi.cpython-311.pyc and b/config/__pycache__/wsgi.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 291d043..a8de23e 100644 --- a/config/settings.py +++ b/config/settings.py @@ -155,6 +155,10 @@ STATICFILES_DIRS = [ BASE_DIR / 'node_modules', ] +# Media files (Uploaded by users) +MEDIA_URL = '/media/' +MEDIA_ROOT = BASE_DIR / 'media' + # Email EMAIL_BACKEND = os.getenv( "EMAIL_BACKEND", @@ -179,4 +183,4 @@ if EMAIL_USE_SSL: # Default primary key field type # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' \ No newline at end of file diff --git a/config/urls.py b/config/urls.py index bcfc074..59061ce 100644 --- a/config/urls.py +++ b/config/urls.py @@ -25,5 +25,6 @@ urlpatterns = [ ] if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets") - urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \ No newline at end of file diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc index 74b1112..be69bfb 100644 Binary files a/core/__pycache__/__init__.cpython-311.pyc and b/core/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392..af95fac 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/apps.cpython-311.pyc b/core/__pycache__/apps.cpython-311.pyc index 6f131d4..a12fce2 100644 Binary files a/core/__pycache__/apps.cpython-311.pyc and b/core/__pycache__/apps.cpython-311.pyc differ diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc index 75bf223..d111623 100644 Binary files a/core/__pycache__/context_processors.cpython-311.pyc and b/core/__pycache__/context_processors.cpython-311.pyc differ diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..ef4e8dd Binary files /dev/null and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640..5575bf4 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/tests.cpython-311.pyc b/core/__pycache__/tests.cpython-311.pyc new file mode 100644 index 0000000..b56467a Binary files /dev/null and b/core/__pycache__/tests.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659..b6c116d 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/utils.cpython-311.pyc b/core/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000..086fbf9 Binary files /dev/null and b/core/__pycache__/utils.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd6..2669d1b 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..6c13593 --- /dev/null +++ b/core/forms.py @@ -0,0 +1,166 @@ +from django import forms +from .models import WardrobeItem, Accessory, Outfit, Category, OutfitFolder + +class WardrobeItemForm(forms.ModelForm): + main_category = forms.ModelChoiceField( + queryset=Category.objects.filter(item_type='wardrobe', parent=None), + required=False, + empty_label="Select Main Category", + widget=forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary', 'id': 'id_main_category'}) + ) + new_main_category = forms.CharField( + max_length=100, required=False, label="Or add new main category", + widget=forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'New main category...'}) + ) + new_subcategory = forms.CharField( + max_length=100, required=False, label="Or add new subcategory", + widget=forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'New subcategory...'}) + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['category'].queryset = Category.objects.none() + self.fields['category'].required = False + self.fields['category'].label = "Subcategory" + self.fields['name'].required = False + + if 'main_category' in self.data: + try: + main_category_id = int(self.data.get('main_category')) + self.fields['category'].queryset = Category.objects.filter(parent_id=main_category_id).order_by('name') + except (ValueError, TypeError): + pass + elif self.instance.pk and self.instance.category and self.instance.category.parent: + self.fields['main_category'].initial = self.instance.category.parent + self.fields['category'].queryset = Category.objects.filter(parent=self.instance.category.parent).order_by('name') + + class Meta: + model = WardrobeItem + fields = ['name', 'category', 'season', 'image'] + widgets = { + 'name': forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'Item name (optional)...'}), + 'category': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary', 'id': 'id_subcategory'}), + 'season': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary'}), + 'image': forms.FileInput(attrs={'class': 'form-control bg-dark text-white border-secondary'}), + } + + def save(self, commit=True): + instance = super().save(commit=False) + new_main = self.cleaned_data.get('new_main_category') + new_sub = self.cleaned_data.get('new_subcategory') + main_cat = self.cleaned_data.get('main_category') + + if new_main: + parent_cat, _ = Category.objects.get_or_create(name=new_main, item_type='wardrobe', parent=None) + if new_sub: + child_cat, _ = Category.objects.get_or_create(name=new_sub, item_type='wardrobe', parent=parent_cat) + instance.category = child_cat + else: + instance.category = parent_cat + elif main_cat: + if new_sub: + child_cat, _ = Category.objects.get_or_create(name=new_sub, item_type='wardrobe', parent=main_cat) + instance.category = child_cat + # else category field itself handles it if selected + + if commit: + instance.save() + return instance + +class AccessoryForm(forms.ModelForm): + main_category = forms.ModelChoiceField( + queryset=Category.objects.filter(item_type='accessory', parent=None), + required=False, + empty_label="Select Main Category", + widget=forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary', 'id': 'id_main_category'}) + ) + new_main_category = forms.CharField( + max_length=100, required=False, label="Or add new main category", + widget=forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'New main category...'}) + ) + new_subcategory = forms.CharField( + max_length=100, required=False, label="Or add new subcategory", + widget=forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'New subcategory...'}) + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['category'].queryset = Category.objects.none() + self.fields['category'].required = False + self.fields['category'].label = "Subcategory" + self.fields['name'].required = False + + if 'main_category' in self.data: + try: + main_category_id = int(self.data.get('main_category')) + self.fields['category'].queryset = Category.objects.filter(parent_id=main_category_id).order_by('name') + except (ValueError, TypeError): + pass + elif self.instance.pk and self.instance.category and self.instance.category.parent: + self.fields['main_category'].initial = self.instance.category.parent + self.fields['category'].queryset = Category.objects.filter(parent=self.instance.category.parent).order_by('name') + + class Meta: + model = Accessory + fields = ['name', 'category', 'season', 'image'] + widgets = { + 'name': forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'Accessory name (optional)...'}), + 'category': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary', 'id': 'id_subcategory'}), + 'season': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary'}), + 'image': forms.FileInput(attrs={'class': 'form-control bg-dark text-white border-secondary'}), + } + + def save(self, commit=True): + instance = super().save(commit=False) + new_main = self.cleaned_data.get('new_main_category') + new_sub = self.cleaned_data.get('new_subcategory') + main_cat = self.cleaned_data.get('main_category') + + if new_main: + parent_cat, _ = Category.objects.get_or_create(name=new_main, item_type='accessory', parent=None) + if new_sub: + child_cat, _ = Category.objects.get_or_create(name=new_sub, item_type='accessory', parent=parent_cat) + instance.category = child_cat + else: + instance.category = parent_cat + elif main_cat: + if new_sub: + child_cat, _ = Category.objects.get_or_create(name=new_sub, item_type='accessory', parent=main_cat) + instance.category = child_cat + + if commit: + instance.save() + return instance + +class OutfitForm(forms.ModelForm): + new_folder = forms.CharField(max_length=100, required=False, label="Or add new folder/group", + widget=forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'Enter new group name...'})) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['name'].required = False + self.fields['season'].required = False + self.fields['items'].required = False + self.fields['accessories'].required = False + + class Meta: + model = Outfit + fields = ['name', 'season', 'items', 'accessories', 'folder'] + widgets = { + 'name': forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'Outfit name (optional)...'}), + 'season': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary'}), + 'items': forms.SelectMultiple(attrs={'class': 'form-select d-none'}), + 'accessories': forms.SelectMultiple(attrs={'class': 'form-select d-none'}), + 'folder': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary'}), + } + + def save(self, commit=True): + instance = super().save(commit=False) + new_folder_name = self.cleaned_data.get('new_folder') + if new_folder_name: + folder, _ = OutfitFolder.objects.get_or_create(name=new_folder_name) + instance.folder = folder + if commit: + instance.save() + self.save_m2m() + return instance \ No newline at end of file diff --git a/core/management/__init__.py b/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/__pycache__/__init__.cpython-311.pyc b/core/management/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..0de3180 Binary files /dev/null and b/core/management/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/commands/__pycache__/__init__.cpython-311.pyc b/core/management/commands/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..70a6260 Binary files /dev/null and b/core/management/commands/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/management/commands/__pycache__/setup_categories.cpython-311.pyc b/core/management/commands/__pycache__/setup_categories.cpython-311.pyc new file mode 100644 index 0000000..c8d3124 Binary files /dev/null and b/core/management/commands/__pycache__/setup_categories.cpython-311.pyc differ diff --git a/core/management/commands/setup_categories.py b/core/management/commands/setup_categories.py new file mode 100644 index 0000000..7ec8c4b --- /dev/null +++ b/core/management/commands/setup_categories.py @@ -0,0 +1,44 @@ +from django.core.management.base import BaseCommand +from core.models import Category + +class Command(BaseCommand): + help = 'Prepopulate hierarchical categories' + + def handle(self, *args, **options): + # Full Reset + Category.objects.all().delete() + + hierarchy = { + 'wardrobe': { + 'Jackets': ['Jackets', 'Coats', 'Zip-Ups'], + 'Shirts': ['Hoodies', 'Pullover', 'Sweater', 'Turtlenecks', 'Shirts'], + 'T-Shirts': ['T-Shirts', 'Armless Shirts', 'Tanktops'], + 'Pants': ['Jeans', 'Pants', 'Cargo Pants', 'Linen'], + 'Shorts': ['Shorts', 'Jorts'], + 'Suit': ['Suits', 'Suit Jackets', 'Shirts', 'Suit Pants', 'Suit Shoes'], + 'Shoes': ['Shoes', 'Sneakers', 'Boots', 'Suit Shoes'], + }, + 'accessory': { + 'Jewelry': ['Rings', 'Pant Chains'], + 'Headwear': ['Headwear'], + } + } + + for item_type, parents in hierarchy.items(): + for parent_name, children in parents.items(): + parent_cat = Category.objects.create( + name=parent_name, + item_type=item_type, + parent=None, + is_preset=True + ) + + for child_name in children: + Category.objects.create( + name=child_name, + item_type=item_type, + parent=parent_cat, + is_preset=True + ) + + self.stdout.write(self.style.SUCCESS('Successfully reset and prepopulated hierarchical categories')) diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..5936452 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,81 @@ +# Generated by Django 5.2.7 on 2026-02-04 11:49 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + 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)), + ('item_type', models.CharField(choices=[('wardrobe', 'Wardrobe'), ('accessory', 'Accessory')], max_length=20)), + ('is_preset', models.BooleanField(default=False)), + ], + options={ + 'verbose_name_plural': 'Categories', + }, + ), + migrations.CreateModel( + name='OutfitFolder', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='Accessory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('image', models.ImageField(upload_to='accessories/')), + ('date_added', models.DateTimeField(auto_now_add=True)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accessories', to='core.category')), + ], + options={ + 'verbose_name_plural': 'Accessories', + }, + ), + migrations.CreateModel( + name='Outfit', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(blank=True, max_length=255, null=True)), + ('season', models.CharField(choices=[('summer', 'Summer'), ('spring', 'Spring'), ('autumn', 'Autumn'), ('winter', 'Winter')], max_length=20)), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('accessories', models.ManyToManyField(related_name='outfits', to='core.accessory')), + ('folder', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='outfits', to='core.outfitfolder')), + ], + ), + migrations.CreateModel( + name='CalendarAssignment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField(unique=True)), + ('outfit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='assignments', to='core.outfit')), + ], + ), + migrations.CreateModel( + name='WardrobeItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('image', models.ImageField(upload_to='wardrobe/')), + ('date_added', models.DateTimeField(auto_now_add=True)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wardrobe_items', to='core.category')), + ], + ), + migrations.AddField( + model_name='outfit', + name='items', + field=models.ManyToManyField(related_name='outfits', to='core.wardrobeitem'), + ), + ] diff --git a/core/migrations/0002_accessory_season_category_parent_outfitfolder_parent_and_more.py b/core/migrations/0002_accessory_season_category_parent_outfitfolder_parent_and_more.py new file mode 100644 index 0000000..71baac9 --- /dev/null +++ b/core/migrations/0002_accessory_season_category_parent_outfitfolder_parent_and_more.py @@ -0,0 +1,69 @@ +# Generated by Django 5.2.7 on 2026-02-04 16:42 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='accessory', + name='season', + field=models.CharField(blank=True, choices=[('summer', 'Summer'), ('spring', 'Spring'), ('autumn', 'Autumn'), ('winter', 'Winter')], max_length=20, null=True), + ), + 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='core.category'), + ), + migrations.AddField( + model_name='outfitfolder', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='core.outfitfolder'), + ), + migrations.AddField( + model_name='wardrobeitem', + name='season', + field=models.CharField(blank=True, choices=[('summer', 'Summer'), ('spring', 'Spring'), ('autumn', 'Autumn'), ('winter', 'Winter')], max_length=20, null=True), + ), + migrations.AlterField( + model_name='accessory', + name='category', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='accessories', to='core.category'), + ), + migrations.AlterField( + model_name='accessory', + name='name', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name='outfit', + name='accessories', + field=models.ManyToManyField(blank=True, related_name='outfits', to='core.accessory'), + ), + migrations.AlterField( + model_name='outfit', + name='items', + field=models.ManyToManyField(blank=True, related_name='outfits', to='core.wardrobeitem'), + ), + migrations.AlterField( + model_name='outfit', + name='season', + field=models.CharField(blank=True, choices=[('summer', 'Summer'), ('spring', 'Spring'), ('autumn', 'Autumn'), ('winter', 'Winter')], max_length=20, null=True), + ), + migrations.AlterField( + model_name='wardrobeitem', + name='category', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wardrobe_items', to='core.category'), + ), + migrations.AlterField( + model_name='wardrobeitem', + name='name', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/core/migrations/0003_outfitfolder_is_preset.py b/core/migrations/0003_outfitfolder_is_preset.py new file mode 100644 index 0000000..60599b1 --- /dev/null +++ b/core/migrations/0003_outfitfolder_is_preset.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-02-04 17:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_accessory_season_category_parent_outfitfolder_parent_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='outfitfolder', + name='is_preset', + field=models.BooleanField(default=False), + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..91d555e Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0002_accessory_season_category_parent_outfitfolder_parent_and_more.cpython-311.pyc b/core/migrations/__pycache__/0002_accessory_season_category_parent_outfitfolder_parent_and_more.cpython-311.pyc new file mode 100644 index 0000000..da282ac Binary files /dev/null and b/core/migrations/__pycache__/0002_accessory_season_category_parent_outfitfolder_parent_and_more.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0003_outfitfolder_is_preset.cpython-311.pyc b/core/migrations/__pycache__/0003_outfitfolder_is_preset.cpython-311.pyc new file mode 100644 index 0000000..44ee15b Binary files /dev/null and b/core/migrations/__pycache__/0003_outfitfolder_is_preset.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/__init__.cpython-311.pyc b/core/migrations/__pycache__/__init__.cpython-311.pyc index 9c833c8..5b2ac5e 100644 Binary files a/core/migrations/__pycache__/__init__.cpython-311.pyc and b/core/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..621a191 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,105 @@ from django.db import models +import os +from .utils import process_clothing_image -# Create your models here. +class Category(models.Model): + TYPE_CHOICES = [ + ('wardrobe', 'Wardrobe'), + ('accessory', 'Accessory'), + ] + name = models.CharField(max_length=100) + item_type = models.CharField(max_length=20, choices=TYPE_CHOICES) + is_preset = models.BooleanField(default=False) + parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children') + + def __get_full_path(self): + if self.parent: + return f"{self.parent.__get_full_path()} > {self.name}" + return self.name + + def __str__(self): + return f"{self.__get_full_path()} ({self.get_item_type_display()})" + + class Meta: + verbose_name_plural = "Categories" + +class WardrobeItem(models.Model): + SEASON_CHOICES = [ + ('summer', 'Summer'), + ('spring', 'Spring'), + ('autumn', 'Autumn'), + ('winter', 'Winter'), + ] + name = models.CharField(max_length=255, blank=True, null=True) + category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, related_name='wardrobe_items') + image = models.ImageField(upload_to='wardrobe/') + season = models.CharField(max_length=20, choices=SEASON_CHOICES, blank=True, null=True) + date_added = models.DateTimeField(auto_now_add=True) + + def save(self, *args, **kwargs): + is_new = self.pk is None + super().save(*args, **kwargs) + if is_new and self.image: + process_clothing_image(self.image.path) + + def __str__(self): + return self.name or f"Wardrobe Item {self.id}" + +class Accessory(models.Model): + SEASON_CHOICES = [ + ('summer', 'Summer'), + ('spring', 'Spring'), + ('autumn', 'Autumn'), + ('winter', 'Winter'), + ] + name = models.CharField(max_length=255, blank=True, null=True) + category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, related_name='accessories') + image = models.ImageField(upload_to='accessories/') + season = models.CharField(max_length=20, choices=SEASON_CHOICES, blank=True, null=True) + date_added = models.DateTimeField(auto_now_add=True) + + def save(self, *args, **kwargs): + is_new = self.pk is None + super().save(*args, **kwargs) + if is_new and self.image: + process_clothing_image(self.image.path) + + def __str__(self): + return self.name or f"Accessory {self.id}" + + class Meta: + verbose_name_plural = "Accessories" + +class OutfitFolder(models.Model): + name = models.CharField(max_length=100) + parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children') + is_preset = models.BooleanField(default=False) + + def __str__(self): + if self.parent: + return f"{self.parent} > {self.name}" + return self.name + +class Outfit(models.Model): + SEASON_CHOICES = [ + ('summer', 'Summer'), + ('spring', 'Spring'), + ('autumn', 'Autumn'), + ('winter', 'Winter'), + ] + name = models.CharField(max_length=255, blank=True, null=True) + season = models.CharField(max_length=20, choices=SEASON_CHOICES, blank=True, null=True) + folder = models.ForeignKey(OutfitFolder, on_delete=models.SET_NULL, null=True, blank=True, related_name='outfits') + items = models.ManyToManyField(WardrobeItem, related_name='outfits', blank=True) + accessories = models.ManyToManyField(Accessory, related_name='outfits', blank=True) + date_created = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.name or f"Outfit {self.id}" + +class CalendarAssignment(models.Model): + date = models.DateField(unique=True) + outfit = models.ForeignKey(Outfit, on_delete=models.CASCADE, related_name='assignments') + + def __str__(self): + return f"{self.date}: {self.outfit}" diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..f154411 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,128 @@ +{% load static %} -
- -