diff --git a/config/__pycache__/__init__.cpython-311.pyc b/config/__pycache__/__init__.cpython-311.pyc index 509f52e..26b777b 100644 Binary files a/config/__pycache__/__init__.cpython-311.pyc and b/config/__pycache__/__init__.cpython-311.pyc differ diff --git a/config/__pycache__/__init__.cpython-312.pyc b/config/__pycache__/__init__.cpython-312.pyc index 0c8d6a1..ec50c95 100644 Binary files a/config/__pycache__/__init__.cpython-312.pyc and b/config/__pycache__/__init__.cpython-312.pyc differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 9169b42..21cb45b 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/settings.cpython-312.pyc b/config/__pycache__/settings.cpython-312.pyc index d2f2e98..6ba0ba4 100644 Binary files a/config/__pycache__/settings.cpython-312.pyc and b/config/__pycache__/settings.cpython-312.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index b4466c4..5a2c090 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-312.pyc b/config/__pycache__/urls.cpython-312.pyc index 588ab68..3a92d48 100644 Binary files a/config/__pycache__/urls.cpython-312.pyc and b/config/__pycache__/urls.cpython-312.pyc differ diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc index 305124f..420d74d 100644 Binary files a/config/__pycache__/wsgi.cpython-311.pyc and b/config/__pycache__/wsgi.cpython-311.pyc differ diff --git a/config/__pycache__/wsgi.cpython-312.pyc b/config/__pycache__/wsgi.cpython-312.pyc index 3b36f20..de08f23 100644 Binary files a/config/__pycache__/wsgi.cpython-312.pyc and b/config/__pycache__/wsgi.cpython-312.pyc differ diff --git a/config/settings.py b/config/settings.py index 5b82183..edb92ac 100644 --- a/config/settings.py +++ b/config/settings.py @@ -38,6 +38,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'phonenumber_field', 'petapp', ] @@ -77,8 +78,12 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'Pet_store', + 'USER': 'postgres', + 'PASSWORD': '111', + 'HOST': '192.168.27.4', + 'PORT': 5432, } } @@ -105,7 +110,7 @@ # Internationalization # https://docs.djangoproject.com/en/5.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = 'ru-RU' TIME_ZONE = 'UTC' @@ -119,9 +124,30 @@ STATIC_URL = 'static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') -STATICFILELS_DIRS = [] +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') +STATICFILES_DIRS = [] # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +LOGIN_REDIRECT_URL = 'home' +LOGOUT_REDIRECT_URL = 'home' +LOGIN_URL = '/auth/' + + +AUTHENTICATION_BACKENDS = [ + 'petapp.authentication.EmailAuthBackend', + # 'django.contrib.auth.backends.ModelBackend', +] + +# PASSWORD_HASHERS = [ +# "django.contrib.auth.hashers.PBKDF2PasswordHasher", +# "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", +# "django.contrib.auth.hashers.Argon2PasswordHasher", +# "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", +# "django.contrib.auth.hashers.ScryptPasswordHasher", +# ] + diff --git a/config/urls.py b/config/urls.py index 7dc100c..26a9f31 100644 --- a/config/urls.py +++ b/config/urls.py @@ -15,6 +15,8 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin +from django.conf.urls.static import static +from django.conf import settings from django.urls import path from petapp import views @@ -24,9 +26,13 @@ path('catalog/', views.catalog, name='catalog'), path('contact/', views.contact, name='contact'), path('about/', views.about, name='about'), - path('auth/', views.auth, name='auth'), + path('auth/', views.email_login, name='auth'), + path('logout/', views.logout_view, name='logout'), path('reg/', views.reg, name='reg'), path('basket/', views.basket, name='basket'), path('user/', views.user, name='user'), path('user/edit/', views.user_edit, name='user_edit'), -] + path('add_basket//', views.add_basket, name='add_basket'), + path('basket/addition///', views.addition_basket, name='addition_basket'), + path('basket/subtraction///', views.subtraction_basket, name='subtraction_basket'), +]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/media/customer/1196210_avatar.jpg b/media/customer/1196210_avatar.jpg new file mode 100644 index 0000000..b26de7b Binary files /dev/null and b/media/customer/1196210_avatar.jpg differ diff --git a/media/customer/1611870_avatar.jpg b/media/customer/1611870_avatar.jpg new file mode 100644 index 0000000..f2ccd9b Binary files /dev/null and b/media/customer/1611870_avatar.jpg differ diff --git a/media/customer/1909392_avatar.jpg b/media/customer/1909392_avatar.jpg new file mode 100644 index 0000000..b26de7b Binary files /dev/null and b/media/customer/1909392_avatar.jpg differ diff --git a/media/customer/2142738_avatar.jpg b/media/customer/2142738_avatar.jpg new file mode 100644 index 0000000..8f9f269 Binary files /dev/null and b/media/customer/2142738_avatar.jpg differ diff --git a/media/customer/2354864_avatar.jpg b/media/customer/2354864_avatar.jpg new file mode 100644 index 0000000..8f9f269 Binary files /dev/null and b/media/customer/2354864_avatar.jpg differ diff --git a/media/customer/2364084_avatar.jpg b/media/customer/2364084_avatar.jpg new file mode 100644 index 0000000..b26de7b Binary files /dev/null and b/media/customer/2364084_avatar.jpg differ diff --git a/media/customer/2673344_avatar.jpg b/media/customer/2673344_avatar.jpg new file mode 100644 index 0000000..f2ccd9b Binary files /dev/null and b/media/customer/2673344_avatar.jpg differ diff --git a/media/customer/3055444_avatar.jpg b/media/customer/3055444_avatar.jpg new file mode 100644 index 0000000..5e2d076 Binary files /dev/null and b/media/customer/3055444_avatar.jpg differ diff --git a/media/customer/3285991_avatar.jpg b/media/customer/3285991_avatar.jpg new file mode 100644 index 0000000..f688d01 Binary files /dev/null and b/media/customer/3285991_avatar.jpg differ diff --git a/media/customer/3343295_avatar.jpg b/media/customer/3343295_avatar.jpg new file mode 100644 index 0000000..225d230 Binary files /dev/null and b/media/customer/3343295_avatar.jpg differ diff --git a/media/customer/3761615_avatar.jpg b/media/customer/3761615_avatar.jpg new file mode 100644 index 0000000..f2ccd9b Binary files /dev/null and b/media/customer/3761615_avatar.jpg differ diff --git a/media/customer/450861_avatar.jpg b/media/customer/450861_avatar.jpg new file mode 100644 index 0000000..5e2d076 Binary files /dev/null and b/media/customer/450861_avatar.jpg differ diff --git a/media/customer/6651825_avatar.jpg b/media/customer/6651825_avatar.jpg new file mode 100644 index 0000000..5e2d076 Binary files /dev/null and b/media/customer/6651825_avatar.jpg differ diff --git a/media/customer/6852386_avatar.jpg b/media/customer/6852386_avatar.jpg new file mode 100644 index 0000000..9a3dadd Binary files /dev/null and b/media/customer/6852386_avatar.jpg differ diff --git a/media/customer/7265800_avatar.jpg b/media/customer/7265800_avatar.jpg new file mode 100644 index 0000000..5e2d076 Binary files /dev/null and b/media/customer/7265800_avatar.jpg differ diff --git a/media/customer/862833_avatar.jpg b/media/customer/862833_avatar.jpg new file mode 100644 index 0000000..f688d01 Binary files /dev/null and b/media/customer/862833_avatar.jpg differ diff --git a/media/customer/8671706_avatar.jpg b/media/customer/8671706_avatar.jpg new file mode 100644 index 0000000..f688d01 Binary files /dev/null and b/media/customer/8671706_avatar.jpg differ diff --git a/media/products/dOnBAHTBsMc.png b/media/products/dOnBAHTBsMc.png new file mode 100644 index 0000000..074b3a8 Binary files /dev/null and b/media/products/dOnBAHTBsMc.png differ diff --git a/media/products/image_1.png b/media/products/image_1.png new file mode 100644 index 0000000..dcfd408 Binary files /dev/null and b/media/products/image_1.png differ diff --git a/media/products/jN5lfuNTsYs_1.png b/media/products/jN5lfuNTsYs_1.png new file mode 100644 index 0000000..dc90508 Binary files /dev/null and b/media/products/jN5lfuNTsYs_1.png differ diff --git a/petapp/__pycache__/__init__.cpython-311.pyc b/petapp/__pycache__/__init__.cpython-311.pyc index 9c35fb3..bfe4490 100644 Binary files a/petapp/__pycache__/__init__.cpython-311.pyc and b/petapp/__pycache__/__init__.cpython-311.pyc differ diff --git a/petapp/__pycache__/__init__.cpython-312.pyc b/petapp/__pycache__/__init__.cpython-312.pyc index ab85162..6720292 100644 Binary files a/petapp/__pycache__/__init__.cpython-312.pyc and b/petapp/__pycache__/__init__.cpython-312.pyc differ diff --git a/petapp/__pycache__/admin.cpython-311.pyc b/petapp/__pycache__/admin.cpython-311.pyc index 8be4441..f2a8b88 100644 Binary files a/petapp/__pycache__/admin.cpython-311.pyc and b/petapp/__pycache__/admin.cpython-311.pyc differ diff --git a/petapp/__pycache__/admin.cpython-312.pyc b/petapp/__pycache__/admin.cpython-312.pyc index 4d2a742..e1ee35b 100644 Binary files a/petapp/__pycache__/admin.cpython-312.pyc and b/petapp/__pycache__/admin.cpython-312.pyc differ diff --git a/petapp/__pycache__/apps.cpython-311.pyc b/petapp/__pycache__/apps.cpython-311.pyc index d4b2cb0..35beb50 100644 Binary files a/petapp/__pycache__/apps.cpython-311.pyc and b/petapp/__pycache__/apps.cpython-311.pyc differ diff --git a/petapp/__pycache__/apps.cpython-312.pyc b/petapp/__pycache__/apps.cpython-312.pyc index 1ec7db1..4111de3 100644 Binary files a/petapp/__pycache__/apps.cpython-312.pyc and b/petapp/__pycache__/apps.cpython-312.pyc differ diff --git a/petapp/__pycache__/authentication.cpython-311.pyc b/petapp/__pycache__/authentication.cpython-311.pyc new file mode 100644 index 0000000..467a8b9 Binary files /dev/null and b/petapp/__pycache__/authentication.cpython-311.pyc differ diff --git a/petapp/__pycache__/authentication.cpython-312.pyc b/petapp/__pycache__/authentication.cpython-312.pyc new file mode 100644 index 0000000..1d04604 Binary files /dev/null and b/petapp/__pycache__/authentication.cpython-312.pyc differ diff --git a/petapp/__pycache__/forms.cpython-311.pyc b/petapp/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..83596b3 Binary files /dev/null and b/petapp/__pycache__/forms.cpython-311.pyc differ diff --git a/petapp/__pycache__/forms.cpython-312.pyc b/petapp/__pycache__/forms.cpython-312.pyc new file mode 100644 index 0000000..0c01e24 Binary files /dev/null and b/petapp/__pycache__/forms.cpython-312.pyc differ diff --git a/petapp/__pycache__/models.cpython-311.pyc b/petapp/__pycache__/models.cpython-311.pyc index 3635745..2aa2c82 100644 Binary files a/petapp/__pycache__/models.cpython-311.pyc and b/petapp/__pycache__/models.cpython-311.pyc differ diff --git a/petapp/__pycache__/models.cpython-312.pyc b/petapp/__pycache__/models.cpython-312.pyc index 124d00e..83f15bf 100644 Binary files a/petapp/__pycache__/models.cpython-312.pyc and b/petapp/__pycache__/models.cpython-312.pyc differ diff --git a/petapp/__pycache__/views.cpython-311.pyc b/petapp/__pycache__/views.cpython-311.pyc index 76ec849..690c8e4 100644 Binary files a/petapp/__pycache__/views.cpython-311.pyc and b/petapp/__pycache__/views.cpython-311.pyc differ diff --git a/petapp/__pycache__/views.cpython-312.pyc b/petapp/__pycache__/views.cpython-312.pyc index bed8b72..ef70712 100644 Binary files a/petapp/__pycache__/views.cpython-312.pyc and b/petapp/__pycache__/views.cpython-312.pyc differ diff --git a/petapp/admin.py b/petapp/admin.py index 8c38f3f..c18430f 100644 --- a/petapp/admin.py +++ b/petapp/admin.py @@ -1,3 +1,55 @@ from django.contrib import admin +from .models import Basket, Customer, Product, Animal_type, Category, Order, OrderDetail, PurchaseHistory, Rating, BasketProduct +from django.utils.safestring import mark_safe -# Register your models here. + +admin.site.site_header = "Страница администратора" +admin.site.site_title = "Зоомагазин puffball" +admin.site.index_title = "Администрирование сайта" + + +class CustomerAdmin(admin.ModelAdmin): + list_display = ('get_last_name', 'get_first_name', 'phone', 'get_email', 'avatar_show') + + def get_last_name(self, obj): + return obj.user.last_name + get_last_name.short_description = 'Фамилия' + + def get_first_name(self, obj): + return obj.user.first_name + get_first_name.short_description = 'Имя' + + def get_email(self, obj): + return obj.user.email + get_first_name.short_description = 'Электронная почта' + + def avatar_show(self, obj): + if obj.photo_avatar: + return mark_safe("".format(obj.photo_avatar.url)) + return "None" + + avatar_show.__name__ = "Картинка" + + +class ProductAdmin(admin.ModelAdmin): + list_display = ("product_name", "price", "availability", "stock", "image_show") + readonly_fields = ['availability'] + + def image_show(self, obj): + if obj.photo_product: + return mark_safe("".format(obj.photo_product.url)) + return "None" + + image_show.__name__ = "Картинка" + + +admin.site.register(Customer, CustomerAdmin) +admin.site.register(Product, ProductAdmin) +admin.site.register(Animal_type) +admin.site.register(Category) +admin.site.register(Basket) +admin.site.register(BasketProduct) +admin.site.register(Order) +admin.site.register(OrderDetail) +admin.site.register(PurchaseHistory) +admin.site.register(Rating) \ No newline at end of file diff --git a/petapp/authentication.py b/petapp/authentication.py new file mode 100644 index 0000000..3978296 --- /dev/null +++ b/petapp/authentication.py @@ -0,0 +1,15 @@ +from django.contrib.auth.backends import ModelBackend +from django.contrib.auth.models import User +from django.contrib.auth import get_user_model + + +User = get_user_model() + +class EmailAuthBackend(ModelBackend): + def authenticate(self, request, email=None, password=None, **kwargs): + try: + user = User.objects.get(email=email) # Используйте email вместо username + if user.check_password(password): + return user + except User.DoesNotExist: + return None diff --git a/petapp/forms.py b/petapp/forms.py new file mode 100644 index 0000000..de61141 --- /dev/null +++ b/petapp/forms.py @@ -0,0 +1,106 @@ +from django import forms +from django.contrib.auth.models import User +from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth import get_user_model +from django.contrib.auth import authenticate +from petapp.authentication import EmailAuthBackend +from petapp.models import * + + +class createUserForm(forms.ModelForm): + last_name = forms.CharField(label='Фамилия', widget=forms.TextInput(attrs={'class': 'form-input'})) + first_name = forms.CharField(label='Имя', widget=forms.TextInput(attrs={'class': 'form-input'})) + email = forms.EmailField(label='Электронная почта', widget=forms.EmailInput(attrs={'class': 'form-input'})) + password = forms.CharField(label='Пароль', widget=forms.PasswordInput(attrs={'class': 'form-input'})) + + class Meta: + model = User + fields = ('last_name', 'first_name', 'email', 'password') + + +class RegForm(forms.ModelForm): + patronymic = forms.CharField(label='Отчество', widget=forms.TextInput(attrs={'class': 'form-input'})) + phone = forms.CharField(label='Номер телефона', widget=forms.TextInput(attrs={'class': 'form-input', 'pattern': r'(?:\+?[\d]{1,3}[-\.\s]?)?(?:(?:[\(\[])?[\d]{3}(?:[\)\]]|[\.-])[\d]{3})(?:[\.-][\d]{4}|[\.\s]?$)', 'data-mask': "'+7 (ddd) ddd-dd-dd'"})) + + class Meta: + model = Customer + fields = ('patronymic', 'phone') + + + +class CombinedRegForm(forms.Form): + def __init__(self, *args, **kwargs): + super(CombinedRegForm, self).__init__(*args, **kwargs) + self.fields['last_name'] = createUserForm().fields['last_name'] + self.fields['first_name'] = createUserForm().fields['first_name'] + self.fields['patronymic'] = RegForm().fields['patronymic'] + self.fields['phone'] = RegForm().fields['phone'] + self.fields['email'] = createUserForm().fields['email'] + self.fields['password'] = createUserForm().fields['password'] + + def clean_last_name(self): + last_name = self.cleaned_data.get('last_name') + if len(last_name) < 2: + raise forms.ValidationError('Фамилия должна содержать не менее 2 символов.') + return last_name + + def clean_first_name(self): + first_name = self.cleaned_data.get('first_name') + if len(first_name) < 2: + raise forms.ValidationError('Имя должно содержать не менее 2 символов.') + return first_name + + def clean_patronymic(self): + patronymic = self.cleaned_data.get('patronymic') + if len(patronymic) < 2: + raise forms.ValidationError('Отчество должно содержать не менее 2 символов.') + return patronymic + + def clean(self): + cleaned_data = super().clean() + email = cleaned_data.get('email') + phone = cleaned_data.get('phone') + + if Customer.objects.filter(phone=phone).exists(): + self.add_error('phone', 'Пользователь с таким номером телефона уже существует.') + + if User.objects.filter(email=email).exists(): + self.add_error('email', 'Пользователь с такой электронной почтой уже существует.') + + + def clean_password(self): + password = self.cleaned_data.get('password') + if len(password) < 8: + raise forms.ValidationError('Пароль должен состоять не менее чем из 8 символов.') + if not any(char.isupper() for char in password): + raise forms.ValidationError('Пароль должен содержать хотя бы одну заглавную букву.') + if not any(char.isdigit() for char in password): + raise forms.ValidationError('Пароль должен содержать не менее одной цифры.') + return password + + +User = get_user_model() + +class EmailLoginForm(forms.Form): + email = forms.EmailField(label='Электронная почта', widget=forms.EmailInput(attrs={'class': 'form-input'})) + password = forms.CharField(label='Пароль', widget=forms.PasswordInput(attrs={'class': 'form-input'})) + + +class PaymentForm(forms.Form): + amount = forms.FloatField(label='Amount to pay', min_value=0) + order_number = forms.CharField(label='Order number', max_length=20) + + + + + + + + + + + + + + + diff --git a/petapp/migrations/0001_initial.py b/petapp/migrations/0001_initial.py index 271915d..89c7013 100644 --- a/petapp/migrations/0001_initial.py +++ b/petapp/migrations/0001_initial.py @@ -1,6 +1,12 @@ -# Generated by Django 5.0.1 on 2024-02-06 13:11 +# Generated by Django 5.0.1 on 2024-04-16 19:10 +import django.core.validators import django.db.models.deletion +import petapp.models +import phonenumber_field.modelfields +import uuid +from decimal import Decimal +from django.conf import settings from django.db import migrations, models @@ -9,57 +15,151 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ migrations.CreateModel( - name='Client', + name='Animal_type', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('last_name', models.CharField(max_length=100)), - ('first_name', models.CharField(max_length=100)), - ('patronymic', models.CharField(max_length=100)), - ('phone', models.CharField(max_length=20)), - ('email', models.EmailField(max_length=254)), - ('address', models.CharField(max_length=255)), - ('registration_date', models.DateField()), - ('photo_avatar', models.BinaryField()), + ('name', models.CharField(max_length=255, unique=True, validators=[django.core.validators.RegexValidator('^[a-zA-ZА-Яа-яЁё]+$', 'Разрешены только буквы.')], verbose_name='Название')), ], + options={ + 'verbose_name': 'Тип животного', + 'verbose_name_plural': 'Типы животных', + }, + ), + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, unique=True, verbose_name='Название')), + ], + options={ + 'verbose_name': 'Категория', + 'verbose_name_plural': 'Категории', + }, + ), + migrations.CreateModel( + name='Customer', + fields=[ + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')), + ('patronymic', models.CharField(max_length=30, validators=[django.core.validators.RegexValidator('^[a-zA-ZА-Яа-яЁё]+$', 'Разрешены только буквы.')], verbose_name='Отчество')), + ('phone', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region=None, unique=True, verbose_name='Номер телефона')), + ('address', models.CharField(blank=True, max_length=255, verbose_name='Адрес')), + ('photo_avatar', models.ImageField(blank=True, null=True, upload_to='customer/', verbose_name='Фото пользователь')), + ], + options={ + 'verbose_name': 'Клиент', + 'verbose_name_plural': 'Клиенты', + 'ordering': ['user__date_joined'], + }, + ), + migrations.CreateModel( + name='Basket', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.customer', verbose_name='Пользователь')), + ], + options={ + 'verbose_name': 'Корзину', + 'verbose_name_plural': 'Корзины', + }, + ), + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order_date', models.DateField(verbose_name='Дата заказа')), + ('shipping_address', models.TextField(verbose_name='Адрес доставки')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.customer', verbose_name='Пользователь')), + ], + options={ + 'verbose_name': 'Заказ', + 'verbose_name_plural': 'Заказы', + 'ordering': ['-order_date'], + }, + ), + migrations.CreateModel( + name='OrderDetail', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order_number', models.UUIDField(default=uuid.uuid4, verbose_name='Номер заказа')), + ('payment_id', models.CharField(blank=True, max_length=128, verbose_name='Идентификатор платежа')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлённая дата ')), + ('status', models.CharField(choices=[('created', 'Created'), ('processing', 'Processing'), ('paid', 'Paid'), ('cancelled', 'Cancelled'), ('error', 'Error')], default='created', max_length=20, verbose_name='Статус')), + ('basket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.basket', verbose_name='Корзина')), + ('order', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='details', to='petapp.order', verbose_name='Заказ')), + ], + options={ + 'verbose_name': 'Детали заказа', + 'verbose_name_plural': 'Детали заказов', + 'ordering': ['-order'], + }, ), migrations.CreateModel( name='Product', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('price', models.DecimalField(decimal_places=2, max_digits=10)), - ('description', models.TextField()), - ('category', models.CharField(max_length=100)), - ('manufacturer', models.CharField(max_length=100)), - ('origin_country', models.CharField(max_length=100)), - ('photo_product', models.BinaryField()), - ('animal_type', models.CharField(max_length=100)), - ('weight', models.FloatField()), - ('rating', models.IntegerField()), + ('product_name', models.CharField(max_length=255, unique=True, verbose_name='Название продукта')), + ('price', models.PositiveIntegerField(default=0, verbose_name='Цена')), + ('description', models.TextField(verbose_name='Описание')), + ('manufacturer', models.CharField(max_length=100, verbose_name='Производитель')), + ('origin_country', models.CharField(max_length=100, verbose_name='Страна производитель')), + ('photo_product', models.ImageField(upload_to='products/', validators=[petapp.models.validate_image_size], verbose_name='Фото продукта')), + ('weight', models.DecimalField(decimal_places=2, default=Decimal('0.0'), max_digits=10, verbose_name='Вес')), + ('availability', models.BooleanField(verbose_name='Наличие')), + ('stock', models.PositiveIntegerField(default=0, verbose_name='Склад')), + ('animal_type', models.ManyToManyField(related_name='products', to='petapp.animal_type', verbose_name='Тип животного')), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.category', verbose_name='Категория')), ], + options={ + 'verbose_name': 'Продукт', + 'verbose_name_plural': 'Продукты', + 'ordering': ['-rating'], + }, ), migrations.CreateModel( - name='Sale', + name='BasketProduct', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sale_date', models.DateField()), - ('quantity', models.IntegerField()), - ('amount', models.DecimalField(decimal_places=2, max_digits=10)), - ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.client')), - ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.product')), + ('quantity', models.PositiveIntegerField(verbose_name='Количество')), + ('basket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.basket', verbose_name='Корзина пользователя')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.product', verbose_name='Продукт')), ], + options={ + 'verbose_name': 'Корзина продуктов', + 'verbose_name_plural': 'Корзины продуктов', + }, ), migrations.CreateModel( - name='Warehouse', + name='PurchaseHistory', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('availability', models.BooleanField()), - ('quantity', models.IntegerField()), - ('product', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='petapp.product')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')), + ('basket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.basket', verbose_name='Корзина')), + ('order_number', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.orderdetail', verbose_name='Номер заказа')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.customer', verbose_name='Пользователь')), + ], + options={ + 'verbose_name': 'История покупок', + 'verbose_name_plural': 'История покупки', + }, + ), + migrations.CreateModel( + name='Rating', + fields=[ + ('purchase_history', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='petapp.purchasehistory', verbose_name='История покупок')), + ('rating', models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(5)], verbose_name='Рейтинг')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.product', verbose_name='Продукт')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='petapp.customer', verbose_name='Пользователь')), ], + options={ + 'verbose_name': 'Рейтинги', + 'verbose_name_plural': 'Рейтинг', + }, ), ] diff --git a/petapp/migrations/__pycache__/0001_initial.cpython-311.pyc b/petapp/migrations/__pycache__/0001_initial.cpython-311.pyc index c6d220a..f0cfc91 100644 Binary files a/petapp/migrations/__pycache__/0001_initial.cpython-311.pyc and b/petapp/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/petapp/migrations/__pycache__/0001_initial.cpython-312.pyc b/petapp/migrations/__pycache__/0001_initial.cpython-312.pyc index ecfdadc..5bdb688 100644 Binary files a/petapp/migrations/__pycache__/0001_initial.cpython-312.pyc and b/petapp/migrations/__pycache__/0001_initial.cpython-312.pyc differ diff --git a/petapp/migrations/__pycache__/__init__.cpython-311.pyc b/petapp/migrations/__pycache__/__init__.cpython-311.pyc index dad0ad4..35c2c29 100644 Binary files a/petapp/migrations/__pycache__/__init__.cpython-311.pyc and b/petapp/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/petapp/migrations/__pycache__/__init__.cpython-312.pyc b/petapp/migrations/__pycache__/__init__.cpython-312.pyc index cf87642..5a79465 100644 Binary files a/petapp/migrations/__pycache__/__init__.cpython-312.pyc and b/petapp/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/petapp/models.py b/petapp/models.py index 42d45b8..6e9d4b6 100644 --- a/petapp/models.py +++ b/petapp/models.py @@ -1,44 +1,199 @@ +import uuid +from PIL import Image from django.db import models +from django.forms import ValidationError +from phonenumber_field.modelfields import PhoneNumberField +from django.core.validators import MaxValueValidator +from decimal import Decimal +from django.core.validators import RegexValidator +from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.dispatch import receiver +from PIL import Image, ImageDraw, ImageFont -class Client(models.Model): - last_name = models.CharField(max_length=100) - first_name = models.CharField(max_length=100) - patronymic = models.CharField(max_length=100) - phone = models.CharField(max_length=20) - email = models.EmailField() - address = models.CharField(max_length=255) - registration_date = models.DateField() - photo_avatar = models.BinaryField() + +def validate_image_size(image): + max_width = 500 + max_height = 500 + img = Image.open(image) + if img.width > max_width or img.height > max_height: + raise ValidationError("Максимальные допустимые размеры изображения - 300x400 пикселей.") + +class Customer(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, verbose_name = ("Пользователь")) + patronymic = models.CharField(max_length=30, unique=False,verbose_name = ("Отчество"), validators=[RegexValidator(r'^[a-zA-ZА-Яа-яЁё]+$', 'Разрешены только буквы.')]) + phone = PhoneNumberField(unique=True, blank=False, verbose_name = ("Номер телефона")) + address = models.CharField(max_length=255, blank=True, verbose_name = ("Адрес")) + photo_avatar = models.ImageField(upload_to='customer/', blank=True, null=True, verbose_name = ("Фото пользователь")) + + class Meta: + verbose_name = "Клиент" + verbose_name_plural = "Клиенты" + ordering = ['user__date_joined'] + + + def save(self, *args, **kwargs): + if not self.photo_avatar: + initials = ''.join([name[0] for name in [self.user.last_name, self.user.first_name]]).upper() + size = (110, 110) + + image = Image.new('RGB', size, '#F0F0F0') + draw = ImageDraw.Draw(image) + font = ImageFont.truetype("petapp\\static\\petapp\\GLOBAL\\fonts\\Gravity-Regular.ttf", 36) + + draw.text((36, 36), initials, fill='#949494', font=font) + image.save(f'media/customer/{self.user.username}_avatar.jpg') + + self.photo_avatar.name = f'customer/{self.user.username}_avatar.jpg' + + super(Customer, self).save(*args, **kwargs) def __str__(self): - return f"{self.last_name}, {self.first_name}" + return f"{self.user.last_name} {self.user.first_name}" +@receiver(post_save, sender=User) +def create_user_profile(sender, instance, created, **kwargs): + if created: + Customer.objects.create(user=instance) + +@receiver(post_save, sender=User) +def save_user_profile(sender, instance, **kwargs): + instance.customer.save() -class Product(models.Model): - name = models.CharField(max_length=255) - price = models.DecimalField(max_digits=10, decimal_places=2) - description = models.TextField() - category = models.CharField(max_length=100) - manufacturer = models.CharField(max_length=100) - origin_country = models.CharField(max_length=100) - photo_product = models.BinaryField() - animal_type = models.CharField(max_length=100) - weight = models.FloatField() - rating = models.IntegerField() + +class Animal_type(models.Model): + name = models.CharField(max_length=255, verbose_name = ("Название"), validators=[RegexValidator(r'^[a-zA-ZА-Яа-яЁё]+$', 'Разрешены только буквы.')], unique=True) def __str__(self): return self.name + class Meta: + verbose_name = "Тип животного" + verbose_name_plural = "Типы животных" + + +class Category(models.Model): + name = models.CharField(max_length=255, unique=True, verbose_name = ("Название")) + + def __str__(self): + return self.name + + class Meta: + verbose_name = "Категория" + verbose_name_plural = "Категории" + + +class Product(models.Model): + product_name = models.CharField(max_length=255, unique=True, verbose_name = ("Название продукта"),) + price = models.PositiveIntegerField(default=0, verbose_name = ("Цена")) + description = models.TextField(verbose_name = ("Описание")) + category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name = ("Категория")) + manufacturer = models.CharField(max_length=100, verbose_name = ("Производитель")) + origin_country = models.CharField(max_length=100, verbose_name = ("Страна производитель")) + photo_product = models.ImageField(upload_to='products/', validators=[validate_image_size], verbose_name = ("Фото продукта")) + animal_type = models.ManyToManyField(Animal_type, related_name='products', verbose_name = ("Тип животного")) + weight = models.DecimalField(default=Decimal('0.0'), max_digits=10, decimal_places=2, verbose_name = ("Вес")) + availability = models.BooleanField(verbose_name = ("Наличие")) + stock = models.PositiveIntegerField(default=0, verbose_name = ("Склад")) + + def save(self, *args, **kwargs): + if self.stock > 0: + self.availability = True + else: + self.availability = False + super(Product, self).save(*args, **kwargs) + + def __str__(self): + return f"{self.product_name} - {self.price} р" + + class Meta: + verbose_name = "Продукт" + verbose_name_plural = "Продукты" + ordering = ['-rating'] + + +class Basket(models.Model): + user = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name = ("Пользователь")) + created_at = models.DateTimeField(auto_now_add=True, verbose_name = ("Дата создания")) + + def __str__(self): + return f"Корзина: {self.user}" + + class Meta: + verbose_name = ("Корзину") + verbose_name_plural = ("Корзины") + + +class BasketProduct(models.Model): + basket = models.ForeignKey(Basket, on_delete=models.CASCADE, verbose_name = ("Корзина пользователя")) + product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name = ("Продукт")) + quantity = models.PositiveIntegerField(verbose_name = ("Количество")) + + def __str__(self): + return f"{self.basket}: {self.product} - {self.quantity}" + + class Meta: + verbose_name = "Корзина продуктов" + verbose_name_plural = "Корзины продуктов" + + +class Order(models.Model): + customer = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name = ("Пользователь")) + order_date = models.DateField(verbose_name = ("Дата заказа")) + shipping_address = models.TextField(verbose_name = ("Адрес доставки")) + + def __str__(self): + return f"Заказ {self.id} от {self.customer}" + + class Meta: + verbose_name = "Заказ" + verbose_name_plural = "Заказы" + ordering = ['-order_date'] + + +class OrderDetail(models.Model): + STATUS_CHOICES = [ + ('created', 'Created'), + ('processing', 'Processing'), + ('paid', 'Paid'), + ('cancelled', 'Cancelled'), + ('error', 'Error'), + ] + + order_number = models.UUIDField(default = uuid.uuid4, verbose_name = ("Номер заказа")) + order = models.OneToOneField(Order, related_name='details', on_delete=models.CASCADE, verbose_name = ("Заказ")) + basket = models.ForeignKey(Basket, on_delete=models.CASCADE, verbose_name = ("Корзина")) + payment_id = models.CharField(max_length=128, blank=True, verbose_name = ("Идентификатор платежа")) + created_at = models.DateTimeField(auto_now_add=True, verbose_name = ("Дата создания")) + updated_at = models.DateTimeField(auto_now=True, verbose_name = ("Обновлённая дата ")) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='created', verbose_name = ("Статус")) + + def __str__(self): + return f"Деталь {self.id} из заказа {self.order_id}" + + class Meta: + verbose_name = "Детали заказа" + verbose_name_plural = "Детали заказов" + ordering = ['-order'] + + +class PurchaseHistory(models.Model): + user = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name = ("Пользователь")) + order_number = models.ForeignKey(OrderDetail, on_delete=models.CASCADE, verbose_name = ("Номер заказа")) + created_at = models.DateTimeField(auto_now_add=True, verbose_name = ("Дата создания")) + basket = models.ForeignKey(Basket, on_delete=models.CASCADE, verbose_name = ("Корзина")) + + class Meta: + verbose_name = "История покупок" + verbose_name_plural = "История покупки" -class Sale(models.Model): - product = models.ForeignKey(Product, on_delete=models.CASCADE) - client = models.ForeignKey(Client, on_delete=models.CASCADE) - sale_date = models.DateField() - quantity = models.IntegerField() - amount = models.DecimalField(max_digits=10, decimal_places=2) +class Rating(models.Model): + user = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name = ("Пользователь")) + purchase_history = models.OneToOneField(PurchaseHistory, on_delete=models.CASCADE, primary_key=True, verbose_name = ("История покупок")) + product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name = ("Продукт")) + rating = models.PositiveSmallIntegerField(validators=[MaxValueValidator(5)], default=0, verbose_name = ("Рейтинг")) -class Warehouse(models.Model): - product = models.OneToOneField(Product, on_delete=models.CASCADE) - availability = models.BooleanField() - quantity = models.IntegerField() \ No newline at end of file + class Meta: + verbose_name = "Рейтинги" + verbose_name_plural = "Рейтинг" diff --git a/petapp/static/petapp/components/users-form/css/users.css b/petapp/static/petapp/components/users-form/css/users.css index cf9839f..6865591 100644 --- a/petapp/static/petapp/components/users-form/css/users.css +++ b/petapp/static/petapp/components/users-form/css/users.css @@ -57,12 +57,12 @@ padding-left: 16px; } -a { +a.reg-text { color: var(--six-text-color); text-align: center; transition: 0.2s; } -a:hover { +a.reg-text:hover { text-shadow: 1px 2px 10px #57a09d; } diff --git a/petapp/static/petapp/components/users-form/css/users.scss b/petapp/static/petapp/components/users-form/css/users.scss index 869cff4..ad80e57 100644 --- a/petapp/static/petapp/components/users-form/css/users.scss +++ b/petapp/static/petapp/components/users-form/css/users.scss @@ -50,7 +50,7 @@ } } } -a { +a.reg-text { color: var(--six-text-color); text-align: center; transition: 0.2s; diff --git a/petapp/static/petapp/page/authpage/css/auth.css b/petapp/static/petapp/page/authpage/css/auth.css index 41960e6..8009cfe 100644 --- a/petapp/static/petapp/page/authpage/css/auth.css +++ b/petapp/static/petapp/page/authpage/css/auth.css @@ -4,4 +4,12 @@ .user-box { align-items: normal; +} + +input.btn-login { + margin-top: 123px; +} + +.auth { + gap: 25px; }/*# sourceMappingURL=auth.css.map */ \ No newline at end of file diff --git a/petapp/static/petapp/page/authpage/css/auth.css.map b/petapp/static/petapp/page/authpage/css/auth.css.map index 1a3e6a4..c09e80e 100644 --- a/petapp/static/petapp/page/authpage/css/auth.css.map +++ b/petapp/static/petapp/page/authpage/css/auth.css.map @@ -1 +1 @@ -{"version":3,"sources":["auth.scss","auth.css"],"names":[],"mappings":"AAAA;EACE,qBAAA;ACCF;;ADCA;EACE,mBAAA;ACEF","file":"auth.css"} \ No newline at end of file +{"version":3,"sources":["auth.scss","auth.css"],"names":[],"mappings":"AAAA;EACE,qBAAA;ACCF;;ADCA;EACE,mBAAA;ACEF;;ADAA;EACE,iBAAA;ACGF;;ADDA;EACE,SAAA;ACIF","file":"auth.css"} \ No newline at end of file diff --git a/petapp/static/petapp/page/authpage/css/auth.scss b/petapp/static/petapp/page/authpage/css/auth.scss index f1b9a7c..9945aa3 100644 --- a/petapp/static/petapp/page/authpage/css/auth.scss +++ b/petapp/static/petapp/page/authpage/css/auth.scss @@ -3,4 +3,10 @@ } .user-box{ align-items: normal; -} \ No newline at end of file +} +input.btn-login{ + margin-top: 123px; +} +.auth{ + gap: 25px +} diff --git a/petapp/static/petapp/page/basketpage/css/basket.css b/petapp/static/petapp/page/basketpage/css/basket.css index d750563..65cc0da 100644 --- a/petapp/static/petapp/page/basketpage/css/basket.css +++ b/petapp/static/petapp/page/basketpage/css/basket.css @@ -54,4 +54,20 @@ .basket .rating__group .rating__star:nth-of-type(5) { z-index: 1; width: 10em; +} + +.item__quantity { + display: flex; + justify-content: space-between; + align-items: center; +} + +.quantity-buttons { + display: flex; + font-size: 22px; +} + +.quantity-buttons a { + margin-left: 7px; + color: black; }/*# sourceMappingURL=basket.css.map */ \ No newline at end of file diff --git a/petapp/static/petapp/page/basketpage/css/basket.css.map b/petapp/static/petapp/page/basketpage/css/basket.css.map index 27074e6..5684d5b 100644 --- a/petapp/static/petapp/page/basketpage/css/basket.css.map +++ b/petapp/static/petapp/page/basketpage/css/basket.css.map @@ -1 +1 @@ -{"version":3,"sources":["basket.scss","basket.css"],"names":[],"mappings":"AAAA;EACE,aAAA;EACA,mBAAA;EACA,sBAAA;EACA,qBAAA;ACCF;ADAE;EACE,0CAAA;EACA,eAAA;EACA,iBAAA;EACA,6BAAA;ACEJ;ADAE;EACE,kBAAA;EACA,WAAA;EACA,WAAA;EACA,gBAAA;EACA,0DAAA;EACA,yBAAA;EACA,2BAAA;ACEJ;ADDI;EACE,kBAAA;EACA,MAAA;EACA,OAAA;EACA,YAAA;EACA,kBAAA;EACA,wBAAA;EACA,qBAAA;EACA,gBAAA;ACGN;ADFM;EAEE,kEAAA;ACGR;ADDM;EACE,0DAAA;ACGR;ADDM;EACE,UAAA;EACA,UAAA;ACGR;ADAM;EACE,UAAA;EACA,UAAA;ACER;ADCM;EACE,UAAA;EACA,UAAA;ACCR;ADEM;EACE,UAAA;EACA,UAAA;ACAR;ADGM;EACE,UAAA;EACA,WAAA;ACDR","file":"basket.css"} \ No newline at end of file +{"version":3,"sources":["basket.scss","basket.css"],"names":[],"mappings":"AAAA;EACE,aAAA;EACA,mBAAA;EACA,sBAAA;EACA,qBAAA;ACCF;ADAE;EACE,0CAAA;EACA,eAAA;EACA,iBAAA;EACA,6BAAA;ACEJ;ADAE;EACE,kBAAA;EACA,WAAA;EACA,WAAA;EACA,gBAAA;EACA,0DAAA;EACA,yBAAA;EACA,2BAAA;ACEJ;ADDI;EACE,kBAAA;EACA,MAAA;EACA,OAAA;EACA,YAAA;EACA,kBAAA;EACA,wBAAA;EACA,qBAAA;EACA,gBAAA;ACGN;ADFM;EAEE,kEAAA;ACGR;ADDM;EACE,0DAAA;ACGR;ADDM;EACE,UAAA;EACA,UAAA;ACGR;ADAM;EACE,UAAA;EACA,UAAA;ACER;ADCM;EACE,UAAA;EACA,UAAA;ACCR;ADEM;EACE,UAAA;EACA,UAAA;ACAR;ADGM;EACE,UAAA;EACA,WAAA;ACDR;;ADMA;EACE,aAAA;EACA,8BAAA;EACA,mBAAA;ACHF;;ADMA;EACE,aAAA;EACA,eAAA;ACHF;;ADMA;EACE,gBAAA;EACA,YAAA;ACHF","file":"basket.css"} \ No newline at end of file diff --git a/petapp/static/petapp/page/basketpage/css/basket.scss b/petapp/static/petapp/page/basketpage/css/basket.scss index e422b1d..5383a58 100644 --- a/petapp/static/petapp/page/basketpage/css/basket.scss +++ b/petapp/static/petapp/page/basketpage/css/basket.scss @@ -59,4 +59,19 @@ } } } -} \ No newline at end of file +} +.item__quantity { + display: flex; + justify-content: space-between; + align-items: center; +} + +.quantity-buttons { + display: flex; + font-size: 22px; +} + +.quantity-buttons a { + margin-left: 7px; + color: black; +} diff --git a/petapp/static/petapp/page/regpage/css/reg.css b/petapp/static/petapp/page/regpage/css/reg.css index 911b310..8bfde9d 100644 --- a/petapp/static/petapp/page/regpage/css/reg.css +++ b/petapp/static/petapp/page/regpage/css/reg.css @@ -18,6 +18,7 @@ flex-direction: row; width: 100%; gap: 122px; + margin-top: 40px; } .reg input { font-family: "Gravity Regular", sans-serif; @@ -29,9 +30,6 @@ .reg .reg__item:nth-child(2) { margin-top: 0; } -.reg .reg__item:nth-child(1) { - margin-top: 45px; -} .reg .r-side, .reg .l-side { display: flex; @@ -42,4 +40,12 @@ } .reg p { margin-top: 12px; +} + +input.btn-login { + margin-bottom: 20px; +} + +.user-box { + height: auto; }/*# sourceMappingURL=reg.css.map */ \ No newline at end of file diff --git a/petapp/static/petapp/page/regpage/css/reg.css.map b/petapp/static/petapp/page/regpage/css/reg.css.map index 25439ad..720bb76 100644 --- a/petapp/static/petapp/page/regpage/css/reg.css.map +++ b/petapp/static/petapp/page/regpage/css/reg.css.map @@ -1 +1 @@ -{"version":3,"sources":["reg.scss","reg.css"],"names":[],"mappings":"AAAA;EACE,qBAAA;ACCF;ADAE;EACE,eAAA;EACA,iBAAA;EACA,kBAAA;EACA,0CAAA;EACA,4BAAA;ACEJ;;ADCA;EACE,mBAAA;ACEF;;ADAA;EACE,gBAAA;EACA,mBAAA;EACA,WAAA;EACA,UAAA;ACGF;ADFE;EACE,0CAAA;EACA,8BAAA;ACIJ;ADFE;EACE,QAAA;ACIJ;ADHI;EACE,aAAA;ACKN;ADHI;EACE,gBAAA;ACKN;ADFE;;EAEE,aAAA;EACA,sBAAA;EACA,SAAA;EACA,WAAA;EACA,gBAAA;ACIJ;ADFE;EACE,gBAAA;ACIJ","file":"reg.css"} \ No newline at end of file +{"version":3,"sources":["reg.scss","reg.css"],"names":[],"mappings":"AAAA;EACE,qBAAA;ACCF;ADAE;EACE,eAAA;EACA,iBAAA;EACA,kBAAA;EACA,0CAAA;EACA,4BAAA;ACEJ;;ADCA;EACE,mBAAA;ACEF;;ADAA;EACE,gBAAA;EACA,mBAAA;EACA,WAAA;EACA,UAAA;EACA,gBAAA;ACGF;ADFE;EACE,0CAAA;EACA,8BAAA;ACIJ;ADFE;EACE,QAAA;ACIJ;ADHI;EACE,aAAA;ACKN;ADDE;;EAEE,aAAA;EACA,sBAAA;EACA,SAAA;EACA,WAAA;EACA,gBAAA;ACGJ;ADDE;EACE,gBAAA;ACGJ;;ADCA;EACE,mBAAA;ACEF;;ADAA;EACE,YAAA;ACGF","file":"reg.css"} \ No newline at end of file diff --git a/petapp/static/petapp/page/regpage/css/reg.scss b/petapp/static/petapp/page/regpage/css/reg.scss index dc57087..ff32d54 100644 --- a/petapp/static/petapp/page/regpage/css/reg.scss +++ b/petapp/static/petapp/page/regpage/css/reg.scss @@ -16,6 +16,7 @@ flex-direction: row; width: 100%; gap: 122px; + margin-top: 40px; input{ font-family: "Gravity Regular", sans-serif; color: var(--three-text-color); @@ -25,9 +26,7 @@ &:nth-child(2) { margin-top: 0; } - &:nth-child(1) { - margin-top: 45px; - } + } .r-side, .l-side { @@ -40,4 +39,12 @@ p { margin-top: 12px; } + +} +input.btn-login{ + margin-bottom: 20px; +} +.user-box{ + height: auto; + } diff --git a/petapp/static/petapp/page/user_editpage/css/user_edit.css b/petapp/static/petapp/page/user_editpage/css/user_edit.css index 37ea384..abe27e3 100644 --- a/petapp/static/petapp/page/user_editpage/css/user_edit.css +++ b/petapp/static/petapp/page/user_editpage/css/user_edit.css @@ -5,7 +5,6 @@ } .edit-info { - max-width: 959px; width: 100%; gap: 0; -moz-column-gap: 122px; @@ -13,7 +12,7 @@ row-gap: 73px; padding-bottom: 20px; flex-wrap: wrap; - justify-content: center; + flex-direction: column; align-items: center; } @media screen and (max-width: 500px) { @@ -48,6 +47,7 @@ input.btn-login { margin-top: 0; color: var(--four-text-color); margin-right: 0; + margin-bottom: 0; } @media screen and (max-width: 500px) { input.btn-login { @@ -59,8 +59,8 @@ input.btn-login { .form__buttons { display: flex; gap: 25px; - margin-left: auto; align-items: center; + margin-left: 32em; } @media screen and (max-width: 500px) { .form__buttons { @@ -75,6 +75,10 @@ input.btn-login { } } +.reg__item { + width: 303px; +} + .user-leave { padding-right: 25px; } diff --git a/petapp/static/petapp/page/user_editpage/css/user_edit.css.map b/petapp/static/petapp/page/user_editpage/css/user_edit.css.map index 08ec2f0..56a0739 100644 --- a/petapp/static/petapp/page/user_editpage/css/user_edit.css.map +++ b/petapp/static/petapp/page/user_editpage/css/user_edit.css.map @@ -1 +1 @@ -{"version":3,"sources":["user_edit.scss","user_edit.css"],"names":[],"mappings":"AAAA;EACE,gBAAA;EACA,iBAAA;EACA,iBAAA;ACCF;;ADCA;EACE,gBAAA;EACA,WAAA;EACA,MAAA;EACA,sBAAA;OAAA,iBAAA;EACA,aAAA;EACA,oBAAA;EACA,eAAA;EACA,uBAAA;EACA,mBAAA;ACEF;ADDE;EAVF;IAWI,aAAA;ECIF;AACF;;ADDE;EADF;IAEI,sBAAA;ECKF;AACF;ADHI;EADF;IAEI,QAAA;ECMJ;AACF;ADHI;EADF;IAEI,SAAA;ECMJ;AACF;;ADHA;EACE,iBAAA;EACA,YAAA;EACA,mBAAA;ACMF;;ADJA;EACE,aAAA;EACA,6BAAA;EACA,eAAA;ACOF;ADNE;EAJF;IAKI,iBAAA;IACA,eAAA;ECSF;AACF;;ADPA;EACE,aAAA;EACA,SAAA;EACA,iBAAA;EACA,mBAAA;ACUF;ADTE;EALF;IAMI,cAAA;IACA,eAAA;ECYF;AACF;;ADRE;EADF;IAEI,wBAAA;ECYF;AACF;;ADTA;EACE,mBAAA;ACYF;ADXE;EAFF;IAGI,kBAAA;IACA,MAAA;IACA,QAAA;IACA,kBAAA;ECcF;AACF;ADbE;EACE,eAAA;EACA,4BAAA;EACA,gBAAA;ACeJ;ADdI;EACE,iCAAA;ACgBN;ADdI;EAPF;IAQI,eAAA;ECiBJ;AACF;;ADdA;EACE,aAAA;EACA,sBAAA;EACA,mBAAA;ACiBF;ADhBE;EAJF;IAKI,mBAAA;ECmBF;AACF;;ADjBA;EACE,0CAAA;EACA,eAAA;EACA,eAAA;EACA,4BAAA;EACA,iCAAA;ACoBF;;ADlBA;EACE,aAAA;ACqBF","file":"user_edit.css"} \ No newline at end of file +{"version":3,"sources":["user_edit.scss","user_edit.css"],"names":[],"mappings":"AAAA;EACE,gBAAA;EACA,iBAAA;EACA,iBAAA;ACCF;;ADCA;EACE,WAAA;EACA,MAAA;EACA,sBAAA;OAAA,iBAAA;EACA,aAAA;EACA,oBAAA;EACA,eAAA;EACA,sBAAA;EACA,mBAAA;ACEF;ADDE;EATF;IAUI,aAAA;ECIF;AACF;;ADDE;EADF;IAEI,sBAAA;ECKF;AACF;ADHI;EADF;IAEI,QAAA;ECMJ;AACF;ADHI;EADF;IAEI,SAAA;ECMJ;AACF;;ADHA;EACE,iBAAA;EACA,YAAA;EACA,mBAAA;ACMF;;ADJA;EACE,aAAA;EACA,6BAAA;EACA,eAAA;EACA,gBAAA;ACOF;ADNE;EALF;IAMI,iBAAA;IACA,eAAA;ECSF;AACF;;ADPA;EACE,aAAA;EACA,SAAA;EACA,mBAAA;EACA,iBAAA;ACUF;ADTE;EALF;IAMI,cAAA;IACA,eAAA;ECYF;AACF;;ADRE;EADF;IAEI,wBAAA;ECYF;AACF;;ADVA;EACE,YAAA;ACaF;;ADVA;EACE,mBAAA;ACaF;ADZE;EAFF;IAGI,kBAAA;IACA,MAAA;IACA,QAAA;IACA,kBAAA;ECeF;AACF;ADdE;EACE,eAAA;EACA,4BAAA;EACA,gBAAA;ACgBJ;ADfI;EACE,iCAAA;ACiBN;ADfI;EAPF;IAQI,eAAA;ECkBJ;AACF;;ADfA;EACE,aAAA;EACA,sBAAA;EACA,mBAAA;ACkBF;ADjBE;EAJF;IAKI,mBAAA;ECoBF;AACF;;ADlBA;EACE,0CAAA;EACA,eAAA;EACA,eAAA;EACA,4BAAA;EACA,iCAAA;ACqBF;;ADnBA;EACE,aAAA;ACsBF","file":"user_edit.css"} \ No newline at end of file diff --git a/petapp/static/petapp/page/user_editpage/css/user_edit.scss b/petapp/static/petapp/page/user_editpage/css/user_edit.scss index 5217031..83ec485 100644 --- a/petapp/static/petapp/page/user_editpage/css/user_edit.scss +++ b/petapp/static/petapp/page/user_editpage/css/user_edit.scss @@ -4,14 +4,13 @@ margin: 30px auto; } .edit-info { - max-width: 959px; width: 100%; gap: 0; column-gap: 122px; row-gap: 73px; padding-bottom: 20px; flex-wrap: wrap; - justify-content: center; + flex-direction: column; align-items: center; @media screen and (max-width: 500px) { row-gap: 17px; @@ -41,6 +40,7 @@ input.btn-login { margin-top: 0; color: var(--four-text-color); margin-right: 0; + margin-bottom: 0; @media screen and (max-width: 500px) { padding: 7px 33px; font-size: 16px; @@ -49,8 +49,8 @@ input.btn-login { .form__buttons { display: flex; gap: 25px; - margin-left: auto; align-items: center; + margin-left: 32em; @media screen and (max-width: 500px) { margin-left: 0; margin-top: 5px; @@ -62,6 +62,9 @@ input.btn-login { margin-top: 0 !important; } } +.reg__item{ + width: 303px; +} .user-leave { padding-right: 25px; diff --git a/petapp/static/petapp/page/userpage/css/user.css b/petapp/static/petapp/page/userpage/css/user.css index 5cfd6d3..7f1b680 100644 --- a/petapp/static/petapp/page/userpage/css/user.css +++ b/petapp/static/petapp/page/userpage/css/user.css @@ -32,6 +32,7 @@ .user-info .user__img { max-width: 110px; max-height: 110px; + border-radius: 70px; } .user-info .user__descript { font-family: "Gravity Light", sans-serif; diff --git a/petapp/static/petapp/page/userpage/css/user.css.map b/petapp/static/petapp/page/userpage/css/user.css.map index 4e06219..ae87b67 100644 --- a/petapp/static/petapp/page/userpage/css/user.css.map +++ b/petapp/static/petapp/page/userpage/css/user.css.map @@ -1 +1 @@ -{"version":3,"sources":["user.scss","user.css"],"names":[],"mappings":"AAAA;EACE,qBAAA;EACA,aAAA;EACA,iBAAA;EACA,iBAAA;EACA,eAAA;EACA,SAAA;ACCF;;ADCA;EACE,SAAA;EACA,sBAAA;EACA,aAAA;EACA,mBAAA;EACA,eAAA;EACA,WAAA;EACA,yCAAA;EACA,mBAAA;ACEF;;ADEE;;EAEE,SAAA;EACA,kBAAA;EACA,gBAAA;ACCJ;;ADGA;EACE,kBAAA;EACA,SAAA;ACAF;ADCE;EACE,gBAAA;EACA,iBAAA;ACCJ;ADCE;EACE,wCAAA;EACA,6BAAA;EACA,aAAA;EACA,eAAA;EACA,sBAAA;ACCJ;ADAI;EACE,0CAAA;EACA,eAAA;ACEN;ADAI;EACE,gBAAA;ACEN;ADAI;EACE,eAAA;ACEN;ADCE;EACE,sCAAA;EACA,eAAA;EACA,YAAA;EACA,SAAA;EACA,4BAAA;EACA,iBAAA;EACA,kBAAA;ACCJ;ADCE;EACE,iBAAA;EACA,gBAAA;EACA,aAAA;ACCJ;ADAI;EACE,eAAA;EACA,4BAAA;ACEN;;ADGA;;EAEE,6BAAA;EACA,0CAAA;EACA,2CAAA;EACA,aAAA;EACA,sBAAA;EACA,uBAAA;ACAF;;ADEA;EACE,2CAAA;ACCF;ADAE;EACE,eAAA;EACA,gBAAA;ACEJ;ADAE;EACE,wCAAA;EACA,aAAA;EACA,gBAAA;EACA,sBAAA;EACA,SAAA;EACA,YAAA;EACA,gBAAA;EACA,eAAA;ACEJ;ADAE;EACE,0CAAA;EACA,eAAA;EACA,6BAAA;EACA,wCAAA;EACA,kBAAA;EACA,YAAA;EACA,gBAAA;EACA,mBAAA;EACA,wCAAA;EACA,eAAA;ACEJ;ADDI;EACE,sCAAA;ACGN;;ADCA;EACE,6CAAA;ACEF","file":"user.css"} \ No newline at end of file +{"version":3,"sources":["user.scss","user.css"],"names":[],"mappings":"AAAA;EACE,qBAAA;EACA,aAAA;EACA,iBAAA;EACA,iBAAA;EACA,eAAA;EACA,SAAA;ACCF;;ADCA;EACE,SAAA;EACA,sBAAA;EACA,aAAA;EACA,mBAAA;EACA,eAAA;EACA,WAAA;EACA,yCAAA;EACA,mBAAA;ACEF;;ADEE;;EAEE,SAAA;EACA,kBAAA;EACA,gBAAA;ACCJ;;ADGA;EACE,kBAAA;EACA,SAAA;ACAF;ADCE;EACE,gBAAA;EACA,iBAAA;EACA,mBAAA;ACCJ;ADCE;EACE,wCAAA;EACA,6BAAA;EACA,aAAA;EACA,eAAA;EACA,sBAAA;ACCJ;ADAI;EACE,0CAAA;EACA,eAAA;ACEN;ADAI;EACE,gBAAA;ACEN;ADAI;EACE,eAAA;ACEN;ADCE;EACE,sCAAA;EACA,eAAA;EACA,YAAA;EACA,SAAA;EACA,4BAAA;EACA,iBAAA;EACA,kBAAA;ACCJ;ADCE;EACE,iBAAA;EACA,gBAAA;EACA,aAAA;ACCJ;ADAI;EACE,eAAA;EACA,4BAAA;ACEN;;ADGA;;EAEE,6BAAA;EACA,0CAAA;EACA,2CAAA;EACA,aAAA;EACA,sBAAA;EACA,uBAAA;ACAF;;ADEA;EACE,2CAAA;ACCF;ADAE;EACE,eAAA;EACA,gBAAA;ACEJ;ADAE;EACE,wCAAA;EACA,aAAA;EACA,gBAAA;EACA,sBAAA;EACA,SAAA;EACA,YAAA;EACA,gBAAA;EACA,eAAA;ACEJ;ADAE;EACE,0CAAA;EACA,eAAA;EACA,6BAAA;EACA,wCAAA;EACA,kBAAA;EACA,YAAA;EACA,gBAAA;EACA,mBAAA;EACA,wCAAA;EACA,eAAA;ACEJ;ADDI;EACE,sCAAA;ACGN;;ADCA;EACE,6CAAA;ACEF","file":"user.css"} \ No newline at end of file diff --git a/petapp/static/petapp/page/userpage/css/user.scss b/petapp/static/petapp/page/userpage/css/user.scss index cdefec7..4ad578d 100644 --- a/petapp/static/petapp/page/userpage/css/user.scss +++ b/petapp/static/petapp/page/userpage/css/user.scss @@ -32,6 +32,7 @@ .user__img { max-width: 110px; max-height: 110px; + border-radius: 70px; } .user__descript { font-family: "Gravity Light", sans-serif; diff --git a/petapp/templates/petapp/about.html b/petapp/templates/petapp/about.html index 7ef7750..11c42f9 100644 --- a/petapp/templates/petapp/about.html +++ b/petapp/templates/petapp/about.html @@ -27,20 +27,6 @@

