Merge pull request 'Reproductor fresón y ajustes' (#4) from frontend into develop
Reviewed-on: #4
This commit is contained in:
commit
04f0fd88eb
|
@ -74,6 +74,12 @@ class Person(models.Model):
|
|||
|
||||
class MovieQuerySet(models.QuerySet):
|
||||
def random_pick(self, random_max=6, min_items=20, **kwargs):
|
||||
"""
|
||||
Regresa películas de manera aleatoria.
|
||||
|
||||
Por defecto tiene que haber al menos 20 películas en la consulta.
|
||||
Por defecto regresa un máximo de 6 películas.
|
||||
"""
|
||||
all = list(Movie.objects.filter(**kwargs).values())
|
||||
if len(all) < min_items:
|
||||
return None
|
||||
|
@ -83,10 +89,24 @@ class MovieQuerySet(models.QuerySet):
|
|||
return random.sample(all, random_max)
|
||||
|
||||
def top_pick(self, key, top_max=6):
|
||||
"""
|
||||
Regresa el top de películas.
|
||||
|
||||
El top corresponde al criterio de búsqueda en 'key'.
|
||||
Por defecto regresa las primeras 6 películas como máximo.
|
||||
"""
|
||||
all = list(Movie.objects.order_by(f"-{key}").values())
|
||||
return all[:top_max]
|
||||
|
||||
def top_random_pick(self, key, top_max=6):
|
||||
"""
|
||||
Regresa el top de películas de manera aleatorias.
|
||||
|
||||
El top corresponde al criterio de búsqueda en 'key'.
|
||||
Por defecto regresa un máximo de 6 películas.
|
||||
A diferencia de 'random_pick', de todo el top selecciona de manera
|
||||
aleatoria según el 'top_max'.
|
||||
"""
|
||||
all = list(Movie.objects.order_by(f"-{key}").values())
|
||||
top = []
|
||||
for movie in all:
|
||||
|
@ -100,6 +120,11 @@ class MovieQuerySet(models.QuerySet):
|
|||
return top
|
||||
|
||||
def home_pick(self):
|
||||
"""
|
||||
Regresa la selección de películas para el 'home'.
|
||||
|
||||
El 'home' es la página principal.
|
||||
"""
|
||||
genders = list(Gender.objects.values_list("id", flat=True))
|
||||
sections = self.home_sections()
|
||||
for gender in genders:
|
||||
|
@ -110,6 +135,11 @@ class MovieQuerySet(models.QuerySet):
|
|||
return sections
|
||||
|
||||
def home_sections(self):
|
||||
"""
|
||||
Regresa la selección de películas en secciones para el 'home'.
|
||||
|
||||
Las secciones son novedades, mejor valorados y más descargados.
|
||||
"""
|
||||
sections = {
|
||||
"Novedades": self.fix_all_data(self.top_pick("id")),
|
||||
"Mejor valorados": self.fix_all_data(self.top_random_pick("stars")),
|
||||
|
@ -119,11 +149,20 @@ class MovieQuerySet(models.QuerySet):
|
|||
return sections
|
||||
|
||||
def fix_all_data(self, movies):
|
||||
"""
|
||||
Enmienda los datos de las películas.
|
||||
"""
|
||||
for movie in movies:
|
||||
self.fix_data(movie, wikipedia=False)
|
||||
return movies
|
||||
|
||||
def fix_data(self, movie, wikipedia=True):
|
||||
"""
|
||||
Enmienda los datos de una película.
|
||||
|
||||
Los enmiendos son formateos de unos valores (que se guardan en nuevas
|
||||
llaves) y de rutas a medios.
|
||||
"""
|
||||
movie["duration_formatted"] = self.format_duration(movie["duration"])
|
||||
movie["count_formatted"] = self.format_count(movie["count"])
|
||||
movie["stars_icons"] = self.format_stars(movie["stars"])
|
||||
|
@ -133,12 +172,25 @@ class MovieQuerySet(models.QuerySet):
|
|||
movie["wiki"] = self.get_wiki(movie)
|
||||
|
||||
def fix_path(self, el):
|
||||
"""
|
||||
Enmienda ruta a medio.
|
||||
|
||||
La ruta es distinta según se esté en producción o en desarrollo
|
||||
('DEBUG').
|
||||
"""
|
||||
if settings.DEBUG:
|
||||
return settings.URL_DEBUG.format(el)
|
||||
else:
|
||||
return settings.MEDIA_ROOT / el
|
||||
|
||||
def fix_summ(self, raw):
|
||||
"""
|
||||
Enmienda sinopsis de Wikipedia.
|
||||
|
||||
Los sumarios de artículos a la Wikipedia vienen con notas al pie que no
|
||||
son necesarias. Para la sinopsis estas notas son eliminadas y se regresa
|
||||
el código HTML como una cadena de caracteres.
|
||||
"""
|
||||
html = BeautifulSoup(raw, "lxml")
|
||||
for ref in html.find_all("sup", "reference"):
|
||||
ref.decompose()
|
||||
|
@ -146,25 +198,52 @@ class MovieQuerySet(models.QuerySet):
|
|||
return " ".join(clean)
|
||||
|
||||
def format_stars(self, num):
|
||||
"""
|
||||
Da formato a la cantidad de estrellas.
|
||||
|
||||
Regresa la cantidad de estrellas en lugar del número de estas.
|
||||
"""
|
||||
stars = "★" * num
|
||||
while len(stars) < 5:
|
||||
stars += "☆"
|
||||
return stars
|
||||
|
||||
def format_count(self, num):
|
||||
"""
|
||||
Da formato a la cantidad de descargas.
|
||||
|
||||
Regresa la cantidad en una cifra separada por comas.
|
||||
"""
|
||||
return "{:,}".format(num)
|
||||
|
||||
def format_duration(self, num):
|
||||
"""
|
||||
Da formato a duración.
|
||||
|
||||
Regresa la duración en horas:minutos.
|
||||
"""
|
||||
secs = num * 60
|
||||
return time.strftime("%H:%M", time.gmtime(secs))
|
||||
|
||||
def get_wiki(self, movie, again=True):
|
||||
"""
|
||||
Obtiene artículo de Wikipedia.
|
||||
|
||||
Primero intenta obtener el artículo por el nombre original. Si no tiene
|
||||
éxito, intenta obtenerlo por el nombre en español. Regresa None si no
|
||||
tuvo ningún éxito.
|
||||
"""
|
||||
wiki = self.get_wiki_page(movie["original_name"])
|
||||
if not wiki:
|
||||
wiki = self.get_wiki_page(movie["name"])
|
||||
return wiki
|
||||
|
||||
def get_wiki_page(self, title):
|
||||
"""
|
||||
Intenta obtener artículo de Wikipedia.
|
||||
|
||||
Si no tiene éxito, regresa 'None'.
|
||||
"""
|
||||
try:
|
||||
lang = settings.LANGUAGE_CODE.split("-")[0]
|
||||
wiki = wikipediaapi.Wikipedia(
|
||||
|
@ -182,12 +261,54 @@ class MovieQuerySet(models.QuerySet):
|
|||
except:
|
||||
return None
|
||||
|
||||
def get_movie(self, id):
|
||||
movie = Movie.objects.select_related().get(pk=id).__dict__
|
||||
def get_movie_by_id(self, id):
|
||||
"""
|
||||
Obtiene película por id.
|
||||
|
||||
Esta obtención también añade objetos relacionados.
|
||||
"""
|
||||
related = ["countries", "genders", "directors", "actors"]
|
||||
movie = Movie.objects.prefetch_related(*related).get(pk=id).__dict__
|
||||
for key, val in movie['_prefetched_objects_cache'].items():
|
||||
movie[key] = list(map(lambda x: x['name'], val.values()))
|
||||
movie.pop("_state")
|
||||
movie.pop("_prefetched_objects_cache")
|
||||
self.fix_data(movie)
|
||||
return movie
|
||||
|
||||
def get_movies(self, prefix, query):
|
||||
"""
|
||||
Obtiene películas buscadas.
|
||||
|
||||
El 'prefix' permite filtrar la consulta por el campo seleccionado.
|
||||
"""
|
||||
selector = self.get_movie_selector(prefix)
|
||||
return f"selector: {selector}; query: {query}"
|
||||
|
||||
def get_movie_selector(self, prefix):
|
||||
"""
|
||||
Obtiene el selector para filtrar la consulta de una búsqueda.
|
||||
|
||||
Esto permite restricciones en la búsqueda con esta sintaxis:
|
||||
* t:Un Título => Buscará las películas que tengan 'Un T…' en sus 'name'
|
||||
* d:Nombre => Buscará las películas que tengan 'N…' en sus 'directors'
|
||||
"""
|
||||
prefixes = {
|
||||
't': 'name',
|
||||
'o': 'original_name',
|
||||
'y': 'year',
|
||||
'c': 'countries',
|
||||
'p': 'countries',
|
||||
'd': 'directors',
|
||||
'a': 'actors',
|
||||
'g': 'genders',
|
||||
's': 'section',
|
||||
}
|
||||
if prefix in prefixes:
|
||||
return prefixes[prefix]
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def upload_cartel(instance, filename):
|
||||
first = filename[0].upper()
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,6 +3,7 @@
|
|||
:root {
|
||||
--color-primary: #375a7f;
|
||||
--color-background: #343c3d;
|
||||
--plyr-color-main: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
#nav {
|
||||
|
@ -48,39 +49,39 @@
|
|||
|
||||
@media screen and (min-width: 300px) {
|
||||
.cartels {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 450px) {
|
||||
.cartels {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
.cartels {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 750px) {
|
||||
.cartels {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
.cartels {
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1920px) {
|
||||
.cartels {
|
||||
width: auto;
|
||||
margin: 0 !important;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
width: auto;
|
||||
margin: 0 !important;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,6 +145,7 @@
|
|||
|
||||
.stats span {
|
||||
padding-right: 2rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stats i {
|
||||
|
@ -178,6 +180,14 @@
|
|||
width: 8rem;
|
||||
}
|
||||
|
||||
.extra-info {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background-color: var(--color-primary);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.about figure {
|
||||
width: 50%;
|
||||
height: auto;
|
||||
|
@ -186,6 +196,12 @@
|
|||
margin: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
table {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Despliegue de la ficha; Cfr:
|
||||
* https://stackoverflow.com/questions/42177216/manipulate-css-class-without-javascript
|
||||
* https://stackoverflow.com/questions/55858255/custom-checkbox-with-css-before-not-working-in-firefox-edge
|
||||
|
@ -214,6 +230,34 @@
|
|||
height: calc(100% - 3em);
|
||||
}
|
||||
|
||||
/* Reproductor */
|
||||
|
||||
button.plyr__control--overlaid {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
section#notice {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
section#notice div {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
section#notice p {
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
section#notice a {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
/* ICONS; cfr: https://css.gg */
|
||||
|
||||
.gg-time {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,49 @@
|
|||
// Deshabilita scroll con la tecla de espacio
|
||||
// Esto es para que en 'player' quede habilitado para reproducción
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.keyCode === 32 && e.target === document.body) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Añade un aviso
|
||||
function add_notice () {
|
||||
const divs = document.querySelectorAll('.plyr');
|
||||
// Ejecuta de nuevo hasta que esté implementado el reproductor
|
||||
if (divs.length == 0) {
|
||||
setTimeout(add_notice, 500);
|
||||
} else {
|
||||
let hero_sec = document.createElement('section'),
|
||||
hero_div = document.createElement('div'),
|
||||
hero_tit = document.createElement('p'),
|
||||
hero_btn = document.createElement('a');
|
||||
hero_sec.id = 'notice';
|
||||
hero_sec.classList.add('hero', 'is-small', 'is-warning', 'has-text-centered');
|
||||
hero_div.classList.add('hero-body');
|
||||
hero_btn.classList.add('button', 'is-small', 'is-success');
|
||||
hero_btn.addEventListener('click', remove_notice);
|
||||
hero_tit.innerText = 'Mauflix funciona con pocos recursos. Para un uso más eficiente y ecológico te recomendamos descargar la película.';
|
||||
hero_btn.innerText = '¡Entendido!';
|
||||
hero_tit.appendChild(hero_btn);
|
||||
hero_div.appendChild(hero_tit);
|
||||
hero_sec.appendChild(hero_div);
|
||||
divs[0].appendChild(hero_sec);
|
||||
}
|
||||
}
|
||||
|
||||
// Elimina aviso
|
||||
function remove_notice () {
|
||||
let notice = document.querySelector('#notice');
|
||||
console.log('notice', notice)
|
||||
if (notice !== null) {
|
||||
notice.parentNode.removeChild(notice);
|
||||
}
|
||||
}
|
||||
|
||||
// Implementa el reproductor
|
||||
const player = new Plyr('#player', {
|
||||
keyboard: { focused: true, global: true },
|
||||
controls: ['play-large', 'play', 'progress', 'current-time', 'mute',
|
||||
'volume', 'pip', 'airplay', 'download', 'fullscreen'],
|
||||
listeners: { seek: add_notice() },
|
||||
});
|
File diff suppressed because one or more lines are too long
|
@ -11,8 +11,8 @@ def home(request):
|
|||
return render(request, "home.html", context)
|
||||
|
||||
|
||||
def search(request):
|
||||
context = {}
|
||||
def search(request, prefix, query):
|
||||
context = {"movies": Movie.objects.get_movies(prefix, query)}
|
||||
return render(request, "search.html", context)
|
||||
|
||||
|
||||
|
@ -27,10 +27,5 @@ def bugs(request):
|
|||
|
||||
|
||||
def movie(request, id):
|
||||
context = {"movie": Movie.objects.get_movie(id)}
|
||||
context = {"movie": Movie.objects.get_movie_by_id(id)}
|
||||
return render(request, "movie.html", context)
|
||||
|
||||
|
||||
def movies(request, key):
|
||||
context = {"sections": {"Llave": "Valor"}}
|
||||
return render(request, "movies.html", context)
|
||||
|
|
|
@ -17,10 +17,11 @@ v1_api.register(ResourceMovies())
|
|||
|
||||
urlpatterns = [
|
||||
path("", views.home, name="home"),
|
||||
path("search/", views.search, name="search"),
|
||||
path("search/<str:prefix>:<str:query>", views.search, name="search"),
|
||||
path("search/<str:query>", views.search, {'prefix': ''}, name="search"),
|
||||
path("search/", views.search, {'prefix': '', 'query': ''}, name="search"),
|
||||
path("about/", views.about, name="about"),
|
||||
path("bugs/", views.bugs, name="bugs"),
|
||||
path("movies/<str:key>", views.movies, name="movies"),
|
||||
path("movie/<int:id>", views.movie, name="movie"),
|
||||
path("ultimas/rss/", LatestMoviesFeed()),
|
||||
path("admin/", admin.site.urls),
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{% include 'section.html' with section=section content=content %}
|
||||
{% else %}
|
||||
{% for gender, val in content.items %}
|
||||
{% include 'section.html' with section=gender content=val %}
|
||||
{% include 'section.html' with gender=True section=gender content=val %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -19,12 +19,34 @@
|
|||
<table class="table infobox">
|
||||
<tbody>
|
||||
<tr><td>Título original</td><td>{{ movie.original_name }}<td></tr>
|
||||
<tr><td>Año</td><td>{{ movie.year }}<td></tr>
|
||||
<tr><td>País</td><td>{{ movie.countries }}<td></tr>
|
||||
<tr><td>Año</td><td>
|
||||
<a href="{% url 'search' 'y' movie.year %}">{{ movie.year }}</a>
|
||||
<td></tr>
|
||||
<tr><td>País</td><td>
|
||||
{% for country in movie.countries %}
|
||||
<a href="{% url 'search' 'p' country %}">
|
||||
{{ country }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
<td></tr>
|
||||
<tr><td>Duración</td><td>{{ movie.duration }} min<td></tr>
|
||||
<tr><td>Dirección</td><td>{{ movie.directors }}<td></tr>
|
||||
<tr><td>Reparto</td><td>{{ movie.actors }}<td></tr>
|
||||
<tr><td>Género</td><td>{{ movie.genders }}<td></tr>
|
||||
<tr><td>Dirección</td><td>
|
||||
{% for director in movie.directors %}
|
||||
<a href="{% url 'search' 'd' director %}">
|
||||
{{ director }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
<td></tr>
|
||||
<tr><td>Reparto</td><td>
|
||||
{% for actor in movie.actors %}
|
||||
<a href="{% url 'search' 'a' actor %}">
|
||||
{{ actor }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
<td></tr>
|
||||
<tr><td>Género</td><td>
|
||||
{% for gender in movie.genders %}
|
||||
<a href="{% url 'search' 'g' gender %}">
|
||||
{{ gender }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
<td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -32,12 +54,21 @@
|
|||
{% if movie.wiki.summary %}
|
||||
<h2 class="subtitle">Sinopsis de <a href="{{ movie.wiki.url }}" target="_blank">Wikipedia</a></h2>
|
||||
{{ movie.wiki.summary | safe }}
|
||||
{% else %}
|
||||
<h2 class="subtitle">Sinopsis de Wikipedia</h2>
|
||||
<p>No se encontró artículo en Wikipedia, haz <a href="https://es.wikipedia.org/wiki/{{ movie.name }}">clic aquí</a> para escribirlo o solicitarlo.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if user.is_superuser %}
|
||||
<video controls>
|
||||
<video id="player" playsinline controls data-poster="{{ movie.cartel }}">
|
||||
<source src="{{ movie.file_name }}" type="video/mp4">
|
||||
</video>
|
||||
{% load static %}
|
||||
<!-- CSS y JS necesario paara el reproductor -->
|
||||
<!-- Cfr. https://github.com/sampotts/plyr -->
|
||||
<link rel="stylesheet" href="{% static 'css/plyr.css' %}">
|
||||
<script type="text/javascript" src="{% static 'js/plyr.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/player.js' %}"></script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<div class="hero-foot">
|
||||
<nav class="tabs is-boxed is-fullwidth">
|
||||
<div class="container">
|
||||
<div class="container {% if request.get_full_path == "/" %}extra-info{% endif %}">
|
||||
<ul>
|
||||
<li><a href="{{ movie.file_name }}" target="_blank" download>Descargar</a></li>
|
||||
{% if request.get_full_path == "/" %}
|
||||
<li><a href="/movie/{{ movie.id }}">Detalles</a></li>
|
||||
<li><a href="/movie/{{ movie.id }}">Ver</a></li>
|
||||
{% else %}
|
||||
<li><a href="{{ movie.file_name }}" target="_blank" download>Descargar</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% for section, content in sections.items %}
|
||||
{% include 'section.html' with section=section content=content %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
<b>TODO: Búsqueda</b>
|
||||
<b>TODO: Búsqueda de {{ movies }}</b>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
<section class="hero">
|
||||
<div class="hero-body hero-cartels">
|
||||
{% if request.get_full_path == "/" %}
|
||||
<p class="title"><a href="movies/{{ section }}">{{ section }}<span class="arrows"/></a></p>
|
||||
<p class="title">
|
||||
{% if gender %}
|
||||
<a href="{% url 'search' 'g' section %}">
|
||||
{% else %}
|
||||
<a href="{% url 'search' 's' section %}">
|
||||
{% endif %}
|
||||
{{ section }}<span class="arrows"/></a>
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="title">{{ section }}</p>
|
||||
{% endif %}
|
||||
|
|
Loading…
Reference in New Issue