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 wikipedia-api
bs4 bs4
lxml lxml
unidecode

View File

@ -98,7 +98,9 @@ class AdminMovie(admin.ModelAdmin):
return return
def _public_telegram(self, message, cartel): 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) url_cartel = settings.URL_CDN.format(cartel)
data = { data = {
"chat_id": settings.CHAT_ID, "chat_id": settings.CHAT_ID,

View File

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

View File

@ -13,6 +13,8 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name="movie", model_name="movie",
name="is_digital", 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): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('main', '0004_auto_20210807_2207'), ("main", "0004_auto_20210807_2207"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='country', model_name="country",
name='id', name="id",
field=models.AutoField(primary_key=True, serialize=False), field=models.AutoField(primary_key=True, serialize=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='gender', model_name="gender",
name='id', name="id",
field=models.AutoField(primary_key=True, serialize=False), field=models.AutoField(primary_key=True, serialize=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='movie', model_name="movie",
name='id', name="id",
field=models.AutoField(primary_key=True, serialize=False), field=models.AutoField(primary_key=True, serialize=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='person', model_name="person",
name='id', name="id",
field=models.AutoField(primary_key=True, serialize=False), field=models.AutoField(primary_key=True, serialize=False),
), ),
] ]

View File

@ -1,10 +1,12 @@
import random import random
import time import time
import re
import wikipediaapi import wikipediaapi
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from pathlib import Path from pathlib import Path
from unidecode import unidecode
class Gender(models.Model): class Gender(models.Model):
@ -71,14 +73,15 @@ class Person(models.Model):
class MovieQuerySet(models.QuerySet): 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. Regresa películas de manera aleatoria.
Por defecto tiene que haber al menos 20 películas en la consulta. Por defecto tiene que haber al menos 20 películas en la consulta.
Por defecto regresa un máximo de 6 películas. 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: if len(all) < min_items:
return None return None
elif len(all) < random_max: elif len(all) < random_max:
@ -94,7 +97,7 @@ class MovieQuerySet(models.QuerySet):
Por defecto regresa las primeras 6 películas como máximo. Por defecto regresa las primeras 6 películas como máximo.
""" """
all = list(Movie.objects.order_by(f"-{key}").values()) 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): def top_random_pick(self, key, top_max=6):
""" """
@ -113,9 +116,10 @@ class MovieQuerySet(models.QuerySet):
else: else:
break break
if len(top) > top_max: if len(top) > top_max:
return random.sample(top, top_max) movies = random.sample(top, top_max)
else: else:
return top movies = top
return self.fix_all(movies)
def home_pick(self): def home_pick(self):
""" """
@ -139,9 +143,9 @@ class MovieQuerySet(models.QuerySet):
Las secciones son novedades, mejor valorados y más descargados. Las secciones son novedades, mejor valorados y más descargados.
""" """
sections = { sections = {
"Novedades": self.fix_all(self.top_pick("id")), "Novedades": self.top_pick("id"),
"Mejor valorados": self.fix_all(self.top_random_pick("stars")), "Mejor valorados": self.top_random_pick("stars"),
"Más descargados": self.fix_all(self.top_pick("count")), "Más descargados": self.top_pick("count"),
} }
sections["genders"] = {} sections["genders"] = {}
return sections return sections
@ -161,6 +165,8 @@ class MovieQuerySet(models.QuerySet):
Los enmiendos son formateos de unos valores (que se guardan en nuevas Los enmiendos son formateos de unos valores (que se guardan en nuevas
llaves) y de rutas a medios. 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["duration_formatted"] = self.format_duration(movie["duration"])
movie["count_formatted"] = self.format_count(movie["count"]) movie["count_formatted"] = self.format_count(movie["count"])
movie["stars_icons"] = self.format_stars(movie["stars"]) 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. ('DEBUG'). La URL_DEBUB apunta a la dirección en nebula.
""" """
if settings.DEBUG: if settings.DEBUG:
if str(Path(el).parent) == '.': if str(Path(el).parent) == "." and len(el) > 0:
el = f"{el[0]}/{el}" el = f"{el[0]}/{el}"
return settings.URL_DEBUG.format(el) return settings.URL_DEBUG.format(el)
else: else:
@ -282,53 +288,146 @@ class MovieQuerySet(models.QuerySet):
except Exception: except Exception:
return None 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): def get_movie_by_id(self, id):
""" """
Obtiene película por id. Obtiene película por id.
Esta obtención también añade objetos relacionados. 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__ movie = Movie.objects.prefetch_related(*related).get(pk=id).__dict__
for key, val in movie['_prefetched_objects_cache'].items(): for key, val in movie["_prefetched_objects_cache"].items():
movie[key] = list(map(lambda x: x['name'], val.values())) movie[key] = list(map(lambda x: x["name"], val.values()))
movie.pop("_state") movie.pop("_state")
movie.pop("_prefetched_objects_cache") movie.pop("_prefetched_objects_cache")
self.fix_data(movie) self.fix_data(movie)
return movie return movie
def get_movies(self, prefix, query): def get_movies(self, query):
""" """
Obtiene películas buscadas. Obtiene películas buscadas.
El 'prefix' permite filtrar la consulta por el campo seleccionado.
""" """
selector = self.get_movie_selector(prefix) movies = Movie.objects.prefetch_related(*self.get_related())
return f"selector: {selector}; query: {query}" 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. Obtiene el selector para filtrar la consulta de una búsqueda.
Esto permite restricciones en la búsqueda con esta sintaxis: 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' * 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 = { prefixes = {
't': 'name', "t": "name",
'o': 'original_name', "o": "original_name",
'y': 'year', "y": "year",
'c': 'countries', "c": "countries",
'p': 'countries', "p": "countries",
'd': 'directors', "d": "directors",
'a': 'actors', "a": "actors",
'g': 'genders', "g": "genders",
's': 'section', "s": "section",
} }
if prefix in prefixes: if prefix in prefixes:
return prefixes[prefix] return prefixes[prefix]
else: else:
return '' return None
def upload_cartel(instance, filename): def upload_cartel(instance, filename):