Почему нас выбирают?

- -
-

Присоединяйтесь к акции PuffGift!

-

Получите уникальную возможность порадовать своих любимцев! Зарегистрируйтесь
- и получите на первую покупку 25% скидку. Кроме того, акция PuffGift дает возможность получать
первым - уведомления о новинках и розыгрышах.

-
- - -
-
- -
diff --git a/petapp/templates/petapp/auth.html b/petapp/templates/petapp/auth.html index 0728c00..5e73c19 100644 --- a/petapp/templates/petapp/auth.html +++ b/petapp/templates/petapp/auth.html @@ -15,26 +15,23 @@ -
diff --git a/petapp/templates/petapp/basket.html b/petapp/templates/petapp/basket.html index 57c9955..09ada93 100644 --- a/petapp/templates/petapp/basket.html +++ b/petapp/templates/petapp/basket.html @@ -15,53 +15,45 @@

Моя корзина

- + {% if basket_items %}
+ {% for item in basket_items %}
- product + product
-

989₽

-

Wagg / Корм для собак 5kg

-
- - - - - -
-
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- - - - - +

{{ item.product.price }} ₽

+

{{ item.product.product_name }} / {{ item.product.category }} для + {% for animal_type in item.product.animal_type.all %} + {{ animal_type.name }} + {% endfor %} + {{ item.product.weight }} kg

