b3184fbdfe
El módulo monolítico (~3200 líneas) se convierte en un paquete cuyo __init__.py re-exporta todas las vistas, de modo que `views.X`, `from home.views import X` y la cadena 'home.views.not_found' (handler404) siguen funcionando sin tocar urls.py. Módulos por sección: _base (imports comunes + require_paid_stripe), auth, pages, account, recruit, characters, store, points, guild, history, players y payments. Cada submódulo hereda los imports con `from ._base import *`; las dependencias entre secciones se resuelven con imports explícitos (guild->auth, recruit/history->account). Se eliminan dos definiciones duplicadas que quedaban muertas al cargar: - get_character_image (primera copia, idéntica a la efectiva) - store_novawow_success_view (stub sin SOAP, pisado por la versión real) Validado con `manage.py check` (0 issues), resolución de las 100 rutas y un chequeo estático de nombres no definidos por módulo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
174 lines
7.4 KiB
Python
174 lines
7.4 KiB
Python
from ._base import *
|
|
|
|
|
|
def store_novawow_cancel_view(request):
|
|
return render(request, 'store/store_novawow_cancel.html')
|
|
def store_novawow_view(request):
|
|
username = request.session.get('username')
|
|
if not username:
|
|
return redirect('login')
|
|
|
|
category_name = request.GET.get("category")
|
|
|
|
# Obtener personajes del usuario desde la base de datos acore_characters
|
|
with connections['acore_characters'].cursor() as cursor:
|
|
cursor.execute("""
|
|
SELECT name
|
|
FROM characters
|
|
WHERE account = (
|
|
SELECT id
|
|
FROM acore_auth.account
|
|
WHERE username = %s
|
|
)
|
|
""", [username])
|
|
characters = [row[0] for row in cursor.fetchall()]
|
|
|
|
# Obtener categorías e ítems
|
|
categories = Category.objects.prefetch_related("items").all()
|
|
store_data = {category.name: list(category.items.values()) for category in categories}
|
|
|
|
# Si se está solicitando una categoría específica, devolver solo los ítems de esa categoría
|
|
if category_name:
|
|
try:
|
|
category = Category.objects.get(name=category_name)
|
|
except Category.DoesNotExist:
|
|
return JsonResponse({'success': False, 'message': 'Categoría no encontrada'}, status=404)
|
|
return JsonResponse({'success': True, 'items': list(category.items.values())})
|
|
|
|
# Obtener carrito desde la sesión
|
|
carrito = request.session.get('carrito', [])
|
|
|
|
# Calcular el precio total con 2 decimales
|
|
total_price = sum(Decimal(item['precio']) for item in carrito)
|
|
total_price_with_iva = (total_price * Decimal('1.21')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
|
|
|
|
if request.method == 'POST':
|
|
# Obtener datos del body
|
|
data = json.loads(request.body)
|
|
character_name = data.get('character')
|
|
carrito_cliente = data.get('carrito') or []
|
|
|
|
if not character_name:
|
|
return JsonResponse({'success': False, 'message': 'Por favor, selecciona un personaje.'})
|
|
|
|
# Verificar si el personaje pertenece al usuario
|
|
if character_name not in characters:
|
|
return JsonResponse({'success': False, 'message': 'No tienes permiso para este personaje.'})
|
|
|
|
if not carrito_cliente:
|
|
return JsonResponse({'success': False, 'message': 'El carrito está vacío.'})
|
|
|
|
# VALIDAR ÍTEMS Y PRECIOS CONTRA LA BASE DE DATOS (nunca confiar en el
|
|
# precio ni en los ítems que manda el cliente).
|
|
try:
|
|
ids_solicitados = [int(item['id']) for item in carrito_cliente]
|
|
except (KeyError, TypeError, ValueError):
|
|
return JsonResponse({'success': False, 'message': 'Carrito no válido.'})
|
|
|
|
items_db = {i.item_id: i for i in Item.objects.filter(item_id__in=ids_solicitados)}
|
|
|
|
carrito = [] # carrito saneado que se guardará en sesión
|
|
total_price = Decimal('0.00')
|
|
for item in carrito_cliente:
|
|
item_id = int(item['id'])
|
|
db_item = items_db.get(item_id)
|
|
if not db_item:
|
|
return JsonResponse({'success': False, 'message': f'Ítem no disponible en la tienda (ID {item_id}).'})
|
|
try:
|
|
cantidad = max(1, int(item.get('cantidad', 1)))
|
|
except (TypeError, ValueError):
|
|
cantidad = 1
|
|
total_price += db_item.price * cantidad
|
|
carrito.append({'id': item_id, 'nombre': db_item.name, 'cantidad': cantidad})
|
|
|
|
total_price_with_iva = (total_price * Decimal('1.21')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
|
|
|
|
# Guardar en sesión el carrito YA VALIDADO (para la entrega tras el pago)
|
|
request.session['carrito'] = carrito
|
|
request.session.modified = True
|
|
|
|
# Descripción del producto a partir de los datos validados
|
|
product_description = ", ".join([f"{it['nombre']} x{it['cantidad']}" for it in carrito])
|
|
|
|
# Obtener detalles del usuario
|
|
with connections['acore_auth'].cursor() as cursor:
|
|
cursor.execute("SELECT id, email, last_ip FROM account WHERE username = %s", [username])
|
|
user_data = cursor.fetchone()
|
|
|
|
if not user_data:
|
|
return JsonResponse({'success': False, 'message': 'Cuenta no encontrada.'})
|
|
|
|
user_id, email, acore_ip = user_data # ID de la cuenta, email y la última IP
|
|
|
|
# Crear sesión de pago con Stripe
|
|
success_url = request.build_absolute_uri(reverse('store-novawow/success'))
|
|
cancel_url = request.build_absolute_uri(reverse('store-novawow/cancel'))
|
|
|
|
# Llamada corregida a create_checkout_session con todos los parámetros requeridos
|
|
stripe_response = create_checkout_session(
|
|
username=username,
|
|
email=email,
|
|
acore_ip=acore_ip,
|
|
account_id=user_id, # Pasar el account_id
|
|
amount=total_price_with_iva, # El precio con IVA
|
|
character_name=character_name,
|
|
success_url=success_url,
|
|
cancel_url=cancel_url,
|
|
product_name=f"Compra de ítems para el personaje: {character_name} : {product_description}"
|
|
)
|
|
|
|
if stripe_response["success"]:
|
|
request.session['store_novawow'] = character_name
|
|
return JsonResponse({
|
|
'success': True,
|
|
'session_id': stripe_response["session_id"],
|
|
'stripe_public_key': settings.STRIPE_PUBLIC_KEY
|
|
})
|
|
else:
|
|
return JsonResponse({'success': False, 'message': f'Error al iniciar el pago: {stripe_response["error"]}'})
|
|
|
|
return render(request, 'store/store_novawow.html', {
|
|
'characters': characters,
|
|
'store_data': store_data,
|
|
'categories': categories,
|
|
'total_price': total_price_with_iva
|
|
})
|
|
@require_paid_stripe(redirect_to='store-novawow')
|
|
def store_novawow_success_view(request):
|
|
# Procesar el comando SOAP tras el pago exitoso
|
|
character_name = request.session.get('store_novawow')
|
|
carrito = request.session.get('carrito', [])
|
|
|
|
if not character_name or not carrito:
|
|
return redirect('store-novawow')
|
|
|
|
# Contar correctamente los ítems por ID
|
|
item_counts = Counter(item["id"] for item in carrito)
|
|
|
|
# Generar la lista de ítems en formato "ID:Cantidad"
|
|
items_str = " ".join(f"{item_id}:{count}" for item_id, count in item_counts.items())
|
|
|
|
# Construir el comando SOAP
|
|
command = f".send items {character_name} 'Gracias por donar a Nova WoW' 'Aquí tienes tu donación' {items_str}"
|
|
|
|
# Ejecutar el comando SOAP
|
|
response = execute_soap_command(command)
|
|
|
|
# Limpiar la sesión después del envío
|
|
request.session.pop('store_novawow', None)
|
|
request.session.pop('carrito', None)
|
|
|
|
if response is None or response.strip() == "":
|
|
message = f"Los ítems han sido añadidos al personaje {character_name} correctamente."
|
|
return render(request, 'store/store_novawow_success.html', {'message': message})
|
|
else:
|
|
return render(request, 'store/store_novawow_success.html', {'message': f"Error: {response}"})
|
|
def store_novawow_buscar_categoria_view(request, categoria):
|
|
try:
|
|
# Buscar la categoría desde el parámetro de la URL
|
|
category = Category.objects.get(name=categoria)
|
|
items = category.items.all().values("id", "name", "price", "image_url")
|
|
return JsonResponse({"success": True, "items": list(items)})
|
|
except Category.DoesNotExist:
|
|
return JsonResponse({"success": False, "message": "Categoría no encontrada"}, status=404)
|