| @ -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 | 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. | # 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 %} | |||||