+
+

Количество: {{ item.quantity }}

+
+ - + + +
-
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
+ + +
+ {% for message in messages %} +
{{ message }}
+ {% endfor %}
+ {% endfor %}
+ {% else %} +

Ваша корзина пуста

+ {% endif %}
- -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/petapp/templates/petapp/catalog.html b/petapp/templates/petapp/catalog.html index 458e3d4..738bab1 100644 --- a/petapp/templates/petapp/catalog.html +++ b/petapp/templates/petapp/catalog.html @@ -14,152 +14,40 @@

Каталог

-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
- -
-
-
+ {% if message %} +

{{ message }}

+ {% else %} +
+ {% for product in products %} +
+ product +
+

{{ product.price }}₽

+

{{ product.product_name }} / {{ product.category }} для + {% for animal_type in product.animal_type.all %} + {{ animal_type.name }} + {% endfor %} + {{ product.weight }} kg

+
+ rate +

+ {% if product.rating_set.all %} + {% for rating in product.rating_set.all %} + {{ rating.rating }} + {% endfor %} + {% else %} + 0 + {% endif %}

+
+ +
+ {% for message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endfor %} +
+ {% endif %}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/petapp/templates/petapp/contact.html b/petapp/templates/petapp/contact.html index 34b257b..9b26347 100644 --- a/petapp/templates/petapp/contact.html +++ b/petapp/templates/petapp/contact.html @@ -62,7 +62,13 @@

