Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2024e2f90c | |||
| 6ec0f4e732 | |||
| 35e7e93600 | |||
| a7f43483f0 | |||
| d773addc53 | |||
| b143d92cb2 |
+5
-4
@@ -4,10 +4,11 @@ ENV PYTHONDONTWRITEBYTECODE=1
|
|||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY pyproject.toml uv.lock /app/
|
COPY pyproject.toml uv.lock /app/
|
||||||
RUN apk --no-cache update && apk --no-cache upgrade
|
|
||||||
RUN pip install --no-cache-dir uv
|
RUN apk --no-cache update && apk --no-cache upgrade \
|
||||||
RUN uv sync --no-dev --no-install-project # Install only dependencies, not the local project package
|
&& pip install --no-cache-dir uv \
|
||||||
|
&& uv sync --no-dev --no-install-project # Install only dependencies, not the local project package
|
||||||
|
|
||||||
COPY . /app/
|
COPY . /app/
|
||||||
RUN chmod +x /app/entrypoint.sh
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
|||||||
+2
-10
@@ -147,14 +147,14 @@ WSGI_APPLICATION = 'proyecto.wsgi.application'
|
|||||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
|
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
|
||||||
# Usa PostgreSQL por defecto (POSTGRES_ENABLED=True); si no, SQLite.
|
# Usa PostgreSQL por defecto (POSTGRES_ENABLED=True); si no, SQLite.
|
||||||
|
|
||||||
if RUNNING_TESTS:
|
if RUNNING_TESTS or not env_bool('POSTGRES_ENABLED', True):
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': BASE_DIR / 'db.sqlite3',
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elif env_bool('POSTGRES_ENABLED', True):
|
else:
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
@@ -165,14 +165,6 @@ elif env_bool('POSTGRES_ENABLED', True):
|
|||||||
'PORT': env_int('POSTGRES_PORT', 5432),
|
'PORT': env_int('POSTGRES_PORT', 5432),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else:
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': BASE_DIR / 'db.sqlite3',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
|
||||||
|
|||||||
-101
@@ -1,101 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -u
|
|
||||||
|
|
||||||
readonly HOSTS=(
|
|
||||||
"aws-docker-mysql"
|
|
||||||
"aws-docker-redis"
|
|
||||||
"aws-docker-celery"
|
|
||||||
"aws-docker"
|
|
||||||
)
|
|
||||||
|
|
||||||
readonly WAIT_SECONDS=5
|
|
||||||
readonly REMOTE_DEPLOY_DIR="/root/deploys"
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
echo "Uso: $0 {start|stop|restart|update}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_status() {
|
|
||||||
local action="$1"
|
|
||||||
local host="$2"
|
|
||||||
local status="$3"
|
|
||||||
|
|
||||||
# Estilo similar al output de OpenRC.
|
|
||||||
printf "* %-8s %-16s [%s]\n" "$action" "$host" "$status"
|
|
||||||
}
|
|
||||||
|
|
||||||
run_remote_compose() {
|
|
||||||
local host="$1"
|
|
||||||
local command="$2"
|
|
||||||
|
|
||||||
ssh -o BatchMode=yes -o LogLevel=ERROR -T "$host" "sudo -n sh -c \"cd '$REMOTE_DEPLOY_DIR' || exit 1; if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then docker compose $command; elif command -v docker-compose >/dev/null 2>&1; then docker-compose $command; else exit 1; fi\"" >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
run_for_all_hosts() {
|
|
||||||
local mode="$1"
|
|
||||||
local host=""
|
|
||||||
local i=0
|
|
||||||
local total=${#HOSTS[@]}
|
|
||||||
|
|
||||||
for host in "${HOSTS[@]}"; do
|
|
||||||
case "$mode" in
|
|
||||||
start)
|
|
||||||
if run_remote_compose "$host" "up -d"; then
|
|
||||||
print_status "Started" "$host" "ok"
|
|
||||||
else
|
|
||||||
print_status "Started" "$host" "fail"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
if run_remote_compose "$host" "down"; then
|
|
||||||
print_status "Stopped" "$host" "ok"
|
|
||||||
else
|
|
||||||
print_status "Stopped" "$host" "fail"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
if run_remote_compose "$host" "down" && run_remote_compose "$host" "up -d"; then
|
|
||||||
print_status "Restarted" "$host" "ok"
|
|
||||||
else
|
|
||||||
print_status "Restarted" "$host" "fail"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
update)
|
|
||||||
if run_remote_compose "$host" "pull" && run_remote_compose "$host" "down" && run_remote_compose "$host" "up -d"; then
|
|
||||||
print_status "Updated" "$host" "ok"
|
|
||||||
else
|
|
||||||
print_status "Updated" "$host" "fail"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
i=$((i + 1))
|
|
||||||
if [ "$i" -lt "$total" ]; then
|
|
||||||
sleep "$WAIT_SECONDS"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ "$#" -ne 1 ]; then
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
start|stop|restart|update)
|
|
||||||
run_for_all_hosts "$1"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@@ -20,7 +20,6 @@ class UserAdmin(admin.ModelAdmin):
|
|||||||
def banear_usuario_action(self, request, queryset):
|
def banear_usuario_action(self, request, queryset):
|
||||||
usuarios_baneados = 0
|
usuarios_baneados = 0
|
||||||
for user in queryset:
|
for user in queryset:
|
||||||
user: User = user
|
|
||||||
# Desactiva usuario
|
# Desactiva usuario
|
||||||
if user.registration_status == User.RegisterStatus.BANNED:
|
if user.registration_status == User.RegisterStatus.BANNED:
|
||||||
continue
|
continue
|
||||||
@@ -43,7 +42,6 @@ class UserAdmin(admin.ModelAdmin):
|
|||||||
def desbanear_usuario_action(self, request, queryset):
|
def desbanear_usuario_action(self, request, queryset):
|
||||||
user_desbaneados = 0
|
user_desbaneados = 0
|
||||||
for user in queryset:
|
for user in queryset:
|
||||||
user: User = user
|
|
||||||
if user.registration_status != User.RegisterStatus.BANNED:
|
if user.registration_status != User.RegisterStatus.BANNED:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
IMAGE_TYPE = "image/*"
|
||||||
|
EMAIL_FORMNAME = "Correo Electrónico"
|
||||||
|
INCORRECT_PASSWORDS = "Las contraseñas no coinciden"
|
||||||
+12
-9
@@ -2,10 +2,13 @@ from django import forms
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import FileExtensionValidator, MinLengthValidator, MaxLengthValidator
|
from django.core.validators import FileExtensionValidator, MinLengthValidator, MaxLengthValidator
|
||||||
from .models import Category
|
from .models import Category
|
||||||
|
from .constants import *
|
||||||
ALLOWED_IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp']
|
ALLOWED_IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp']
|
||||||
ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
|
ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def validate_image_file(value):
|
def validate_image_file(value):
|
||||||
ext = value.name.split('.')[-1].lower()
|
ext = value.name.split('.')[-1].lower()
|
||||||
if ext not in ALLOWED_IMAGE_EXTENSIONS:
|
if ext not in ALLOWED_IMAGE_EXTENSIONS:
|
||||||
@@ -76,7 +79,7 @@ class ProductForm(forms.Form):
|
|||||||
widget = forms.ClearableFileInput(
|
widget = forms.ClearableFileInput(
|
||||||
attrs = {
|
attrs = {
|
||||||
'class': 'form-control',
|
'class': 'form-control',
|
||||||
'accept': 'image/*'
|
'accept': IMAGE_TYPE
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -133,7 +136,7 @@ class SecondaryImageForm(forms.Form):
|
|||||||
widget = forms.ClearableFileInput(
|
widget = forms.ClearableFileInput(
|
||||||
attrs = {
|
attrs = {
|
||||||
'class': 'form-control',
|
'class': 'form-control',
|
||||||
'accept': 'image/*'
|
'accept': IMAGE_TYPE
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -192,7 +195,7 @@ class UserRegisterForm(forms.Form):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
label = "Correo Electrónico",
|
label = EMAIL_FORMNAME,
|
||||||
max_length = 255,
|
max_length = 255,
|
||||||
required = True,
|
required = True,
|
||||||
widget = forms.TextInput(
|
widget = forms.TextInput(
|
||||||
@@ -236,7 +239,7 @@ class UserRegisterForm(forms.Form):
|
|||||||
password = cleaned_data.get("password")
|
password = cleaned_data.get("password")
|
||||||
password_confirm = cleaned_data.get("password_confirm")
|
password_confirm = cleaned_data.get("password_confirm")
|
||||||
if password and password_confirm and password != password_confirm:
|
if password and password_confirm and password != password_confirm:
|
||||||
raise ValidationError("Las contraseñas no coinciden.")
|
raise ValidationError(INCORRECT_PASSWORDS)
|
||||||
|
|
||||||
|
|
||||||
class EditProfileForm(forms.Form):
|
class EditProfileForm(forms.Form):
|
||||||
@@ -253,7 +256,7 @@ class EditProfileForm(forms.Form):
|
|||||||
widget=forms.TextInput(attrs={'class': 'form-control'})
|
widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||||
)
|
)
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
label="Correo Electrónico",
|
label=EMAIL_FORMNAME,
|
||||||
max_length=254,
|
max_length=254,
|
||||||
required=True,
|
required=True,
|
||||||
widget=forms.EmailInput(attrs={'class': 'form-control'})
|
widget=forms.EmailInput(attrs={'class': 'form-control'})
|
||||||
@@ -285,7 +288,7 @@ class ChangePasswordForm(forms.Form):
|
|||||||
new_password = cleaned_data.get("new_password")
|
new_password = cleaned_data.get("new_password")
|
||||||
confirm_password = cleaned_data.get("confirm_password")
|
confirm_password = cleaned_data.get("confirm_password")
|
||||||
if new_password and confirm_password and new_password != confirm_password:
|
if new_password and confirm_password and new_password != confirm_password:
|
||||||
raise ValidationError("Las contraseñas no coinciden.")
|
raise ValidationError(INCORRECT_PASSWORDS)
|
||||||
if new_password and len(new_password) < 8:
|
if new_password and len(new_password) < 8:
|
||||||
raise ValidationError("La contraseña debe tener al menos 8 caracteres.")
|
raise ValidationError("La contraseña debe tener al menos 8 caracteres.")
|
||||||
|
|
||||||
@@ -343,7 +346,7 @@ class ShippingAddressForm(forms.Form):
|
|||||||
|
|
||||||
class ResetPasswordForm(forms.Form):
|
class ResetPasswordForm(forms.Form):
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
label="Correo Electrónico",
|
label=EMAIL_FORMNAME,
|
||||||
max_length=254,
|
max_length=254,
|
||||||
required=True,
|
required=True,
|
||||||
widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'tu@email.com'})
|
widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'tu@email.com'})
|
||||||
@@ -369,7 +372,7 @@ class ResetPasswordPhase2Form(forms.Form):
|
|||||||
password = cleaned_data.get("password")
|
password = cleaned_data.get("password")
|
||||||
verify_password = cleaned_data.get("verify_password")
|
verify_password = cleaned_data.get("verify_password")
|
||||||
if password and verify_password and password != verify_password:
|
if password and verify_password and password != verify_password:
|
||||||
raise ValidationError("Las contraseñas no coinciden.")
|
raise ValidationError(INCORRECT_PASSWORDS)
|
||||||
|
|
||||||
|
|
||||||
class ReviewForm(forms.Form):
|
class ReviewForm(forms.Form):
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-05-26 08:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tienda', '0009_alter_cartitem_quantity_alter_orderitem_quantity_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cart',
|
||||||
|
name='session_key',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=40),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='session_key',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=40),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='stockreservation',
|
||||||
|
name='session_key',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=40),
|
||||||
|
),
|
||||||
|
]
|
||||||
+4
-3
@@ -48,6 +48,7 @@ class VerificationCode(models.Model):
|
|||||||
default = VerificationModes.VERIFY_ACCOUNT
|
default = VerificationModes.VERIFY_ACCOUNT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def generate(user: User, code_mode: str) -> VerificationCode:
|
def generate(user: User, code_mode: str) -> VerificationCode:
|
||||||
while True:
|
while True:
|
||||||
code = "".join(random.choices(string.ascii_letters+string.digits, k=64))
|
code = "".join(random.choices(string.ascii_letters+string.digits, k=64))
|
||||||
@@ -163,7 +164,7 @@ class StockReservation(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name="stock_reservations")
|
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name="stock_reservations")
|
||||||
session_key = models.CharField(max_length=40, null=True, blank=True)
|
session_key = models.CharField(max_length=40, default="", blank=True)
|
||||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_ACTIVE)
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_ACTIVE)
|
||||||
payment_method = models.CharField(max_length=20, choices=PAYMENT_CHOICES)
|
payment_method = models.CharField(max_length=20, choices=PAYMENT_CHOICES)
|
||||||
expires_at = models.DateTimeField(db_index=True)
|
expires_at = models.DateTimeField(db_index=True)
|
||||||
@@ -193,7 +194,7 @@ class StockReservationItem(models.Model):
|
|||||||
|
|
||||||
class Cart(models.Model):
|
class Cart(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
|
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
session_key = models.CharField(max_length=40, null=True, blank=True)
|
session_key = models.CharField(max_length=40, default="", blank=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
@@ -258,7 +259,7 @@ class Order(models.Model):
|
|||||||
|
|
||||||
buyer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='orders')
|
buyer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='orders')
|
||||||
shipping_address = models.ForeignKey('ShippingAddress', on_delete=models.SET_NULL, null=True, blank=True, related_name='orders')
|
shipping_address = models.ForeignKey('ShippingAddress', on_delete=models.SET_NULL, null=True, blank=True, related_name='orders')
|
||||||
session_key = models.CharField(max_length=40, null=True, blank=True)
|
session_key = models.CharField(max_length=40, default="", blank=True)
|
||||||
total = models.FloatField(default=0)
|
total = models.FloatField(default=0)
|
||||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_PAID)
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_PAID)
|
||||||
payment_method = models.CharField(max_length=20, choices=PAYMENT_CHOICES, default=PAYMENT_MANUAL)
|
payment_method = models.CharField(max_length=20, choices=PAYMENT_CHOICES, default=PAYMENT_MANUAL)
|
||||||
|
|||||||
+5
-2
@@ -17,15 +17,18 @@ class Recibo(FPDF):
|
|||||||
|
|
||||||
def generar_recibo(cliente: str, total: float, objetos: list, metodo_pago: str, transaction_code: str):
|
def generar_recibo(cliente: str, total: float, objetos: list, metodo_pago: str, transaction_code: str):
|
||||||
pdf = Recibo()
|
pdf = Recibo()
|
||||||
font_path = "/fonts/Roboto-Regular.ttf"
|
|
||||||
pdf.add_font('Roboto', '', '/fonts/Roboto-Regular.ttf')
|
pdf.add_font('Roboto', '', '/fonts/Roboto-Regular.ttf')
|
||||||
pdf.add_font('Roboto', 'B', '/fonts/Roboto-Bold.ttf')
|
pdf.add_font('Roboto', 'B', '/fonts/Roboto-Bold.ttf')
|
||||||
pdf.add_page()
|
pdf.add_page()
|
||||||
pdf.set_font('Roboto', size=12)
|
pdf.set_font('Roboto', size=12)
|
||||||
|
|
||||||
|
METODOS_MAP = {"stripe": "Stripe", "paypal": "PayPal", "manual": "Manual"}
|
||||||
|
metodo_mostrar = METODOS_MAP.get(metodo_pago, metodo_pago)
|
||||||
|
|
||||||
pdf.cell(0, 10, f"Cliente: {cliente}", ln=True)
|
pdf.cell(0, 10, f"Cliente: {cliente}", ln=True)
|
||||||
pdf.cell(0, 10, f"ID de transaccion: {transaction_code}", ln=True)
|
pdf.cell(0, 10, f"ID de transaccion: {transaction_code}", ln=True)
|
||||||
pdf.cell(0, 10, f"")
|
pdf.cell(0, 10, f"Metodo de pago: {metodo_mostrar}", ln=True)
|
||||||
|
pdf.cell(0, 10, "")
|
||||||
|
|
||||||
DATA = []
|
DATA = []
|
||||||
DATA.append(
|
DATA.append(
|
||||||
|
|||||||
Reference in New Issue
Block a user