Habilitación de búsquedas perronas

This commit is contained in:
perro tuerto 2023-01-20 14:01:45 -08:00
parent c64fa107b4
commit bece885226
22 changed files with 626 additions and 62 deletions

View File

@ -7,3 +7,4 @@ django-admin-list-filter-dropdown
wikipedia-api
bs4
lxml
unidecode

View File

@ -98,7 +98,9 @@ class AdminMovie(admin.ModelAdmin):
return
def _public_telegram(self, message, cartel):
url = f"https://api.telegram.org/bot{settings.TOKEN_TELEGRAM}/sendPhoto"
url = (
f"https://api.telegram.org/bot{settings.TOKEN_TELEGRAM}/sendPhoto"
)
url_cartel = settings.URL_CDN.format(cartel)
data = {
"chat_id": settings.CHAT_ID,

View File

@ -79,7 +79,9 @@ class Migration(migrations.Migration):
),
(
"is_actor",
models.BooleanField(default=False, verbose_name="Es Actor"),
models.BooleanField(
default=False, verbose_name="Es Actor"
),
),
(
"is_director",

View File

@ -13,6 +13,8 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="movie",
name="is_digital",
field=models.BooleanField(default=False, verbose_name="Es digital"),
field=models.BooleanField(
default=False, verbose_name="Es digital"
),
),
]

View File