позвоните нам

Желаете приобрести товар?

Авторизируйтесь для оформления заказа вашим любимцам

- + +
diff --git a/petapp/templates/petapp/main.html b/petapp/templates/petapp/main.html index 052657a..af1e546 100644 --- a/petapp/templates/petapp/main.html +++ b/petapp/templates/petapp/main.html @@ -22,94 +22,35 @@

Волшебные скидки и выгодные пр +{% if top_rated_products %}

Популярное

+ {% for product in top_rated_products %}
- product + product
-

989₽

-

Wagg / Корм для собак 5kg

+

{{ product.price }}₽

+

{{ product.product_name }} / {{ product.category }} для {% for animal_type in product.animal_type.all %} + {{ animal_type.name }} + {% endfor %} + {{ product.weight }} kg

rate -

4.9

-
-
-
-
- product -
-

499₽

-

Wagg / Корм для морских свинок 4kg

-
- rate -

4.8

-
-
-
-
- product -
-

299₽

-

Wagg / Угощение для кошек 60g

-
- rate -

4.8

-
-
-
-
- product -
-

989₽

-

Wagg / Корм для собак 5kg

-
- rate -

4.9

-
-
-
-
- product -
-

499₽

-

Wagg / Корм для морских свинок 4kg

-
- rate -

