| @ -0,0 +1 @@ | |||||
| migrations/ | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.contrib import admin | |||||
| # Register your models here. | |||||
| @ -0,0 +1,6 @@ | |||||
| from django.apps import AppConfig | |||||
| class AccountsConfig(AppConfig): | |||||
| default_auto_field = 'django.db.models.BigAutoField' | |||||
| name = 'accounts' | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.db import models | |||||
| # Create your models here. | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.test import TestCase | |||||
| # Create your tests here. | |||||
| @ -0,0 +1,8 @@ | |||||
| # accounts/urls.py | |||||
| from django.urls import path | |||||
| from django.contrib.auth import views as auth_views | |||||
| urlpatterns = [ | |||||
| path('login/', auth_views.LoginView.as_view(), name='login'), | |||||
| path('logout/', auth_views.LogoutView.as_view(), name='logout'), | |||||
| ] | |||||
| @ -0,0 +1 @@ | |||||
| from django.shortcuts import render | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.contrib import admin | |||||
| # Register your models here. | |||||
| @ -0,0 +1,6 @@ | |||||
| from django.apps import AppConfig | |||||
| class ApuntesConfig(AppConfig): | |||||
| default_auto_field = 'django.db.models.BigAutoField' | |||||
| name = 'apuntes' | |||||
| @ -0,0 +1,52 @@ | |||||
| from django import forms | |||||
| from django.contrib.auth.forms import UserCreationForm, UserChangeForm | |||||
| from .models import Cuentas, Apuntes, ReyMotaUser, Tipos | |||||
| class CuentasForm(forms.ModelForm): | |||||
| class Meta: | |||||
| model = Cuentas | |||||
| fields = ['nombre', 'saldo_inicial', 'tipo'] | |||||
| nombre = forms.CharField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| saldo_inicial = forms.DecimalField( | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| tipo = forms.ModelChoiceField( | |||||
| queryset=Tipos.objects.all(), | |||||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||||
| class ApuntesForm(forms.ModelForm): | |||||
| class Meta: | |||||
| model = Apuntes | |||||
| fields = ['fecha', 'cta_origen', 'cta_destino', 'importe'] | |||||
| fecha = forms.DateField( | |||||
| widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'})) | |||||
| cta_origen = forms.ModelChoiceField( | |||||
| queryset=Cuentas.objects.all(), | |||||
| widget=forms.Select(attrs={'class': 'form-control'})) | |||||
| cta_destino = forms.ModelChoiceField( | |||||
| queryset=Cuentas.objects.all(), | |||||
| widget=forms.Select(attrs={'class': 'form-control'})) | |||||
| importe = forms.DecimalField( | |||||
| widget=forms.NumberInput(attrs={'class': 'form-control'})) | |||||
| class ReyMotaUserCreationForm(UserCreationForm): | |||||
| class Meta: | |||||
| model = ReyMotaUser | |||||
| fields = ("email", "nombre", "foto") | |||||
| labels = {'email': 'Dirección de correo'} | |||||
| class ReyMotaUserChangeForm(UserChangeForm): | |||||
| class Meta: | |||||
| model = ReyMotaUser | |||||
| fields = ("email", "foto") | |||||
| @ -0,0 +1,34 @@ | |||||
| from django.contrib.auth.base_user import BaseUserManager | |||||
| from django.utils.translation import gettext_lazy as _ | |||||
| class ReyMotaUserManager(BaseUserManager): | |||||
| """ | |||||
| ReyMota user model manager where email is the unique identifiers | |||||
| for authentication instead of usernames. | |||||
| """ | |||||
| def create_user(self, email, password, **extra_fields): | |||||
| """ | |||||
| Create and save a user with the given email and password. | |||||
| """ | |||||
| if not email: | |||||
| raise ValueError(_("The Email must be set")) | |||||
| email = self.normalize_email(email) | |||||
| user = self.model(email=email, **extra_fields) | |||||
| user.set_password(password) | |||||
| user.save() | |||||
| return user | |||||
| def create_superuser(self, email, password, **extra_fields): | |||||
| """ | |||||
| Create and save a SuperUser with the given email and password. | |||||
| """ | |||||
| extra_fields.setdefault("is_staff", True) | |||||
| extra_fields.setdefault("is_superuser", True) | |||||
| extra_fields.setdefault("is_active", True) | |||||
| if extra_fields.get("is_staff") is not True: | |||||
| raise ValueError(_("Superuser must have is_staff=True.")) | |||||
| if extra_fields.get("is_superuser") is not True: | |||||
| raise ValueError(_("Superuser must have is_superuser=True.")) | |||||
| return self.create_user(email, password, **extra_fields) | |||||
| @ -0,0 +1,66 @@ | |||||
| # Generated by Django 5.1 on 2024-09-02 14:10 | |||||
| import django.db.models.deletion | |||||
| from django.db import migrations, models | |||||
| class Migration(migrations.Migration): | |||||
| initial = True | |||||
| dependencies = [ | |||||
| ('auth', '0012_alter_user_first_name_max_length'), | |||||
| ] | |||||
| operations = [ | |||||
| migrations.CreateModel( | |||||
| name='Cuentas', | |||||
| fields=[ | |||||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||||
| ('nombre', models.TextField(max_length=20)), | |||||
| ('saldo_inicial', models.DecimalField(decimal_places=2, max_digits=10)), | |||||
| ('saldo_actual', models.DecimalField(decimal_places=2, max_digits=10)), | |||||
| ], | |||||
| ), | |||||
| migrations.CreateModel( | |||||
| name='Tipos', | |||||
| fields=[ | |||||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||||
| ('tipo', models.TextField(max_length=10)), | |||||
| ], | |||||
| ), | |||||
| migrations.CreateModel( | |||||
| name='ReyMotaUser', | |||||
| fields=[ | |||||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||||
| ('password', models.CharField(max_length=128, verbose_name='password')), | |||||
| ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), | |||||
| ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), | |||||
| ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), | |||||
| ('foto', models.ImageField(blank=True, default='profile_images/default.jpg', upload_to='profile_images')), | |||||
| ('is_staff', models.BooleanField(default=False)), | |||||
| ('is_active', models.BooleanField(default=True)), | |||||
| ('nombre', models.CharField(blank=True, max_length=200, null=True)), | |||||
| ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), | |||||
| ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), | |||||
| ], | |||||
| options={ | |||||
| 'abstract': False, | |||||
| }, | |||||
| ), | |||||
| migrations.CreateModel( | |||||
| name='Apuntes', | |||||
| fields=[ | |||||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||||
| ('fecha', models.DateField()), | |||||
| ('importe', models.DecimalField(decimal_places=2, max_digits=10)), | |||||
| ('cta_destino', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='destino', to='apuntes.cuentas')), | |||||
| ('cta_origen', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='origen', to='apuntes.cuentas')), | |||||
| ], | |||||
| ), | |||||
| migrations.AddField( | |||||
| model_name='cuentas', | |||||
| name='tipo', | |||||
| field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='apuntes.tipos'), | |||||
| ), | |||||
| ] | |||||
| @ -0,0 +1,37 @@ | |||||
| from django.db import models | |||||
| from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin | |||||
| from django.utils.translation import gettext_lazy as _ | |||||
| from .managers import ReyMotaUserManager | |||||
| # Create your models here. | |||||
| class ReyMotaUser(AbstractBaseUser, PermissionsMixin): | |||||
| email = models.EmailField(_("email address"), unique=True) | |||||
| foto = models.ImageField(upload_to="profile_images", default="profile_images/default.jpg", blank=True) | |||||
| is_staff = models.BooleanField(default=False) | |||||
| is_active = models.BooleanField(default=True) | |||||
| nombre = models.CharField(max_length=200, blank=True, null=True) | |||||
| USERNAME_FIELD = "email" | |||||
| REQUIRED_FIELDS = [] | |||||
| objects = ReyMotaUserManager() | |||||
| def __str__(self): | |||||
| return self.email | |||||
| class Tipos(models.Model): | |||||
| tipo = models.TextField(max_length=10) | |||||
| class Cuentas(models.Model): | |||||
| nombre = models.TextField(max_length=20) | |||||
| saldo_inicial = models.DecimalField(max_digits=10, decimal_places=2) | |||||
| saldo_actual = models.DecimalField(max_digits=10, decimal_places=2) | |||||
| tipo = models.ForeignKey(Tipos, on_delete=models.CASCADE) | |||||
| class Apuntes(models.Model): | |||||
| fecha = models.DateField() | |||||
| cta_origen = models.ForeignKey(Cuentas, on_delete=models.CASCADE, related_name='origen') | |||||
| cta_destino = models.ForeignKey(Cuentas, on_delete=models.CASCADE, related_name='destino') | |||||
| importe = models.DecimalField(max_digits=10, decimal_places=2) | |||||
| @ -0,0 +1,14 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block content %} | |||||
| <div class="app-card p-5 text-center shadow-sm"> | |||||
| <h1 class="page-title mb-4">404<br><span class="font-weight-light">Página no encontrada</span></h1> | |||||
| <div class="mb-4"> | |||||
| Lo siento, no hemos podido encontrar la página que buscas. | |||||
| </div> | |||||
| <a class="btn app-btn-primary" href="{{ url_for('paginas.index') }}">Ir a la página de inicio</a> | |||||
| </div> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,8 @@ | |||||
| {% load static %} | |||||
| {% load filtros_de_entorno %} | |||||
| <div class="app-branding"> | |||||
| <a class="app-logo" href="{% url 'principal' %}"><img class="logo-icon me-2" src="{% static 'images/reymota-logo.svg' %}" alt="logo"><span class="logo-text">FINANZAS</span><span style="color: blue; font-size: 14px;"> v {{ "VERSION"|muestra_version }}</span></a> | |||||
| </div><!--//app-branding--> | |||||
| @ -0,0 +1,142 @@ | |||||
| {% load static %} | |||||
| <header class="app-header fixed-top"> | |||||
| <div class="app-header-inner"> | |||||
| <div class="container-fluid py-2"> | |||||
| <div class="app-header-content"> | |||||
| <div class="row justify-content-between align-items-center"> | |||||
| <div class="col-auto"> | |||||
| <a id="sidepanel-toggler" class="sidepanel-toggler d-inline-block d-xl-none" href="#"> | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30" role="img"><title>Menu</title><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg> | |||||
| </a> | |||||
| </div><!--//col--> | |||||
| <div class="app-utilities col-auto"> | |||||
| <div class="app-utility-item app-user-dropdown dropdown"> | |||||
| {% if user.is_authenticated %} | |||||
| <a class="dropdown-toggle" id="user-dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><img src=" {{user.foto.url }}"></a> | |||||
| {% else %} | |||||
| <a class="dropdown-toggle" id="user-dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Sin usuario</a> | |||||
| {% endif %} | |||||
| <ul class="dropdown-menu" aria-labelledby="user-dropdown-toggle"> | |||||
| {% if user.is_authenticated %} | |||||
| <li> | |||||
| <a class="dropdown-item">{{ user.nombre }}</a> | |||||
| </li> | |||||
| <li><a class="dropdown-item"> | |||||
| <form method="post" action="{% url 'logout' %}" > | |||||
| {% csrf_token %} | |||||
| <button | |||||
| style="background: none!important; | |||||
| border: none; | |||||
| padding: 0!important; | |||||
| /*optional*/ | |||||
| font-family: arial, sans-serif; | |||||
| /*input has OS specific font-family*/ | |||||
| /*color: #069; | |||||
| text-decoration: underline;*/ | |||||
| cursor: pointer;" | |||||
| type="submit">Salir</button> | |||||
| </form></a> | |||||
| </li> | |||||
| {% else %} | |||||
| <li><a class="dropdown-item" href="{% url 'login' %}">Entrar</a></li> | |||||
| {% endif %} | |||||
| </ul> | |||||
| </div><!--//app-user-dropdown--> | |||||
| </div><!--//app-utilities--> | |||||
| </div><!--//row--> | |||||
| </div><!--//app-header-content--> | |||||
| </div><!--//container-fluid--> | |||||
| </div><!--//app-header-inner--> | |||||
| <div id="app-sidepanel" class="app-sidepanel"> | |||||
| <div id="sidepanel-drop" class="sidepanel-drop"></div> | |||||
| <div class="sidepanel-inner d-flex flex-column"> | |||||
| <a href="#" id="sidepanel-close" class="sidepanel-close d-xl-none">×</a> | |||||
| {% include "_branding.html" %} | |||||
| <nav id="app-nav-main" class="app-nav app-nav-main flex-grow-1"> | |||||
| <ul class="app-menu list-unstyled accordion" id="menu-accordion"> | |||||
| <li class="nav-item"> | |||||
| <!--//Bootstrap Icons: https://icons.getbootstrap.com/ --> | |||||
| <a class="nav-link active" href="{% url 'principal' %}"> | |||||
| <span class="nav-icon"> | |||||
| <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-house-door" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path fill-rule="evenodd" d="M7.646 1.146a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 .146.354v7a.5.5 0 0 1-.5.5H9.5a.5.5 0 0 1-.5-.5v-4H7v4a.5.5 0 0 1-.5.5H2a.5.5 0 0 1-.5-.5v-7a.5.5 0 0 1 .146-.354l6-6zM2.5 7.707V14H6v-4a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5v4h3.5V7.707L8 2.207l-5.5 5.5z"/> | |||||
| <path fill-rule="evenodd" d="M13 2.5V6l-2-2V2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5z"/> | |||||
| </svg> | |||||
| </span> | |||||
| <span class="nav-link-text">Principal</span> | |||||
| </a><!--//nav-link--> | |||||
| </li><!--//nav-item--> | |||||
| <li class="nav-item"> | |||||
| <!--//Bootstrap Icons: https://icons.getbootstrap.com/ --> | |||||
| <a class="nav-link" href="{% url 'lista_apuntes' %}"> | |||||
| <span class="nav-icon"> | |||||
| <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-card-list" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path fill-rule="evenodd" d="M14.5 3h-13a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/> | |||||
| <path fill-rule="evenodd" d="M5 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 5 8zm0-2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0 5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5z"/> | |||||
| <circle cx="3.5" cy="5.5" r=".5"/> | |||||
| <circle cx="3.5" cy="8" r=".5"/> | |||||
| <circle cx="3.5" cy="10.5" r=".5"/> | |||||
| </svg> | |||||
| </span> | |||||
| <span class="nav-link-text">Apuntes</span> | |||||
| </a><!--//nav-link--> | |||||
| </li><!--//nav-item--> | |||||
| <li class="nav-item"> | |||||
| <!--//Bootstrap Icons: https://icons.getbootstrap.com/ --> | |||||
| <a class="nav-link" href="{% url 'lista_cuentas' %}"> | |||||
| <span class="nav-icon"> | |||||
| <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-card-list" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path fill-rule="evenodd" d="M14.5 3h-13a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/> | |||||
| <path fill-rule="evenodd" d="M5 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 5 8zm0-2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0 5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5z"/> | |||||
| <circle cx="3.5" cy="5.5" r=".5"/> | |||||
| <circle cx="3.5" cy="8" r=".5"/> | |||||
| <circle cx="3.5" cy="10.5" r=".5"/> | |||||
| </svg> | |||||
| </span> | |||||
| <span class="nav-link-text">Cuentas</span> | |||||
| </a><!--//nav-link--> | |||||
| </li><!--//nav-item--> | |||||
| {% if user.is_authenticated %} | |||||
| <li class="nav-item has-submenu"> | |||||
| <!--//Bootstrap Icons: https://icons.getbootstrap.com/ --> | |||||
| <a class="nav-link submenu-toggle" href="#" data-bs-toggle="collapse" data-bs-target="#submenu-1" aria-expanded="false" aria-controls="submenu-1"> | |||||
| <span class="nav-icon"> | |||||
| <!--//Bootstrap Icons: https://icons.getbootstrap.com/ --> | |||||
| <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-files" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path fill-rule="evenodd" d="M4 2h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2zm0 1a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4z"/> | |||||
| <path d="M6 0h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2v-1a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1H4a2 2 0 0 1 2-2z"/> | |||||
| </svg> | |||||
| </span> | |||||
| <span class="nav-link-text">Añadir</span> | |||||
| <span class="submenu-arrow"> | |||||
| <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-chevron-down" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"/> | |||||
| </svg> | |||||
| </span><!--//submenu-arrow--> | |||||
| </a><!--//nav-link--> | |||||
| <div id="submenu-1" class="collapse submenu submenu-1" data-bs-parent="#menu-accordion"> | |||||
| <ul class="submenu-list list-unstyled"> | |||||
| <li class="submenu-item"><a class="submenu-link" href="{% url 'nuevo_apunte' %}">Apuntes</a></li> | |||||
| <li class="submenu-item"><a class="submenu-link" href="{% url 'nueva_cuenta' %}">Cuenta</a></li> | |||||
| </ul> | |||||
| </div> | |||||
| </li><!--//nav-item--> | |||||
| {% endif %} | |||||
| </ul><!--//app-menu--> | |||||
| </nav><!--//app-nav--> | |||||
| </div><!--//sidepanel-inner--> | |||||
| </div><!--//app-sidepanel--> | |||||
| </header><!--//app-header--> | |||||
| @ -0,0 +1,10 @@ | |||||
| <!-- | |||||
| La clase app-auth-footer hace algo que implica que el texto quede | |||||
| en la parte de abajo de la ventana con el tamaño que esta tenga en el momento | |||||
| de renderizar el texto. En algunos casos esto causa un efecto no deseado. Por ejemplo en letras.html | |||||
| --> | |||||
| <!--<footer class="app-auth-footer">--> | |||||
| <div class="container text-center py-3"> | |||||
| <small class="copyright">(c) <a class="app-link" href="http://reymota.es" target="_blank">Celestino Rey</a></small> | |||||
| </div> | |||||
| <!--</footer>//app-auth-footer--> | |||||
| @ -0,0 +1,23 @@ | |||||
| {% load static %} | |||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <title>Registro de vehículos y sus apuntes</title> | |||||
| <!-- Meta --> | |||||
| <meta charset="utf-8"> | |||||
| <meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||||
| <meta name="description" content="Portal - Bootstrap 5 Admin Dashboard Template For Developers"> | |||||
| <meta name="author" content="Xiaoying Riley at 3rd Wave Media"> | |||||
| <link rel="shortcut icon" href="{% static 'images/favicon.ico' %}"> | |||||
| <!-- FontAwesome JS--> | |||||
| <script defer src="{% static 'plugins/fontawesome/js/all.min.js' %}"></script> | |||||
| <!-- App CSS --> | |||||
| <link id="theme-style" rel="stylesheet" href="{% static 'css/portal.css' %}"> | |||||
| </head> | |||||
| @ -0,0 +1,32 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block content %} | |||||
| <div class="container-xl"> | |||||
| <div class="app-card app-card-notification shadow-sm mb-4"> | |||||
| <div class="app-card-header px-4 py-3"> | |||||
| <div class="row g-3 align-items-center"> | |||||
| <div class="col-12 col-lg-auto text-center text-lg-start"> | |||||
| <h4 class="notification-title mb-1">{{ apunte.fecha }}</h4> | |||||
| <ul class="notification-meta list-inline mb-0"> | |||||
| <li class="list-inline-item"><a href="{% url 'detalle_cuenta' apunte.cuenta_id %}">{{ apunte.cuenta.matricula }}</a></li> | |||||
| <li class="list-inline-item">|</li> | |||||
| <li class="list-inline-item">{{ apunte.kms }} kms</li> | |||||
| <li class="list-inline-item">|</li> | |||||
| <li class="list-inline-item">{{ apunte.litros }} litros</li> | |||||
| <li class="list-inline-item">|</li> | |||||
| <li class="list-inline-item">{{ apunte.importe }} €</li> | |||||
| <li class="list-inline-item">|</li> | |||||
| <li class="list-inline-item">{{ apunte.kmsrecorridos }} kms. recorridos</li> | |||||
| <li class="list-inline-item">|</li> | |||||
| <li class="list-inline-item">{{ apunte.consumo }} litros/100 kms</li> | |||||
| <li class="list-inline-item">|</li> | |||||
| <li class="list-inline-item">{{ apunte.precioxlitro }} €/litros</li> | |||||
| </ul> | |||||
| </div><!--//col--> | |||||
| </tr> | |||||
| </div><!--//row--> | |||||
| </div><!--//app-card-header--> | |||||
| </div><!--//app-card--> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,72 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block content %} | |||||
| <div class="container-xl"> | |||||
| <div class="app-card app-card-notification shadow-sm mb-4"> | |||||
| <div class="app-card-header px-4 py-3"> | |||||
| <div class="row g-3 align-items-center"> | |||||
| <div class="col-12 col-lg-auto text-center text-lg-start"> | |||||
| {% if cuenta.foto %} | |||||
| <p><img src="{{ cuenta.foto.url }}" alt="{{ cuenta.matricula}}" style="width:200px;height:200px;"></p> | |||||
| {% else %} | |||||
| <p>No hay imágen disponible</p> | |||||
| {% endif %} | |||||
| </div> | |||||
| <div class="col-12 col-lg-auto text-center text-lg-start"> | |||||
| <h4>{{ cuenta.matricula }}</h4> | |||||
| <ul class="notification-meta list-inline mb-0"> | |||||
| <li class="list-inline-item">{{ cuenta.matricula }}</li> | |||||
| </ul> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="app-card-body p-4"> | |||||
| {% if apuntes %} | |||||
| <table class="table app-table-hover mb-0 text-left"> | |||||
| <thead> | |||||
| <tr> | |||||
| <th class="cell">Fecha</th> | |||||
| <th class="cell">Kilómetros</th> | |||||
| <th class="cell">Litros</th> | |||||
| <th class="cell">Importe</th> | |||||
| <th class="cell">Descuento</th> | |||||
| <th class="cell">Precio por litro</th> | |||||
| <th class="cell">Kms recorridos</th> | |||||
| <th class="cell">Consumo/100 kms</th> | |||||
| </tr> | |||||
| </thead> | |||||
| <tbody> | |||||
| {% for apunte in apuntes %} | |||||
| <tr> | |||||
| <td class="cell"><a href="{% url 'detalle_apunte' apunte.id %}">{{ apunte.fecha }}</a></td> | |||||
| <td class="cell">{{ apunte.kms }}</td> | |||||
| <td class="cell">{{ apunte.litros }}</td> | |||||
| <td class="cell">{{ apunte.importe }}</td> | |||||
| <td class="cell">{{ apunte.descuento }}</td> | |||||
| <td class="cell">{{ apunte.precioxlitro }}</td> | |||||
| <td class="cell">{{ apunte.kmsrecorridos }}</td> | |||||
| <td class="cell">{{ apunte.consumo }}</td> | |||||
| </tr> | |||||
| {% endfor %} | |||||
| </tbody> | |||||
| </table> | |||||
| {% else %} | |||||
| <p>No se han encontrado apuntes para este cuenta</p> | |||||
| {% endif %} | |||||
| </div> | |||||
| </div> | |||||
| <div class="row g-3 mb-4 align-items-center justify-content-between"> | |||||
| <div class="col-auto"> | |||||
| <a class="btn app-btn-secondary" href="{% url 'lista_apuntes' %}">Volver al inicio</a> | |||||
| </div> | |||||
| <div class="col-auto"> | |||||
| <a class="btn app-btn-primary" href="{% url 'nuevo_apunte' %}">Añadir apunte</a> <!-- Faltaría poner el id del cuenta--> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,17 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block content %} | |||||
| <div class="column is-4 is-offset-4"> | |||||
| <h3>{% if form.instance.pk %}Editar apunte{% else %}Nuevo apunte{% endif %}</h3> | |||||
| <div class="box"> | |||||
| <form method="POST" enctype="multipart/form-data"> | |||||
| {% csrf_token %} | |||||
| {{ form.as_p }} | |||||
| <div class="text mb-3"> | |||||
| <button type="submit" class="btn app-btn-primary w-100 theme-btn mx-auto">Guardar</button> | |||||
| </div> | |||||
| </form> | |||||
| {{ form.media }} | |||||
| </div> | |||||
| </div> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,17 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block content %} | |||||
| <div class="column is-4 is-offset-4"> | |||||
| <h3>{% if form.instance.pk %}Editar vehículo{% else %}Nuevo vehículo{% endif %}</h3> | |||||
| <div class="box"> | |||||
| <form method="POST" enctype="multipart/form-data"> | |||||
| {% csrf_token %} | |||||
| {{ form.as_p }} | |||||
| <div class="text mb-3"> | |||||
| <button type="submit" class="btn app-btn-primary w-100 theme-btn mx-auto">Guardar</button> | |||||
| </div> | |||||
| </form> | |||||
| </div> | |||||
| </div> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,26 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block content %} | |||||
| <div class="container-xl"> | |||||
| <h1 class="app-page-title">Introducción</h1> | |||||
| <div class="app-card alert alert-dismissible shadow-sm mb-4 border-left-decoration" role="alert"> | |||||
| <div class="inner"> | |||||
| <div class="app-card-body p-3 p-lg-4"> | |||||
| <h3 class="mb-3">¡Bienvenido a la gestión financiera personal!</h3> | |||||
| <div class="row gx-5 gy-3"> | |||||
| <!-- | |||||
| <div class="col-12 col-lg-9"> | |||||
| <div>Pensado inicialmente para guardar las letras de las canciones de Bruce Springsteen.</div> | |||||
| </div>--><!--//col--> | |||||
| </div><!--//row--> | |||||
| <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | |||||
| </div><!--//app-card-body--> | |||||
| </div><!--//inner--> | |||||
| </div><!--//app-card--> | |||||
| </div><!--//container-fluid--> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,57 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block content %} | |||||
| <div class="container-xl"> | |||||
| <div class="row g-3 mb-4 align-items-center justify-content-between"> | |||||
| <div class="col-auto"> | |||||
| <h1 class="app-page-title mb-0">apuntes</h1> | |||||
| </div> | |||||
| </div><!--//row--> | |||||
| <div class="col-auto"> | |||||
| <div class="page-utilities"> | |||||
| <div class="row g-4 justify-content-start justify-content-md-end align-items-center"> | |||||
| <div class="col-auto"> | |||||
| <a class="btn app-btn-primary" href="{% url 'nuevo_apunte' %}">Añadir apunte</a> | |||||
| </div> | |||||
| </div><!--//row--> | |||||
| </div><!--//table-utilities--> | |||||
| </div><!--//col-auto--> | |||||
| <div class="app-card app-card-notification shadow-sm mb-4"> | |||||
| <div class="app-card-body p-4"> | |||||
| <table class="table app-table-hover mb-0 text-left"> | |||||
| <thead> | |||||
| <tr> | |||||
| <th class="cell">Fecha</th> | |||||
| <th class="cell">Cuenta</th> | |||||
| <th class="cell">Kilómetros</th> | |||||
| <th class="cell">Litros</th> | |||||
| <th class="cell">Importe</th> | |||||
| <th class="cell">Descuento</th> | |||||
| <th class="cell">Precio por litro</th> | |||||
| <th class="cell">Kms recorridos</th> | |||||
| <th class="cell">Consumo/100 kms</th> | |||||
| </tr> | |||||
| </thead> | |||||
| {% for apunte in apuntes %} | |||||
| <tbody> | |||||
| <tr> | |||||
| <td class="cell"><a href="{% url 'detalle_apunte' apunte.id %}">{{ apunte.fecha }}</a></td> | |||||
| <td class="cell"><a href="{% url 'detalle_cuenta' apunte.cuenta.id %}">{{ apunte.cuenta.matricula }}</a></td> | |||||
| <td class="cell">{{ apunte.kms }}</td> | |||||
| <td class="cell">{{ apunte.litros }}</td> | |||||
| <td class="cell">{{ apunte.importe }} €</td> | |||||
| <td class="cell">{{ apunte.descuento }} €</td> | |||||
| <td class="cell">{{ apunte.precioxlitro }} €</td> | |||||
| <td class="cell">{{ apunte.kmsrecorridos }}</td> | |||||
| <td class="cell">{{ apunte.consumo }}</td> | |||||
| </tr> | |||||
| </tbody> | |||||
| {% endfor %} | |||||
| </table> | |||||
| </div> | |||||
| </div><!--//container-fluid--> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,65 @@ | |||||
| {% extends 'base.html' %} | |||||
| {% block content %} | |||||
| <div class="container-xl"> | |||||
| <div class="row g-3 mb-4 align-items-center justify-content-between"> | |||||
| <div class="col-auto"> | |||||
| <h1 class="app-page-title mb-0">Cuentas</h1> | |||||
| </div> | |||||
| </div><!--//row--> | |||||
| <div class="col-auto"> | |||||
| <div class="page-utilities"> | |||||
| <div class="row g-4 justify-content-start justify-content-md-end align-items-center"> | |||||
| <div class="col-auto"> | |||||
| <a class="btn app-btn-primary" href="{% url 'nueva_cuenta' %}">Añadir cuenta</a> | |||||
| </div> | |||||
| </div><!--//row--> | |||||
| </div><!--//table-utilities--> | |||||
| </div><!--//col-auto--> | |||||
| <div class="row g-4"> | |||||
| {% for cuenta in cuentas %} | |||||
| <div class="col-6 col-md-4 col-xl-3 col-xxl-2"> | |||||
| <div class="app-card app-card-doc shadow-sm h-100"> | |||||
| <div class="app-card-body p-3 has-card-actions"> | |||||
| <h4 class="app-doc-title truncate mb-0"><a href="{% url 'detalle_cuenta' cuenta.id %}">{{ cuenta.matricula}}</a></h4> | |||||
| <div class="app-card-actions"> | |||||
| <div class="dropdown"> | |||||
| <div class="dropdown-toggle no-toggle-arrow" data-bs-toggle="dropdown" aria-expanded="false"> | |||||
| <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-three-dots-vertical" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path fill-rule="evenodd" d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/> | |||||
| </svg> | |||||
| </div><!--//dropdown-toggle--> | |||||
| <ul class="dropdown-menu"> | |||||
| <li><a class="dropdown-item" href="{% url 'detalle_cuenta' cuenta.id %}"><svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-eye me-2" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path fill-rule="evenodd" d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.134 13.134 0 0 0 1.66 2.043C4.12 11.332 5.88 12.5 8 12.5c2.12 0 3.879-1.168 5.168-2.457A13.134 13.134 0 0 0 14.828 8a13.133 13.133 0 0 0-1.66-2.043C11.879 4.668 10.119 3.5 8 3.5c-2.12 0-3.879 1.168-5.168 2.457A13.133 13.133 0 0 0 1.172 8z"/> | |||||
| <path fill-rule="evenodd" d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> | |||||
| </svg>Ver</a></li> | |||||
| <li><a class="dropdown-item" href="{% url 'editar_cuenta' cuenta.id %}"><svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil me-2" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/> | |||||
| </svg>Editar</a></li> | |||||
| <li><a class="dropdown-item" href="{% url 'eliminar_cuenta' cuenta.id %}"><svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download me-2" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path fill-rule="evenodd" d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/> | |||||
| <path fill-rule="evenodd" d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/> | |||||
| </svg>Eliminar</a></li> | |||||
| <!-- | |||||
| <li><hr class="dropdown-divider"></li> | |||||
| <li><a class="dropdown-item" href="#"><svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-trash me-2" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/> | |||||
| <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/> | |||||
| </svg>Delete</a></li> | |||||
| --> | |||||
| </ul> | |||||
| </div><!--//dropdown--> | |||||
| </div><!--//app-card-actions--> | |||||
| </div><!--//app-card-body--> | |||||
| </div> | |||||
| </div> | |||||
| {% endfor %} | |||||
| </div> | |||||
| </div><!--//container-fluid--> | |||||
| {% endblock %} | |||||
| @ -0,0 +1,34 @@ | |||||
| {% load static %} | |||||
| {% include "_head.html" %} | |||||
| <body class="app"> | |||||
| {% include "_cabecera.html" %} | |||||
| <div class="app-wrapper"> | |||||
| <div class="app-content pt-3 p-md-3 p-lg-4"> | |||||
| {% block content %}{% endblock %} | |||||
| </div><!--//app-content--> | |||||
| {% include "_footer.html" %} | |||||
| </div><!--//app-wrapper--> | |||||
| <!-- Javascript --> | |||||
| <script src="{% static 'plugins/popper.min.js' %}"></script> | |||||
| <script src="{% static 'plugins/bootstrap/js/bootstrap.min.js' %}"></script> | |||||
| <!-- Charts JS --> | |||||
| <script src="{% static 'plugins/chart.js/chart.min.js' %}"></script> | |||||
| <script src="{% static 'js/index-charts.js' %}"></script> | |||||
| <!-- La línea siguiente es para la demo de los charts en charts.html --> | |||||
| <script src="{% static 'js/charts-demo.js' %}"></script> | |||||
| <!-- Page Specific JS --> | |||||
| <script src="{% static 'js/app.js' %}"></script> | |||||
| </body> | |||||
| </html> | |||||
| @ -0,0 +1,5 @@ | |||||
| {% if user.is_authenticated %} | |||||
| <a class="dropdown-toggle" id="user-dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><img src="{{ url_for('paginas.uploaded_file', filename=current_user.photo) }}" alt="user profile"></a> | |||||
| {% else %} | |||||
| <a class="dropdown-toggle" id="user-dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><img src="{{ url_for('static', filename='images/reymota-logo.svg') }}" alt="user profile"></a> | |||||
| {% endif %} | |||||
| @ -0,0 +1,89 @@ | |||||
| {% load i18n static %} | |||||
| {% include "_head.html" %} | |||||
| <body class="app app-login p-0"> | |||||
| <div class="row g-0 app-auth-wrapper"> | |||||
| <div class="col-12 col-md-7 col-lg-6 auth-main-col text-center p-5"> | |||||
| <div class="d-flex flex-column align-content-end"> | |||||
| <div class="app-auth-body mx-auto"> | |||||
| {% include "_branding.html" %} | |||||
| <h2 class="auth-heading text-center mb-5">Entrar en Finanzas</h2> | |||||
| {% if form.errors and not form.non_field_errors %} | |||||
| <p class="errornote"> | |||||
| {% blocktranslate count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktranslate %} | |||||
| </p> | |||||
| {% endif %} | |||||
| {% if form.non_field_errors %} | |||||
| {% for error in form.non_field_errors %} | |||||
| <p class="errornote"> | |||||
| {{ error }} | |||||
| </p> | |||||
| {% endfor %} | |||||
| {% endif %} | |||||
| <div class="auth-form-container text-start" id="content-main"> | |||||
| {% if user.is_authenticated %} | |||||
| <p class="errornote"> | |||||
| {% blocktranslate trimmed %} | |||||
| You are authenticated as {{ username }}, but are not authorized to | |||||
| access this page. Would you like to login to a different account? | |||||
| {% endblocktranslate %} | |||||
| </p> | |||||
| {% endif %} | |||||
| <form class="auth-form login-form" action="{{ app_path }}" method="post" id="login-form">{% csrf_token %} | |||||
| <div class="text mb-3"> | |||||
| {{ form.username.errors }} | |||||
| {{ form.username.label_tag }} | |||||
| </div> | |||||
| <div class="text mb-3"> | |||||
| {{ form.username }} | |||||
| </div> | |||||
| <div class="password mb-3"> | |||||
| {{ form.password.errors }} | |||||
| {{ form.password.label_tag }} | |||||
| </div> | |||||
| <div class="password mb-3"> | |||||
| {{ form.password }} | |||||
| <input type="hidden" name="next" value="{{ next }}"> | |||||
| </div> | |||||
| {% url 'admin_password_reset' as password_reset_url %} | |||||
| {% if password_reset_url %} | |||||
| <div class="password-reset-link"> | |||||
| <a href="{{ password_reset_url }}">{% translate 'Forgotten your password or username?' %}</a> | |||||
| </div> | |||||
| {% endif %} | |||||
| <div class="text-center"> | |||||
| <input type="submit" class="btn app-btn-primary w-100 theme-btn mx-auto" value="{% translate 'Log in' %}"> | |||||
| </div> | |||||
| </form> | |||||
| </div> | |||||
| </div> | |||||
| {% include "_footer.html" %} | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-12 col-md-5 col-lg-6 h-100 auth-background-col"> | |||||
| <div class="auth-background-holder"> | |||||
| </div> | |||||
| <div class="auth-background-mask"></div> | |||||
| <div class="auth-background-overlay p-3 p-lg-5"> | |||||
| <div class="d-flex flex-column align-content-end h-100"> | |||||
| <div class="h-100"></div> | |||||
| <!-- | |||||
| <div class="overlay-content p-3 p-lg-4 rounded"> | |||||
| <h5 class="mb-3 overlay-title">Explore Portal Admin Template</h5> | |||||
| <div>Portal is a free Bootstrap 5 admin dashboard template. You can download and view the template license <a href="https://themes.3rdwavemedia.com/bootstrap-templates/admin-dashboard/portal-free-bootstrap-admin-dashboard-template-for-developers/">here</a>.</div> | |||||
| </div> | |||||
| --> | |||||
| </div> | |||||
| </div><!--//auth-background-overlay--> | |||||
| </div><!--//auth-background-col--> | |||||
| </div><!--//row--> | |||||
| </body> | |||||
| </html> | |||||
| @ -0,0 +1,7 @@ | |||||
| {% block title %}Logged out{% endblock %} | |||||
| {% block content %} | |||||
| Logged out | |||||
| You have been successfully logged out. You can log-in again. | |||||
| {% endblock %} | |||||
| @ -0,0 +1,89 @@ | |||||
| {% load i18n static %} | |||||
| {% include "_head.html" %} | |||||
| <body class="app app-login p-0"> | |||||
| <div class="row g-0 app-auth-wrapper"> | |||||
| <div class="col-12 col-md-7 col-lg-6 auth-main-col text-center p-5"> | |||||
| <div class="d-flex flex-column align-content-end"> | |||||
| <div class="app-auth-body mx-auto"> | |||||
| {% include "_branding.html" %} | |||||
| <h2 class="auth-heading text-center mb-5">Entrar en Finanzas</h2> | |||||
| {% if form.errors and not form.non_field_errors %} | |||||
| <p class="errornote"> | |||||
| {% blocktranslate count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktranslate %} | |||||
| </p> | |||||
| {% endif %} | |||||
| {% if form.non_field_errors %} | |||||
| {% for error in form.non_field_errors %} | |||||
| <p class="errornote"> | |||||
| {{ error }} | |||||
| </p> | |||||
| {% endfor %} | |||||
| {% endif %} | |||||
| <div class="auth-form-container text-start" id="content-main"> | |||||
| {% if user.is_authenticated %} | |||||
| <p class="errornote"> | |||||
| {% blocktranslate trimmed %} | |||||
| You are authenticated as {{ username }}, but are not authorized to | |||||
| access this page. Would you like to login to a different account? | |||||
| {% endblocktranslate %} | |||||
| </p> | |||||
| {% endif %} | |||||
| <form class="auth-form login-form" action="{{ app_path }}" method="post" id="login-form">{% csrf_token %} | |||||
| <div class="text mb-3"> | |||||
| {{ form.username.errors }} | |||||
| {{ form.username.label_tag }} | |||||
| </div> | |||||
| <div class="text mb-3"> | |||||
| {{ form.username }} | |||||
| </div> | |||||
| <div class="password mb-3"> | |||||
| {{ form.password.errors }} | |||||
| {{ form.password.label_tag }} | |||||
| </div> | |||||
| <div class="password mb-3"> | |||||
| {{ form.password }} | |||||
| <input type="hidden" name="next" value="{{ next }}"> | |||||
| </div> | |||||
| {% url 'admin_password_reset' as password_reset_url %} | |||||
| {% if password_reset_url %} | |||||
| <div class="password-reset-link"> | |||||
| <a href="{{ password_reset_url }}">{% translate 'Forgotten your password or username?' %}</a> | |||||
| </div> | |||||
| {% endif %} | |||||
| <div class="text-center"> | |||||
| <input type="submit" class="btn app-btn-primary w-100 theme-btn mx-auto" value="{% translate 'Log in' %}"> | |||||
| </div> | |||||
| </form> | |||||
| </div> | |||||
| </div> | |||||
| {% include "_footer.html" %} | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-12 col-md-5 col-lg-6 h-100 auth-background-col"> | |||||
| <div class="auth-background-holder"> | |||||
| </div> | |||||
| <div class="auth-background-mask"></div> | |||||
| <div class="auth-background-overlay p-3 p-lg-5"> | |||||
| <div class="d-flex flex-column align-content-end h-100"> | |||||
| <div class="h-100"></div> | |||||
| <!-- | |||||
| <div class="overlay-content p-3 p-lg-4 rounded"> | |||||
| <h5 class="mb-3 overlay-title">Explore Portal Admin Template</h5> | |||||
| <div>Portal is a free Bootstrap 5 admin dashboard template. You can download and view the template license <a href="https://themes.3rdwavemedia.com/bootstrap-templates/admin-dashboard/portal-free-bootstrap-admin-dashboard-template-for-developers/">here</a>.</div> | |||||
| </div> | |||||
| --> | |||||
| </div> | |||||
| </div><!--//auth-background-overlay--> | |||||
| </div><!--//auth-background-col--> | |||||
| </div><!--//row--> | |||||
| </body> | |||||
| </html> | |||||
| @ -0,0 +1,10 @@ | |||||
| <!DOCTYPE html> | |||||
| <html> | |||||
| <head> | |||||
| <title>Cerrar Sesión</title> | |||||
| </head> | |||||
| <body> | |||||
| <h2>Has cerrado sesión con éxito</h2> | |||||
| <a href="{% url 'login' %}">Iniciar sesión nuevamente</a> | |||||
| </body> | |||||
| </html> | |||||
| @ -0,0 +1,67 @@ | |||||
| {% include "_head.html" %} | |||||
| <body class="app app-signup p-0"> | |||||
| <div class="row g-0 app-auth-wrapper"> | |||||
| <div class="col-12 col-md-7 col-lg-6 auth-main-col text-center p-5"> | |||||
| <div class="d-flex flex-column align-content-end"> | |||||
| <div class="app-auth-body mx-auto"> | |||||
| {% include "_branding.html" %} | |||||
| <h2 class="auth-heading text-center mb-4">Registrarse en Finanzas</h2> | |||||
| <div class="auth-form-container text-start mx-auto"> | |||||
| <form class="auth-form auth-signup-form" method="POST" enctype="multipart/form-data"> | |||||
| {% csrf_token %} | |||||
| {% for field in form %} | |||||
| <p> | |||||
| {{ field.label_tag }}<br> | |||||
| {{ field }} | |||||
| {% if field.help_text %} | |||||
| <small style="color: grey">{{ field.help_text }}</small> | |||||
| {% endif %} | |||||
| {% for error in field.errors %} | |||||
| <p style="color: red">{{ error }}</p> | |||||
| {% endfor %} | |||||
| </p> | |||||
| {% endfor %} | |||||
| <div class="item border-bottom py-3"></div> | |||||
| <div class="text-center"> | |||||
| <button type="submit" class="btn app-btn-primary w-100 theme-btn mx-auto">Sign Up</button> | |||||
| </div> | |||||
| </form><!--//auth-form--> | |||||
| <div class="auth-option text-center pt-5">¿Ya tienes una cuenta? <a class="text-link" href="{% url 'login' %}" >Entra</a></div> | |||||
| </div><!--//auth-form-container--> | |||||
| </div><!--//auth-body--> | |||||
| {% include "_footer.html" %} | |||||
| </div><!--//flex-column--> | |||||
| </div><!--//auth-main-col--> | |||||
| <div class="col-12 col-md-5 col-lg-6 h-100 auth-background-col"> | |||||
| <div class="auth-background-holder"> | |||||
| </div> | |||||
| <div class="auth-background-mask"></div> | |||||
| <div class="auth-background-overlay p-3 p-lg-5"> | |||||
| <div class="d-flex flex-column align-content-end h-100"> | |||||
| <div class="h-100"></div> | |||||
| <!-- | |||||
| <div class="overlay-content p-3 p-lg-4 rounded"> | |||||
| <h5 class="mb-3 overlay-title">Explore Portal Admin Template</h5> | |||||
| <div>Portal is a free Bootstrap 5 admin dashboard template. You can download and view the template license <a href="https://themes.3rdwavemedia.com/bootstrap-templates/admin-dashboard/portal-free-bootstrap-admin-dashboard-template-for-developers/">here</a>.</div> | |||||
| </div> | |||||
| --> | |||||
| </div> | |||||
| </div><!--//auth-background-overlay--> | |||||
| </div><!--//auth-background-col--> | |||||
| </div><!--//row--> | |||||
| </body> | |||||
| </html> | |||||
| @ -0,0 +1,9 @@ | |||||
| import os | |||||
| from django import template | |||||
| register = template.Library() | |||||
| @register.filter | |||||
| def muestra_version(clave): | |||||
| return os.getenv(clave, '') | |||||
| @ -0,0 +1,3 @@ | |||||
| from django.test import TestCase | |||||
| # Create your tests here. | |||||
| @ -0,0 +1,17 @@ | |||||
| from django.urls import path | |||||
| from . import views | |||||
| urlpatterns = [ | |||||
| path('cuentas/', views.lista_cuentas, name='lista_cuentas'), | |||||
| path('cuentas/nuevo/', views.nueva_cuenta, name='nueva_cuenta'), | |||||
| path('cuentas/<int:cuenta_id>/', views.detalle_cuenta, name='detalle_cuenta'), | |||||
| path('cuentas/<int:cuenta_id>/editar/', views.editar_cuenta, name='editar_cuenta'), | |||||
| path('cuentas/<int:cuenta_id>/eliminar/', views.eliminar_cuenta, name='eliminar_cuenta'), | |||||
| path('apuntes/', views.lista_apuntes, name='lista_apuntes'), | |||||
| path('apuntes/nuevo/', views.nuevo_apunte, name='nuevo_apunte'), | |||||
| path('apuntes/<int:apunte_id>/', views.detalle_apunte, name='detalle_apunte'), | |||||
| path('apuntes/<int:apunte_id>/editar/', views.editar_apunte, name='editar_apunte'), | |||||
| path('apuntes/<int:apunte_id>/eliminar/', views.eliminar_apunte, name='eliminar_apunte'), | |||||
| ] | |||||
| @ -0,0 +1,145 @@ | |||||
| from django.shortcuts import render | |||||
| from django.contrib.auth.decorators import login_required | |||||
| from django.shortcuts import render, get_object_or_404, redirect | |||||
| # Create your views here. | |||||
| from .models import Cuentas, Apuntes | |||||
| from .forms import CuentasForm, ApuntesForm | |||||
| @login_required | |||||
| def principal(request): | |||||
| cuentas = Cuentas.objects.all() | |||||
| apuntes = Apuntes.objects.all() | |||||
| return render(request, 'apuntes/index.html', {'cuentas': cuentas, 'apuntes': apuntes}) | |||||
| # Vistas para los cuentas | |||||
| @login_required | |||||
| def lista_cuentas(request): | |||||
| cuentas = Cuentas.objects.all() | |||||
| return render(request, 'apuntes/lista_cuentas.html', {'cuentas': cuentas}) | |||||
| @login_required | |||||
| def detalle_cuenta(request, cuenta_id): | |||||
| cuenta = get_object_or_404(Cuentas, pk=cuenta_id) | |||||
| apuntes = Apuntes.objects.filter(cuenta=cuenta_id) | |||||
| return render(request, 'apuntes/detalle_cuenta.html', {'cuenta': cuenta, 'apuntes': apuntes}) | |||||
| @login_required | |||||
| def nueva_cuenta(request): | |||||
| if request.method == 'POST': | |||||
| form = CuentasForm(request.POST, request.FILES) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('lista_cuentas') | |||||
| else: | |||||
| form = CuentasForm() | |||||
| return render(request, 'apuntes/form_cuenta.html', {'form': form}) | |||||
| @login_required | |||||
| def editar_cuenta(request, cuenta_id): | |||||
| cuenta = get_object_or_404(Cuentas, pk=cuenta_id) | |||||
| if request.method == 'POST': | |||||
| form = CuentasForm(request.POST, request.FILES, instance=cuenta) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('lista_cuentas') | |||||
| else: | |||||
| form = CuentasForm(instance=cuenta) | |||||
| return render(request, 'apuntes/form_cuenta.html', {'form': form}) | |||||
| @login_required | |||||
| def eliminar_cuenta(request, cuenta_id): | |||||
| cuenta = get_object_or_404(Cuentas, pk=cuenta_id) | |||||
| cuenta.delete() | |||||
| return redirect('lista_cuentas') | |||||
| # Vistas para los apuntes | |||||
| @login_required | |||||
| def lista_apuntes(request): | |||||
| apuntes = Apuntes.objects.all() | |||||
| return render(request, 'apuntes/lista_apuntes.html', {'apuntes': apuntes}) | |||||
| @login_required | |||||
| def detalle_apunte(request, apunte_id): | |||||
| apunte = get_object_or_404(Apuntes, pk=apunte_id) | |||||
| return render(request, 'apuntes/detalle_apunte.html', {'apunte': apunte}) | |||||
| @login_required | |||||
| def nuevo_apunte(request): | |||||
| cuentas = Cuentas.objects.all() # vamos a ver si hay vehículos dados de alta | |||||
| if cuentas: | |||||
| if request.method == 'POST': | |||||
| form = ApuntesForm(request.POST, request.FILES) | |||||
| if form.is_valid(): | |||||
| instancia = form.save(commit=False) | |||||
| aplica_descuento = form.cleaned_data['aplica_descuento'] | |||||
| if aplica_descuento: | |||||
| instancia.descuento = float(instancia.importe) * 0.03 | |||||
| else: | |||||
| instancia.descuento = 0.0 | |||||
| instancia.importe = float(instancia.importe) - instancia.descuento | |||||
| if instancia.litros > 0: | |||||
| instancia.precioxlitro = round(instancia.importe / float(instancia.litros), 2) | |||||
| else: | |||||
| instancia.precioxlitro = 0 | |||||
| # lee todos los apuntes del vehículo | |||||
| # apuntes = Apuntes.query.filter_by(cuenta_id=cuenta_id).all() | |||||
| if Apuntes.objects.filter(cuenta_id=instancia.cuenta): | |||||
| apuntes = Apuntes.objects.filter(cuenta_id=instancia.cuenta).order_by('-fecha')[0] | |||||
| instancia.kmsrecorridos = instancia.kms - apuntes.kms | |||||
| if instancia.kmsrecorridos > 0: | |||||
| instancia.consumo = round(instancia.litros * 100 / instancia.kmsrecorridos, 2) | |||||
| else: | |||||
| instancia.kmsrecorridos = 0 | |||||
| instancia.consumo = 0 | |||||
| instancia.save() | |||||
| return redirect('lista_apuntes') | |||||
| else: | |||||
| form = ApuntesForm() | |||||
| return render(request, 'apuntes/form_apunte.html', {'form': form}) | |||||
| else: | |||||
| return render(request, 'apuntes/index.html') | |||||
| @login_required | |||||
| def editar_apunte(request, apunte_id): | |||||
| apunte = get_object_or_404(Apuntes, pk=apunte_id) | |||||
| if request.method == 'POST': | |||||
| form = ApuntesForm(request.POST, request.FILES, instance=apunte) | |||||
| if form.is_valid(): | |||||
| form.save() | |||||
| return redirect('lista_apuntes') | |||||
| else: | |||||
| form = ApuntesForm(instance=apunte) | |||||
| return render(request, 'apuntes/form_apunte.html', {'form': form}) | |||||
| @login_required | |||||
| def eliminar_apunte(request, apunte_id): | |||||
| apunte = Apuntes.objects.get(pk=apunte_id) | |||||
| apunte.delete() | |||||
| return redirect('lista_apuntes') | |||||
| @ -0,0 +1,16 @@ | |||||
| """ | |||||
| ASGI config for finanzas project. | |||||
| It exposes the ASGI callable as a module-level variable named ``application``. | |||||
| For more information on this file, see | |||||
| https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ | |||||
| """ | |||||
| import os | |||||
| from django.core.asgi import get_asgi_application | |||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'finanzas.settings') | |||||
| application = get_asgi_application() | |||||
| @ -0,0 +1,141 @@ | |||||
| """ | |||||
| Django settings for finanzas project. | |||||
| Generated by 'django-admin startproject' using Django 5.1. | |||||
| For more information on this file, see | |||||
| https://docs.djangoproject.com/en/5.1/topics/settings/ | |||||
| For the full list of settings and their values, see | |||||
| https://docs.djangoproject.com/en/5.1/ref/settings/ | |||||
| """ | |||||
| from pathlib import Path | |||||
| # Build paths inside the project like this: BASE_DIR / 'subdir'. | |||||
| BASE_DIR = Path(__file__).resolve().parent.parent | |||||
| # Quick-start development settings - unsuitable for production | |||||
| # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ | |||||
| # SECURITY WARNING: keep the secret key used in production secret! | |||||
| SECRET_KEY = 'django-insecure-vu#zk4g8pj-qoov#8^i$&s8n_ipp2r3h+o$z1w(1%d=6+i@erm' | |||||
| # SECURITY WARNING: don't run with debug turned on in production! | |||||
| DEBUG = True | |||||
| ALLOWED_HOSTS = [] | |||||
| # Application definition | |||||
| INSTALLED_APPS = [ | |||||
| 'django.contrib.admin', | |||||
| 'django.contrib.auth', | |||||
| 'django.contrib.contenttypes', | |||||
| 'django.contrib.sessions', | |||||
| 'django.contrib.messages', | |||||
| 'django.contrib.staticfiles', | |||||
| 'apuntes', | |||||
| ] | |||||
| MIDDLEWARE = [ | |||||
| 'django.middleware.security.SecurityMiddleware', | |||||
| 'django.contrib.sessions.middleware.SessionMiddleware', | |||||
| 'django.middleware.common.CommonMiddleware', | |||||
| 'django.middleware.csrf.CsrfViewMiddleware', | |||||
| 'django.contrib.auth.middleware.AuthenticationMiddleware', | |||||
| 'django.contrib.messages.middleware.MessageMiddleware', | |||||
| 'django.middleware.clickjacking.XFrameOptionsMiddleware', | |||||
| ] | |||||
| ROOT_URLCONF = 'finanzas.urls' | |||||
| TEMPLATES = [ | |||||
| { | |||||
| 'BACKEND': 'django.template.backends.django.DjangoTemplates', | |||||
| 'DIRS': [], | |||||
| 'APP_DIRS': True, | |||||
| 'OPTIONS': { | |||||
| 'context_processors': [ | |||||
| 'django.template.context_processors.debug', | |||||
| 'django.template.context_processors.request', | |||||
| 'django.contrib.auth.context_processors.auth', | |||||
| 'django.contrib.messages.context_processors.messages', | |||||
| ], | |||||
| }, | |||||
| }, | |||||
| ] | |||||
| WSGI_APPLICATION = 'finanzas.wsgi.application' | |||||
| # Database | |||||
| # https://docs.djangoproject.com/en/5.1/ref/settings/#databases | |||||
| DATABASES = { | |||||
| 'default': { | |||||
| 'ENGINE': 'django.db.backends.sqlite3', | |||||
| 'NAME': BASE_DIR / 'db.sqlite3', | |||||
| } | |||||
| } | |||||
| # Password validation | |||||
| # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators | |||||
| AUTH_PASSWORD_VALIDATORS = [ | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', | |||||
| }, | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', | |||||
| }, | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', | |||||
| }, | |||||
| { | |||||
| 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', | |||||
| }, | |||||
| ] | |||||
| # Internationalization | |||||
| # https://docs.djangoproject.com/en/5.1/topics/i18n/ | |||||
| LANGUAGE_CODE = 'es-es' | |||||
| TIME_ZONE = 'Europe/Madrid' | |||||
| USE_I18N = True | |||||
| USE_TZ = True | |||||
| I18N = True | |||||
| L10N = True | |||||
| DECIMAL_SEPARATOR = ',' | |||||
| THOUSAND_SEPARATOR = '.' | |||||
| # Static files (CSS, JavaScript, Images) | |||||
| # https://docs.djangoproject.com/en/5.1/howto/static-files/ | |||||
| STATIC_URL = 'static/' | |||||
| STATIC_ROOT = BASE_DIR / "staticfiles" | |||||
| # Default primary key field type | |||||
| # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field | |||||
| DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' | |||||
| LOGIN_URL = '/accounts/login/' | |||||
| LOGIN_REDIRECT_URL = 'principal' | |||||
| LOGOUT_REDIRECT_URL = 'principal' | |||||
| AUTH_USER_MODEL = "apuntes.ReyMotaUser" | |||||
| MEDIA_ROOT = BASE_DIR / "mediafiles" | |||||
| MEDIA_URL = '/media/' | |||||
| if DEBUG is False: | |||||
| CSRF_TRUSTED_ORIGINS = os.environ.get("CSRF_TRUSTED_ORIGINS").split(" ") | |||||
| @ -0,0 +1,35 @@ | |||||
| """ | |||||
| URL configuration for finanzas project. | |||||
| The `urlpatterns` list routes URLs to views. For more information please see: | |||||
| https://docs.djangoproject.com/en/5.1/topics/http/urls/ | |||||
| Examples: | |||||
| Function views | |||||
| 1. Add an import: from my_app import views | |||||
| 2. Add a URL to urlpatterns: path('', views.home, name='home') | |||||
| Class-based views | |||||
| 1. Add an import: from other_app.views import Home | |||||
| 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') | |||||
| Including another URLconf | |||||
| 1. Import the include() function: from django.urls import include, path | |||||
| 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) | |||||
| """ | |||||
| from django.contrib import admin | |||||
| from django.urls import path, include | |||||
| from django.conf.urls.static import static | |||||
| from django.conf import settings | |||||
| from django.views.generic.base import TemplateView # new | |||||
| urlpatterns = [ | |||||
| path('obreros/', admin.site.urls), | |||||
| path('apuntes/', include('apuntes.urls')), | |||||
| path("accounts/", include("accounts.urls")), # new | |||||
| path("accounts/", include("django.contrib.auth.urls")), | |||||
| path("", TemplateView.as_view(template_name="apuntes/index.html"), | |||||
| name="principal"), # new | |||||
| ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) | |||||
| @ -0,0 +1,16 @@ | |||||
| """ | |||||
| WSGI config for finanzas project. | |||||
| It exposes the WSGI callable as a module-level variable named ``application``. | |||||
| For more information on this file, see | |||||
| https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ | |||||
| """ | |||||
| import os | |||||
| from django.core.wsgi import get_wsgi_application | |||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'finanzas.settings') | |||||
| application = get_wsgi_application() | |||||
| @ -0,0 +1,22 @@ | |||||
| #!/usr/bin/env python | |||||
| """Django's command-line utility for administrative tasks.""" | |||||
| import os | |||||
| import sys | |||||
| def main(): | |||||
| """Run administrative tasks.""" | |||||
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'finanzas.settings') | |||||
| try: | |||||
| from django.core.management import execute_from_command_line | |||||
| except ImportError as exc: | |||||
| raise ImportError( | |||||
| "Couldn't import Django. Are you sure it's installed and " | |||||
| "available on your PYTHONPATH environment variable? Did you " | |||||
| "forget to activate a virtual environment?" | |||||
| ) from exc | |||||
| execute_from_command_line(sys.argv) | |||||
| if __name__ == '__main__': | |||||
| main() | |||||