Files
NovaWoW/home/models.py
T
Inna 758435fe77 Corrige bugs de la revisión de home/views.py (dinero, bypass de pago, 500s)
Seguridad / dinero:
- store_novawow_view: recalcula precios y valida ítems contra la BD (Item) en vez
  de confiar en el precio/ítems que manda el cliente; guarda en sesión un carrito
  ya validado para la entrega
- *_success_view (x9): @require_paid_stripe verifica el pago real en Stripe por
  session_id (Stripe lo añade a la success_url) y marca StripeLog.fulfilled para
  evitar re-entregas; cierra el bypass de ir directo a la success-url sin pagar
- gold_character_view: deja de truncar el precio (int(4.99)->4); pasa el precio real

Correctitud / 500s:
- change_password_view: .get(...,'') evita AttributeError si falta un campo
- revive_character_view: lógica de éxito corregida (vacío=éxito, no vacío=error,
  None=fallo de comunicación) — antes marcaba error como éxito y caía a HTML
- store category y recruit_a_friend: guardas para Category.DoesNotExist y cuenta None

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 17:56:20 +00:00

313 lines
12 KiB
Python

# home/models.py
from django.db import models
from django_ckeditor_5.fields import CKEditor5Field
from django.core.exceptions import ValidationError
from django.utils import timezone
import uuid
from django.contrib.auth.models import User
import secrets
class Noticia(models.Model):
titulo = models.CharField(max_length=200)
contenido = CKEditor5Field('Contenido', config_name='default')
fecha_publicacion = models.DateTimeField(auto_now=True) # Cambiado a DateTimeField
fecha_fin_evento = models.DateField(null=True, blank=True)
enlace = models.URLField(max_length=200, blank=True, null=True)
def __str__(self):
return self.titulo
class MaintenanceMode(models.Model):
is_active = models.BooleanField(default=False)
def __str__(self):
return "Maintenance Mode"
class UnstuckHistory(models.Model):
character_name = models.CharField(max_length=50)
used_at = models.DateTimeField()
class ReviveHistory(models.Model):
character_name = models.CharField(max_length=50)
used_at = models.DateTimeField()
def __str__(self):
return f"{self.character_name} - {self.used_at}"
class ChangeFactionPrice(models.Model):
price = models.DecimalField(max_digits=6, decimal_places=2, default=10.00)
def __str__(self):
return f"Precio para cambiar facción: {self.price} EUR"
class LevelUpPrice(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2, default=10.00)
def __str__(self):
return f"Subir a nivel 80: {self.price} EUR"
class GoldPrice(models.Model):
gold_amount = models.IntegerField() # Cantidad de oro
price = models.DecimalField(max_digits=10, decimal_places=2) # Precio en EUR
def __str__(self):
return f"{self.gold_amount} oro - {self.price} EUR"
class TransferPrice(models.Model):
price = models.DecimalField(max_digits=5, decimal_places=2)
def __str__(self):
return f"Precio de transferencia: {self.price} EUR"
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
def __str__(self):
return self.name
class Item(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="items")
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2) # Precio en euros
item_id = models.IntegerField(unique=True) # ID del ítem en el juego
image_url = models.URLField() # URL de la imagen del ítem
def __str__(self):
return self.name
class RenamePrice(models.Model):
price = models.DecimalField(max_digits=5, decimal_places=2, default=2.00)
def __str__(self):
return f"{self.price} EUR"
class CustomizePrice(models.Model):
price = models.DecimalField(max_digits=5, decimal_places=2, default=1.00)
def __str__(self):
return f"{self.price} EUR"
class ChangeRacePrice(models.Model):
price = models.DecimalField(max_digits=5, decimal_places=2)
def __str__(self):
return f"Cambiar raza: {self.price} EUR"
class ClienteCategoria(models.Model):
nombre = models.CharField(max_length=100)
descripcion = models.CharField(max_length=200, blank=True, null=True)
def __str__(self):
return self.nombre
class SistemaOperativo(models.Model):
categoria = models.ForeignKey(ClienteCategoria, related_name='sistemas_operativos', on_delete=models.CASCADE)
nombre = models.CharField(max_length=100)
url_descarga = models.URLField()
def __str__(self):
return f"{self.nombre} - {self.categoria.nombre}"
class ServerSelection(models.Model):
server_id = models.IntegerField()
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
port = models.IntegerField()
gamebuild = models.IntegerField()
def __str__(self):
expansion = 'WotLK' if self.gamebuild == 12340 else 'Cataclysm' if self.gamebuild == 15595 else 'Unknown'
return f"{self.name} ({expansion})"
class RecruitAFriend(models.Model):
title = models.CharField(max_length=255, default="Recluta a un amigo")
information = models.CharField(max_length=255, default="Información")
content = CKEditor5Field('Contenido', config_name='default')
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class DownloadClientPage(models.Model):
title = models.CharField(max_length=255, default="Descarga del cliente")
content = CKEditor5Field('Contenido', config_name='default')
mac_instructions = CKEditor5Field('Instrucciones para macOS', blank=True, null=True, config_name='default')
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class ContentCreator(models.Model):
title = models.CharField(max_length=200)
description = CKEditor5Field('Descripción', config_name='default')
content_type = models.CharField(max_length=60, default="Contenido general")
subtitle = models.CharField(max_length=60, default="Nova WoW")
youtube_video_url = models.URLField(max_length=300, blank=True, null=True)
avatar_image_url = models.URLField(max_length=300, blank=True, null=True)
youtube_channel_url = models.URLField(max_length=300, blank=True, null=True)
facebook_page_url = models.URLField(max_length=300, blank=True, null=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.titles
class RecruitReward(models.Model):
required_friends = models.IntegerField()
reward_name = models.CharField(max_length=100)
item_id = models.IntegerField()
item_quantity = models.IntegerField(default=1)
item_link = models.URLField()
icon_class = models.CharField(max_length=50, help_text="Clase CSS para el icono (ej. 'icontinyl q3')")
def __str__(self):
return f"{self.required_friends} amigos - {self.reward_name}"
class ClaimedReward(models.Model):
recruit_reward = models.ForeignKey(RecruitReward, on_delete=models.CASCADE, related_name='claimed_rewards')
account_id = models.IntegerField()
character_name = models.CharField(max_length=100)
username = models.CharField(max_length=100)
claimed_at = models.DateTimeField(auto_now_add=True)
ip_address = models.CharField(max_length=45, default='127.0.0.1') # Añadir un valor predeterminado
def __str__(self):
return f"{self.username} - {self.recruit_reward.reward_name} - {self.claimed_at}"
from django.db import models
from django.utils import timezone
from django.db import models
from django.utils import timezone
class AccountActivation(models.Model):
# En 3.4.3 (Battle.net) la identidad es el email; el username de la cuenta
# de juego se genera al activar (<bnetId>#1), por eso es opcional aquí.
username = models.CharField(max_length=17, null=True, blank=True)
email = models.EmailField()
old_email = models.EmailField(null=True, blank=True)
password = models.CharField(max_length=64)
# salt/verifier ya no se precalculan en el registro (se calculan al activar,
# tanto el de bnet como el de la cuenta de juego). Se conservan opcionales
# por compatibilidad con el flujo de cambio de email.
salt = models.BinaryField(max_length=32, null=True, blank=True)
verifier = models.BinaryField(max_length=256, null=True, blank=True)
recruiter_id = models.IntegerField(null=True, blank=True)
hash = models.CharField(max_length=32, unique=True)
old_email_hash = models.CharField(max_length=32, null=True, blank=True)
is_used = models.BooleanField(default=False) # Indica si el old_email_hash ha sido usado
is_new_email_used = models.BooleanField(default=False) # Indica si el hash del nuevo correo ha sido usado
created_at = models.DateTimeField(default=timezone.now)
def is_expired(self):
return timezone.now() > self.created_at + timezone.timedelta(hours=1)
def __str__(self):
return f"Activation for {self.username}"
class SecurityToken(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
token = models.CharField(max_length=6)
created_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField()
ip_address = models.GenericIPAddressField()
def __str__(self):
return f"Token for {self.user.username if self.user else 'Desconocido'}"
class GuildRenameSettings(models.Model):
cost = models.DecimalField(max_digits=10, decimal_places=2, default=10, help_text="Costo en euros para renombrar una hermandad.")
def __str__(self):
return f"Configuración de renombrar hermandad - Costo: {self.cost} €"
class VoteSite(models.Model):
name = models.CharField(max_length=100, unique=True)
url = models.URLField(max_length=200, help_text="URL del sitio de votación")
image_url = models.URLField(max_length=300, help_text="URL completa de la imagen")
points = models.IntegerField(default=1, help_text="Puntos otorgados por votar")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class VoteLog(models.Model):
account_id = models.IntegerField()
vote_site = models.ForeignKey('VoteSite', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
last_vote_time = models.DateTimeField(null=True, blank=True)
processed = models.BooleanField(default=False)
processed_at = models.DateTimeField(null=True, blank=True)
def __str__(self):
return f"Voto para {self.vote_site.name} por cuenta {self.account_id}"
class HomeApiPoints(models.Model):
accountID = models.IntegerField(unique=True)
vp = models.IntegerField(default=0)
dp = models.IntegerField(default=0)
class Meta:
db_table = 'home_api_points'
def __str__(self):
return f"Cuenta {self.accountID} - PV: {self.vp}, DP: {self.dp}"
class LoginAttempt(models.Model):
username = models.CharField(max_length=100)
status = models.CharField(max_length=50) # Ejemplo: 'Éxito' o 'Fracaso'
ip_address = models.GenericIPAddressField() # IP del intento de inicio de sesión
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.username} - {self.status} - {self.timestamp}"
class StripeLog(models.Model):
account_id = models.IntegerField() # ID de la cuenta en acore_auth
username = models.CharField(max_length=100) # Nombre de la cuenta
email = models.EmailField() # Correo electrónico
acore_ip = models.GenericIPAddressField() # IP de acore_auth
stripe_ip = models.GenericIPAddressField(blank=True, null=True) # IP detectada por Stripe
product_name = models.TextField() # Producto comprado
amount = models.DecimalField(max_digits=10, decimal_places=2) # Precio total
session_id = models.CharField(max_length=255) # ID de la sesión de Stripe
mode = models.CharField(max_length=10, choices=[("test", "Test"), ("live", "Live")]) # Modo de Stripe
character_name = models.CharField(max_length=100) # Nombre del personaje que compró
timestamp = models.DateTimeField(auto_now_add=True) # Fecha y hora
fulfilled = models.BooleanField(default=False) # Si la entrega ya se procesó (evita re-entregas)
def __str__(self):
return f"{self.username} compró {self.product_name} por {self.amount}€ ({self.mode})"
class Pedido(models.Model):
transaction_id = models.CharField(max_length=100, unique=True, null=True, blank=True)
email_cliente = models.EmailField()
estado = models.CharField(max_length=20, default="Pendiente")
monto = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return f"Pedido {self.transaction_id} - {self.estado}"