4.8

-
-
-
-
- product -
-

299₽

-

Wagg / Угощение для кошек 60g

-
- rate -

4.8

+

+ {% if product.avg_rating %} + {{ product.avg_rating }} + {% else %} + 0 + {% endif %} +

+ {% endfor %}
- - -
-

Присоединяйтесь к акции PuffGift!

-

Получите уникальную возможность порадовать своих любимцев! Зарегистрируйтесь
- и получите на первую покупку 25% скидку. Кроме того, акция PuffGift дает возможность получать
первым - уведомления о новинках и розыгрышах.

-
- - - - - -
-
- +{% endif %}
@@ -122,11 +63,11 @@

Почему нас выбирают?

благополучие наших питомцев - ваш приоритет, поэтому мы уделяем особое внимание
выбору качественных и безопасных продуктов для
животных.

-

Подробнее

+

Подробнее

products
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/petapp/templates/petapp/reg.html b/petapp/templates/petapp/reg.html index 7973bc9..040c938 100644 --- a/petapp/templates/petapp/reg.html +++ b/petapp/templates/petapp/reg.html @@ -16,39 +16,35 @@

Регистрация

-
+ + {% csrf_token %}
-
- - -
-
- - -
-
- - -
+ {% for field in form.visible_fields|slice:":3" %} +
+ {% if field.errors %} +

