| @ -0,0 +1,45 @@ | |||
| from django import forms | |||
| from .models import Artista, Album, Song | |||
| class ArtistaForm(forms.ModelForm): | |||
| class Meta: | |||
| model = Artista | |||
| fields = ['nombre', 'biografia', 'foto'] | |||
| nombre = forms.CharField( | |||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||
| biografia = forms.CharField( | |||
| widget=forms.TextInput(attrs={'class': 'form-control'})) | |||
| class AlbumForm(forms.ModelForm): | |||
| class Meta: | |||
| model = Album | |||
| fields = ['name', 'artist', 'year', 'cover_image'] | |||
| year = forms.DateField( | |||
| widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'})) | |||
| artist = forms.ModelChoiceField( | |||
| queryset=Artista.objects.all(), | |||
| widget=forms.Select(attrs={'class': 'form-control'})) | |||
| class SongForm(forms.ModelForm): | |||
| class Meta: | |||
| model = Song | |||
| fields = ['title', 'artist', 'album', 'year', 'lyrics'] | |||
| year = forms.DateField( | |||
| widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'})) | |||
| artist = forms.ModelChoiceField( | |||
| queryset=Artista.objects.all(), | |||
| widget=forms.Select(attrs={'class': 'form-control'})) | |||
| album = forms.ModelChoiceField( | |||
| queryset=Album.objects.all(), # habría que seleccionar los álbumes del artista | |||
| widget=forms.Select(attrs={'class': 'form-control'})) | |||
| @ -0,0 +1,52 @@ | |||
| # Generated by Django 4.2 on 2024-09-10 13:23 | |||
| import django.core.validators | |||
| from django.db import migrations, models | |||
| import django.db.models.deletion | |||
| import lyrics.models | |||
| class Migration(migrations.Migration): | |||
| initial = True | |||
| dependencies = [ | |||
| ] | |||
| operations = [ | |||
| migrations.CreateModel( | |||
| name='Album', | |||
| fields=[ | |||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||
| ('name', models.CharField(max_length=200)), | |||
| ('year', models.PositiveBigIntegerField(default=2024, validators=[django.core.validators.MinValueValidator(1984), lyrics.models.max_value_current_year])), | |||
| ('cover_image', models.ImageField(blank=True, null=True, upload_to='cover_image/')), | |||
| ], | |||
| ), | |||
| migrations.CreateModel( | |||
| name='Artista', | |||
| fields=[ | |||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||
| ('nombre', models.CharField(max_length=200)), | |||
| ('biografia', models.TextField(blank=True, null=True)), | |||
| ('foto', models.ImageField(blank=True, null=True, upload_to='artistas/')), | |||
| ], | |||
| ), | |||
| migrations.CreateModel( | |||
| name='Song', | |||
| fields=[ | |||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||
| ('title', models.CharField(max_length=200)), | |||
| ('year', models.PositiveBigIntegerField(default=2024, validators=[django.core.validators.MinValueValidator(1984), lyrics.models.max_value_current_year])), | |||
| ('lyrics', models.CharField(max_length=1000)), | |||
| ('pista', models.DecimalField(blank=True, decimal_places=0, max_digits=5, null=True)), | |||
| ('album', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lyrics.album')), | |||
| ('artist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lyrics.artista')), | |||
| ], | |||
| ), | |||
| migrations.AddField( | |||
| model_name='album', | |||
| name='artist', | |||
| field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lyrics.artista'), | |||
| ), | |||
| ] | |||
| @ -1,3 +1,44 @@ | |||
| from django.db import models | |||
| import datetime | |||
| from django.core.validators import MaxValueValidator, MinValueValidator | |||
| from django.utils.translation import gettext_lazy as _ | |||
| def current_year(): | |||
| return datetime.date.today().year | |||
| def max_value_current_year(value): | |||
| return MaxValueValidator(current_year())(value) | |||
| class Artista(models.Model): | |||
| nombre = models.CharField(max_length=200) | |||
| biografia = models.TextField(blank=True, null=True) | |||
| foto = models.ImageField(upload_to='artistas/', blank=True, null=True) # Nuevo campo | |||
| def __str__(self): | |||
| return self.nombre | |||
| class Album(models.Model): | |||
| name = models.CharField(max_length=200) | |||
| artist = models.ForeignKey(Artista, on_delete=models.CASCADE) | |||
| year = models.PositiveBigIntegerField(default=current_year(), validators=[MinValueValidator(1984), max_value_current_year]) | |||
| cover_image = models.ImageField(upload_to='cover_image/', blank=True, null=True) # Nuevo campo | |||
| def __str__(self): | |||
| return self.name | |||
| class Song(models.Model): | |||
| title = models.CharField(max_length=200) | |||
| artist = models.ForeignKey(Artista, on_delete=models.CASCADE) | |||
| album = models.ForeignKey(Album, on_delete=models.CASCADE) | |||
| year = models.PositiveBigIntegerField(default=current_year(), validators=[MinValueValidator(1984), max_value_current_year]) | |||
| lyrics = models.CharField(max_length=1000) | |||
| pista = models.DecimalField(max_digits=5, decimal_places=0, blank=True, null=True) | |||
| def __str__(self): | |||
| return self.title | |||
| # Create your models here. | |||
| @ -0,0 +1,27 @@ | |||
| from django.urls import path | |||
| from . import views | |||
| app_name='lyrics' | |||
| urlpatterns = [ | |||
| path('', views.principal, name='principal'), | |||
| path('artistas/', views.lista_artistas, name='lista_artistas'), | |||
| path('artistas/nuevo/', views.nuevo_artista, name='nuevo_artista'), | |||
| path('artistas/<int:artista_id>/', views.detalle_artista, name='detalle_artista'), | |||
| path('artistas/<int:artista_id>/editar/', views.editar_artista, name='editar_artista'), | |||
| path('artistas/<int:artista_id>/eliminar/', views.eliminar_artista, name='eliminar_artista'), | |||
| path('album/', views.lista_albumes, name='lista_albumes'), | |||
| path('album/nuevo/', views.nuevo_album, name='nuevo_album'), | |||
| path('album/<int:album_id>/', views.detalle_album, name='detalle_album'), | |||
| path('album/<int:album_id>/editar/', views.editar_album, name='editar_album'), | |||
| path('album/<int:album_id>/eliminar/', views.eliminar_album, name='eliminar_album'), | |||
| path('song/', views.lista_songs, name='lista_songs'), | |||
| path('song/nuevo/', views.nuevo_song, name='nuevo_song'), | |||
| path('song/<int:song_id>/', views.detalle_song, name='detalle_song'), | |||
| path('song/<int:song_id>/editar/', views.editar_song, name='editar_song'), | |||
| path('song/<int:song_id>/eliminar/', views.eliminar_song, name='eliminar_song'), | |||
| ] | |||
| @ -1,3 +1,196 @@ | |||
| from django.shortcuts import render | |||
| # Create your views here. | |||
| from django.shortcuts import render, get_object_or_404, redirect | |||
| from django.contrib.auth.decorators import login_required | |||
| from .models import Artista, Album, Song | |||
| from .forms import ArtistaForm, AlbumForm, SongForm | |||
| @login_required | |||
| def principal(request): | |||
| artistas = Artista.objects.all() | |||
| albumes = Album.objects.all() | |||
| return render(request, 'lyrics/index.html', {'artistas': artistas, 'albumes': albumes}) | |||
| ######################### | |||
| # Vistas para los artistas | |||
| @login_required | |||
| def lista_artistas(request): | |||
| artistas = Artista.objects.all() | |||
| return render(request, 'lyrics/lista_artistas.html', {'artistas': artistas}) | |||
| @login_required | |||
| def detalle_artista(request, artista_id): | |||
| artista = get_object_or_404(Artista, pk=artista_id) | |||
| albumes = Album.objects.filter(artista=artista_id) | |||
| return render(request, 'lyrics/detalle_artista.html', {'artista': artista, 'albumes': albumes}) | |||
| @login_required | |||
| def nuevo_artista(request): | |||
| if request.method == 'POST': | |||
| form = ArtistaForm(request.POST, request.FILES) | |||
| if form.is_valid(): | |||
| form.save() | |||
| return redirect('lyrics:lista_artistas') | |||
| else: | |||
| form = ArtistaForm() | |||
| return render(request, 'lyrics/form_artista.html', {'form': form}) | |||
| @login_required | |||
| def editar_artista(request, artista_id): | |||
| artista = get_object_or_404(Artista, pk=artista_id) | |||
| if request.method == 'POST': | |||
| form = ArtistaForm(request.POST, request.FILES, instance=artista) | |||
| if form.is_valid(): | |||
| form.save() | |||
| return redirect('lyrics:lista_artistas') | |||
| else: | |||
| form = ArtistaForm(instance=artista) | |||
| return render(request, 'lyrics/form_artista.html', {'form': form}) | |||
| @login_required | |||
| def eliminar_artista(request, artista_id): | |||
| artista = get_object_or_404(Artista, pk=artista_id) | |||
| artista.delete() | |||
| return redirect('lyrics:lista_artistas') | |||
| ######################### | |||
| # Vistas para los albumes | |||
| @login_required | |||
| def lista_albumes(request): | |||
| albumes = Album.objects.all() | |||
| return render(request, 'lyrics/lista_albumes.html', {'albumes': albumes}) | |||
| @login_required | |||
| def detalle_album(request, album_id): | |||
| album = get_object_or_404(Album, pk=album_id) | |||
| return render(request, 'lyrics/detalle_album.html', {'album': album}) | |||
| @login_required | |||
| def nuevo_album(request): | |||
| artistas = Artista.objects.all() # vamos a ver si hay vehículos dados de alta | |||
| if artistas: | |||
| if request.method == 'POST': | |||
| form = AlbumForm(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 albumes del vehículo | |||
| # albumes = Albums.query.filter_by(artista_id=artista_id).all() | |||
| if Album.objects.filter(artista_id=instancia.artista): | |||
| albumes = Album.objects.filter(artista_id=instancia.artista).order_by('-fecha')[0] | |||
| instancia.kmsrecorridos = instancia.kms - albumes.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('lyrics:lista_albumes') | |||
| else: | |||
| form = AlbumForm() | |||
| return render(request, 'lyrics/form_album.html', {'form': form}) | |||
| else: | |||
| return render(request, 'lyrics/index.html') | |||
| @login_required | |||
| def editar_album(request, album_id): | |||
| album = get_object_or_404(Album, pk=album_id) | |||
| if request.method == 'POST': | |||
| form = AlbumForm(request.POST, request.FILES, instance=album) | |||
| if form.is_valid(): | |||
| form.save() | |||
| return redirect('lyrics:lista_albumes') | |||
| else: | |||
| form = AlbumForm(instance=album) | |||
| return render(request, 'lyrics/form_album.html', {'form': form}) | |||
| @login_required | |||
| def eliminar_album(request, album_id): | |||
| album = Album.objects.get(pk=album_id) | |||
| album.delete() | |||
| return redirect('lyrics:lista_albumes') | |||
| ######################### | |||
| # Vistas para los songs | |||
| @login_required | |||
| def lista_songs(request): | |||
| songs = Song.objects.all() | |||
| return render(request, 'lyrics/lista_songs.html', {'songs': songs}) | |||
| @login_required | |||
| def detalle_song(request, song_id): | |||
| song = get_object_or_404(Song, pk=song_id) | |||
| albumes = Album.objects.filter(song=song_id) | |||
| return render(request, 'lyrics/detalle_song.html', {'song': song, 'albumes': albumes}) | |||
| @login_required | |||
| def nuevo_song(request): | |||
| if request.method == 'POST': | |||
| form = SongForm(request.POST, request.FILES) | |||
| if form.is_valid(): | |||
| form.save() | |||
| return redirect('lyrics:lista_songs') | |||
| else: | |||
| form = SongForm() | |||
| return render(request, 'lyrics/form_song.html', {'form': form}) | |||
| @login_required | |||
| def editar_song(request, song_id): | |||
| song = get_object_or_404(Song, pk=song_id) | |||
| if request.method == 'POST': | |||
| form = SongForm(request.POST, request.FILES, instance=song) | |||
| if form.is_valid(): | |||
| form.save() | |||
| return redirect('lyrics:lista_songs') | |||
| else: | |||
| form = SongForm(instance=song) | |||
| return render(request, 'lyrics/form_song.html', {'form': form}) | |||
| @login_required | |||
| def eliminar_song(request, song_id): | |||
| song = get_object_or_404(Song, pk=song_id) | |||
| song.delete() | |||
| return redirect('lyrics:lista_songs') | |||
| @ -0,0 +1,24 @@ | |||
| {% 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">{{ album.name }}</h4> | |||
| <ul class="notification-meta list-inline mb-0"> | |||
| <li class="list-inline-item"><a href="{% url 'lyrics:detalle_album' album.album_id %}">{{ album.album.name }}</a></li> | |||
| <li class="list-inline-item">|</li> | |||
| <li class="list-inline-item">{{ album.artist }}</li> | |||
| <li class="list-inline-item">|</li> | |||
| <li class="list-inline-item">{{ album.year }}</li> | |||
| </ul> | |||
| </div><!--//col--> | |||
| </tr> | |||
| </div><!--//row--> | |||
| </div><!--//app-card-header--> | |||
| </div><!--//app-card--> | |||
| {% endblock %} | |||
| @ -0,0 +1,62 @@ | |||
| {% 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 artista.foto %} | |||
| <p><img src="{{ artista.foto.url }}" alt="{{ artista.nombre}}" 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>{{ artista.nombre }}</h4> | |||
| <ul class="notification-meta list-inline mb-0"> | |||
| <li class="list-inline-item">{{ artista.nombre }}</li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="app-card-body p-4"> | |||
| {% if albumes %} | |||
| <table class="table app-table-hover mb-0 text-left"> | |||
| <thead> | |||
| <tr> | |||
| <th class="cell">Título</th> | |||
| <th class="cell">Artista</th> | |||
| <th class="cell">Year</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| {% for album in albumes %} | |||
| <tr> | |||
| <td class="cell"><a href="{% url 'lyrics:detalle_album' album.id %}">{{ album.name }}</a></td> | |||
| <td class="cell">{{ album.artist }}</td> | |||
| <td class="cell">{{ album.year }}</td> | |||
| </tr> | |||
| {% endfor %} | |||
| </tbody> | |||
| </table> | |||
| {% else %} | |||
| <p>No se han encontrado albums para este artista</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 'lyrics:lista_albumes' %}">Volver al inicio</a> | |||
| </div> | |||
| <div class="col-auto"> | |||
| <a class="btn app-btn-primary" href="{% url 'lyrics:nuevo_album' %}">Añadir album</a> <!-- Faltaría poner el id del artista--> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {% endblock %} | |||
| @ -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">{{ repostaje.fecha }}</h4> | |||
| <ul class="notification-meta list-inline mb-0"> | |||
| <li class="list-inline-item"><a href="{% url 'repostajes:detalle_vehiculo' repostaje.vehiculo_id %}">{{ repostaje.vehiculo.matricula }}</a></li> | |||
| <li class="list-inline-item">|</li> | |||
| <li class="list-inline-item">{{ repostaje.kms }} kms</li> | |||
| <li class="list-inline-item">|</li> | |||
| <li class="list-inline-item">{{ repostaje.litros }} litros</li> | |||
| <li class="list-inline-item">|</li> | |||
| <li class="list-inline-item">{{ repostaje.importe }} €</li> | |||
| <li class="list-inline-item">|</li> | |||
| <li class="list-inline-item">{{ repostaje.kmsrecorridos }} kms. recorridos</li> | |||
| <li class="list-inline-item">|</li> | |||
| <li class="list-inline-item">{{ repostaje.consumo }} litros/100 kms</li> | |||
| <li class="list-inline-item">|</li> | |||
| <li class="list-inline-item">{{ repostaje.precioxlitro }} €/litros</li> | |||
| </ul> | |||
| </div><!--//col--> | |||
| </tr> | |||
| </div><!--//row--> | |||
| </div><!--//app-card-header--> | |||
| </div><!--//app-card--> | |||
| {% endblock %} | |||
| @ -0,0 +1,17 @@ | |||
| {% extends 'base.html' %} | |||
| {% block content %} | |||
| <div class="column is-4 is-offset-4"> | |||
| <h3>{% if form.instance.pk %}Editar álbum{% else %}Nuevo álbum{% 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 artista{% else %}Nuevo artista{% 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,17 @@ | |||
| {% extends 'base.html' %} | |||
| {% block content %} | |||
| <div class="column is-4 is-offset-4"> | |||
| <h3>{% if form.instance.pk %}Editar repostaje{% else %}Nuevo repostaje{% 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,64 @@ | |||
| {% 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 de letras!</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 class="row g-4 mb-4"> | |||
| <div class="col-lg-4 col-md-6 d-flex align-items-stretch mt-4 mt-lg-0" data-aos="zoom-in" data-aos-delay="300"> | |||
| <div class="icon-box iconbox-pink"> | |||
| <div class="icon"> | |||
| <svg width="100" height="100" viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg"> | |||
| <path stroke="none" stroke-width="0" fill="#f5f5f5" d="M300,541.5067337569781C382.14930387511276,545.0595476570109,479.8736841581634,548.3450877840088,526.4010558755058,480.5488172755941C571.5218469581645,414.80211281144784,517.5187510058486,332.0715597781072,496.52539010469104,255.14436215662573C477.37192572678356,184.95920475031193,473.57363656557914,105.61284051026155,413.0603344069578,65.22779650032875C343.27470386102294,18.654635553484475,251.2091493199835,5.337323636656869,175.0934190732945,40.62881213300186C97.87086631185822,76.43348514350839,51.98124368387456,156.15599469081315,36.44837278890362,239.84606092416172C21.716077023791087,319.22268207091537,43.775223500013084,401.1760424656574,96.891909868211,461.97329694683043C147.22146801428983,519.5804099606455,223.5754009179313,538.201503339737,300,541.5067337569781"></path> | |||
| </svg> | |||
| <i class='bx bx-bowl-hot'></i> | |||
| </div> | |||
| <h4><a href="{% url 'lyrics:lista_artistas' %}">Artistas</a></h4> | |||
| <p>Listado de vehículoss</p> | |||
| </div> | |||
| <div class="icon-box iconbox-pink"> | |||
| <div class="icon"> | |||
| <svg width="100" height="100" viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg"> | |||
| <path stroke="none" stroke-width="0" fill="#f5f5f5" d="M300,541.5067337569781C382.14930387511276,545.0595476570109,479.8736841581634,548.3450877840088,526.4010558755058,480.5488172755941C571.5218469581645,414.80211281144784,517.5187510058486,332.0715597781072,496.52539010469104,255.14436215662573C477.37192572678356,184.95920475031193,473.57363656557914,105.61284051026155,413.0603344069578,65.22779650032875C343.27470386102294,18.654635553484475,251.2091493199835,5.337323636656869,175.0934190732945,40.62881213300186C97.87086631185822,76.43348514350839,51.98124368387456,156.15599469081315,36.44837278890362,239.84606092416172C21.716077023791087,319.22268207091537,43.775223500013084,401.1760424656574,96.891909868211,461.97329694683043C147.22146801428983,519.5804099606455,223.5754009179313,538.201503339737,300,541.5067337569781"></path> | |||
| </svg> | |||
| <i class='bx bx-bowl-hot'></i> | |||
| </div> | |||
| <h4><a href="{% url 'lyrics:lista_albumes' %}">Álbumes</a></h4> | |||
| <p>Relación de repostajes</p> | |||
| </div> | |||
| <div class="icon-box iconbox-pink"> | |||
| <div class="icon"> | |||
| <svg width="100" height="100" viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg"> | |||
| <path stroke="none" stroke-width="0" fill="#f5f5f5" d="M300,541.5067337569781C382.14930387511276,545.0595476570109,479.8736841581634,548.3450877840088,526.4010558755058,480.5488172755941C571.5218469581645,414.80211281144784,517.5187510058486,332.0715597781072,496.52539010469104,255.14436215662573C477.37192572678356,184.95920475031193,473.57363656557914,105.61284051026155,413.0603344069578,65.22779650032875C343.27470386102294,18.654635553484475,251.2091493199835,5.337323636656869,175.0934190732945,40.62881213300186C97.87086631185822,76.43348514350839,51.98124368387456,156.15599469081315,36.44837278890362,239.84606092416172C21.716077023791087,319.22268207091537,43.775223500013084,401.1760424656574,96.891909868211,461.97329694683043C147.22146801428983,519.5804099606455,223.5754009179313,538.201503339737,300,541.5067337569781"></path> | |||
| </svg> | |||
| <i class='bx bx-bowl-hot'></i> | |||
| </div> | |||
| <h4><a href="{% url 'lyrics:lista_songs' %}">Canciones</a></h4> | |||
| <p>Listado de vehículoss</p> | |||
| </div> | |||
| </div> | |||
| </div><!--//row--> | |||
| </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">Álbumes</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 'lyrics:nuevo_album' %}">Añadir álbum</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">Vehículo</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 repostaje in repostajes %} | |||
| <tbody> | |||
| <tr> | |||
| <td class="cell"><a href="{% url 'repostajes:detalle_repostaje' repostaje.id %}">{{ repostaje.fecha }}</a></td> | |||
| <td class="cell"><a href="{% url 'repostajes:detalle_vehiculo' repostaje.vehiculo.id %}">{{ repostaje.vehiculo.matricula }}</a></td> | |||
| <td class="cell">{{ repostaje.kms }}</td> | |||
| <td class="cell">{{ repostaje.litros }}</td> | |||
| <td class="cell">{{ repostaje.importe }} €</td> | |||
| <td class="cell">{{ repostaje.descuento }} €</td> | |||
| <td class="cell">{{ repostaje.precioxlitro }} €</td> | |||
| <td class="cell">{{ repostaje.kmsrecorridos }}</td> | |||
| <td class="cell">{{ repostaje.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">Artistas</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 'lyrics:nuevo_artista' %}">Añadir artista</a> | |||
| </div> | |||
| </div><!--//row--> | |||
| </div><!--//table-utilities--> | |||
| </div><!--//col-auto--> | |||
| <div class="row g-4"> | |||
| {% for artista in artistas %} | |||
| <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"> | |||
| {% if artista.foto %} | |||
| <img src="{{ artista.foto.url }}" alt="Foto del artista" style="width:200px;height:200px;"> | |||
| {% else %} | |||
| Sin imágen | |||
| {% endif %} | |||
| <h4 class="app-doc-title truncate mb-0"><a href="{% url 'lyrics:detalle_artista' artista.id %}">{{ artista.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 'lyrics:detalle_artista' artista.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 'lyrics:editar_artista' artista.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 'lyrics:eliminar_artista' artista.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> | |||
| <!-- | |||
| </ul> | |||
| </div><!--//dropdown--> | |||
| </div><!--//app-card-actions--> | |||
| </div><!--//app-card-body--> | |||
| </div> | |||
| </div> | |||
| {% endfor %} | |||
| </div> | |||
| </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">Repostajes</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 'repostajes:nuevo_repostaje' %}">Añadir repostaje</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">Vehículo</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 repostaje in repostajes %} | |||
| <tbody> | |||
| <tr> | |||
| <td class="cell"><a href="{% url 'repostajes:detalle_repostaje' repostaje.id %}">{{ repostaje.fecha }}</a></td> | |||
| <td class="cell"><a href="{% url 'repostajes:detalle_vehiculo' repostaje.vehiculo.id %}">{{ repostaje.vehiculo.matricula }}</a></td> | |||
| <td class="cell">{{ repostaje.kms }}</td> | |||
| <td class="cell">{{ repostaje.litros }}</td> | |||
| <td class="cell">{{ repostaje.importe }} €</td> | |||
| <td class="cell">{{ repostaje.descuento }} €</td> | |||
| <td class="cell">{{ repostaje.precioxlitro }} €</td> | |||
| <td class="cell">{{ repostaje.kmsrecorridos }}</td> | |||
| <td class="cell">{{ repostaje.consumo }}</td> | |||
| </tr> | |||
| </tbody> | |||
| {% endfor %} | |||
| </table> | |||
| </div> | |||
| </div><!--//container-fluid--> | |||
| {% endblock %} | |||