1. biblioteca_project/views.py - Vistas Principales
Ubicación: biblioteca_project/views.py
Descripción: Vistas para la página de inicio y ejemplos.
from django.shortcuts import render
from libros.models import Libro, Autor, Categoria, Prestamo
from django.contrib.auth.models import User
def home(request):
"""Vista de la página principal"""
context = {
'total_libros': Libro.objects.count(),
'total_autores': Autor.objects.count(),
'total_categorias': Categoria.objects.count(),
'total_prestamos': Prestamo.objects.filter(estado='activo').count(),
}
return render(request, 'home.html', context)
def ejemplos_rest(request):
"""Vista de ejemplos API REST"""
return render(request, 'ejemplos_rest.html')
def ejemplos_soap(request):
"""Vista de ejemplos SOAP"""
return render(request, 'ejemplos_soap.html')
def ejemplos_admin(request):
"""Vista de ejemplos Panel Admin"""
return render(request, 'ejemplos_admin.html')
2. libros/views.py - Vistas de la App Libros
Ubicación: libros/views.py
Descripción: ViewSets para API REST y vistas tradicionales.
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Q, Count
from rest_framework import viewsets, filters
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from django_filters.rest_framework import DjangoFilterBackend
from datetime import date, timedelta
from .models import Libro, Autor, Categoria, Editorial, Prestamo
from .serializers import (
LibroSerializer, AutorSerializer, CategoriaSerializer,
EditorialSerializer, PrestamoSerializer
)
# ========== VIEWSETS REST API ==========
class LibroViewSet(viewsets.ModelViewSet):
"""ViewSet para gestión de libros via API REST"""
queryset = Libro.objects.all()
serializer_class = LibroSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['categoria', 'autor', 'editorial', 'estado']
search_fields = ['titulo', 'isbn', 'autor__nombre', 'autor__apellido']
ordering_fields = ['titulo', 'fecha_publicacion', 'stock_disponible']
ordering = ['titulo']
class AutorViewSet(viewsets.ModelViewSet):
"""ViewSet para gestión de autores"""
queryset = Autor.objects.all()
serializer_class = AutorSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ['nombre', 'apellido', 'nacionalidad']
ordering_fields = ['nombre', 'apellido']
ordering = ['apellido']
class CategoriaViewSet(viewsets.ModelViewSet):
"""ViewSet para gestión de categorías"""
queryset = Categoria.objects.all()
serializer_class = CategoriaSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
class EditorialViewSet(viewsets.ModelViewSet):
"""ViewSet para gestión de editoriales"""
queryset = Editorial.objects.all()
serializer_class = EditorialSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
class PrestamoViewSet(viewsets.ModelViewSet):
"""ViewSet para gestión de préstamos"""
queryset = Prestamo.objects.all()
serializer_class = PrestamoSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ['estado', 'usuario', 'libro']
ordering_fields = ['fecha_prestamo', 'fecha_devolucion_esperada']
ordering = ['-fecha_prestamo']
# ========== VISTAS TRADICIONALES ==========
def index(request):
"""Página de inicio de la app libros"""
context = {
'total_libros': Libro.objects.count(),
'total_autores': Autor.objects.count(),
'prestamos_activos': Prestamo.objects.filter(estado='activo').count(),
'total_usuarios': User.objects.count(),
}
return render(request, 'libros/index.html', context)
def catalogo(request):
"""Vista del catálogo de libros con filtros"""
libros_list = Libro.objects.select_related('autor', 'categoria', 'editorial').all()
# Aplicar filtros
categoria_id = request.GET.get('categoria')
autor_id = request.GET.get('autor')
estado = request.GET.get('estado')
if categoria_id:
libros_list = libros_list.filter(categoria_id=categoria_id)
if autor_id:
libros_list = libros_list.filter(autor_id=autor_id)
if estado == 'disponible':
libros_list = libros_list.filter(stock_disponible__gt=0)
elif estado == 'prestado':
libros_list = libros_list.filter(stock_disponible=0)
# Paginación
paginator = Paginator(libros_list, 12)
page_number = request.GET.get('page')
libros = paginator.get_page(page_number)
context = {
'libros': libros,
'total_libros': Libro.objects.count(),
'categorias': Categoria.objects.all(),
'autores': Autor.objects.all(),
}
return render(request, 'libros/catalogo.html', context)
def detalle_libro(request, libro_id):
"""Vista de detalle de un libro"""
libro = get_object_or_404(
Libro.objects.select_related('autor', 'categoria', 'editorial'),
id=libro_id
)
context = {'libro': libro}
return render(request, 'libros/detalle_libro.html', context)
def busqueda(request):
"""Vista de búsqueda de libros"""
query = request.GET.get('q', '')
resultados = []
if query:
resultados = Libro.objects.filter(
Q(titulo__icontains=query) |
Q(isbn__icontains=query) |
Q(autor__nombre__icontains=query) |
Q(autor__apellido__icontains=query)
).select_related('autor', 'categoria')[:50]
context = {
'query': query,
'resultados': resultados,
'categorias': Categoria.objects.all(),
'autores': Autor.objects.all(),
}
return render(request, 'libros/busqueda.html', context)
def estadisticas(request):
"""Vista de estadísticas del sistema"""
from django.contrib.auth.models import User
import json
from datetime import timedelta
# Libros por categoría
libros_por_categoria = Categoria.objects.annotate(
total=Count('libro')
).order_by('-total')
# Datos para gráfico de categorías (convertir a listas de Python)
categorias_labels = list(libros_por_categoria.values_list('nombre', flat=True))
categorias_data = list(libros_por_categoria.values_list('total', flat=True))
# Préstamos del último mes (por día)
hoy = date.today()
hace_30_dias = hoy - timedelta(days=30)
prestamos_labels = []
prestamos_data = []
for i in range(30):
dia = hace_30_dias + timedelta(days=i)
prestamos_del_dia = Prestamo.objects.filter(
fecha_prestamo__date=dia
).count()
prestamos_labels.append(dia.strftime('%d/%m'))
prestamos_data.append(prestamos_del_dia)
# Autores más prestados
autores_prestados = Autor.objects.annotate(
total_prestamos=Count('libro__prestamo')
).order_by('-total_prestamos')[:10]
# Libros más populares
libros_populares = Libro.objects.annotate(
total_prestamos=Count('prestamo')
).order_by('-total_prestamos')[:10]
context = {
'total_libros': Libro.objects.count(),
'prestamos_activos': Prestamo.objects.filter(estado='activo').count(),
'total_usuarios': User.objects.count(),
'libros_disponibles': Libro.objects.filter(stock_disponible__gt=0).count(),
'libros_por_categoria': libros_por_categoria,
# Convertir a JSON para JavaScript (usar json.dumps)
'categorias_labels': json.dumps(categorias_labels),
'categorias_data': json.dumps(categorias_data),
'prestamos_labels': json.dumps(prestamos_labels),
'prestamos_data': json.dumps(prestamos_data),
'top_autores': autores_prestados,
'top_libros': libros_populares,
}
return render(request, 'libros/estadisticas.html', context)
@login_required
def mi_cuenta(request):
"""Vista de perfil de usuario"""
prestamos_activos = Prestamo.objects.filter(
usuario=request.user,
estado='activo'
).select_related('libro', 'libro__autor')
historial_prestamos = Prestamo.objects.filter(
usuario=request.user
).exclude(estado='activo').select_related('libro')[:20]
context = {
'prestamos_activos': prestamos_activos,
'historial_prestamos': historial_prestamos,
}
return render(request, 'libros/mi_cuenta.html', context)
@login_required
def solicitar_prestamo(request, libro_id):
"""Vista para solicitar un préstamo"""
libro = get_object_or_404(Libro, id=libro_id)
if request.method == 'POST':
dias = int(request.POST.get('dias', 14))
fecha_devolucion = date.today() + timedelta(days=dias)
if libro.stock_disponible > 0:
prestamo = Prestamo.objects.create(
libro=libro,
usuario=request.user,
fecha_devolucion_esperada=fecha_devolucion,
estado='activo'
)
# Reducir stock
libro.stock_disponible -= 1
libro.save()
return redirect('mi_cuenta')
context = {'libro': libro}
return render(request, 'libros/solicitar_prestamo.html', context)
@login_required
def renovar_prestamo(request, prestamo_id):
"""Vista para renovar un préstamo"""
prestamo = get_object_or_404(Prestamo, id=prestamo_id, usuario=request.user)
if prestamo.estado == 'activo':
# Añadir 14 días más
prestamo.fecha_devolucion_esperada += timedelta(days=14)
prestamo.estado = 'renovado'
prestamo.save()
return redirect('mi_cuenta')
3. biblioteca_project/urls.py - URLs Principales
Ubicación: biblioteca_project/urls.py
Descripción: Configuración de URLs del proyecto.
from django.contrib import admin
from django.urls import path, include, re_path
from django.contrib.auth import views as auth_views
from libros.soap_services import django_soap_application
from . import views
urlpatterns = [
# Página principal
path('', views.home, name='home'),
# Páginas de ejemplos
path('ejemplos/rest/', views.ejemplos_rest, name='ejemplos_rest'),
path('ejemplos/soap/', views.ejemplos_soap, name='ejemplos_soap'),
path('ejemplos/admin/', views.ejemplos_admin, name='ejemplos_admin'),
# Admin de Django
path('admin/', admin.site.urls),
# Servicio SOAP
re_path(r'^soap/', django_soap_application),
# API REST
path('api/', include('libros.urls')),
# Autenticación
path('login/', auth_views.LoginView.as_view(template_name='login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'),
]
4. libros/urls.py - URLs de la App Libros
Ubicación: libros/urls.py
Descripción: URLs para vistas y API REST de libros.
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
# Router para API REST
router = DefaultRouter()
router.register(r'libros', views.LibroViewSet)
router.register(r'autores', views.AutorViewSet)
router.register(r'categorias', views.CategoriaViewSet)
router.register(r'editoriales', views.EditorialViewSet)
router.register(r'prestamos', views.PrestamoViewSet)
urlpatterns = [
# API REST
path('', include(router.urls)),
# Vistas tradicionales
path('index/', views.index, name='index'),
path('catalogo/', views.catalogo, name='catalogo'),
path('libro//', views.detalle_libro, name='detalle_libro'),
path('busqueda/', views.busqueda, name='busqueda'),
path('estadisticas/', views.estadisticas, name='estadisticas'),
path('mi-cuenta/', views.mi_cuenta, name='mi_cuenta'),
path('solicitar-prestamo//', views.solicitar_prestamo, name='solicitar_prestamo'),
path('renovar-prestamo//', views.renovar_prestamo, name='renovar_prestamo'),
]
5. biblioteca_project/settings.py - Configuración (Extracto)
Ubicación: biblioteca_project/settings.py
Descripción: Configuraciones clave del proyecto.
# INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Apps de terceros
'rest_framework',
'django_filters',
'spyne',
# Apps del proyecto
'libros',
]
# REST FRAMEWORK
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
}
# BASE DE DATOS MYSQL
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'biblioteca_dbutres',
'USER': 'root',
'PASSWORD': '12345678',
'HOST': 'localhost',
'PORT': '3306',
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
'charset': 'utf8mb4',
},
}
}
# ZONA HORARIA
USE_TZ = False
TIME_ZONE = 'America/Hermosillo'
# TEMPLATES
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'biblioteca_project' / 'templates'],
'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',
],
},
},
]
# ARCHIVOS ESTÁTICOS
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
# CONFIGURACIÓN DE LOGIN
LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'
6. libros/serializers.py - Serializers REST
Ubicación: libros/serializers.py
Descripción: Serializers para la API REST.
from rest_framework import serializers
from .models import Libro, Autor, Categoria, Editorial, Prestamo
class AutorSerializer(serializers.ModelSerializer):
"""Serializer para el modelo Autor"""
nombre_completo = serializers.CharField(read_only=True)
class Meta:
model = Autor
fields = ['id', 'nombre', 'apellido', 'nombre_completo',
'nacionalidad', 'biografia', 'fecha_nacimiento']
class CategoriaSerializer(serializers.ModelSerializer):
"""Serializer para el modelo Categoria"""
class Meta:
model = Categoria
fields = ['id', 'nombre', 'descripcion']
class EditorialSerializer(serializers.ModelSerializer):
"""Serializer para el modelo Editorial"""
class Meta:
model = Editorial
fields = ['id', 'nombre', 'pais', 'sitio_web', 'fecha_fundacion']
class LibroSerializer(serializers.ModelSerializer):
"""Serializer para el modelo Libro"""
autor_nombre = serializers.CharField(source='autor.nombre_completo', read_only=True)
categoria_nombre = serializers.CharField(source='categoria.nombre', read_only=True)
editorial_nombre = serializers.CharField(source='editorial.nombre', read_only=True)
class Meta:
model = Libro
fields = [
'id', 'titulo', 'isbn', 'descripcion', 'fecha_publicacion',
'numero_paginas', 'idioma', 'stock_total', 'stock_disponible',
'estado', 'autor', 'autor_nombre', 'categoria', 'categoria_nombre',
'editorial', 'editorial_nombre', 'fecha_creacion'
]
read_only_fields = ['fecha_registro', 'ultima_actualizacion']
class PrestamoSerializer(serializers.ModelSerializer):
"""Serializer para el modelo Prestamo"""
libro_titulo = serializers.CharField(source='libro.titulo', read_only=True)
usuario_nombre = serializers.CharField(source='usuario.username', read_only=True)
class Meta:
model = Prestamo
fields = [
'id', 'libro', 'libro_titulo', 'usuario', 'usuario_nombre',
'fecha_prestamo', 'fecha_devolucion_esperada', 'fecha_devolucion_real',
'estado', 'renovaciones', 'multa', 'notas'
]
read_only_fields = ['fecha_prestamo']
def validate(self, data):
"""Validar que hay stock disponible"""
libro = data.get('libro')
if libro and libro.stock_disponible <= 0:
raise serializers.ValidationError(
"No hay ejemplares disponibles de este libro."
)
return data
def create(self, validated_data):
"""Crear préstamo y actualizar stock"""
prestamo = super().create(validated_data)
libro = prestamo.libro
libro.stock_disponible -= 1
libro.save()
return prestamo
7. libros/admin.py - Configuración Admin
Ubicación: libros/admin.py
Descripción: Configuración del panel administrativo de Django.
from django.contrib import admin
from .models import Autor, Categoria, Editorial, Libro, Prestamo
@admin.register(Autor)
class AutorAdmin(admin.ModelAdmin):
list_display = ['nombre', 'apellido', 'nacionalidad', 'fecha_nacimiento']
search_fields = ['nombre', 'apellido', 'nacionalidad']
list_filter = ['nacionalidad']
@admin.register(Categoria)
class CategoriaAdmin(admin.ModelAdmin):
list_display = ['nombre', 'descripcion']
search_fields = ['nombre']
@admin.register(Editorial)
class EditorialAdmin(admin.ModelAdmin):
list_display = ['nombre', 'pais', 'sitio_web']
search_fields = ['nombre', 'pais']
list_filter = ['pais']
@admin.register(Libro)
class LibroAdmin(admin.ModelAdmin):
list_display = ['titulo', 'autor', 'categoria', 'stock_disponible', 'estado']
search_fields = ['titulo', 'isbn', 'autor__nombre', 'autor__apellido']
list_filter = ['categoria', 'editorial', 'estado', 'idioma']
readonly_fields = ['fecha_registro']
fieldsets = (
('Información Básica', {
'fields': ('titulo', 'autor', 'isbn', 'descripcion')
}),
('Detalles de Publicación', {
'fields': ('editorial', 'fecha_publicacion', 'numero_paginas', 'idioma')
}),
('Categorización', {
'fields': ('categoria',)
}),
('Stock', {
'fields': ('stock_total', 'stock_disponible', 'estado')
}),
('Metadatos', {
'fields': ('fecha_creacion',),
'classes': ('collapse',)
}),
)
@admin.register(Prestamo)
class PrestamoAdmin(admin.ModelAdmin):
list_display = ['libro', 'usuario', 'fecha_prestamo',
'fecha_devolucion_esperada', 'estado']
search_fields = ['libro_titulo', 'usuario_username', 'usuario__email']
list_filter = ['estado', 'fecha_prestamo']
readonly_fields = ['fecha_prestamo']
date_hierarchy = 'fecha_prestamo'
fieldsets = (
('Información del Préstamo', {
'fields': ('libro', 'usuario')
}),
('Fechas', {
'fields': ('fecha_prestamo', 'fecha_devolucion_esperada',
'fecha_devolucion_real')
}),
('Estado', {
'fields': ('estado', 'observaciones')
}),
)
✅ Resumen de Views y URLs
Se han documentado 7 archivos completos:
1. biblioteca_project/views.py - 4 vistas principales
2. libros/views.py - 10 vistas + 5 ViewSets REST
3. biblioteca_project/urls.py - URLs del proyecto
4. libros/urls.py - URLs de la app + Router REST
5. settings.py - Configuración clave
6. serializers.py - 5 serializers REST
7. admin.py - Configuración del admin
Total de código:
- 15 vistas tradicionales
- 5 ViewSets REST
- 5 Serializers
- 4 Admin classes
- Router REST completo
Para implementar:
1. Copia cada archivo a su ubicación
2. Verifica las importaciones
3. Aplica las migraciones
4. Prueba cada endpoint