{{ field.errors }}

+ {% endif %} + + {{ field }} +
+ {% endfor %}
-
-
- - -
-
- - -
-
- - -
+ {% for field in form.visible_fields|slice:"3:" %} +
+ {% if field.errors %} +

{{ field.errors }}

+ {% endif %} + + {{ field }} +
+ {% endfor %} +
+ +
+ - -
diff --git a/petapp/templates/petapp/user.html b/petapp/templates/petapp/user.html index 0eec426..85fffa0 100644 --- a/petapp/templates/petapp/user.html +++ b/petapp/templates/petapp/user.html @@ -13,27 +13,32 @@ {% block content %}
-

Моя корзина

-

4 заказа

-

15.866 ₽

+ {% if total_items > 0 %} +

{{ total_items }} товара

+

{{ basket_total }} ₽

+ {% else %} +

Корзина пуста

+ {% endif %}
@@ -44,7 +49,7 @@

Моя корзина

Мои доставки

-

4 заказа

+

{{ total_items }} заказа

@@ -53,4 +58,4 @@

Мои доставки

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/petapp/templates/petapp/user_edit.html b/petapp/templates/petapp/user_edit.html index e44dbea..c25fb65 100644 --- a/petapp/templates/petapp/user_edit.html +++ b/petapp/templates/petapp/user_edit.html @@ -9,49 +9,59 @@ {% endblock %} -{% block title %}Страница редактированние данных{% endblock %} +{% block title %}Страница регистрации{% endblock %} {% block content %}
-
- avatar - -
-
-
-
- - -
-
- - -
-
- - -
+ + {% csrf_token %} +
+ +
+ {% if messages %} +
+ {% for message in messages %} +

