fix: docker build

This commit is contained in:
2026-03-23 12:17:50 +01:00
parent 421047aec2
commit d7df0d0e50
12 changed files with 170 additions and 125 deletions
+18 -12
View File
@@ -1,20 +1,26 @@
FROM python:3.13-slim FROM python:3.13-alpine3.23 as builder
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV UV_SYSTEM_PYTHON=1 ENV UV_SYSTEM_PYTHON=1
COPY pyproject.toml .
RUN pip install uv && uv pip install -r pyproject.toml
RUN mkdir /code FROM python:3.13-alpine3.23
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
WORKDIR /code WORKDIR /code
COPY pyproject.toml /code
RUN apt-get update && \ # Set user and group
apt-get install -y gcc git nginx gettext vim libpq-dev && \ ARG user=apps
pip install uv && \ ARG uid=1001
uv pip install -r pyproject.toml && \ ARG gid=1001
apt-get autoremove -y RUN groupadd -g ${gid} ${user}
RUN useradd -u ${uid} -g ${user} -s /bin/sh -m ${user}
COPY . /code RUN apk update && apk add gcc gettext vim libpq-dev
RUN chown -R ${user}:${user} /code
USER ${user}
COPY --chown=${user}:${user} . /code
RUN ["sh", "./scripts/prebuild.sh"]
CMD ["sh", "./scripts/run.sh"] CMD ["sh", "./scripts/run.sh"]
+31 -1
View File
@@ -103,7 +103,10 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'es' LANGUAGE_CODE = 'es'
LANGUAGES = (('en', _('Inglés')), ('es', _('Castellano'))) LANGUAGES = (
('en', _('Inglés')),
('es', _('Castellano')),
)
TIME_ZONE = 'Europe/Madrid' TIME_ZONE = 'Europe/Madrid'
@@ -143,3 +146,30 @@ EMAIL_USE_SSL = False
CELERY_BROKER_URL = env.str('CELERY_BROKER_URL', default='memory://localhost:8000//') CELERY_BROKER_URL = env.str('CELERY_BROKER_URL', default='memory://localhost:8000//')
CELERY_TIME_ZONE = TIME_ZONE CELERY_TIME_ZONE = TIME_ZONE
CELERY_ALWAYS_EAGER = DEBUG CELERY_ALWAYS_EAGER = DEBUG
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
},
'file': {
'class': 'logging.FileHandler',
'filename': 'error.log',
'level': 'ERROR',
},
},
'loggers': {
'': {
'handlers': ['console'],
'level': 'INFO',
},
'error': {
'handlers': ['file'],
'level': 'ERROR',
'propagate': False
},
},
}
-44
View File
@@ -1,44 +0,0 @@
server {
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log notice;
listen 80;
# Static files
location /static {
alias /code/static;
}
# Media files
location /media {
alias /code/media;
}
# Reverse proxy
location / {
proxy_pass http://127.0.0.1:8000;
proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
# security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
}
+1 -1
View File
@@ -6,7 +6,7 @@ readme = "README.md"
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"boto3==1.34.105", "boto3==1.34.105",
"celery==5.4.0", "celery==5.6.2",
"django==6.0.2", "django==6.0.2",
"django-celery-beat==2.9.0", "django-celery-beat==2.9.0",
"django-cors-headers==3.13.0", "django-cors-headers==3.13.0",
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
python manage.py collectstatic
python manage.py compilemessages
-3
View File
@@ -8,9 +8,6 @@ WORKER_NUM_PROCESSES=${WORKER_NUM_PROCESSES:=1}
RUN_SERVER=${RUN_SERVER:="FALSE"} RUN_SERVER=${RUN_SERVER:="FALSE"}
RUN_CELERY=${RUN_CELERY:="FALSE"} RUN_CELERY=${RUN_CELERY:="FALSE"}
python manage.py compilemessages
python manage.py build_tailwind_theme
if [ $RUN_SERVER = "TRUE" ]; then if [ $RUN_SERVER = "TRUE" ]; then
uvicorn --workers $ASGI_WORKERS --host $ASGI_HOST --port $ASGI_PORT config.asgi:application uvicorn --workers $ASGI_WORKERS --host $ASGI_HOST --port $ASGI_PORT config.asgi:application
elif [ $RUN_CELERY = "TRUE" ]; then elif [ $RUN_CELERY = "TRUE" ]; then
+56 -13
View File
@@ -41,7 +41,11 @@ class Tag(models.Model):
class ProductCategory(models.Model): class ProductCategory(models.Model):
name = models.CharField(max_length=32) name = models.CharField(max_length=32)
parent = models.ForeignKey( parent = models.ForeignKey(
'shop.ProductCategory', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('categoría padre') 'shop.ProductCategory',
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name=_('categoría padre'),
) )
promoted = models.BooleanField(default=False, verbose_name=_('promocionada')) promoted = models.BooleanField(default=False, verbose_name=_('promocionada'))
hidden = models.BooleanField(default=False, verbose_name=_('oculta')) hidden = models.BooleanField(default=False, verbose_name=_('oculta'))
@@ -101,7 +105,11 @@ class ProductPrice(TimestampedModel):
) )
tax = models.ForeignKey('shop.Tax', on_delete=models.PROTECT, verbose_name=_('impuesto aplicable')) tax = models.ForeignKey('shop.Tax', on_delete=models.PROTECT, verbose_name=_('impuesto aplicable'))
price_with_tax = models.DecimalField( price_with_tax = models.DecimalField(
max_digits=11, decimal_places=2, blank=True, null=True, verbose_name=_('precio con impuestos') max_digits=11,
decimal_places=2,
blank=True,
null=True,
verbose_name=_('precio con impuestos'),
) )
current = models.BooleanField(default=False, verbose_name=_('es el precio actual')) current = models.BooleanField(default=False, verbose_name=_('es el precio actual'))
@@ -329,7 +337,9 @@ class Order(TimestampedModel):
status = models.CharField(max_length=3, default=Statuses.STATUS_PENDING, verbose_name=_('estado')) status = models.CharField(max_length=3, default=Statuses.STATUS_PENDING, verbose_name=_('estado'))
shipping_status = models.CharField( shipping_status = models.CharField(
max_length=3, default=ShippingStatuses.STATUS_NOT_READY, verbose_name=_('estado de envío') max_length=3,
default=ShippingStatuses.STATUS_NOT_READY,
verbose_name=_('estado de envío'),
) )
base_total = models.DecimalField( base_total = models.DecimalField(
@@ -357,15 +367,25 @@ class Order(TimestampedModel):
contact_phone = models.CharField(max_length=32, blank=True, verbose_name=_('teléfono de contacto')) contact_phone = models.CharField(max_length=32, blank=True, verbose_name=_('teléfono de contacto'))
amount_paid = models.DecimalField( amount_paid = models.DecimalField(
default=Decimal('0.00'), max_digits=11, decimal_places=2, verbose_name=_('cantidad pagada') default=Decimal('0.00'),
max_digits=11, decimal_places=2,
verbose_name=_('cantidad pagada'),
) )
shipping_method = models.ForeignKey( shipping_method = models.ForeignKey(
'shop.ShippingMethod', blank=True, null=True, on_delete=models.SET_NULL, verbose_name=_('método de envío') 'shop.ShippingMethod',
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name=_('método de envío'),
) )
from_cart = models.ForeignKey( from_cart = models.ForeignKey(
'shop.Cart', on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('carrito de origen de pedido') 'shop.Cart',
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name=_('carrito de origen de pedido'),
) )
def calculate_total_from_lines(self): def calculate_total_from_lines(self):
@@ -407,7 +427,11 @@ class CustomerAddress(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, verbose_name=_('usuario')) user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, verbose_name=_('usuario'))
vat_id = models.CharField( vat_id = models.CharField(
max_length=16, blank=True, null=True, default='', verbose_name=_('número de identificación fiscal') max_length=16,
blank=True,
null=True,
default='',
verbose_name=_('número de identificación fiscal'),
) )
full_name = models.CharField(max_length=128, blank=False, null=False, verbose_name=_('dirección')) full_name = models.CharField(max_length=128, blank=False, null=False, verbose_name=_('dirección'))
address = models.CharField(max_length=128, blank=False, null=False, verbose_name=_('dirección')) address = models.CharField(max_length=128, blank=False, null=False, verbose_name=_('dirección'))
@@ -415,12 +439,19 @@ class CustomerAddress(models.Model):
address_zip = models.CharField(max_length=16, blank=False, null=False, verbose_name=_('código postal')) address_zip = models.CharField(max_length=16, blank=False, null=False, verbose_name=_('código postal'))
address_state = models.CharField(max_length=64, blank=False, null=False, verbose_name=_('provincia')) address_state = models.CharField(max_length=64, blank=False, null=False, verbose_name=_('provincia'))
address_country = models.CharField( address_country = models.CharField(
max_length=64, default=settings.SHOP_COUNTRY, blank=False, null=False, verbose_name=_('país') max_length=64,
default=settings.SHOP_COUNTRY,
blank=False,
null=False,
verbose_name=_('país'),
) )
address_phone = models.CharField(max_length=16, blank=True, null=True, default='', verbose_name=_('teléfono')) address_phone = models.CharField(max_length=16, blank=True, null=True, default='', verbose_name=_('teléfono'))
address_type = models.CharField( address_type = models.CharField(
max_length=4, choices=Types.choices, default=Types.SHIPPING, verbose_name=_('tipo de dirección') max_length=4,
choices=Types.choices,
default=Types.SHIPPING,
verbose_name=_('tipo de dirección'),
) )
email = models.EmailField(blank=False, null=False, verbose_name=_('e-mail')) email = models.EmailField(blank=False, null=False, verbose_name=_('e-mail'))
default = models.BooleanField(default=True, verbose_name=_('por defecto')) default = models.BooleanField(default=True, verbose_name=_('por defecto'))
@@ -463,13 +494,19 @@ class Payment(models.Model):
hash = models.UUIDField(verbose_name=_('Hash'), default=uuid4, primary_key=True) hash = models.UUIDField(verbose_name=_('Hash'), default=uuid4, primary_key=True)
amount = models.DecimalField(max_digits=8, decimal_places=2, default=Decimal('0'), verbose_name=_('Cantidad')) amount = models.DecimalField(max_digits=8, decimal_places=2, default=Decimal('0'), verbose_name=_('Cantidad'))
order = models.ForeignKey( order = models.ForeignKey(
'shop.Order', on_delete=models.PROTECT, verbose_name=_('pedido'), related_name='payments' 'shop.Order',
on_delete=models.PROTECT,
verbose_name=_('pedido'),
related_name='payments',
) )
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('cliente')) user = models.ForeignKey(User, blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('cliente'))
creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('Fecha de creación')) creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('Fecha de creación'))
metadata = models.JSONField(default=dict, verbose_name=_('Metadata')) metadata = models.JSONField(default=dict, verbose_name=_('Metadata'))
method = models.CharField( method = models.CharField(
max_length=6, choices=MethodChoices.choices, default=MethodChoices.REDSYS, verbose_name=_('método de pago') max_length=6,
choices=MethodChoices.choices,
default=MethodChoices.REDSYS,
verbose_name=_('método de pago'),
) )
class Meta: class Meta:
@@ -480,10 +517,16 @@ class Payment(models.Model):
class ShopSettings(SingletonModel): class ShopSettings(SingletonModel):
debug = models.BooleanField(default=True, verbose_name=_('Modo depuración')) debug = models.BooleanField(default=True, verbose_name=_('Modo depuración'))
merchant_code = models.CharField( merchant_code = models.CharField(
max_length=9, blank=False, null=False, verbose_name=_('Identificación de comercio') max_length=9,
blank=False,
null=False,
verbose_name=_('Identificación de comercio'),
) )
currency_code = models.CharField( currency_code = models.CharField(
max_length=4, default='978', choices=CURRENCY_CODES, verbose_name=_('Código de moneda') max_length=4,
default='978',
choices=CURRENCY_CODES,
verbose_name=_('Código de moneda'),
) # 978 == EURO ) # 978 == EURO
terminal = models.CharField(max_length=8, verbose_name=_('Terminal')) terminal = models.CharField(max_length=8, verbose_name=_('Terminal'))
shared_secret = models.CharField(max_length=100, verbose_name=_('Clave de Redsys')) shared_secret = models.CharField(max_length=100, verbose_name=_('Clave de Redsys'))
+24 -22
View File
@@ -7,14 +7,13 @@ from decimal import Decimal
import pyDes import pyDes
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db import transaction from django.db import transaction
from django.db.models import Sum from django.http import HttpRequest
from django.utils import timezone from django.utils import timezone
from django.utils.text import gettext_lazy as _ from django.utils.text import gettext_lazy as _
from shop.exceptions import RedsysPaymentException, RedsysValidationException from shop.exceptions import RedsysPaymentException, RedsysValidationException
from shop.models import Cart, CartItem, Order, OrderLine, Payment, Product, ProductBatch, ProductPrice, ShippingMethod from shop.models import Cart, CartItem, Order, OrderLine, Payment, Product, ProductBatch, ProductPrice, ShippingMethod
from shop.settings import ERROR_CODES from shop.settings import ERROR_CODES
from shop.signals import clear_cart
User = get_user_model() User = get_user_model()
@@ -69,7 +68,7 @@ def create_order(
def delete_product_batch(batch: ProductBatch): def delete_product_batch(batch: ProductBatch):
product = batch.product product = batch.product
product.stock = max(product.stock - batch.quantity, 0) product.stock = max(product.stock - batch.quantity, Decimal('0.00'))
product.save() product.save()
batch.delete() batch.delete()
@@ -77,20 +76,20 @@ def delete_product_batch(batch: ProductBatch):
def create_order_from_cart( def create_order_from_cart(
cart: Cart, cart: Cart,
shipping_method: ShippingMethod, shipping_method: ShippingMethod,
billing_address_full_name='', billing_address_full_name: str = '',
billing_address_address='', billing_address_address: str = '',
billing_address_town='', billing_address_town: str = '',
billing_address_state='', billing_address_state: str = '',
billing_address_country='', billing_address_country: str = '',
billing_address_zip='', billing_address_zip: str = '',
shipping_address_full_name='', shipping_address_full_name: str = '',
shipping_address_address='', shipping_address_address: str = '',
shipping_address_town='', shipping_address_town: str = '',
shipping_address_state='', shipping_address_state: str = '',
shipping_address_country='', shipping_address_country: str = '',
shipping_address_zip='', shipping_address_zip: str = '',
shipping_address_phone='', shipping_address_phone: str = '',
email='', email: str = '',
): ):
order = Order.objects.create( order = Order.objects.create(
billing_address=f'{billing_address_full_name} {billing_address_address}', billing_address=f'{billing_address_full_name} {billing_address_address}',
@@ -155,7 +154,7 @@ def add_shipping_order_line(order, shipping_method):
order.calculate_total_from_lines() order.calculate_total_from_lines()
def compute_signature(salt, payload, key): def compute_signature(salt: str, payload: str, key: str) -> bytes:
""" """
:param salt: order number (Ds_Order or Ds_Merchant_Order) :param salt: order number (Ds_Order or Ds_Merchant_Order)
:param payload: Ds_MerchantParameters :param payload: Ds_MerchantParameters
@@ -171,7 +170,7 @@ def compute_signature(salt, payload, key):
return base64.b64encode(payload_hash) return base64.b64encode(payload_hash)
def validate_payment_for_order(request, order: Order) -> Decimal: def validate_payment_for_order(request: HttpRequest, order: Order) -> Decimal:
""" """
example_response_data = { example_response_data = {
'Ds_MerchantCode': '999008881', 'Ds_MerchantCode': '999008881',
@@ -262,15 +261,18 @@ def update_order_payment_status(order: Order):
order.save() order.save()
def delete_cart_items_from_order(order): def delete_cart_items_from_order(order: Order):
if order.status == Order.Statuses.STATUS_PAID and order.from_cart: if order.status == Order.Statuses.STATUS_PAID and order.from_cart:
CartItem.objects.filter(cart=order.from_cart).delete() CartItem.objects.filter(cart=order.from_cart).delete()
def add_payment_to_order(order: Order, amount): def add_payment_to_order(order: Order, amount: Decimal):
with transaction.atomic(): with transaction.atomic():
payment = Payment.objects.create( payment = Payment.objects.create(
amount=amount, order=order, user=order.user, method=Payment.MethodChoices.REDSYS amount=amount,
order=order,
user=order.user,
method=Payment.MethodChoices.REDSYS,
) )
order.amount_paid += payment.amount order.amount_paid += payment.amount
Generated
+17 -19
View File
@@ -100,7 +100,7 @@ wheels = [
[[package]] [[package]]
name = "celery" name = "celery"
version = "5.4.0" version = "5.6.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "billiard" }, { name = "billiard" },
@@ -110,12 +110,12 @@ dependencies = [
{ name = "click-repl" }, { name = "click-repl" },
{ name = "kombu" }, { name = "kombu" },
{ name = "python-dateutil" }, { name = "python-dateutil" },
{ name = "tzdata" }, { name = "tzlocal" },
{ name = "vine" }, { name = "vine" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/8a/9c/cf0bce2cc1c8971bf56629d8f180e4ca35612c7e79e6e432e785261a8be4/celery-5.4.0.tar.gz", hash = "sha256:504a19140e8d3029d5acad88330c541d4c3f64c789d85f94756762d8bca7e706", size = 1575692 } sdist = { url = "https://files.pythonhosted.org/packages/8f/9d/3d13596519cfa7207a6f9834f4b082554845eb3cd2684b5f8535d50c7c44/celery-5.6.2.tar.gz", hash = "sha256:4a8921c3fcf2ad76317d3b29020772103581ed2454c4c042cc55dcc43585009b", size = 1718802 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/90/c4/6a4d3772e5407622feb93dd25c86ce3c0fee746fa822a777a627d56b4f2a/celery-5.4.0-py3-none-any.whl", hash = "sha256:369631eb580cf8c51a82721ec538684994f8277637edde2dfc0dacd73ed97f64", size = 425983 }, { url = "https://files.pythonhosted.org/packages/dd/bd/9ecd619e456ae4ba73b6583cc313f26152afae13e9a82ac4fe7f8856bfd1/celery-5.6.2-py3-none-any.whl", hash = "sha256:3ffafacbe056951b629c7abcf9064c4a2366de0bdfc9fdba421b97ebb68619a5", size = 445502 },
] ]
[[package]] [[package]]
@@ -550,18 +550,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/04/da4d140d601609f1afd76c036be0b434165d701c1b9b660db895b4fa1d2f/django_watchman-1.3.0-py2.py3-none-any.whl", hash = "sha256:5f04300bd7fbdd63b8a883b2730ed1e4d9b0f9991133b33a1281134b81f466eb", size = 20090 }, { url = "https://files.pythonhosted.org/packages/f9/04/da4d140d601609f1afd76c036be0b434165d701c1b9b660db895b4fa1d2f/django_watchman-1.3.0-py2.py3-none-any.whl", hash = "sha256:5f04300bd7fbdd63b8a883b2730ed1e4d9b0f9991133b33a1281134b81f466eb", size = 20090 },
] ]
[[package]]
name = "djangorestframework"
version = "3.15.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2c/ce/31482eb688bdb4e271027076199e1aa8d02507e530b6d272ab8b4481557c/djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad", size = 1067420 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/b6/fa99d8f05eff3a9310286ae84c4059b08c301ae4ab33ae32e46e8ef76491/djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20", size = 1071235 },
]
[[package]] [[package]]
name = "executing" name = "executing"
version = "2.2.1" version = "2.2.1"
@@ -975,7 +963,6 @@ dependencies = [
{ name = "django-storages" }, { name = "django-storages" },
{ name = "django-unfold" }, { name = "django-unfold" },
{ name = "django-watchman" }, { name = "django-watchman" },
{ name = "djangorestframework" },
{ name = "gonk" }, { name = "gonk" },
{ name = "ipython" }, { name = "ipython" },
{ name = "pillow" }, { name = "pillow" },
@@ -1003,7 +990,7 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "boto3", specifier = "==1.34.105" }, { name = "boto3", specifier = "==1.34.105" },
{ name = "celery", specifier = "==5.4.0" }, { name = "celery", specifier = "==5.6.2" },
{ name = "dj-database-url", specifier = "==1.0.0" }, { name = "dj-database-url", specifier = "==1.0.0" },
{ name = "django", specifier = "==6.0.2" }, { name = "django", specifier = "==6.0.2" },
{ name = "django-celery-beat", specifier = "==2.9.0" }, { name = "django-celery-beat", specifier = "==2.9.0" },
@@ -1016,7 +1003,6 @@ requires-dist = [
{ name = "django-storages", specifier = "==1.13.2" }, { name = "django-storages", specifier = "==1.13.2" },
{ name = "django-unfold", specifier = "==0.42.0" }, { name = "django-unfold", specifier = "==0.42.0" },
{ name = "django-watchman", specifier = "==1.3.0" }, { name = "django-watchman", specifier = "==1.3.0" },
{ name = "djangorestframework", specifier = "==3.15.2" },
{ name = "gonk", specifier = "==0.6.1" }, { name = "gonk", specifier = "==0.6.1" },
{ name = "ipython", specifier = "==8.16.1" }, { name = "ipython", specifier = "==8.16.1" },
{ name = "pillow", specifier = "==11.0.0" }, { name = "pillow", specifier = "==11.0.0" },
@@ -1091,6 +1077,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521 }, { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521 },
] ]
[[package]]
name = "tzlocal"
version = "5.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026 },
]
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.6.3" version = "2.6.3"
+3 -1
View File
@@ -43,4 +43,6 @@ class StylingMixin:
field.widget.attrs.update({'class': ' '.join(self.classes)}) field.widget.attrs.update({'class': ' '.join(self.classes)})
if self.placeholder_for_field.get(field_name): if self.placeholder_for_field.get(field_name):
field.widget.attrs.update({'placeholder': self.placeholder_for_field.get(field_name)}) field.widget.attrs.update({
'placeholder': self.placeholder_for_field.get(field_name),
})
-2
View File
@@ -52,8 +52,6 @@
--leading-tight: 1.25; --leading-tight: 1.25;
--radius-md: 0.375rem; --radius-md: 0.375rem;
--radius-lg: 0.5rem; --radius-lg: 0.5rem;
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--animate-spin: spin 1s linear infinite; --animate-spin: spin 1s linear infinite;
--default-transition-duration: 150ms; --default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+16 -7
View File
@@ -10,7 +10,10 @@ from django.views.generic import TemplateView
from config.mixins import HTMXFormComponent from config.mixins import HTMXFormComponent
from shop.filters import ProductFilter from shop.filters import ProductFilter
from shop.models import CartItem, CustomerAddress, Product, ProductPrice, ShippingMethod, WishlistedProduct from shop.models import (
CartItem, CustomerAddress, Product, ProductPrice,
ShippingMethod, WishlistedProduct,
)
from users.forms.change_password import ChangePasswordForm from users.forms.change_password import ChangePasswordForm
from users.forms.info import UserInfoForm from users.forms.info import UserInfoForm
from web.forms import CustomerAddressForm from web.forms import CustomerAddressForm
@@ -140,7 +143,11 @@ class UserInfoFormComponentView(HTMXFormComponent):
return reverse('web:user_info_component') return reverse('web:user_info_component')
def get_initial_values(self, instance): def get_initial_values(self, instance):
return {'email': instance.email, 'first_name': instance.first_name, 'last_name': instance.last_name} return {
'email': instance.email,
'first_name': instance.first_name,
'last_name': instance.last_name
}
class ChangePasswordFormComponentView(HTMXFormComponent): class ChangePasswordFormComponentView(HTMXFormComponent):
@@ -165,18 +172,20 @@ class ListCustomerAddressComponent(TemplateView):
template_name = 'components/users/retrieve_update_address.html' template_name = 'components/users/retrieve_update_address.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
return {'customer_addresses': CustomerAddress.objects.filter(user=self.request.user)} return {
'customer_addresses': CustomerAddress.objects.filter(user=self.request.user)
}
def update_customer_address(request, pk): def update_customer_address(request, pk):
customer_address = get_object_or_404(CustomerAddress, pk=pk, user=request.user) customer_address = get_object_or_404(CustomerAddress, pk=pk, user=request.user)
form = CustomerAddressForm(request.POST, instance=customer_address) form = CustomerAddressForm(request.POST, instance=customer_address)
if form.is_valid(): if not form.is_valid():
form.save() return HttpResponse(form.errors, status=400)
return HttpResponse(status=200, headers={'HX-Trigger': 'updated_addresses'})
return HttpResponse(form.errors, status=400) form.save()
return HttpResponse(status=200, headers={'HX-Trigger': 'updated_addresses'})
list_products = ListProducts.as_view() list_products = ListProducts.as_view()