diff --git a/README.md b/README.md index fa0237d..ef0f9d4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # OpenBootCampPython Repository to store Python boot camp. + +Ejercicio 12: En este ejercicio tendrás que crear una aplicación en Django que almacene datos de directores de cine y luego se puedan ver sus películas, así como una descripción de las mismas. + +Tienes que personalizar el admin de la aplicación y a su vez crear las vistas de cada una de las partes de la aplicación. diff --git a/catalog/__init__.py b/catalog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/catalog/admin.py b/catalog/admin.py new file mode 100644 index 0000000..aadfd34 --- /dev/null +++ b/catalog/admin.py @@ -0,0 +1,35 @@ +from django.contrib import admin +from .models import Director, Genre, Movie, Country + + +class MovieInline(admin.TabularInline): + model = Movie + exclude = ('id',) + + +@admin.register(Director) +class DirectorAdmin(admin.ModelAdmin): + list_display = ('id', 'last_name', 'first_name', 'date_of_birth', 'date_of_death') + list_filter = ('date_of_birth', 'date_of_death') + fields = ['first_name', 'last_name', ('date_of_birth', 'date_of_death')] + exclude = ('id',) + inlines = [MovieInline] + + +@admin.register(Movie) +class MovieAdmin(admin.ModelAdmin): + + def display_genre(self): + """Create a string for the Genre. This is required to display genre in Admin.""" + return ', '.join(genre.name for genre in self.genre.all()[:3]) + + display_genre.short_description = 'Genre' + + list_display = ('id', 'title', 'director', display_genre, 'country') + list_filter = ('country', 'genre', 'director') + exclude = ('id',) + + +# Register your models here. +admin.site.register(Genre) +admin.site.register(Country) diff --git a/catalog/apps.py b/catalog/apps.py new file mode 100644 index 0000000..a5993c6 --- /dev/null +++ b/catalog/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CatalogConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'catalog' diff --git a/catalog/models.py b/catalog/models.py new file mode 100644 index 0000000..77c1dec --- /dev/null +++ b/catalog/models.py @@ -0,0 +1,80 @@ +from django.db import models +from django.urls import reverse +import uuid + + +class Director(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular director') + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + objects = models.Manager() + + class Meta: + ordering = ['last_name', 'first_name'] + + def get_absolute_url(self): + """Returns the URL to access a particular director instance.""" + return reverse('director-detail', args=[str(self.id)]) + + def __str__(self): + """String for representing the Model object.""" + return f'{self.last_name}, {self.first_name}' + + +class Genre(models.Model): + """Model representing a movie genre.""" + name = models.CharField(primary_key=True, max_length=200, help_text='Enter a movie genre (e.g. Science Fiction)') + objects = models.Manager() + + def __str__(self): + """String for representing the Model object.""" + return self.name + + class Meta: + ordering = ['name'] + + +class Country(models.Model): + """Model representing a movie country""" + name = models.CharField(primary_key=True, max_length=200, help_text='Enter a movie country (e.g. Spain)') + objects = models.Manager() + + def __str__(self): + """String for representing the Model object.""" + return self.name + + class Meta: + ordering = ['name'] + + +class Movie(models.Model): + """Model representing a movie""" + objects = models.Manager() + id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular movie') + title = models.CharField(max_length=200) + + # Foreign Key used because movie can only have one author, but authors can have multiple books + # Director is a string rather than an object because it hasn't been declared yet in the file + director = models.ForeignKey(Director, on_delete=models.SET_NULL, null=True) + + summary = models.TextField(max_length=1000, help_text='Enter a brief description of the movie') + + # ManyToManyField used because genre can contain many movies. Movies can cover many genres. + # Genre class has already been defined, so we can specify the object above. + genre = models.ManyToManyField(Genre, help_text='Select a genre for this movie') + + country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True, + help_text='Select a country for this movie') + + def __str__(self): + """String for representing the Model object.""" + return self.title + + def get_absolute_url(self): + """Returns the URL to access a detail record for this movie.""" + return reverse('movie-detail', args=[str(self.id)]) + + class Meta: + ordering = ['title'] diff --git a/catalog/static/css/styles.css b/catalog/static/css/styles.css new file mode 100644 index 0000000..40a543c --- /dev/null +++ b/catalog/static/css/styles.css @@ -0,0 +1,5 @@ +.sidebar-nav { + margin-top: 20px; + padding: 0; + list-style: none; +} diff --git a/catalog/templates/base_generic.html b/catalog/templates/base_generic.html new file mode 100644 index 0000000..8e817b6 --- /dev/null +++ b/catalog/templates/base_generic.html @@ -0,0 +1,45 @@ + + + + {% block title %}Local Library{% endblock %} + + + + + {% load static %} + + + +
+
+
+ {% block sidebar %} + + {% endblock %} +
+
{% block content %}{% endblock %}{% block pagination %} + {% if is_paginated %} + + {% endif %} + {% endblock %} +
+
+
+ + diff --git a/catalog/templates/catalog/director_detail.html b/catalog/templates/catalog/director_detail.html new file mode 100644 index 0000000..5719bc6 --- /dev/null +++ b/catalog/templates/catalog/director_detail.html @@ -0,0 +1,18 @@ +{% extends "base_generic.html" %} + +{% block content %} +