@ -6,28 +6,28 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0004_auto_20210807_2207'),
("main", "0004_auto_20210807_2207"),
]
operations = [
migrations.AlterField(
model_name='country',
name='id',
model_name="country",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='gender',
name='id',
model_name="gender",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='movie',
name='id',
model_name="movie",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='person',
name='id',
model_name="person",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
]

View File

@ -1,10 +1,12 @@
import random
import time
import re
import wikipediaapi
from bs4 import BeautifulSoup
from django.conf import settings
from django.db import models
from pathlib import Path
from unidecode import unidecode
class Gender(models.Model):
@ -71,14 +73,15 @@ class Person(models.Model):
class MovieQuerySet(models.QuerySet):
def random_pick(self, random_max=6, min_items=20, **kwargs):
def random_pick(self, random_max=6, min_items=20, all=None, **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 all is None:
all = list(Movie.objects.filter(**kwargs).values())
if len(all) < min_items:
return None
elif len(all) < random_max:
@ -94,7 +97,7 @@ class MovieQuerySet(models.QuerySet):
Por defecto regresa las primeras 6 películas como máximo.
"""
all = list(Movie.objects.order_by(f"-{key}").values())
return all[:top_max]
return self.fix_all(all[:top_max])
def top_random_pick(self, key, top_max=6):
"""
@ -113,9 +116,10 @@ class MovieQuerySet(models.QuerySet):
else:
break
if len(top) > top_max:
return random.sample(top, top_max)
movies = random.sample(top, top_max)
else:
return top
movies = top
return self.fix_all(movies)
def home_pick(self):
"""
@ -139,9 +143,9 @@ class MovieQuerySet(models.QuerySet):
Las secciones son novedades, mejor valorados y más descargados.
"""
sections = {
"Novedades": self.fix_all(self.top_pick("id")),
"Mejor valorados": self.fix_all(self.top_random_pick("stars")),
"Más descargados": self.fix_all(self.top_pick("count")),
"Novedades": self.top_pick("id"),
"Mejor valorados": self.top_random_pick("stars"),
"Más descargados": self.top_pick("count"),
}
sections["genders"] = {}
return sections
@ -161,6 +165,8 @@ class MovieQuerySet(models.QuerySet):
Los enmiendos son formateos de unos valores (que se guardan en nuevas
llaves) y de rutas a medios.
"""
if len(movie["file_name"]) == 0 or len(movie["cartel"]) == 0:
print(f"WARN: película sin ruta de video o cartel:\n{movie}")
movie["duration_formatted"] = self.format_duration(movie["duration"])
movie["count_formatted"] = self.format_count(movie["count"])
movie["stars_icons"] = self.format_stars(movie["stars"])
@ -177,7 +183,7 @@ class MovieQuerySet(models.QuerySet):
('DEBUG'). La URL_DEBUB apunta a la dirección en nebula.
"""
if settings.DEBUG:
if str(Path(el).parent) == '.':
if str(Path(el).parent) == "." and len(el) > 0:
el = f"{el[0]}/{el}"
return settings.URL_DEBUG.format(el)
else:
@ -282,53 +288,146 @@ class MovieQuerySet(models.QuerySet):
except Exception:
return None
def get_related(self):
"""
Regresa los campos relacionados a las películas.
"""
return ["countries", "genders", "directors", "actors"]
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"]
related = self.get_related()
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()))
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):
def get_movies(self, 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}"
movies = Movie.objects.prefetch_related(*self.get_related())
for q in self.get_queries(query):
selector = None
if re.match(r"^\w:", q) is not None:
selector = self.get_selector(re.sub(r"^(\w):.*", r"\1", q))
q = re.sub(r"^\w:(.*)", r"\1", q)
movies = self.get_movies_by_query(selector, q, movies)
if len(movies) != len(Movie.objects.all()):
if hasattr(movies, "values"):
movies = self.fix_all(list(movies.values()))
movies = self.random_pick(random_max=100, min_items=0, all=movies)
return movies
else:
return []
def get_movie_selector(self, prefix):
def get_movies_by_query(self, selector, query, movies):
"""
Obtiene película por query.
"""
if selector is None:
return self.get_movies_by_query_any(query, movies)
elif selector != "section":
return self.get_movies_by_query_selector(selector, query, movies)
else:
return self.get_movies_by_query_section(query, movies)
def get_movies_by_query_selector(self, selector, query, movies):
"""
Obtiene película por query que tiene selector 'w:'.
"""
if selector == "year":
selector = f"{selector}__iregex"
try:
query = int(query)
except Exception:
query = 0
else:
if selector == "name" or selector == "original_name":
selector = f"{selector}__unaccent__iregex"
else:
selector = f"{selector}__name__unaccent__iregex"
query = f"[[:<:]]{query}"
kwargs = {selector: query}
return movies.filter(**kwargs)
def get_movies_by_query_section(self, query, movies):
"""
Obtiene película por query que tiene selector 'w:' para sección.
"""
if query == "mejor valorados":
return self.top_random_pick("stars", top_max=100)
elif query == "novedades":
return self.top_pick("id", top_max=100)
elif query == "mas descargados":
return self.top_pick("count", top_max=100)
else:
return list(movies.values())
def get_movies_by_query_any(self, query, movies):
"""
Obtiene película por query que no tiene ningún selector.
"""
for field in ["name"] + self.get_related():
if field != "name":
field = f"{field}__name"
kwargs = {f"{field}__unaccent__iregex": f"[[:<:]]{query}"}
result = movies.filter(**kwargs)
if len(result) > 0:
movies = result
return movies
def get_queries(self, query):
"""
Devuelve un conjunto de queries sanitizado; p. ej.:
de: d:Bruno--Stagñaro y:1997 pizzá, birra, faso
a: ['d:bruno-stagnaro', 'y:1997', 'pizza', 'birra', 'faso']
"""
queries = re.sub(r"\s+", " ", unidecode(str(query)).lower()).split()
return list(map(lambda q: self.clean_query(q), queries))
def clean_query(self, word):
"""
Limpia query de caracteres 'W' al inicio y al final. También sustituye
'-' por un espacio.
"""
word = re.sub(r"^\W+", "", word)
word = re.sub(r"\W+$", "", word)
word = re.sub(r"-+", " ", word)
return word
def get_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'
* 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'
El guion (-) se usa como separador de palabras, puede contener acentos
o eñe, aunque va a ser decodificado.
"""
prefixes = {
't': 'name',
'o': 'original_name',
'y': 'year',
'c': 'countries',
'p': 'countries',
'd': 'directors',
'a': 'actors',
'g': 'genders',
's': 'section',
"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 ''
return None
def upload_cartel(instance, filename):

View File

@ -19,6 +19,10 @@
display: flex !important;
}
.navbar-item img {
width: 100%;
}
/* Cada flecha en los títulos de secciones */
.arrows {
font-size: 1.5rem;
@ -291,6 +295,21 @@ section#notice a {
margin: auto;
}
/* SEARCH */
.search-div > * {
display: inline-block;
}
.search-div input {
width: calc(100% - 4.1rem);
}
.search-div button {
width: 3.8rem;
height: 3.8rem;
}
/* ICONS; cfr: https://css.gg */
.gg-time {
@ -396,3 +415,30 @@ section#notice a {
border-bottom-right-radius: 40px;
right: -6px
}
.gg-search {
box-sizing: border-box;
position: relative;
display: block;
transform: scale(var(--ggs,1));
width: 16px;
height: 16px;
border: 2px solid;
border-radius: 100%;
margin-left: -4px;
margin-top: -4px
}
.gg-search::after {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
border-radius: 3px;
width: 2px;
height: 8px;
background: currentColor;
transform: rotate(-45deg);
top: 10px;
left: 12px
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="512"
height="512"
viewBox="0 0 135.46666 135.46666"
version="1.1"
id="svg8"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="favicon.svg"
inkscape:export-filename="favicon.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2">
<rect
x="127.8628"
y="131.33685"
width="44.228352"
height="64.944321"
id="rect971" />
<rect
x="214.46635"
y="79.221107"
width="123.64536"
height="102.02128"
id="rect957" />
<rect
x="214.46635"
y="79.221107"
width="123.64536"
height="102.02128"
id="rect1119" />
<rect
x="214.46635"
y="79.221107"
width="123.64536"
height="102.02128"
id="rect1159" />
<rect
x="214.46635"
y="79.221107"
width="123.64536"
height="102.02128"
id="rect1213" />
<rect
x="214.46635"
y="79.221107"
width="123.64536"
height="102.02128"
id="rect1166" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#375a7f"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="0.38532642"
inkscape:cx="-120.67691"
inkscape:cy="-124.56971"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
showguides="true"
inkscape:guide-bbox="true"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="rect2398"
style="opacity:1"
transform="translate(0.01602397)" />
<g
id="rect2400"
style="opacity:1"
transform="translate(0.01602397)" />
<g
id="rect2402"
style="opacity:1"
transform="translate(0.01602397)" />
<g
id="rect2404"
style="opacity:1" />
<g
id="rect2406"
style="opacity:1" />
<g
id="rect2408"
style="opacity:1" />
<text
xml:space="preserve"
id="text969"
style="font-size:10.5833px;line-height:1;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect971);display:inline" />
<path
id="path1541"
style="fill:#ffffff;stroke-width:0.332183"
d="M 0.09001667,21.743483 V 96.133805 H 23.957074 v 3.321382 H 0.09001667 v 2.315573 H 23.957074 v 3.32143 H 0.08764702 c -0.0010022,0.79886 -0.01360684,1.27101 -0.02280029,2.27787 H 23.957843 v 1.93632 1.38511 H 0.031897 c -0.01359326,1.02732 -0.031898666667,3.03169 -0.031898666667,3.03169 L 30.611198,113.67122 H 63.749981 L 53.352288,100.57164 h 28.257975 l -10.732186,13.09959 h 33.737113 l 30.85148,-0.047 c 0,0 -9.7e-4,-1.95701 -0.003,-2.93278 H 111.5898 v -2.17185 -1.14953 h 23.86948 c -0.001,-1.0085 -0.003,-1.54115 -0.003,-2.27792 h -23.86679 v -2.17422 -1.14716 h 23.85768 l -0.01,-2.315573 h -23.84954 v -2.171896 -1.149536 h 23.84116 L 135.21275,21.743483 90.171052,52.752817 h -0.124906 c -6.977213,-2.430397 -14.470662,-3.754838 -22.27472,-3.754838 -7.798681,0 -15.288346,1.323621 -22.260545,3.750098 h -0.141319 z"
inkscape:export-xdpi="300"
inkscape:export-ydpi="300" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="377.03391"
height="256"
viewBox="0 0 99.756886 67.733332"
version="1.1"
id="svg8"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="logo.svg"
inkscape:export-filename="favicon.png"
inkscape:export-xdpi="130.36493"
inkscape:export-ydpi="130.36493"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2">
<rect
x="127.8628"
y="131.33685"
width="44.228352"
height="64.944321"
id="rect971" />
<rect
x="214.46635"
y="79.221107"
width="123.64536"
height="102.02128"
id="rect957" />
<rect
x="214.46635"
y="79.221107"
width="123.64536"
height="102.02128"
id="rect1119" />
<rect
x="214.46635"
y="79.221107"
width="123.64536"
height="102.02128"
id="rect1159" />
<rect
x="214.46635"
y="79.221107"
width="123.64536"
height="102.02128"
id="rect1213" />
<rect
x="214.46635"
y="79.221107"
width="123.64536"
height="102.02128"
id="rect1166" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#375a7f"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="0.38532642"
inkscape:cx="-120.67691"
inkscape:cy="-124.56971"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
showguides="true"
inkscape:guide-bbox="true"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="rect2398"
style="opacity:1"
transform="translate(0.01602397)" />
<g
id="rect2400"
style="opacity:1"
transform="translate(0.01602397)" />
<g
id="rect2402"
style="opacity:1"
transform="translate(0.01602397)" />
<g
id="rect2404"
style="opacity:1" />
<g
id="rect2406"
style="opacity:1" />
<g
id="rect2408"
style="opacity:1" />
<text
xml:space="preserve"
id="text969"
style="font-size:10.5833px;line-height:1;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect971);display:inline" />
<path
id="path1541"
style="fill:#ffffff;stroke-width:0.244618"
d="m 0.066289,0 v 54.780614 h 17.575567 v 2.445847 H 0.066289 v 1.705175 h 17.575567 v 2.44588 H 0.064544 c -7.38e-4,0.588275 -0.01002,0.935969 -0.01679,1.677414 h 17.594668 v 1.425891 1.019988 H 0.02349 C 0.01348,66.257327 0,67.733333 0,67.733333 l 22.541915,-0.03826 H 46.945128 L 39.288326,58.04861 h 20.809013 l -7.903121,9.646463 H 77.03804 l 22.71885,-0.03458 c 0,0 -7.1e-4,-1.441129 -0.002,-2.159681 H 82.174101 V 63.901477 63.054966 H 99.75145 c -7.4e-4,-0.742653 -0.002,-1.134892 -0.002,-1.677447 H 82.173871 V 59.776437 58.931672 H 99.74253 l -0.007,-1.705174 H 82.172861 V 55.627126 54.780614 H 99.72936 L 99.56991,0 66.401456,22.835099 h -0.09198 c -5.13798,-1.789731 -10.656114,-2.765042 -16.402978,-2.765042 -5.742905,0 -11.258252,0.974707 -16.39254,2.761551 h -0.104067 z"
inkscape:export-xdpi="300"
inkscape:export-ydpi="300" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -11,8 +11,12 @@ def home(request):
return render(request, "home.html", context)
def search(request, prefix, query):
context = {"movies": Movie.objects.get_movies(prefix, query)}
def search(request):
if request.GET.get("q"):
query = request.GET["q"]
else:
query = ""
context = {"movies": Movie.objects.get_movies(query), "query": query}
return render(request, "search.html", context)
@ -21,6 +25,11 @@ def about(request):
return render(request, "about.html", context)
def help(request):
context = {}
return render(request, "help.html", context)
def bugs(request):
context = {}
return render(request, "bugs.html", context)

View File

@ -48,6 +48,7 @@ INSTALLED_APPS = [
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.postgres",
"main.apps.MainConfig",
]

View File

@ -8,6 +8,7 @@ from django.conf.urls import include
from main import views
from main.feeds import LatestMoviesFeed
# ~ from main.api import ResourceMovies
@ -17,10 +18,9 @@ from main.feeds import LatestMoviesFeed
urlpatterns = [
path("", views.home, name="home"),
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("search/", views.search, name="search"),
path("about/", views.about, name="about"),
path("help/", views.help, name="help"),
path("bugs/", views.bugs, name="bugs"),
path("movie/<int:id>", views.movie, name="movie"),
path("ultimas/rss/", LatestMoviesFeed()),

View File

@ -14,7 +14,7 @@
<body>
<section class="hero is-small is-info">
<div class="hero-body has-text-centered">
<p>Otras plataformas tienen todas las películas, excepto las que nos gustan :)</p>
<p>Mauflix. Otras plataformas tienen todas las películas, excepto las que nos gustan :)</p>
</div>
</section>
{% include 'nav.html' %}

103
source/templates/help.html Normal file
View File

@ -0,0 +1,103 @@
{% extends "base.html" %}
{% block content %}
<div class="hero full">
<div class="hero-body">
<p class="title">Ayuda</p>
<div class="content">
<h2 id="busqueda">Sobre las búsquedas</h2>
<p>En las búsquedas existen las siguientes consideraciones:</p>
<ul>
<li>
Las búsquedas tienen un límite de cien resultados.
</li>
<li>
Cualquier búsqueda ignora tildes, mayúsculas y eñes,
así que «<a href="{% url 'search' %}?q=Iñarritú">Iñarritú</a>»
es lo mismo que «<a href="{% url 'search' %}?q=inarritu">inarritu</a>».
</li>
<li>
Cuando una búsqueda encuentra más de cien películas,
los cien resultados son seleccionados y ordenados de manera aleatoria:
<b>nunca obtendrás los mismos resultados</b>.
</li>
</ul>
<h2 id="refinamiento">Refinamiento en las búsquedas</h2>
<p>Para buscar por campos específicos se usan algunos de los siguientes prefijos:</p>
<table class="table is-striped is-narrow">
<thead>
<tr>
<th>prefijo:ejemplo</th>
<th>Descripción</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="{% url 'search' %}?q=t:lucio">t:lucio</a></td>
<td>Busca «lucio» en los títulos</td>
</tr>
<tr>
<td><a href="{% url 'search' %}?q=o:monkey">o:monkey</a></td>
<td>Busca «monkey» en los títulos en idioma original</td>
</tr>
<tr>
<td><a href="{% url 'search' %}?q=y:1989">y:1989</a></td>
<td>Busca películas publicadas en el año (<i>year</i>) «1989»</td>
</tr>
<tr>
<td><a href="{% url 'search' %}?q=p:México">p:México</a></td>
<td>Busca películas hechas en el país «México»</td>
</tr>
<tr>
<td><a href="{% url 'search' %}?q=d:varda">d:varda</a></td>
<td>Busca películas dirigidas por «varda»</td>
</tr>
<tr>
<td><a href="{% url 'search' %}?q=a:uma">a:uma</a></td>
<td>Busca películas en donde actúe «uma»</td>
</tr>
<tr>
<td><a href="{% url 'search' %}?q=g:drama">g:drama</a></td>
<td>Busca películas del género «drama»</td>
</tr>
<tr>
</tbody>
</table>
<p>Esto añade las siguientes consideraciones:</p>
<ul>
<li>
Para dos o más palabras con prefijos se usan guiones en lugar de espacios,
como en «<a href="{% url 'search' %}?q=p:estados-unidos">p:estados-unidos</a>».
</li>
<li>
Se pueden usar varias palabras con prefijos o no para restringir la búsqueda,
como en «<a href="{% url 'search' %}?q=p:mexico+d:buñuel+cielo">p:mexico d:buñuel cielo</a>».
</li>
</ul>
<h2 id="api">API</h2>
<p>
Los resultados de las búsquedas pueden ser en JSON si usas la
<a href="https://es.wikipedia.org/wiki/Interfaz_de_programaci%C3%B3n_de_aplicaciones" target="_blank">API</a>.
</p>
<h3>Obtención de películas</h3>
<p>
La API para obtener películas usa la misma sintaxis a una búsqueda ordinaria,
solo cambia la url <code>search</code> por <code>api</code>.
</p>
<p>
Es decir, en lugar de
<code><a href="{% url 'search' %}?q=p:mexico+d:cuaron">search/?q=p:mexico+d:cuaron</a></code> usa
<code><a href="{% url 'search' %}?q=p:mexico+d:cuaron"><b>api</b>/?q=p:mexico+d:cuaron</a></code>.
</p>
<h3>Obtención de ficha</h3>
<p>
Para obtener una película en específico solo necesitas su ID.
<p>
Por ejemplo, para obtener la ficha de <a href="{% url 'movie' id=1596 %}"><i>Gremlins</i></a> usa
<code><a href="{% url 'search' %}?id=1596">api/?id=1596</a></code>.
</p>
<p><i>Happy hacking</i> 😎</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -2,7 +2,13 @@
{% block content %}
{% for section, content in sections.items %}
{% if section != "genders"%}
{% include 'section.html' with section=section content=content %}
{% if section == 'Más descargados' %}
{% if user.is_authenticated %}
{% include 'section.html' with section=section content=content %}
{% endif %}
{% else %}
{% include 'section.html' with section=section content=content %}
{% endif %}
{% else %}
{% for gender, val in content.items %}
{% include 'section.html' with gender=True section=gender content=val %}

View File

@ -2,8 +2,10 @@
{% if request.get_full_path == "/" %}
<div class="stats">
<p class="stars">{{ movie.stars_icons }}</p>
<p><i class="gg-time"></i>{{ movie.duration_formatted }}</p>
<p><i class="gg-software-download"></i>{{ movie.count_formatted }}</p>
<p><i class="gg-time"></i>{{ movie.duration_formatted | safe }}</p>
{% if user.is_authenticated %}
<p><i class="gg-software-download"></i>{{ movie.count_formatted }}</p>
{% endif %}
</div>
{% else %}
<div class="about">
@ -20,30 +22,30 @@
<tbody>
<tr><td>Título original</td><td>{{ movie.original_name }}<td></tr>
<tr><td>Año</td><td>
<a href="{% url 'search' 'y' movie.year %}">{{ movie.year }}</a>
<a href="{% url 'search' %}?q=y:{{ movie.year | slugify }}">{{ movie.year }}</a>
<td></tr>
<tr><td>País</td><td>
{% for country in movie.countries %}
<a href="{% url 'search' 'p' country %}">
<a href="{% url 'search' %}?q=p:{{ country | slugify }}">
{{ 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>
{% for director in movie.directors %}
<a href="{% url 'search' 'd' director %}">
<a href="{% url 'search' %}?q=d:{{ director | slugify }}">
{{ 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 %}">
<a href="{% url 'search' %}?q=a:{{ actor | slugify}}">
{{ 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 %}">
<a href="{% url 'search' %}?q=g:{{ gender | slugify }}">
{{ gender }}</a>{% if not forloop.last %},{% endif %}
{% endfor %}
<td></tr>
@ -59,7 +61,7 @@
<p>No se encontró su 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 %}
{% if user.is_authenticated %}
<video id="player" playsinline controls data-poster="{{ movie.cartel }}">
<source src="{{ movie.file_name }}" type="video/mp4">
</video>

View File

@ -3,8 +3,10 @@
{% if request.get_full_path != "/" %}
<p class="stats">
<span class="stars">{{ movie.stars_icons }}</span>
<span><i class="gg-time"></i>{{ movie.duration_formatted }}</span>
<span><i class="gg-software-download"></i>{{ movie.count_formatted }}</span>
<span><i class="gg-time"></i>{{ movie.duration_formatted | safe }}</span>
{% if user.is_authenticated %}
<span><i class="gg-software-download"></i>{{ movie.count_formatted }}</span>
{% endif %}
</p>
{% endif %}
</div>

View File

@ -15,15 +15,18 @@
{% if request.get_full_path != "/" %}
<a class="navbar-item" href="{% url 'home' %}">Inicio</a>
{% endif %}
{% if request.get_full_path != "/search/" %}
{% if request.path != "/search/" %}
<a class="navbar-item" href="{% url 'search' %}">Buscar</a>
{% endif %}
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">Más</a>
<div class="navbar-dropdown">
<a class="navbar-item" href="{% url 'about' %}" >Acerca</a>
<a class="navbar-item" href="{% url 'help' %}" >Ayuda</a>
<!--
<hr class="navbar-divider">
<a class="navbar-item" href="{% url 'bugs' %}">Reporta un problema</a>
-->
</div>
</div>
</div>

View File

@ -2,6 +2,32 @@
{% block content %}
<b>TODO: Búsqueda de {{ movies }}</b>
<div class="hero">
<form action="/search" method="get">
<div class="hero-body search-div">
<input id="search-bar" class="input is-primary is-large" type="text" placeholder="Quiero ver…" value="{{ query }}" name="q">
<div class="control">
<button class="button is-primary"><i class="gg-search"></i></button>
</div>
</div>
</form>
</div>
{% if movies %}
{% include 'section.html' with section='Resultados de búsqueda:' content=movies %}
{% else %}
<section class="hero">
<div class="hero-body">
<p class="title">
{% if request.get_full_path == "/search/" %}
Explora el catálogo de Mauflix.
{% else %}
¡Ups! Tu búsqueda no arrojó ningún resultado.
{% endif %}
</p>
<p class="subtitle">Visita la <a href="{% url 'help' %}">ayuda</a> para obtener mejores resultados.
</div>
</section>
{% endif %}
{% endblock %}

View File

@ -3,9 +3,9 @@
{% if request.get_full_path == "/" %}
<p class="title">
{% if gender %}
<a href="{% url 'search' 'g' section %}">
<a href="{% url 'search' %}?q=g:{{ section | slugify }}">
{% else %}
<a href="{% url 'search' 's' section %}">
<a href="{% url 'search' %}?q=s:{{ section | slugify }}">
{% endif %}
{{ section }}<span class="arrows"/></a>
</p>