{{ message }}

+ {% endfor %} +
+ {% endif %} + -
-
- - +
+
+
+ + +
+
+ + +
+
+ + +
-
- - -
-
- - +
+
+ + +
+
+ + +
+
+ + +
@@ -64,4 +74,4 @@
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/petapp/views.py b/petapp/views.py index a6187f7..b0f8586 100644 --- a/petapp/views.py +++ b/petapp/views.py @@ -1,13 +1,37 @@ -from django.shortcuts import render +import random +from django.shortcuts import render, get_object_or_404 +from petapp.models import Customer +from petapp.forms import * +from django.db import IntegrityError +from django.shortcuts import render, redirect +from django.contrib.auth import login +from django.contrib.auth import logout +from django.contrib.auth.models import User +from django.contrib.auth.decorators import login_required +from django.contrib import messages +from .authentication import EmailAuthBackend +from django.core.files.base import ContentFile +from django.db.models import Avg +from django.http import Http404, HttpResponse -# Create your views here. # Main navigation pages def index(request): - return render(request, 'petapp/main.html') + top_rated_products = Product.objects.annotate(avg_rating=Avg('rating__rating')).order_by('-avg_rating')[:3] + + return render(request, 'petapp/main.html', {'top_rated_products': top_rated_products}) def catalog(request): - return render(request, 'petapp/catalog.html') + product_name = Product.objects.all() + category = Product.objects.all() + animal_type = Product.objects.all() + rating = Rating.objects.all() + + if not Product.objects.exists(): + message = "Товаров временно нет" + return render(request, 'petapp/catalog.html', {'message': message}) + + return render(request, 'petapp/catalog.html', {'products': product_name, 'category' : category, 'animal_type' : animal_type, 'rating' : rating}) def contact(request): return render(request, 'petapp/contact.html') @@ -17,19 +41,296 @@ def about(request): # Auth and Reg pages -def auth(request): - return render(request, 'petapp/auth.html') - def reg(request): - return render(request, 'petapp/reg.html') + if request.method == 'POST': + auth_form = createUserForm(request.POST) + reg_form = RegForm(request.POST) + form = CombinedRegForm(request.POST) + + if form.is_valid(): + email = form.cleaned_data.get('email') + password = form.cleaned_data.get('password') + phone = form.cleaned_data.get('phone') + + + if not User.objects.filter(email=email).exists() and not Customer.objects.filter(phone=phone).exists(): + if 'last_name' in form.cleaned_data and 'first_name' in form.cleaned_data and 'patronymic' in form.cleaned_data: + if len(form.cleaned_data['last_name']) >= 2 and len(form.cleaned_data['first_name']) >= 2 and len(form.cleaned_data['patronymic']) >= 2: + auth_form.instance.username = f'{random.randrange(10000000)}' + user = auth_form.save() + user.set_password(user.password) + user = auth_form.save() + customer = reg_form.save(commit=False) + customer.user = user + + try: + customer.save() + user = EmailAuthBackend().authenticate(request=request, email=email, password=password) + if user is not None: + login(request, user) + return redirect('home') + + except IntegrityError: + form.add_error('phone', 'Пользователь с таким номером телефона уже существует.') + else: + form.add_error(None, 'Фамилия, имя и отчество должны содержать не менее 2 символов.') + else: + form.add_error(None, 'Фамилия, имя и отчество являются обязательными полями.') + else: + form.add_error(None, 'Пользователь с такой электронной почтой или номером телефона уже существует.') + + else: + form = CombinedRegForm() + + return render(request, 'petapp/reg.html', {'form': form}) + + +def email_login(request): + if request.user.is_authenticated: + return redirect('user') + + if request.method == 'POST': + form = EmailLoginForm(request.POST) + if form.is_valid(): + email = form.cleaned_data['email'] + password = form.cleaned_data['password'] + user = EmailAuthBackend().authenticate(request=request, email=email, password=password) + if user is not None: + login(request, user) + return redirect('home') + else: + form.add_error(None, "Ошибка аутентификации") + else: + form = EmailLoginForm() + return render(request, 'petapp/auth.html', {'form': form}) + +@login_required +def logout_view(request): + logout(request) + return redirect('home') + # Bakset page +@login_required def basket(request): - return render (request, 'petapp/basket.html') + customer = Customer.objects.get(user=request.user) + if not Basket.objects.filter(user=customer).exists(): + basket = Basket.objects.create(user=customer) + basket = Basket.objects.get(user=customer) + basket_items = BasketProduct.objects.filter(basket=basket).order_by('product_id') + basket_total = 0 + for a in basket_items: + basket_total += int(a.quantity) * int(a.product.price) + + if request.method == 'POST': + form = PaymentForm(request.POST) + if form.is_valid(): + amount = basket_total + + order_detail = OrderDetail.objects.create( + amount=amount, + basket=basket + ) + order = Order.objects.create( + user=customer, + details=order_detail + ) + return redirect('buy', order_num=order.id) + else: + form = PaymentForm() + + return render(request, 'petapp/basket.html', {"basket_items": basket_items, "basket_total": basket_total, "basket": basket, 'form': form}) + +@login_required +def add_basket(request, pk): + customer = Customer.objects.get(user=request.user) + if Basket.objects.filter(user=customer).exists(): + basket = Basket.objects.get(user=customer) + product = get_object_or_404(Product, pk=pk) + if BasketProduct.objects.filter(product=product, basket=basket).exists(): + basket_product = BasketProduct.objects.get(product=product, basket=basket) + if int(basket_product.quantity) >= basket_product.product.stock: + messages.error(request, "Ошибка, товаров на складе больше нет.") + else: + basket_items = BasketProduct.objects.filter(pk=basket_product.pk, basket=basket).update(quantity=int(basket_product.quantity) + 1) + else: + basket_items = BasketProduct.objects.create(product=product, basket=basket, quantity=1) + else: + basket = Basket.objects.create(user=customer) + product = get_object_or_404(Product, pk=pk) + basket_items = BasketProduct.objects.create(product=product, basket=basket, quantity=1) + return redirect('catalog') + + +def addition_basket(request, product, basket): + basket_product = BasketProduct.objects.get(pk=product) + product = basket_product.product + if int(basket_product.quantity) >= basket_product.product.stock: + messages.error(request, "Ошибка, товаров на складе больше нет.") + else: + basket_items = BasketProduct.objects.filter(pk=basket_product.pk, basket=basket).update(quantity=int(basket_product.quantity) + 1) + return redirect("basket") + +def subtraction_basket(request, product, basket): + basket_product = BasketProduct.objects.get(pk=product) + if basket_product.quantity == 1: + basket_product.delete() + else: + basket_items = BasketProduct.objects.filter(pk=product, basket_id=basket).update(quantity = int(basket_product.quantity) - 1) + return redirect("basket") #User page def user(request): - return render(request, 'petapp/user.html') + if request.user.is_authenticated: + customer = Customer.objects.get(user=request.user) + user = customer.user + + if not Basket.objects.filter(user=customer).exists(): + basket = Basket.objects.create(user=customer) + basket = Basket.objects.get(user=customer) + basket_items = BasketProduct.objects.filter(basket=basket).order_by('product_id') + total_items = 0 + basket_total = 0 + for a in basket_items: + basket_total += int(a.quantity) * int(a.product.price) + total_items += a.quantity + return render(request, 'petapp/user.html', {'user': user, 'customer': customer,"basket_total": basket_total, 'total_items': total_items}) def user_edit(request): - return render(request, 'petapp/user_edit.html') \ No newline at end of file + if request.user.is_authenticated: + customer = Customer.objects.get(user=request.user) + user = customer.user + + if request.method == 'POST': + user.last_name = request.POST.get('surname') + user.first_name = request.POST.get('name') + user.email = request.POST.get('email') + + if User.objects.filter(email=user.email).exclude(pk=user.pk).exists(): + messages.error(request, "Пользователь с такой электронной почтой или номером телефона уже существует.") + return render(request, 'petapp/user_edit.html', {'user': user, 'customer': customer}) + + if Customer.objects.filter(phone=customer.phone).exclude(pk=customer.pk).exists(): + messages.error(request, "Phone number must be unique.") + return render(request, 'petapp/user_edit.html', {'user': user, 'customer': customer}) + + name_validator = RegexValidator(regex=r'^[a-zA-Zа-яА-Я]{2,}$', message="ФИО должно содержать только русские или английские буквы и быть длиной не менее 2 символов.") + + try: + name_validator(user.last_name) + except ValidationError as e: + messages.error(request, e.message) + + try: + name_validator(user.first_name) + except ValidationError as e: + messages.error(request, e.message) + + patronymic = request.POST.get('patronymic') + if patronymic: + try: + name_validator(patronymic) + customer.patronymic = patronymic + except ValidationError as e: + messages.error(request, e.message) + + phone = request.POST.get('phone') + if phone: + if Customer.objects.filter(phone=phone).exclude(pk=customer.pk).exists(): + messages.error(request, "Пользователь с таким номером телефона уже существует.") + return render(request, 'petapp/user_edit.html', {'user': user, 'customer': customer}) + customer.phone = phone + + address = request.POST.get('address') + if address: + customer.address = address + + if 'photo_avatar' in request.FILES: + photo = request.FILES['photo_avatar'] + if not photo.name.endswith(('.jpg', '.jpeg', '.png')): + messages.error(request, "Допустимы только файлы формата JPG или PNG.") + + else: + if customer.photo_avatar: + old_photo_path = customer.photo_avatar.path + customer.photo_avatar.delete(save=False) + + new_photo_name = f"user_{user.id}_avatar" + customer.photo_avatar.save(f"{new_photo_name}.jpg", ContentFile(photo.read())) + + if not messages.get_messages(request): + user.save() + customer.save() + return redirect('user') + + return render(request, 'petapp/user_edit.html', {'user': user, 'customer': customer}) + + + + order = Order.objects.filter(id=order_num).first() + if order and order.details: + uuids = uuid.uuid4() + payment = Payment.create({ + "amount": { + "value": str(order.details.amount), + "currency": "RUB" + }, + "confirmation": { + "type": "redirect", + "return_url": f"http://127.0.0.1:8000/basket/buy/{order.id}/confirm-buy/" + }, + "capture": True, + "description": f"Заказ {order.id}" + }, uuids) + + payment_id = payment.id + order.details.payment_id = payment_id + order.details.status = 'processing' + order.details.save() + + return redirect(f'https://yoomoney.ru/checkout/payments/v2/contract?orderId={payment_id}') + else: + # Обработка случая, когда заказ не найден или не имеет деталей + return HttpResponse("Заказ не найден или не имеет деталей") + + + order = Order.objects.filter(order_number=pk).first() + payment = Payment.find_one(OrderDetail.payment_id) + + if payment.description == f"Заказ {order.id}" and order.order_number == pk and request.user == order.user: + if payment.status == "succeeded": + error = False + OrderDetail.status = 'paid' + order.save() + basket = get_object_or_404(Basket, user=request.user) + basket_items = BasketProduct.objects.filter(basket=basket).order_by('product_id') + + for item in basket_items: + product = Product.objects.get(pk=item.product.pk) + if PurchaseHistory.objects.filter(order_number=order, product=product).exists(): + break + else: + PurchaseHistory.objects.create(order_number=order, user=request.user, product=product, quantity=item.quantity) + + purchase = PurchaseHistory.objects.get(order_number=order, product=product) + + + product_detail = Product.objects.filter(user__isnull=True, product=product)[:item.quantity] + + for pr_det in product_detail: + purchase.details.add(pr_det) + pr_det.user = request.user + pr_det.save() + if product.stock - item.quantity == 0: + product.availability = True + else: + product.stock = product.stock - item.quantity + product.save() + + basket.delete() + return redirect('profile') + else: + error = True + return render(request, 'petapp/confirm.html', {'basket_items': basket_items, 'error':error}) + raise Http404("Произошла ошибка" ) + diff --git a/requirements.txt b/requirements.txt index 7ef8071..830359a 100644 Binary files a/requirements.txt and b/requirements.txt differ