Nombre: {{ director.first_name }} {{ director.last_name }}

+

Fecha nacimiento: {{ director.date_of_birth }}

+

Fecha fallecimiento: {{ director.date_of_death }}

+
+

Peliculas

+ + {% for movie in director.movie_set.all %} +
+

+ {{ movie.title }}

+ {% endfor %} +
+ + +{% endblock %} \ No newline at end of file diff --git a/catalog/templates/catalog/director_list.html b/catalog/templates/catalog/director_list.html new file mode 100644 index 0000000..545efba --- /dev/null +++ b/catalog/templates/catalog/director_list.html @@ -0,0 +1,16 @@ +{% extends "base_generic.html" %} + +{% block content %} +

Director List

+ {% if director_list %} + + {% else %} +

There are no director in the library.

+ {% endif %} +{% endblock %} diff --git a/catalog/templates/catalog/movie_detail.html b/catalog/templates/catalog/movie_detail.html new file mode 100644 index 0000000..67627e0 --- /dev/null +++ b/catalog/templates/catalog/movie_detail.html @@ -0,0 +1,11 @@ +{% extends "base_generic.html" %} + +{% block content %} +

Título: {{ movie.title }}

+ +

Director: {{ movie.director }}

+

Resumen: {{ movie.summary }}

+

País: {{ movie.country }}

+

Género: {{ movie.genre.all|join:", " }}

+ +{% endblock %} \ No newline at end of file diff --git a/catalog/templates/catalog/movie_list.html b/catalog/templates/catalog/movie_list.html new file mode 100644 index 0000000..d721e0a --- /dev/null +++ b/catalog/templates/catalog/movie_list.html @@ -0,0 +1,16 @@ +{% extends "base_generic.html" %} + +{% block content %} +

Movie List

+ {% if movie_list %} + + {% else %} +

There are no movie in the library.

+ {% endif %} +{% endblock %} diff --git a/catalog/templates/index.html b/catalog/templates/index.html new file mode 100644 index 0000000..e1d6b90 --- /dev/null +++ b/catalog/templates/index.html @@ -0,0 +1,22 @@ +{% extends "base_generic.html" %} + +{% block content %} +

Local Videoclub Home

+

Welcome to Videoclub, a website developed by Izri!

+

Dynamic content

+

The videoclub has the following record counts:

