Compare commits

..

3 Commits

Author SHA1 Message Date
Daniel (elordenador) 4661bcdffd Merge pull request #62 from dsaub/development
Merge entire work to latest
2026-05-04 13:43:50 +02:00
Daniel (elordenador) 27c06fe0b5 Merge pull request #33 from dsaub/development
Build and Push Docker Image / test (push) Waiting to run
Build and Push Docker Image / docker (push) Blocked by required conditions
Fix mobile header alignment and improve navbar responsiveness
2026-04-20 15:57:04 +02:00
Daniel (elordenador) 44bf6df686 Merge pull request #22 from dsaub/development
Enhance stock management, payment systems, and testing coverage
2026-04-20 12:25:33 +02:00
27 changed files with 198584 additions and 88 deletions
-1
View File
@@ -8,4 +8,3 @@ __pycache__/
tienda/__pycache__/ tienda/__pycache__/
proyecto/__pycache__/ proyecto/__pycache__/
media media
staticfiles
-61
View File
@@ -1,61 +0,0 @@
# AGENTS.md - Django Tienda Project
## Commands
```bash
# Run tests (runs tienda/tests.py by default)
make test
# Or manually
python manage.py test
# Run dev server
python manage.py runserver
# Run migrations
python manage.py migrate
# Static files (production)
python manage.py collectstatic
```
## Prerequisites
- **Redis**: Must be running on `redis://127.0.0.1:6379/1` for sessions and caching
- Start: `sudo systemctl start redis-server` (Linux) or `brew services start redis` (macOS)
- Verify: `redis-cli ping` → PONG
- **PostgreSQL**: Default database; set `POSTGRES_ENABLED=False` to use SQLite
- **Environment**: Copy `.env.example` to `.env` and configure required vars
## Important Quirks
1. **Migrations**: If `makemigrations` fails with error code 130, **check `tienda/migrations/`** - the file is often created despite the error
2. **Test DB**: Uses SQLite regardless of POSTGRES_ENABLED (hardcoded in settings.py)
3. **App URL**: Not at `/` - access at `http://localhost:8000/tienda/`
4. **Admin**: At `/admin/` (not `/tienda/admin/`)
5. **Custom User Model**: AUTH_USER_MODEL = 'tienda.User' - use for all user-related queries
## Architecture
- `proyecto/` - Django settings, URLs, WSGI/ASGI
- `tienda/` - Main app (models, views, admin, templates)
- `tienda/static/` - CSS, JS, images, fonts
- Templates extend `tienda/templates/tienda/base.html`
## Shipping Restrictions
Only sells to Almería province, Spain (postal codes 04xxx). All addresses saved with country "España".
## External Services
- **Payment**: Stripe + PayPal (configured via .env)
- **Storage**: S3 support - set `S3_ENABLE=True` to enable
- **Email**: SMTP required (see .env.example)
- **Async**: Celery uses Redis broker
## Useful References
- Full developer docs: `.github/copilot-instructions.md`
- Redis setup: `docs/REDIS_SETUP.md`
- PayPal: `docs/PAYPAL_SETUP.md`, `docs/PAYPAL_TROUBLESHOOTING.md`
- View documentation: `docs/views/`
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -6
View File
@@ -33,11 +33,7 @@ def enviar_correo_confirmacion(id: int):
@shared_task @shared_task
def enviar_correo_recuperacion(email: str): def enviar_correo_recuperacion(email: str):
usuario: User | None
try:
usuario = User.objects.get(email=email) usuario = User.objects.get(email=email)
except User.DoesNotExist as e:
usuario = None
if usuario is not None: if usuario is not None:
ver_code = VerificationCode.objects.create( ver_code = VerificationCode.objects.create(
code_mode = VerificationCode.VerificationModes.RESET_PASSWORD, code_mode = VerificationCode.VerificationModes.RESET_PASSWORD,
@@ -57,8 +53,7 @@ def enviar_correo_recuperacion(email: str):
) )
send_hemail(email, "Reset de Contraseña", html_content, "Estas reseteando la contraseña...") send_hemail(email, "Reset de Contraseña", html_content, "Estas reseteando la contraseña...")
else:
print("User does not exist, Cancelling TASK.")
# Purchased items should be a list of dictionary, the dictionary must follow this tags: amount, product name, price (each) # Purchased items should be a list of dictionary, the dictionary must follow this tags: amount, product name, price (each)
@shared_task @shared_task
+1 -18
View File
@@ -240,24 +240,7 @@ def login(request: HttpRequest):
# Autenticar usuario # Autenticar usuario
user = authenticate(request, username=username, password=password) user = authenticate(request, username=username, password=password)
if user is None: if user is None: # Bug de error 500 en caso de fallar la contra
data: str = cache.get(f"tries_login_{username}")
logins: int
if data is None:
logins = int(data)
else:
logins = 0
if logins >= 5:
# Si ha fallado 5 intentos de login...
audit_logger.info(
"LOGIN_FAILED email=%s reason=rate_limited", username
)
messages.error(request, "Has sufrido de Rate Limit por fallar 5 veces la contraseña")
return render(request, "tienda/login.html")
logins+=1
cache.set(f"tries_login_{username}", str(logins), 600)
messages.error(request, "Correo electrónico o contraseña incorrectos.") messages.error(request, "Correo electrónico o contraseña incorrectos.")
return render(request, "tienda/login.html") return render(request, "tienda/login.html")
user = User.objects.get(username=user.username) user = User.objects.get(username=user.username)