from django.contrib import messages from django.db.models.deletion import ProtectedError from django.shortcuts import redirect, render from django.urls import reverse_lazy from django.views.generic import CreateView, DeleteView, ListView, UpdateView from .forms import DividendoForm, DivisaForm, OperacionForm, ValorForm from .models import Dividendo, Divisa, Operacion, Valor from .services import ( actualizar_cambios, actualizar_precios, desglose_fifo, posiciones, resumen_fiscal, totales, ) class CancelarUrlMixin: """Aporta la URL de cancelación al formulario (vuelve atrás si hay referer).""" def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['cancelar_url'] = self.request.META.get('HTTP_REFERER') or str(self.success_url) return ctx def dashboard(request): pos = posiciones() context = { 'posiciones': pos, 'abiertas': [p for p in pos if p['acciones'] > 0], 'cerradas': [p for p in pos if p['acciones'] == 0], 'totales': totales(pos), } return render(request, 'cartera/dashboard.html', context) def actualizar(request): if request.method == 'POST': cambios, errores_cambios = actualizar_cambios() if cambios: messages.success(request, f'Cambios de moneda actualizados: {cambios}') for par, motivo in errores_cambios: messages.warning(request, f'{par}: {motivo}') correctos, errores = actualizar_precios() if correctos: messages.success(request, f'Precios actualizados: {correctos}') for ticker, motivo in errores: messages.warning(request, f'{ticker}: {motivo}') return redirect('cartera:dashboard') def fifo(request): filas = desglose_fifo() context = { 'filas': filas, 'fiscal': resumen_fiscal(filas), 'total': sum((f['resultado_eur'] or 0 for f in filas), start=0), } return render(request, 'cartera/fifo.html', context) class OperacionListView(ListView): model = Operacion template_name = 'cartera/operacion_list.html' context_object_name = 'operaciones' def get_queryset(self): qs = Operacion.objects.select_related('valor', 'valor__divisa').order_by('-fecha', '-pk') valor = self.request.GET.get('valor') if valor: qs = qs.filter(valor_id=valor) return qs def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['valores'] = Valor.objects.order_by('ticker') ctx['valor_filtro'] = self.request.GET.get('valor', '') return ctx class OperacionCreateView(CancelarUrlMixin, CreateView): model = Operacion form_class = OperacionForm template_name = 'cartera/form.html' success_url = reverse_lazy('cartera:operaciones') titulo = 'Nueva operación' def form_valid(self, form): messages.success(self.request, 'Operación registrada') return super().form_valid(form) class OperacionUpdateView(CancelarUrlMixin, UpdateView): model = Operacion form_class = OperacionForm template_name = 'cartera/form.html' success_url = reverse_lazy('cartera:operaciones') titulo = 'Editar operación' def form_valid(self, form): messages.success(self.request, 'Operación actualizada') return super().form_valid(form) class OperacionDeleteView(CancelarUrlMixin, DeleteView): model = Operacion template_name = 'cartera/confirm_delete.html' success_url = reverse_lazy('cartera:operaciones') titulo = 'Eliminar operación' def form_valid(self, form): messages.success(self.request, 'Operación eliminada') return super().form_valid(form) class DividendoListView(ListView): model = Dividendo template_name = 'cartera/dividendo_list.html' context_object_name = 'dividendos' queryset = Dividendo.objects.select_related('valor') class DividendoCreateView(CancelarUrlMixin, CreateView): model = Dividendo form_class = DividendoForm template_name = 'cartera/form.html' success_url = reverse_lazy('cartera:dividendos') titulo = 'Nuevo dividendo' def form_valid(self, form): messages.success(self.request, 'Dividendo registrado') return super().form_valid(form) class DividendoUpdateView(CancelarUrlMixin, UpdateView): model = Dividendo form_class = DividendoForm template_name = 'cartera/form.html' success_url = reverse_lazy('cartera:dividendos') titulo = 'Editar dividendo' def form_valid(self, form): messages.success(self.request, 'Dividendo actualizado') return super().form_valid(form) class DividendoDeleteView(CancelarUrlMixin, DeleteView): model = Dividendo template_name = 'cartera/confirm_delete.html' success_url = reverse_lazy('cartera:dividendos') titulo = 'Eliminar dividendo' def form_valid(self, form): messages.success(self.request, 'Dividendo eliminado') return super().form_valid(form) class ValorListView(ListView): model = Valor template_name = 'cartera/valor_list.html' context_object_name = 'valores' class ValorCreateView(CancelarUrlMixin, CreateView): model = Valor form_class = ValorForm template_name = 'cartera/form.html' success_url = reverse_lazy('cartera:valores') titulo = 'Nuevo valor' def form_valid(self, form): messages.success(self.request, 'Valor creado') return super().form_valid(form) class ValorUpdateView(CancelarUrlMixin, UpdateView): model = Valor form_class = ValorForm template_name = 'cartera/form.html' success_url = reverse_lazy('cartera:valores') titulo = 'Editar valor' def form_valid(self, form): messages.success(self.request, 'Valor actualizado') return super().form_valid(form) class ValorDeleteView(CancelarUrlMixin, DeleteView): model = Valor template_name = 'cartera/confirm_delete.html' success_url = reverse_lazy('cartera:valores') titulo = 'Eliminar valor' def form_valid(self, form): messages.success(self.request, 'Valor eliminado (con todas sus operaciones)') return super().form_valid(form) class DivisaListView(ListView): model = Divisa template_name = 'cartera/divisa_list.html' context_object_name = 'divisas' class DivisaCreateView(CancelarUrlMixin, CreateView): model = Divisa form_class = DivisaForm template_name = 'cartera/form.html' success_url = reverse_lazy('cartera:divisas') titulo = 'Nueva divisa' def form_valid(self, form): messages.success(self.request, 'Divisa creada') return super().form_valid(form) class DivisaUpdateView(CancelarUrlMixin, UpdateView): model = Divisa form_class = DivisaForm template_name = 'cartera/form.html' success_url = reverse_lazy('cartera:divisas') titulo = 'Editar divisa' def form_valid(self, form): messages.success(self.request, 'Divisa actualizada') return super().form_valid(form) class DivisaDeleteView(CancelarUrlMixin, DeleteView): model = Divisa template_name = 'cartera/confirm_delete.html' success_url = reverse_lazy('cartera:divisas') titulo = 'Eliminar divisa' def post(self, request, *args, **kwargs): try: return super().post(request, *args, **kwargs) except ProtectedError: messages.error( request, 'No se puede eliminar: hay valores que usan esta divisa.', ) return redirect('cartera:divisas')