View File

@ -19,6 +19,10 @@
display: flex !important; display: flex !important;
} }
.navbar-item img {
width: 100%;
}
/* Cada flecha en los títulos de secciones */ /* Cada flecha en los títulos de secciones */
.arrows { .arrows {
font-size: 1.5rem; font-size: 1.5rem;
@ -291,6 +295,21 @@ section#notice a {
margin: auto; 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 */ /* ICONS; cfr: https://css.gg */
.gg-time { .gg-time {
@ -396,3 +415,30 @@ section#notice a {
border-bottom-right-radius: 40px; border-bottom-right-radius: 40px;
right: -6px 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) return render(request, "home.html", context)
def search(request, prefix, query): def search(request):
context = {"movies": Movie.objects.get_movies(prefix, query)} 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) return render(request, "search.html", context)
@ -21,6 +25,11 @@ def about(request):
return render(request, "about.html", context) return render(request, "about.html", context)
def help(request):
context = {}
return render(request, "help.html", context)
def bugs(request): def bugs(request):
context = {} context = {}
return render(request, "bugs.html", context) return render(request, "bugs.html", context)

View File

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

View File

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

View File

@ -14,7 +14,7 @@
<body> <body>
<section class="hero is-small is-info"> <section class="hero is-small is-info">
<div class="hero-body has-text-centered"> <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> </div>
</section> </section>
{% include 'nav.html' %} {% 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 %} {% block content %}
{% for section, content in sections.items %} {% for section, content in sections.items %}
{% if section != "genders"%} {% 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 %} {% else %}
{% for gender, val in content.items %} {% for gender, val in content.items %}
{% include 'section.html' with gender=True section=gender content=val %} {% include 'section.html' with gender=True section=gender content=val %}

View File

@ -2,8 +2,10 @@
{% if request.get_full_path == "/" %} {% if request.get_full_path == "/" %}
<div class="stats"> <div class="stats">
<p class="stars">{{ movie.stars_icons }}</p> <p class="stars">{{ movie.stars_icons }}</p>
<p><i class="gg-time"></i>{{ movie.duration_formatted }}</p> <p><i class="gg-time"></i>{{ movie.duration_formatted | safe }}</p>
<p><i class="gg-software-download"></i>{{ movie.count_formatted }}</p> {% if user.is_authenticated %}
<p><i class="gg-software-download"></i>{{ movie.count_formatted }}</p>
{% endif %}
</div> </div>
{% else %} {% else %}
<div class="about"> <div class="about">
@ -20,30 +22,30 @@
<tbody> <tbody>
<tr><td>Título original</td><td>{{ movie.original_name }}<td></tr> <tr><td>Título original</td><td>{{ movie.original_name }}<td></tr>
<tr><td>Año</td><td> <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> <td></tr>
<tr><td>País</td><td> <tr><td>País</td><td>
{% for country in movie.countries %} {% 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 %} {{ country }}</a>{% if not forloop.last %},{% endif %}
{% endfor %} {% endfor %}
<td></tr> <td></tr>
<tr><td>Duración</td><td>{{ movie.duration }} min<td></tr> <tr><td>Duración</td><td>{{ movie.duration }} min<td></tr>
<tr><td>Dirección</td><td> <tr><td>Dirección</td><td>
{% for director in movie.directors %} {% 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 %} {{ director }}</a>{% if not forloop.last %},{% endif %}
{% endfor %} {% endfor %}
<td></tr> <td></tr>
<tr><td>Reparto</td><td> <tr><td>Reparto</td><td>
{% for actor in movie.actors %} {% 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 %} {{ actor }}</a>{% if not forloop.last %},{% endif %}
{% endfor %} {% endfor %}
<td></tr> <td></tr>
<tr><td>Género</td><td> <tr><td>Género</td><td>
{% for gender in movie.genders %} {% 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 %} {{ gender }}</a>{% if not forloop.last %},{% endif %}
{% endfor %} {% endfor %}
<td></tr> <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> <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 %} {% endif %}
</div> </div>
{% if user.is_superuser %} {% if user.is_authenticated %}
<video id="player" playsinline controls data-poster="{{ movie.cartel }}"> <video id="player" playsinline controls data-poster="{{ movie.cartel }}">
<source src="{{ movie.file_name }}" type="video/mp4"> <source src="{{ movie.file_name }}" type="video/mp4">
</video> </video>

View File

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

View File

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

View File

@ -2,6 +2,32 @@
{% block content %} {% 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 %} {% endblock %}

View File

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