| @ -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() | |||