Compare commits
6 Commits
9d7a7f7432
...
2024e2f90c
| Author | SHA1 | Date | |
|---|---|---|---|
| 2024e2f90c | |||
| 6ec0f4e732 | |||
| 35e7e93600 | |||
| a7f43483f0 | |||
| d773addc53 | |||
| b143d92cb2 |
+5
-4
@@ -4,10 +4,11 @@ ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml uv.lock /app/
|
||||
RUN apk --no-cache update && apk --no-cache upgrade
|
||||
RUN pip install --no-cache-dir uv
|
||||
RUN uv sync --no-dev --no-install-project # Install only dependencies, not the local project package
|
||||
COPY pyproject.toml uv.lock /app/
|
||||
|
||||
RUN apk --no-cache update && apk --no-cache upgrade \
|
||||
&& pip install --no-cache-dir uv \
|
||||
&& uv sync --no-dev --no-install-project # Install only dependencies, not the local project package
|
||||
|
||||
COPY . /app/
|
||||
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
|
||||
# Usa PostgreSQL por defecto (POSTGRES_ENABLED=True); si no, SQLite.
|
||||
|
||||
if RUNNING_TESTS:
|
||||
if RUNNING_TESTS or not env_bool('POSTGRES_ENABLED', True):
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
elif env_bool('POSTGRES_ENABLED', True):
|
||||
else:
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
@@ -165,14 +165,6 @@ elif env_bool('POSTGRES_ENABLED', True):
|
||||
'PORT': env_int('POSTGRES_PORT', 5432),
|
||||
}
|
||||
}
|
||||
else:
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# 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):
|
||||
usuarios_baneados = 0
|
||||
for user in queryset:
|
||||
user: User = user
|
||||
# Desactiva usuario
|
||||
if user.registration_status == User.RegisterStatus.BANNED:
|
||||
continue
|
||||
@@ -43,7 +42,6 @@ class UserAdmin(admin.ModelAdmin):
|
||||
def desbanear_usuario_action(self, request, queryset):
|
||||
user_desbaneados = 0
|
||||
for user in queryset:
|
||||
user: User = user
|
||||
if user.registration_status != User.RegisterStatus.BANNED:
|
||||
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.validators import FileExtensionValidator, MinLengthValidator, MaxLengthValidator
|
||||
from .models import Category
|
||||
|
||||
from .constants import *
|
||||
ALLOWED_IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp']
|
||||
ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
|
||||
|
||||
|
||||
|
||||
|
||||
def validate_image_file(value):
|
||||
ext = value.name.split('.')[-1].lower()
|
||||
if ext not in ALLOWED_IMAGE_EXTENSIONS:
|
||||
@@ -76,7 +79,7 @@ class ProductForm(forms.Form):
|
||||
widget = forms.ClearableFileInput(
|
||||
attrs = {
|
||||
'class': 'form-control',
|
||||
'accept': 'image/*'
|
||||
'accept': IMAGE_TYPE
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -133,7 +136,7 @@ class SecondaryImageForm(forms.Form):
|
||||
widget = forms.ClearableFileInput(
|
||||
attrs = {
|
||||
'class': 'form-control',
|
||||
'accept': 'image/*'
|
||||
'accept': IMAGE_TYPE
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -192,7 +195,7 @@ class UserRegisterForm(forms.Form):
|
||||
)
|
||||
)
|
||||
email = forms.EmailField(
|
||||
label = "Correo Electrónico",
|
||||
label = EMAIL_FORMNAME,
|
||||
max_length = 255,
|
||||
required = True,
|
||||
widget = forms.TextInput(
|
||||
@@ -236,7 +239,7 @@ class UserRegisterForm(forms.Form):
|
||||
password = cleaned_data.get("password")
|
||||
password_confirm = cleaned_data.get("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):
|
||||
@@ -253,7 +256,7 @@ class EditProfileForm(forms.Form):
|
||||
widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||
)
|
||||
email = forms.EmailField(
|
||||
label="Correo Electrónico",
|
||||
label=EMAIL_FORMNAME,
|
||||
max_length=254,
|
||||
required=True,
|
||||
widget=forms.EmailInput(attrs={'class': 'form-control'})
|
||||
@@ -285,7 +288,7 @@ class ChangePasswordForm(forms.Form):
|
||||
new_password = cleaned_data.get("new_password")
|
||||
confirm_password = cleaned_data.get("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:
|
||||
raise ValidationError("La contraseña debe tener al menos 8 caracteres.")
|
||||
|
||||
@@ -343,7 +346,7 @@ class ShippingAddressForm(forms.Form):
|
||||
|
||||
class ResetPasswordForm(forms.Form):
|
||||
email = forms.EmailField(
|
||||
label="Correo Electrónico",
|
||||
label=EMAIL_FORMNAME,
|
||||
max_length=254,
|
||||
required=True,
|
||||
widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'tu@email.com'})
|
||||
@@ -369,7 +372,7 @@ class ResetPasswordPhase2Form(forms.Form):
|
||||
password = cleaned_data.get("password")
|
||||
verify_password = cleaned_data.get("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):
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def generate(user: User, code_mode: str) -> VerificationCode:
|
||||
while True:
|
||||
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")
|
||||
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)
|
||||
payment_method = models.CharField(max_length=20, choices=PAYMENT_CHOICES)
|
||||
expires_at = models.DateTimeField(db_index=True)
|
||||
@@ -193,7 +194,7 @@ class StockReservationItem(models.Model):
|
||||
|
||||
class Cart(models.Model):
|
||||
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)
|
||||
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')
|
||||
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)
|
||||
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)
|
||||
|
||||
+5
-2
@@ -17,15 +17,18 @@ class Recibo(FPDF):
|
||||
|
||||
def generar_recibo(cliente: str, total: float, objetos: list, metodo_pago: str, transaction_code: str):
|
||||
pdf = Recibo()
|
||||
font_path = "/fonts/Roboto-Regular.ttf"
|
||||
pdf.add_font('Roboto', '', '/fonts/Roboto-Regular.ttf')
|
||||
pdf.add_font('Roboto', 'B', '/fonts/Roboto-Bold.ttf')
|
||||
pdf.add_page()
|
||||
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"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.append(
|
||||
|
||||
Reference in New Issue
Block a user