+ +{% endblock %} diff --git a/catalog/tests.py b/catalog/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/catalog/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/catalog/urls.py b/catalog/urls.py new file mode 100644 index 0000000..f23410d --- /dev/null +++ b/catalog/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.index, name='index'), + path('movies/', views.MovieListView.as_view(), name='movies'), + path('movie/', views.MovieDetailView.as_view(), name='movie-detail'), + path('directors/', views.DirectorListView.as_view(), name='directors'), + path('director/', views.DirectorDetailView.as_view(), name='director-detail'), +] diff --git a/catalog/views.py b/catalog/views.py new file mode 100644 index 0000000..ca86ce7 --- /dev/null +++ b/catalog/views.py @@ -0,0 +1,76 @@ +from django.shortcuts import render +from .models import Movie, Director +from django.views import generic + + +def index(request): + """View function for home page of site.""" + + # Generate counts of some main objects + num_movies = Movie.objects.all().count() + num_directors = Director.objects.count() + num_action = Movie.objects.filter(genre__exact='Acción').count() + num_animation = Movie.objects.filter(genre__exact='Animación').count() + num_romance = Movie.objects.filter(genre__exact='Romance').count() + num_fantasy = Movie.objects.filter(genre__exact='Fantasía').count() + num_terror = Movie.objects.filter(genre__exact='Terror').count() + num_scifi = Movie.objects.filter(genre__exact='Ciencia Ficción').count() + num_adventure = Movie.objects.filter(genre__exact='Aventura').count() + num_mistery = Movie.objects.filter(genre__exact='Misterio').count() + num_thriller = Movie.objects.filter(genre__exact='Suspense').count() + num_drama = Movie.objects.filter(genre__exact='Drama').count() + context = { + 'num_movies': num_movies, + 'num_directors': num_directors, + 'num_action': num_action, + 'num_animation': num_animation, + 'num_romance': num_romance, + 'num_fantasy': num_fantasy, + 'num_terror': num_terror, + 'num_scifi': num_scifi, + 'num_adventure': num_adventure, + 'num_mistery': num_mistery, + 'num_thriller': num_thriller, + 'num_drama': num_drama, + } + + # Render the HTML template index.html with the data in the context variable + return render(request, 'index.html', context=context) + + +class MovieListView(generic.ListView): + model = Movie + paginate_by = 2 + + def get_queryset(self): + return Movie.objects.all().order_by('title') + + def get_context_data(self, **kwargs): + # Call the base implementation first to get the context + context = super(MovieListView, self).get_context_data(**kwargs) + # Create any data and add it to the context + context['some_data'] = 'This is just some data' + return context + + +class MovieDetailView(generic.DetailView): + model = Movie + + +class DirectorListView(generic.ListView): + model = Director + paginate_by = 2 + + def get_queryset(self): + return Director.objects.all().order_by('last_name', 'first_name') + + def get_context_data(self, **kwargs): + # Call the base implementation first to get the context + context = super(DirectorListView, self).get_context_data(**kwargs) + # Create any data and add it to the context + context['some_data'] = 'This is just some data' + return context + + +class DirectorDetailView(generic.DetailView): + model = Director diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..fc28c58 --- /dev/null +++ b/manage.py @@ -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', 'wikiCine.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() diff --git a/requirements.txt b/requirements.txt index 4c933dc..fbb289d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,6 @@ -i https://pypi.org/simple -asgiref==3.5.2 ; python_version >= '3.7' +asgiref==3.5.2 django==4.1.4 -pip==22.3.1 +psycopg2-binary==2.9.5 pysqlite3==0.4.7 -setuptools==65.6.3 ; python_version >= '3.7' -sqlparse==0.4.3 ; python_version >= '3.5' -wheel==0.38.0 +sqlparse==0.4.3 diff --git a/src/main.py b/src/main.py deleted file mode 100644 index a47b6a3..0000000 --- a/src/main.py +++ /dev/null @@ -1,10 +0,0 @@ -def main(): - """Main script for pythonBootCamp project - :return: None - :rtype: NoneType - """ - pass - - -if __name__ == '__main__': - main() diff --git a/wikiCine/__init__.py b/wikiCine/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wikiCine/asgi.py b/wikiCine/asgi.py new file mode 100644 index 0000000..01ff336 --- /dev/null +++ b/wikiCine/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for wikiCine 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/4.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wikiCine.settings') + +application = get_asgi_application() diff --git a/wikiCine/settings.py b/wikiCine/settings.py new file mode 100644 index 0000000..04004b5 --- /dev/null +++ b/wikiCine/settings.py @@ -0,0 +1,127 @@ +""" +Django settings for wikiCine project. + +Generated by 'django-admin startproject' using Django 4.1.4. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.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/4.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +with open('.env') as f: + SECRET_KEY = f.read().strip() + +# 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', + 'catalog.apps.CatalogConfig', +] + +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 = 'wikiCine.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 = 'wikiCine.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'OPTIONS': { + 'service': 'videoclub', + 'passfile': '.pgpass', + }, + } +} + +# Password validation +# https://docs.djangoproject.com/en/4.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/4.1/topics/i18n/ + +LANGUAGE_CODE = 'es-ES' + +TIME_ZONE = 'Europe/Madrid' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/wikiCine/urls.py b/wikiCine/urls.py new file mode 100644 index 0000000..116463f --- /dev/null +++ b/wikiCine/urls.py @@ -0,0 +1,27 @@ +"""wikiCine URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.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 +from django.urls import include +from django.views.generic import RedirectView +from django.conf import settings +from django.conf.urls.static import static + +urlpatterns = [ + path('admin/', admin.site.urls), + path('catalog/', include('catalog.urls')), + path('', RedirectView.as_view(url='catalog/', permanent=True)), +] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/wikiCine/wsgi.py b/wikiCine/wsgi.py new file mode 100644 index 0000000..24433e7 --- /dev/null +++ b/wikiCine/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for wikiCine 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/4.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wikiCine.settings') + +application = get_wsgi_application()