This commit is contained in:
el Mau 2023-02-09 23:02:57 -06:00
commit b903232abb
81 changed files with 2629 additions and 7222 deletions

View File

@ -3,5 +3,8 @@ Mastodon.py
Pillow
psycopg2-binary
httpx
django-tastypie
django-admin-list-filter-dropdown
wikipedia-api
bs4
lxml
unidecode

View File

@ -18,6 +18,7 @@ def run_in_thread(fn):
t = threading.Thread(target=fn, args=k, kwargs=kw)
t.start()
return t
return run
@ -25,35 +26,30 @@ def run_in_thread(fn):
class AdminPerson(admin.ModelAdmin):
list_per_page = 50
list_display = (
'name',
'country',
'is_actor',
'is_director',
'is_woman',
"name",
"country",
"is_actor",
"is_director",
"is_woman",
)
search_fields = ('name',)
list_filter = ('is_woman', 'is_actor', 'is_director', 'country')
search_fields = ("name",)
list_filter = ("is_woman", "is_actor", "is_director", "country")
@admin.register(models.Movie)
class AdminMovie(admin.ModelAdmin):
list_per_page = 50
list_display = (
'name',
'original_name',
'year',
'duration',
'count')
search_fields = ('name', 'original_name')
filter_horizontal = ('directors',)
list_display = ("name", "original_name", "year", "duration", "count")
search_fields = ("name", "original_name")
filter_horizontal = ("directors",)
list_filter = (
'is_digital',
('directors__name', DropdownFilter),
('countries__name', DropdownFilter),
('year', DropdownFilter),
)
"is_digital",
("directors__name", DropdownFilter),
("countries__name", DropdownFilter),
("year", DropdownFilter),
)
_is_new = False
actions = ['published']
actions = ["published"]
def save_model(self, request, obj, form, change):
self._is_new = obj.pk is None
@ -68,30 +64,32 @@ class AdminMovie(admin.ModelAdmin):
return
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == 'directors':
kwargs['queryset'] = models.Person.objects.filter(is_director=True)
if db_field.name == 'actors':
kwargs['queryset'] = models.Person.objects.filter(is_actor=True)
if db_field.name == "directors":
kwargs["queryset"] = models.Person.objects.filter(is_director=True)
if db_field.name == "actors":
kwargs["queryset"] = models.Person.objects.filter(is_actor=True)
return super().formfield_for_manytomany(db_field, request, **kwargs)
def published(self, request, queryset):
obj = queryset[0]
self._public(obj)
self.message_user(request, 'Publicado correctamente', messages.SUCCESS)
published.short_description = 'Republicar en redes'
self.message_user(request, "Publicado correctamente", messages.SUCCESS)
published.short_description = "Republicar en redes"
def _public_mastodon(self, message, cartel):
MT = {
'jpg': 'image/jpeg',
'png': 'image/png',
"jpg": "image/jpeg",
"png": "image/png",
}
message += '\n\n#mauflix'
message += "\n\n#mauflix"
media_type = MT[cartel.url[-3:]]
try:
server = Mastodon(
access_token=settings.TOKEN_MASTODON,
api_base_url=settings.URL_MASTODON)
api_base_url=settings.URL_MASTODON,
)
media = server.media_post(cartel.read(), media_type)
server.status_post(message, media_ids=media)
@ -100,9 +98,15 @@ 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, 'photo': url_cartel, 'caption': message}
data = {
"chat_id": settings.CHAT_ID,
"photo": url_cartel,
"caption": message,
}
result = httpx.post(url, data=data).json()
return
@ -121,4 +125,3 @@ Año: {obj.year}
admin.site.register(models.Gender)
admin.site.register(models.Country)

View File

@ -1,79 +0,0 @@
#!/usr/bin/env python3
from django.db.models import Q
from tastypie import fields
from tastypie.authentication import Authentication
from tastypie.resources import ModelResource
from tastypie.throttle import BaseThrottle
from tastypie.constants import ALL, ALL_WITH_RELATIONS
from main.models import Movie
from main.models import Person
from main.models import Country
from django.conf import settings
class CustomAuthentication(Authentication):
def is_authenticated(self, request, **kwargs):
if not 'ApiToken' in request.headers:
return False
if request.headers['ApiToken'] != settings.API_TOKEN:
return False
return True
class ResourceDirectors(ModelResource):
class Meta:
queryset = Person.objects.all().filter(is_director=True)
resource_name = 'directors'
class ResourceCountries(ModelResource):
class Meta:
queryset = Country.objects.all()
resource_name = 'countries'
class ResourceMovies(ModelResource):
directors = fields.ToManyField(ResourceDirectors, 'directors', full=True)
countries = fields.ToManyField(ResourceCountries, 'countries', full=True)
class Meta:
queryset = Movie.objects.all().order_by('-id')
resource_name = 'movies'
excludes = ['count', 'duration', 'published', 'stars']
throttle = BaseThrottle(throttle_at=50)
allowed_methods = ['get']
authentication = CustomAuthentication()
limit = 10
max_limit = 10
filtering = {
'name': ALL,
'original_name': ALL,
}
def dehydrate_directors(self, bundle):
names = ', '.join([d.obj.name for d in bundle.data['directors']])
return names
def dehydrate_countries(self, bundle):
names = ', '.join([c.obj.name for c in bundle.data['countries']])
return names
# ~ def build_filters(self, filters=None, **kwargs):
# ~ orm_filters = super().build_filters(filters or {}, **kwargs)
# ~ return orm_filters
def apply_filters(self, request, applicable_filters):
objects = self.get_object_list(request)
query = applicable_filters['name__icontains']
qset = (Q(name__icontains=query) | Q(original_name__icontains=query))
objects = objects.filter(qset)
return objects

View File

@ -2,4 +2,4 @@ from django.apps import AppConfig
class MainConfig(AppConfig):
name = 'main'
name = "main"

View File

@ -6,12 +6,12 @@ from .models import Movie
class LatestMoviesFeed(Feed):
title = 'Lo ultimo en MauFlix'
link = ''
description = 'Ultimas diez películas disponibles en MauFlix'
title = "Lo ultimo en MauFlix"
link = ""
description = "Ultimas diez películas disponibles en MauFlix"
def items(self):
return Movie.objects.order_by('-id')[:10]
return Movie.objects.order_by("-id")[:10]
def item_title(self, item):
return item.name
@ -21,4 +21,4 @@ class LatestMoviesFeed(Feed):
return message
def item_link(self, item):
return reverse('movies', args=[item.pk])
return reverse("movies", args=[item.pk])

View File

@ -8,76 +8,220 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='Country',
name="Country",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=250)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=250)),
],
options={
'verbose_name': 'País',
'verbose_name_plural': 'Paises',
'ordering': ['name'],
'unique_together': {('name',)},
"verbose_name": "País",
"verbose_name_plural": "Paises",
"ordering": ["name"],
"unique_together": {("name",)},
},
),
migrations.CreateModel(
name='Gender',
name="Gender",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=250)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=250)),
],
options={
'verbose_name': 'Género',
'verbose_name_plural': 'Generos',
'ordering': ['name'],
'unique_together': {('name',)},
"verbose_name": "Género",
"verbose_name_plural": "Generos",
"ordering": ["name"],
"unique_together": {("name",)},
},
),
migrations.CreateModel(
name='Person',
name="Person",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=500, verbose_name='Nombre')),
('date_born', models.DateField(blank=True, null=True, verbose_name='Fecha de nacimiento')),
('is_actor', models.BooleanField(default=False, verbose_name='Es Actor')),
('is_director', models.BooleanField(default=False, verbose_name='Es Director')),
('photo', models.ImageField(blank=True, null=True, upload_to='%Y/%m/%d/', verbose_name='Fotografía')),
('country', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='country', to='main.Country')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(max_length=500, verbose_name="Nombre"),
),
(
"date_born",
models.DateField(
blank=True,
null=True,
verbose_name="Fecha de nacimiento",
),
),
(
"is_actor",
models.BooleanField(
default=False, verbose_name="Es Actor"
),
),
(
"is_director",
models.BooleanField(
default=False, verbose_name="Es Director"
),
),
(
"photo",
models.ImageField(
blank=True,
null=True,
upload_to="%Y/%m/%d/",
verbose_name="Fotografía",
),
),
(
"country",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="country",
to="main.Country",
),
),
],
options={
'verbose_name': 'Persona',
'verbose_name_plural': 'Personas',
'ordering': ['name'],
'unique_together': {('name',)},
"verbose_name": "Persona",
"verbose_name_plural": "Personas",
"ordering": ["name"],
"unique_together": {("name",)},
},
),
migrations.CreateModel(
name='Movie',
name="Movie",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=1000, verbose_name='Nombre')),
('original_name', models.CharField(blank=True, default='', max_length=1000, verbose_name='Nombre original')),
('file_name', models.CharField(blank=True, default='', max_length=1000, verbose_name='Nombre archivo')),
('year', models.PositiveSmallIntegerField(default=1900, verbose_name='Año')),
('duration', models.PositiveSmallIntegerField(default=0, verbose_name='Duración')),
('cartel', models.ImageField(blank=True, null=True, upload_to='%Y/%m/%d/', verbose_name='Cartel')),
('count', models.PositiveIntegerField(default=0, verbose_name='Descargas')),
('stars', models.PositiveSmallIntegerField(default=0, verbose_name='Estrellas')),
('actors', models.ManyToManyField(blank=True, related_name='actors', to='main.Person', verbose_name='Reparto')),
('countries', models.ManyToManyField(blank=True, related_name='countries', to='main.Country', verbose_name='País')),
('directors', models.ManyToManyField(related_name='directors', to='main.Person', verbose_name='Director')),
('genders', models.ManyToManyField(blank=True, related_name='genders', to='main.Gender', verbose_name='Género')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(max_length=1000, verbose_name="Nombre"),
),
(
"original_name",
models.CharField(
blank=True,
default="",
max_length=1000,
verbose_name="Nombre original",
),
),
(
"file_name",
models.CharField(
blank=True,
default="",
max_length=1000,
verbose_name="Nombre archivo",
),
),
(
"year",
models.PositiveSmallIntegerField(
default=1900, verbose_name="Año"
),
),
(
"duration",
models.PositiveSmallIntegerField(
default=0, verbose_name="Duración"
),
),
(
"cartel",
models.ImageField(
blank=True,
null=True,
upload_to="%Y/%m/%d/",
verbose_name="Cartel",
),
),
(
"count",
models.PositiveIntegerField(
default=0, verbose_name="Descargas"
),
),
(
"stars",
models.PositiveSmallIntegerField(
default=0, verbose_name="Estrellas"
),
),
(
"actors",
models.ManyToManyField(
blank=True,
related_name="actors",
to="main.Person",
verbose_name="Reparto",
),
),
(
"countries",
models.ManyToManyField(
blank=True,
related_name="countries",
to="main.Country",
verbose_name="País",
),
),
(
"directors",
models.ManyToManyField(
related_name="directors",
to="main.Person",
verbose_name="Director",
),
),
(
"genders",
models.ManyToManyField(
blank=True,
related_name="genders",
to="main.Gender",
verbose_name="Género",
),
),
],
options={
'verbose_name': 'Película',
'verbose_name_plural': 'Películas',
'ordering': ['name'],
'unique_together': {('name', 'original_name')},
"verbose_name": "Película",
"verbose_name_plural": "Películas",
"ordering": ["name"],
"unique_together": {("name", "original_name")},
},
),
]

View File

@ -7,18 +7,23 @@ import main.models
class Migration(migrations.Migration):
dependencies = [
('main', '0001_initial'),
("main", "0001_initial"),
]
operations = [
migrations.AddField(
model_name='movie',
name='published',
field=models.BooleanField(default=True, verbose_name='¿Publicar?'),
model_name="movie",
name="published",
field=models.BooleanField(default=True, verbose_name="¿Publicar?"),
),
migrations.AlterField(
model_name='movie',
name='cartel',
field=models.ImageField(blank=True, null=True, upload_to=main.models.upload_cartel, verbose_name='Cartel'),
model_name="movie",
name="cartel",
field=models.ImageField(
blank=True,
null=True,
upload_to=main.models.upload_cartel,
verbose_name="Cartel",
),
),
]

View File

@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0002_auto_20200602_2302'),
("main", "0002_auto_20200602_2302"),
]
operations = [
migrations.AddField(
model_name='movie',
name='is_digital',
field=models.BooleanField(default=False, verbose_name='Es digital'),
model_name="movie",
name="is_digital",
field=models.BooleanField(
default=False, verbose_name="Es digital"
),
),
]

View File

@ -6,23 +6,25 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0003_movie_is_digital'),
("main", "0003_movie_is_digital"),
]
operations = [
migrations.AddField(
model_name='person',
name='is_woman',
field=models.BooleanField(default=False, verbose_name='Es mujer'),
model_name="person",
name="is_woman",
field=models.BooleanField(default=False, verbose_name="Es mujer"),
),
migrations.AlterField(
model_name='country',
name='name',
field=models.CharField(max_length=250, verbose_name='País'),
model_name="country",
name="name",
field=models.CharField(max_length=250, verbose_name="País"),
),
migrations.AlterField(
model_name='movie',
name='actors',
field=models.ManyToManyField(blank=True, related_name='actors', to='main.Person'),
model_name="movie",
name="actors",
field=models.ManyToManyField(
blank=True, related_name="actors", to="main.Person"
),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 3.2.15 on 2023-01-09 18:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0004_auto_20210807_2207"),
]
operations = [
migrations.AlterField(
model_name="country",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="gender",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="movie",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="person",
name="id",
field=models.AutoField(primary_key=True, serialize=False),
),
]

View File

@ -1,6 +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):
@ -8,10 +14,10 @@ class Gender(models.Model):
name = models.CharField(max_length=250)
class Meta:
unique_together = ['name']
ordering = ['name']
verbose_name = 'Género'
verbose_name_plural = 'Generos'
unique_together = ["name"]
ordering = ["name"]
verbose_name = "Género"
verbose_name_plural = "Generos"
def __str__(self):
return self.name
@ -19,162 +25,489 @@ class Gender(models.Model):
class Country(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=250, verbose_name='País')
name = models.CharField(max_length=250, verbose_name="País")
class Meta:
unique_together = ['name']
ordering = ['name']
verbose_name = 'País'
verbose_name_plural = 'Paises'
unique_together = ["name"]
ordering = ["name"]
verbose_name = "País"
verbose_name_plural = "Paises"
def __str__(self):
return self.name
class PersonQuerySet(models.QuerySet):
def directors(self):
rows = self.filter(is_director=True).values_list('name', flat=True)
rows = self.filter(is_director=True).values_list("name", flat=True)
return rows
def actors(self):
rows = self.filter(is_actor=True).values_list('name', flat=True)
rows = self.filter(is_actor=True).values_list("name", flat=True)
return rows
class Person(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField('Nombre',
max_length=500)
country = models.ForeignKey(Country,
related_name='country', on_delete=models.PROTECT)
date_born = models.DateField('Fecha de nacimiento',
null=True, blank=True)
is_actor = models.BooleanField('Es Actor',
default=False)
is_director = models.BooleanField('Es Director',
default=False)
is_woman = models.BooleanField('Es mujer',
default=False)
photo = models.ImageField('Fotografía', upload_to='%Y/%m/%d/',
null=True, blank=True)
name = models.CharField("Nombre", max_length=500)
country = models.ForeignKey(
Country, related_name="country", on_delete=models.PROTECT
)
date_born = models.DateField("Fecha de nacimiento", null=True, blank=True)
is_actor = models.BooleanField("Es Actor", default=False)
is_director = models.BooleanField("Es Director", default=False)
is_woman = models.BooleanField("Es mujer", default=False)
photo = models.ImageField(
"Fotografía", upload_to="%Y/%m/%d/", null=True, blank=True
)
objects = PersonQuerySet.as_manager()
class Meta:
unique_together = ['name']
ordering = ['name']
verbose_name = 'Persona'
verbose_name_plural = 'Personas'
unique_together = ["name"]
ordering = ["name"]
verbose_name = "Persona"
verbose_name_plural = "Personas"
def __str__(self):
return self.name
class MovieQuerySet(models.QuerySet):
def random_pick(self, random_max=6, min_items=20, all=None, **kwargs):
"""
Regresa películas de manera aleatoria.
def _to_str(self, q):
return ','.join([r.name for r in q.all()])
def _to_link(self, file_name):
# ~ folder = file_name[0].upper()
# ~ if folder.isdigit():
# ~ folder = '0'
url = settings.URL_CDN.format(file_name)
return url
def _to_image(self, img, director=''):
data = ''
if img:
url = '../'
if director:
url = '../../'
data = url + img.url
return data
def to_dict(self, query='', director='', id=0):
rows = self.all().order_by('-id')[:10]
if query == 'all':
rows = self.all()
elif id:
rows = self.filter(id=id)
director = ' '
Por defecto tiene que haber al menos 20 películas en la consulta.
Por defecto regresa un máximo de 6 películas.
"""
if all is None:
all = list(Movie.objects.filter(**kwargs).values())
if len(all) < min_items:
return None
elif len(all) < random_max:
return all
else:
if director:
rows = self.filter(directors__name__iexact=director)
elif query:
rows = self.filter(name__icontains=query)
return random.sample(all, random_max)
movies = []
for row in rows:
data = {}
data['id'] = row.id
data['url'] = self._to_link(row.file_name)
data['name'] = row.name
data['year'] = row.year
data['original_name'] = row.original_name
data['duration'] = row.duration
data['director'] = self._to_str(row.directors)
data['country'] = self._to_str(row.countries)
data['count'] = row.count
data['image'] = self._to_image(row.cartel, director)
movies.append(data)
def top_pick(self, key, top_max=6):
"""
Regresa el top de películas.
matrix = []
for i in range (0, len(movies), 4):
matrix.append(movies[i:i+4])
return matrix
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 self.fix_all(all[:top_max])
def get_directors(self):
data = self.distinct().values_list(
'directors__name', flat=True).order_by('directors')
return data
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:
if movie[key] == all[0][key]:
top.append(movie)
else:
break
if len(top) > top_max:
movies = random.sample(top, top_max)
else:
movies = top
return self.fix_all(movies)
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:
key = Gender.objects.get(pk=gender).name
picked = self.random_pick(genders=gender)
if picked:
sections["genders"][key] = self.fix_all(picked)
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.top_pick("id"),
"Mejor valorados": self.top_random_pick("stars"),
"Más descargados": self.top_pick("count"),
}
sections["genders"] = {}
return sections
def fix_all(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.
"""
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"])
movie["file_name"] = self.fix_path(movie["file_name"])
movie["cartel"] = self.fix_path(movie["cartel"])
if wikipedia:
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'). La URL_DEBUB apunta a la dirección en nebula.
"""
if settings.DEBUG:
if str(Path(el).parent) == "." and len(el) > 0:
el = f"{el[0]}/{el}"
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()
for crossref in html.find_all("dl"):
crossref.decompose()
clean = list(map(lambda x: str(x), html.body.children))
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 'Nh Nm'; p. ej.: 1h 22m, 2h, 15m.
"""
secs = num * 60
hours = self.format_duration_num("%H", secs)
mins = self.format_duration_num("%M", secs)
if hours == "":
return mins
elif mins == "":
return hours
else:
return f"{hours}&nbsp;{mins}"
def format_duration_num(self, num_type, secs):
"""
Da formato a cada número de la duración.
Extrae hora o minuto de la duración, la pasa a int y si no es cero,
regresa Ns; p. ej.: 1h, 55m.
"""
num = int(time.strftime(num_type, time.gmtime(secs)))
sym = num_type[-1].lower()
if num == 0:
return ""
else:
return f"{num}{sym}"
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(
lang, extract_format=wikipediaapi.ExtractFormat.HTML
)
page = wiki.page(title)
if page.exists():
return {
"title": page.title,
"url": page.fullurl,
"summary": self.fix_summ(page.summary),
}
else:
return None
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 = 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()))
movie.pop("_state")
movie.pop("_prefetched_objects_cache")
self.fix_data(movie)
return movie
def get_movies(self, query):
"""
Obtiene películas buscadas.
"""
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_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'
* 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",
}
if prefix in prefixes:
return prefixes[prefix]
else:
return None
def api(self, request):
"""
Obtiene resultados de la API.
"""
msg = f"Vista {request.get_host()}/help/#api para más información."
result = {"ERROR": msg}
try:
if request.GET.get("q"):
query = request.GET["q"]
result = {"movies": Movie.objects.get_movies(query)}
elif request.GET.get("id"):
query = request.GET["id"]
result = {"movie": Movie.objects.get_movie_by_id(query)}
else:
result["Exception"] = f"Invalid {request.GET}"
except Exception:
result["Exception"] = f"Invalid {request.GET}"
if request.user.is_authenticated:
return result
else:
return self.clean_api_response(result)
def clean_api_response(self, result):
"""
Elimina la URL de la película para usuarios no autenticados.
"""
url = "file_name"
if "movies" in result:
for movie in result["movies"]:
del movie[url]
elif "movie" in result:
del result["movie"][url]
return result
def upload_cartel(instance, filename):
first = filename[0].upper()
if first.isdigit():
first = '0'
return f'{first}/{filename}'
first = "0"
return f"{first}/{filename}"
class Movie(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField('Nombre',
max_length=1000)
original_name = models.CharField('Nombre original',
max_length=1000, default='', blank=True)
file_name = models.CharField('Nombre archivo',
max_length=1000, default='', blank=True)
year = models.PositiveSmallIntegerField('Año',
default=1900)
duration = models.PositiveSmallIntegerField('Duración',
default=0)
directors = models.ManyToManyField(Person, verbose_name='Director',
related_name='directors')
actors = models.ManyToManyField(Person,
related_name='actors', blank=True)
countries = models.ManyToManyField(Country,
related_name='countries', verbose_name='País', blank=True)
genders = models.ManyToManyField(Gender,
related_name='genders', verbose_name='Género', blank=True)
cartel = models.ImageField('Cartel', upload_to=upload_cartel,
null=True, blank=True)
count = models.PositiveIntegerField('Descargas',
default=0)
stars = models.PositiveSmallIntegerField('Estrellas',
default=0)
published = models.BooleanField('¿Publicar?',
default=True)
is_digital = models.BooleanField('Es digital',
default=False)
name = models.CharField("Nombre", max_length=1000)
original_name = models.CharField(
"Nombre original", max_length=1000, default="", blank=True
)
file_name = models.CharField(
"Nombre archivo", max_length=1000, default="", blank=True
)
year = models.PositiveSmallIntegerField("Año", default=1900)
duration = models.PositiveSmallIntegerField("Duración", default=0)
directors = models.ManyToManyField(
Person, verbose_name="Director", related_name="directors"
)
actors = models.ManyToManyField(Person, related_name="actors", blank=True)
countries = models.ManyToManyField(
Country, related_name="countries", verbose_name="País", blank=True
)
genders = models.ManyToManyField(
Gender, related_name="genders", verbose_name="Género", blank=True
)
cartel = models.ImageField(
"Cartel", upload_to=upload_cartel, null=True, blank=True
)
count = models.PositiveIntegerField("Descargas", default=0)
stars = models.PositiveSmallIntegerField("Estrellas", default=0)
published = models.BooleanField("¿Publicar?", default=True)
is_digital = models.BooleanField("Es digital", default=False)
objects = MovieQuerySet.as_manager()
class Meta:
unique_together = ['name', 'original_name']
ordering = ['name']
verbose_name = 'Película'
verbose_name_plural = 'Películas'
unique_together = ["name", "original_name"]
ordering = ["name"]
verbose_name = "Película"
verbose_name_plural = "Películas"
def __str__(self):
return self.name

File diff suppressed because one or more lines are too long

1
source/main/static/css/bulma.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,444 @@
/* BASE */
:root {
--color-primary: #375a7f;
--color-background: #343c3d;
--plyr-color-main: var(--color-primary) !important;
}
#nav {
z-index: 101;
}
#nav.is-fixed-top {
position: sticky;
top: -1px;
}
#menu.force-display {
display: flex !important;
}
.navbar-item img {
width: 100%;
}
/* Cada flecha en los títulos de secciones */
.arrows {
font-size: 1.5rem;
}
.arrows:before {
content: " ";
}
/* HOME */
/* Cada sección de pelis */
.hero-cartels {
margin-bottom: 0;
padding-bottom: 0;
}
.cartels {
clear: both;
width: calc(100% + 3rem);
margin-left: -1.5rem !important;
padding-left: 0;
display: grid;
align-items: center;
grid-template-columns: repeat(1, 1fr);
}
@media screen and (min-width: 300px) {
.cartels {
grid-template-columns: repeat(2, 1fr);
}
}
@media screen and (min-width: 450px) {
.cartels {
grid-template-columns: repeat(3, 1fr);
}
}
@media screen and (min-width: 600px) {
.cartels {
grid-template-columns: repeat(4, 1fr);
}
}
@media screen and (min-width: 750px) {
.cartels {
grid-template-columns: repeat(5, 1fr);
}
}
@media screen and (min-width: 900px) {
.cartels {
grid-template-columns: repeat(6, 1fr);
}
}
@media screen and (min-width: 1920px) {
.cartels {
width: auto;
margin: 0 !important;
grid-template-columns: repeat(6, 1fr);
}
}
/* Cada peli */
.cartel {
position: relative;
float: left;
min-width: 150px;
min-height: 225px;
max-width: 300px;
max-height: 450px;
}
.cartel img {
object-fit: cover;
}
.cartel .info {
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 102;
}
/* HOME / MOVIE */
/* Cada ficha de peli */
.movie-head {
padding: 2rem 2rem 0 2rem !important;
}
.movie-body {
padding: 2rem !important;
}
.full .movie-body {
padding: 0 2rem !important;
line-height: 2;
}
.movie-body video {
display: block;
height: 100%;
width: auto;
margin: auto;
}
.info {
background-color: var(--color-background);
}
.info .full {
height: 100%;
min-height: 100%;
}
.stats span {
padding-right: 2rem;
white-space: nowrap;
}
.stats i {
display: inline-block;
margin-right: .5rem;
}
.stats i.gg-time {
font-size: 1rem;
}
.stars {
font-size: 1.5rem;
}
.about {
max-width: 70rem;
margin: auto;
margin-bottom: 2rem;
}
.about p + p {
margin-top: 1rem;
}
.about *:not(h2) + h2 {
margin-top: 1.5rem;
}
.infobox td:first-child {
text-align: right;
width: 8rem;
}
.extra-info {
position: absolute;
bottom: 0;
width: 100%;
background-color: var(--color-primary);
overflow: hidden;
}
.about figure {
width: 50%;
height: auto;
max-width: 300px;
max-height: 450px;
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
*/
.toggle:checked ~ .info {
display: block;
}
.toggle {
position: absolute;
z-index: 100;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.toggle:checked {
display: block;
z-index: 103;
position: absolute;
top: 0;
right: 0;
width: 100%;
height: calc(100% - 3em);
}
/* Tooltip de la ficha; Cfr:
* https://www.w3schools.com/howto/howto_css_tooltip.asp
*/
/* Tooltip container */
.tooltip {
position: relative;
}
/* Tooltip text */
.tooltip .tooltiptext {
visibility: hidden;
opacity: 0;
width: 100%;
background-color: var(--color-background);
color: #fff;
border-top: 3px solid var(--color-primary);
text-align: center;
font-size: 1.25rem;
padding: .5em;
position: absolute;
z-index: 1;
top: 0;
left: 0;
transition: opacity 0.3s;
}
/* Show the tooltip text when you mouse over the tooltip container */
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
/* 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;
}
/* 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 {
box-sizing: border-box;
position: relative;
display: block;
transform: scale(var(--ggs,1));
width: 14px;
height: 14px;
border-radius: 100%;
border: 2px solid transparent;
box-shadow: 0 0 0 2px currentColor;
}
.gg-time::after {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 6px;
height: 6px;
border-left: 2px solid;
border-bottom: 2px solid;
top: 0;
left: 4px;
}
.gg-software-download {
box-sizing: border-box;
position: relative;
display: block;
transform: scale(var(--ggs,1));
width: 16px;
height: 6px;
border: 2px solid;
border-top: 0;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
margin-top: 8px
}
.gg-software-download::after {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 8px;
height: 8px;
border-left: 2px solid;
border-bottom: 2px solid;
transform: rotate(-45deg);
left: 2px;
bottom: 4px
}
.gg-software-download::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
border-radius: 3px;
width: 2px;
height: 10px;
background: currentColor;
left: 5px;
bottom: 5px
}
.gg-link {
box-sizing: border-box;
position: relative;
display: block;
transform: rotate(-45deg) scale(var(--ggs,1));
width: 8px;
height: 2px;
background: currentColor;
border-radius: 4px
}
.gg-link::after,
.gg-link::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
border-radius: 3px;
width: 8px;
height: 10px;
border: 2px solid;
top: -4px
}
.gg-link::before {
border-right: 0;
border-top-left-radius: 40px;
border-bottom-left-radius: 40px;
left: -6px
}
.gg-link::after {
border-left: 0;
border-top-right-radius: 40px;
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
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

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.

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

@ -0,0 +1,19 @@
document.addEventListener('DOMContentLoaded', () => {
modMenu();
});
// Habilita el menú de hamburguesa y que esté fijo
function modMenu() {
document.getElementById('nav').classList.add('is-fixed-top');
document.getElementById('menu').classList.remove('force-display');
// Cfr: https://bulma.io/documentation/components/navbar/#navbar-burger
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
$navbarBurgers.forEach( el => {
el.addEventListener('click', () => {
const target = el.dataset.target;
const $target = document.getElementById(target);
el.classList.toggle('is-active');
$target.classList.toggle('is-active');
});
});
}

View File

@ -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

View File

@ -1,5 +1,6 @@
from django.shortcuts import render
from django.http import HttpResponse
from django.http import JsonResponse
from django.db.models import F
from django.views.decorators.csrf import csrf_exempt
@ -7,58 +8,39 @@ from .models import Movie, Person
def home(request):
context = {"sections": Movie.objects.home_pick()}
return render(request, "home.html", context)
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)
def about(request):
context = {}
return render(request, 'home.html', context)
return render(request, "about.html", context)
def movies(request, args=''):
if args:
try:
id = int(args)
return by_id(request, id)
except ValueError:
pass
count = 0
data = Movie.objects.to_dict(args.strip())
directors = Person.objects.directors()
if data:
count = (len(data) - 1) * 4 + (len(data[-1]))
context = {'movies': data, 'args': args, 'count': count,
'directors': directors, 'selected_director': ''}
return render(request, 'movies.html', context)
def help(request):
context = {}
return render(request, "help.html", context)
def by_id(request, id):
count = 0
data = Movie.objects.to_dict(id=id)
directors = Person.objects.directors()
if data:
count = (len(data) - 1) * 4 + (len(data[-1]))
context = {'movies': data, 'args': id, 'count': count,
'directors': directors, 'selected_director': ''}
return render(request, 'movies.html', context)
def bugs(request):
context = {}
return render(request, "bugs.html", context)
def by_director(request, name):
data = Movie.objects.to_dict(director=name)
directors = Person.objects.directors()
count = (len(data) - 1) * 4 + (len(data[-1]))
context = {'movies': data, 'args': 'by_director', 'count': count,
'directors': directors, 'selected_director': name}
return render(request, 'movies.html', context)
def movie(request, id):
context = {"movie": Movie.objects.get_movie_by_id(id)}
return render(request, "movie.html", context)
def addcount(request, id):
response = ''
if id:
try:
movie = Movie.objects.get(id=id)
count = movie.count
movie.count = F('count') + 1
movie.save()
response = count + 1
except Movie.DoesNotExist:
pass
return HttpResponse(response)
def api(request):
context = Movie.objects.api(request)
return JsonResponse(context)

View File

@ -5,7 +5,7 @@ import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mauflix.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mauflix.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
@ -17,5 +17,5 @@ def main():
execute_from_command_line(sys.argv)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@ -2,4 +2,4 @@
# ~ class SuitConfig(DjangoSuitConfig):
# ~ layout = 'horizontal'
# ~ layout = 'horizontal'

View File

@ -21,70 +21,72 @@ from .conf import (
TOKEN_TELEGRAM,
CHAT_ID,
API_TOKEN,
)
URL_DEBUG,
)
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PATH_TEMPLATES = os.path.join(BASE_DIR, 'templates')
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL = 'media/'
PATH_TEMPLATES = os.path.join(BASE_DIR, "templates")
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
MEDIA_ROOT = os.path.join(BASE_DIR, "media/")
MEDIA_URL = "media/"
ALLOWED_HOSTS = ['mauflix.elmau.net', 'elmau.net']
ALLOWED_HOSTS = ["mauflix.elmau.net", "elmau.net"]
if DEBUG:
ALLOWED_HOSTS = ['*']
ALLOWED_HOSTS = ["*"]
# Application definition
INSTALLED_APPS = [
'django_admin_listfilter_dropdown',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'main.apps.MainConfig',
"django_admin_listfilter_dropdown",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.postgres",
"main.apps.MainConfig",
]
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',
"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 = 'mauflix.urls'
ROOT_URLCONF = "mauflix.urls"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [PATH_TEMPLATES],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [PATH_TEMPLATES],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = 'mauflix.wsgi.application'
WSGI_APPLICATION = "mauflix.wsgi.application"
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {'default': DEFAULT_DB}
DATABASES = {"default": DEFAULT_DB}
# Password validation
@ -92,16 +94,16 @@ DATABASES = {'default': DEFAULT_DB}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
@ -109,9 +111,9 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'es-MX'
LANGUAGE_CODE = "es-MX"
TIME_ZONE = 'America/Mexico_City'
TIME_ZONE = "America/Mexico_City"
USE_I18N = True
@ -123,24 +125,24 @@ USE_TZ = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'
# ~ STATICFILES_DIRS = [STATIC_ROOT]
STATIC_URL = "/static/"
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "main/static/"),
]
DISALLOWED_USER_AGENTS = (
re.compile(r'.*Googlebot', re.IGNORECASE),
re.compile(r'.*Spider', re.IGNORECASE),
re.compile(r'.*bingbot', re.IGNORECASE),
re.compile(r'.*MJ12bot', re.IGNORECASE),
re.compile(r'.*Slurp', re.IGNORECASE),
re.compile(r'.*python-requests', re.IGNORECASE),
re.compile(r'.*Netcraft', re.IGNORECASE),
re.compile(r'.*AhrefsBot', re.IGNORECASE),
re.compile(r".*Googlebot", re.IGNORECASE),
re.compile(r".*Spider", re.IGNORECASE),
re.compile(r".*bingbot", re.IGNORECASE),
re.compile(r".*MJ12bot", re.IGNORECASE),
re.compile(r".*Slurp", re.IGNORECASE),
re.compile(r".*python-requests", re.IGNORECASE),
re.compile(r".*Netcraft", re.IGNORECASE),
re.compile(r".*AhrefsBot", re.IGNORECASE),
)
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_AGE = 3600
# ~ URL_CDN = 'https://sos-ch-dk-2.exo.io/mauflix/{}/{}'
URL_CDN = 'https://mauflix.elmau.net/{}'
URL_MASTODON = 'https://mstdn.mx/'
URL_CDN = "https://mauflix.elmau.net/{}"
URL_MASTODON = "https://mstdn.mx/"

View File

@ -1,29 +1,20 @@
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from django.conf.urls import include
from tastypie.api import Api
from main import views
from main.feeds import LatestMoviesFeed
from main.api import ResourceMovies
v1_api = Api(api_name='v1')
v1_api.register(ResourceMovies())
urlpatterns = [
path('', views.home, name='home'),
path('movies/', views.movies, name='movies'),
path('movies/<str:args>', views.movies, name='movies'),
path('movies/director/<str:name>', views.by_director, name='by_director'),
path('addcount/<int:id>/', views.addcount, name='addcount'),
path('ultimas/rss/', LatestMoviesFeed()),
path('admin/', admin.site.urls),
path('api/', include(v1_api.urls)),
path("", views.home, name="home"),
path("search/", views.search, name="search"),
path("api/", views.api, name="api"),
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()),
path("admin/", admin.site.urls),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mauflix.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mauflix.settings")
application = get_wsgi_application()

View File

@ -14,7 +14,7 @@ select.admin-autocomplete {
.select2-container--admin-autocomplete.select2-container--focus .select2-selection,
.select2-container--admin-autocomplete.select2-container--open .select2-selection {
border-color: #999;
border-color: var(--body-quiet-color);
min-height: 30px;
}
@ -29,13 +29,13 @@ select.admin-autocomplete {
}
.select2-container--admin-autocomplete .select2-selection--single {
background-color: #fff;
border: 1px solid #ccc;
background-color: var(--body-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
color: #444;
color: var(--body-fg);
line-height: 30px;
}
@ -46,7 +46,7 @@ select.admin-autocomplete {
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder {
color: #999;
color: var(--body-quiet-color);
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
@ -80,7 +80,7 @@ select.admin-autocomplete {
}
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single {
background-color: #eee;
background-color: var(--darkened-bg);
cursor: default;
}
@ -94,8 +94,8 @@ select.admin-autocomplete {
}
.select2-container--admin-autocomplete .select2-selection--multiple {
background-color: white;
border: 1px solid #ccc;
background-color: var(--body-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: text;
}
@ -104,8 +104,10 @@ select.admin-autocomplete {
box-sizing: border-box;
list-style: none;
margin: 0;
padding: 0 5px;
padding: 0 10px 5px 5px;
width: 100%;
display: flex;
flex-wrap: wrap;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered li {
@ -113,7 +115,7 @@ select.admin-autocomplete {
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder {
color: #999;
color: var(--body-quiet-color);
margin-top: 5px;
float: left;
}
@ -123,11 +125,13 @@ select.admin-autocomplete {
float: right;
font-weight: bold;
margin: 5px;
position: absolute;
right: 0;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #ccc;
background-color: var(--darkened-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: default;
float: left;
@ -137,7 +141,7 @@ select.admin-autocomplete {
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove {
color: #999;
color: var(--body-quiet-color);
cursor: pointer;
display: inline-block;
font-weight: bold;
@ -145,7 +149,7 @@ select.admin-autocomplete {
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #333;
color: var(--body-fg);
}
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline {
@ -163,12 +167,12 @@ select.admin-autocomplete {
}
.select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple {
border: solid #999 1px;
border: solid var(--body-quiet-color) 1px;
outline: 0;
}
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--multiple {
background-color: #eee;
background-color: var(--darkened-bg);
cursor: default;
}
@ -186,12 +190,20 @@ select.admin-autocomplete {
border-bottom-right-radius: 0;
}
.select2-container--admin-autocomplete .select2-search--dropdown {
background: var(--darkened-bg);
}
.select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
border: 1px solid #ccc;
background: var(--body-bg);
color: var(--body-fg);
border: 1px solid var(--border-color);
border-radius: 4px;
}
.select2-container--admin-autocomplete .select2-search--inline .select2-search__field {
background: transparent;
color: var(--body-fg);
border: none;
outline: 0;
box-shadow: none;
@ -201,6 +213,8 @@ select.admin-autocomplete {
.select2-container--admin-autocomplete .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto;
color: var(--body-fg);
background: var(--body-bg);
}
.select2-container--admin-autocomplete .select2-results__option[role=group] {
@ -208,11 +222,12 @@ select.admin-autocomplete {
}
.select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] {
color: #999;
color: var(--body-quiet-color);
}
.select2-container--admin-autocomplete .select2-results__option[aria-selected=true] {
background-color: #ddd;
background-color: var(--selected-bg);
color: var(--body-fg);
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option {
@ -249,8 +264,8 @@ select.admin-autocomplete {
}
.select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
background-color: #79aec8;
color: white;
background-color: var(--primary);
color: var(--primary-fg);
}
.select2-container--admin-autocomplete .select2-results__group {

View File

@ -4,6 +4,93 @@
@import url(fonts.css);
/* VARIABLE DEFINITIONS */
:root {
--primary: #79aec8;
--secondary: #417690;
--accent: #f5dd5d;
--primary-fg: #fff;
--body-fg: #333;
--body-bg: #fff;
--body-quiet-color: #666;
--body-loud-color: #000;
--header-color: #ffc;
--header-branding-color: var(--accent);
--header-bg: var(--secondary);
--header-link-color: var(--primary-fg);
--breadcrumbs-fg: #c4dce8;
--breadcrumbs-link-fg: var(--body-bg);
--breadcrumbs-bg: var(--primary);
--link-fg: #447e9b;
--link-hover-color: #036;
--link-selected-fg: #5b80b2;
--hairline-color: #e8e8e8;
--border-color: #ccc;
--error-fg: #ba2121;
--message-success-bg: #dfd;
--message-warning-bg: #ffc;
--message-error-bg: #ffefef;
--darkened-bg: #f8f8f8; /* A bit darker than --body-bg */
--selected-bg: #e4e4e4; /* E.g. selected table cells */
--selected-row: #ffc;
--button-fg: #fff;
--button-bg: var(--primary);
--button-hover-bg: #609ab6;
--default-button-bg: var(--secondary);
--default-button-hover-bg: #205067;
--close-button-bg: #888; /* Previously #bbb, contrast 1.92 */
--close-button-hover-bg: #747474;
--delete-button-bg: #ba2121;
--delete-button-hover-bg: #a41515;
--object-tools-fg: var(--button-fg);
--object-tools-bg: var(--close-button-bg);
--object-tools-hover-bg: var(--close-button-hover-bg);
}
@media (prefers-color-scheme: dark) {
:root {
--primary: #264b5d;
--primary-fg: #eee;
--body-fg: #eeeeee;
--body-bg: #121212;
--body-quiet-color: #e0e0e0;
--body-loud-color: #ffffff;
--breadcrumbs-link-fg: #e0e0e0;
--breadcrumbs-bg: var(--primary);
--link-fg: #81d4fa;
--link-hover-color: #4ac1f7;
--link-selected-fg: #6f94c6;
--hairline-color: #272727;
--border-color: #353535;
--error-fg: #e35f5f;
--message-success-bg: #006b1b;
--message-warning-bg: #583305;
--message-error-bg: #570808;
--darkened-bg: #212121;
--selected-bg: #1b1b1b;
--selected-row: #00363a;
--close-button-bg: #333333;
--close-button-hover-bg: #666666;
}
}
html, body {
height: 100%;
}
@ -13,19 +100,20 @@ body {
padding: 0;
font-size: 14px;
font-family: "Roboto","Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif;
color: #333;
background: #fff;
color: var(--body-fg);
background: var(--body-bg);
}
/* LINKS */
a:link, a:visited {
color: #447e9b;
color: var(--link-fg);
text-decoration: none;
transition: color 0.15s, background 0.15s;
}
a:focus, a:hover {
color: #036;
color: var(--link-hover-color);
}
a:focus {
@ -37,7 +125,7 @@ a img {
}
a.section:link, a.section:visited {
color: #fff;
color: var(--header-link-color);
text-decoration: none;
}
@ -64,7 +152,7 @@ h1 {
margin: 0 0 20px;
font-weight: 300;
font-size: 20px;
color: #666;
color: var(--body-quiet-color);
}
h2 {
@ -80,7 +168,7 @@ h2.subhead {
h3 {
font-size: 14px;
margin: .8em 0 .3em 0;
color: #666;
color: var(--body-quiet-color);
font-weight: bold;
}
@ -93,7 +181,7 @@ h4 {
h5 {
font-size: 10px;
margin: 1.5em 0 .5em 0;
color: #666;
color: var(--body-quiet-color);
text-transform: uppercase;
letter-spacing: 1px;
}
@ -131,7 +219,7 @@ fieldset {
min-width: 0;
padding: 0;
border: none;
border-top: 1px solid #eee;
border-top: 1px solid var(--hairline-color);
}
blockquote {
@ -144,14 +232,14 @@ blockquote {
code, pre {
font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
color: #666;
color: var(--body-quiet-color);
font-size: 12px;
overflow-x: auto;
}
pre.literal-block {
margin: 10px;
background: #eee;
background: var(--darkened-bg);
padding: 6px 8px;
}
@ -161,8 +249,8 @@ code strong {
hr {
clear: both;
color: #eee;
background-color: #eee;
color: var(--hairline-color);
background-color: var(--hairline-color);
height: 1px;
border: none;
margin: 0;
@ -183,7 +271,7 @@ hr {
.help, p.help, form p.help, div.help, form div.help, div.help li {
font-size: 11px;
color: #999;
color: var(--body-quiet-color);
}
div.help ul {
@ -199,7 +287,7 @@ p img, h1 img, h2 img, h3 img, h4 img, td img {
}
.quiet, a.quiet:link, a.quiet:visited {
color: #999;
color: var(--body-quiet-color);
font-weight: normal;
}
@ -211,20 +299,23 @@ p img, h1 img, h2 img, h3 img, h4 img, td img {
white-space: nowrap;
}
.hidden {
display: none;
}
/* TABLES */
table {
border-collapse: collapse;
border-color: #ccc;
border-color: var(--border-color);
}
td, th {
font-size: 13px;
line-height: 16px;
border-bottom: 1px solid #eee;
border-bottom: 1px solid var(--hairline-color);
vertical-align: top;
padding: 8px;
font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif;
}
th {
@ -234,37 +325,37 @@ th {
thead th,
tfoot td {
color: #666;
color: var(--body-quiet-color);
padding: 5px 10px;
font-size: 11px;
background: #fff;
background: var(--body-bg);
border: none;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
border-top: 1px solid var(--hairline-color);
border-bottom: 1px solid var(--hairline-color);
}
tfoot td {
border-bottom: none;
border-top: 1px solid #eee;
border-top: 1px solid var(--hairline-color);
}
thead th.required {
color: #000;
color: var(--body-loud-color);
}
tr.alt {
background: #f6f6f6;
background: var(--darkened-bg);
}
tr:nth-child(odd), .row-form-errors {
background: #fff;
background: var(--body-bg);
}
tr:nth-child(even),
tr:nth-child(even) .errorlist,
tr:nth-child(odd) + .row-form-errors,
tr:nth-child(odd) + .row-form-errors .errorlist {
background: #f9f9f9;
background: var(--darkened-bg);
}
/* SORTABLE TABLES */
@ -273,15 +364,15 @@ thead th {
padding: 5px 10px;
line-height: normal;
text-transform: uppercase;
background: #f6f6f6;
background: var(--darkened-bg);
}
thead th a:link, thead th a:visited {
color: #666;
color: var(--body-quiet-color);
}
thead th.sorted {
background: #eee;
background: var(--selected-bg);
}
thead th.sorted .text {
@ -300,7 +391,7 @@ table thead th .text a {
}
table thead th .text a:focus, table thead th .text a:hover {
background: #eee;
background: var(--selected-bg);
}
thead th.sorted a.sortremove {
@ -347,12 +438,12 @@ table thead th.sorted .sortoptions a.sortremove:after {
left: 3px;
font-weight: 200;
font-size: 18px;
color: #999;
color: var(--body-quiet-color);
}
table thead th.sorted .sortoptions a.sortremove:focus:after,
table thead th.sorted .sortoptions a.sortremove:hover:after {
color: #447e9b;
color: var(--link-fg);
}
table thead th.sorted .sortoptions a.sortremove:focus,
@ -399,16 +490,18 @@ textarea {
input[type=text], input[type=password], input[type=email], input[type=url],
input[type=number], input[type=tel], textarea, select, .vTextField {
border: 1px solid #ccc;
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 5px 6px;
margin-top: 0;
color: var(--body-fg);
background-color: var(--body-bg);
}
input[type=text]:focus, input[type=password]:focus, input[type=email]:focus,
input[type=url]:focus, input[type=number]:focus, input[type=tel]:focus,
textarea:focus, select:focus, .vTextField:focus {
border-color: #999;
border-color: var(--body-quiet-color);
}
select {
@ -424,12 +517,13 @@ select[multiple] {
/* FORM BUTTONS */
.button, input[type=submit], input[type=button], .submit-row input, a.button {
background: #79aec8;
background: var(--button-bg);
padding: 10px 15px;
border: none;
border-radius: 4px;
color: #fff;
color: var(--button-fg);
cursor: pointer;
transition: background 0.15s;
}
a.button {
@ -439,7 +533,7 @@ a.button {
.button:active, input[type=submit]:active, input[type=button]:active,
.button:focus, input[type=submit]:focus, input[type=button]:focus,
.button:hover, input[type=submit]:hover, input[type=button]:hover {
background: #609ab6;
background: var(--button-hover-bg);
}
.button[disabled], input[type=submit][disabled], input[type=button][disabled] {
@ -450,13 +544,13 @@ a.button {
float: right;
border: none;
font-weight: 400;
background: #417690;
background: var(--default-button-bg);
}
.button.default:active, input[type=submit].default:active,
.button.default:focus, input[type=submit].default:focus,
.button.default:hover, input[type=submit].default:hover {
background: #205067;
background: var(--default-button-hover-bg);
}
.button[disabled].default,
@ -471,7 +565,7 @@ input[type=button][disabled].default {
.module {
border: none;
margin-bottom: 30px;
background: #fff;
background: var(--body-bg);
}
.module p, .module ul, .module h3, .module h4, .module dl, .module pre {
@ -497,8 +591,8 @@ input[type=button][disabled].default {
font-weight: 400;
font-size: 13px;
text-align: left;
background: #79aec8;
color: #fff;
background: var(--primary);
color: var(--header-link-color);
}
.module caption,
@ -525,18 +619,18 @@ ul.messagelist li {
font-size: 13px;
padding: 10px 10px 10px 65px;
margin: 0 0 10px 0;
background: #dfd url(../img/icon-yes.svg) 40px 12px no-repeat;
background: var(--message-success-bg) url(../img/icon-yes.svg) 40px 12px no-repeat;
background-size: 16px auto;
color: #333;
color: var(--body-fg);
}
ul.messagelist li.warning {
background: #ffc url(../img/icon-alert.svg) 40px 14px no-repeat;
background: var(--message-warning-bg) url(../img/icon-alert.svg) 40px 14px no-repeat;
background-size: 14px auto;
}
ul.messagelist li.error {
background: #ffefef url(../img/icon-no.svg) 40px 12px no-repeat;
background: var(--message-error-bg) url(../img/icon-no.svg) 40px 12px no-repeat;
background-size: 16px auto;
}
@ -546,24 +640,26 @@ ul.messagelist li.error {
display: block;
padding: 10px 12px;
margin: 0 0 10px 0;
color: #ba2121;
border: 1px solid #ba2121;
color: var(--error-fg);
border: 1px solid var(--error-fg);
border-radius: 4px;
background-color: #fff;
background-color: var(--body-bg);
background-position: 5px 12px;
overflow-wrap: break-word;
}
ul.errorlist {
margin: 0 0 4px;
padding: 0;
color: #ba2121;
background: #fff;
color: var(--error-fg);
background: var(--body-bg);
}
ul.errorlist li {
font-size: 13px;
display: block;
margin-bottom: 4px;
overflow-wrap: break-word;
}
ul.errorlist li:first-child {
@ -587,7 +683,7 @@ td ul.errorlist li {
.form-row.errors {
margin: 0;
border: none;
border-bottom: 1px solid #eee;
border-bottom: 1px solid var(--hairline-color);
background: none;
}
@ -597,7 +693,7 @@ td ul.errorlist li {
.errors input, .errors select, .errors textarea,
td ul.errorlist + input, td ul.errorlist + select, td ul.errorlist + textarea {
border: 1px solid #ba2121;
border: 1px solid var(--error-fg);
}
.description {
@ -608,20 +704,19 @@ td ul.errorlist + input, td ul.errorlist + select, td ul.errorlist + textarea {
/* BREADCRUMBS */
div.breadcrumbs {
background: #79aec8;
background: var(--breadcrumbs-bg);
padding: 10px 40px;
border: none;
font-size: 14px;
color: #c4dce8;
color: var(--breadcrumbs-fg);
text-align: left;
}
div.breadcrumbs a {
color: #fff;
color: var(--breadcrumbs-link-fg);
}
div.breadcrumbs a:focus, div.breadcrumbs a:hover {
color: #c4dce8;
color: var(--breadcrumbs-fg);
}
/* ACTION ICONS */
@ -647,11 +742,11 @@ div.breadcrumbs a:focus, div.breadcrumbs a:hover {
}
a.deletelink:link, a.deletelink:visited {
color: #CC3434;
color: #CC3434; /* XXX Probably unused? */
}
a.deletelink:focus, a.deletelink:hover {
color: #993333;
color: #993333; /* XXX Probably unused? */
text-decoration: none;
}
@ -666,14 +761,6 @@ a.deletelink:focus, a.deletelink:hover {
margin-top: -48px;
}
.form-row .object-tools {
margin-top: 5px;
margin-bottom: 5px;
float: none;
height: 2em;
padding-left: 3.5em;
}
.object-tools li {
display: block;
float: left;
@ -689,29 +776,29 @@ a.deletelink:focus, a.deletelink:hover {
display: block;
float: left;
padding: 3px 12px;
background: #999;
background: var(--object-tools-bg);
color: var(--object-tools-fg);
font-weight: 400;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #fff;
}
.object-tools a:focus, .object-tools a:hover {
background-color: #417690;
background-color: var(--object-tools-hover-bg);
}
.object-tools a:focus{
text-decoration: none;
}
.object-tools a.viewsitelink, .object-tools a.golink,.object-tools a.addlink {
.object-tools a.viewsitelink, .object-tools a.addlink {
background-repeat: no-repeat;
background-position: right 7px center;
padding-right: 26px;
}
.object-tools a.viewsitelink, .object-tools a.golink {
.object-tools a.viewsitelink {
background-image: url(../img/tooltag-arrowright.svg);
}
@ -813,13 +900,13 @@ table#change-history tbody th {
justify-content: space-between;
align-items: center;
padding: 10px 40px;
background: #417690;
color: #ffc;
background: var(--header-bg);
color: var(--header-color);
overflow: hidden;
}
#header a:link, #header a:visited {
color: #fff;
color: var(--header-link-color);
}
#header a:focus , #header a:hover {
@ -835,11 +922,11 @@ table#change-history tbody th {
margin: 0 20px 0 0;
font-weight: 300;
font-size: 24px;
color: #f5dd5d;
color: var(--accent);
}
#branding h1, #branding h1 a:link, #branding h1 a:visited {
color: #f5dd5d;
color: var(--accent);
}
#branding h2 {
@ -847,7 +934,7 @@ table#change-history tbody th {
font-size: 14px;
margin: -8px 0 8px 0;
font-weight: normal;
color: #ffc;
color: var(--header-color);
}
#branding a:hover {
@ -871,14 +958,14 @@ table#change-history tbody th {
#user-tools a:focus, #user-tools a:hover {
text-decoration: none;
border-bottom-color: #79aec8;
color: #79aec8;
border-bottom-color: var(--primary);
color: var(--primary);
}
/* SIDEBAR */
#content-related {
background: #f8f8f8;
background: var(--darkened-bg);
}
#content-related .module {
@ -886,8 +973,7 @@ table#change-history tbody th {
}
#content-related h3 {
font-size: 14px;
color: #666;
color: var(--body-quiet-color);
padding: 0 16px;
margin: 0 0 16px;
}
@ -916,22 +1002,22 @@ table#change-history tbody th {
background: none;
padding: 16px;
margin-bottom: 16px;
border-bottom: 1px solid #eaeaea;
border-bottom: 1px solid var(--hairline-color);
font-size: 18px;
color: #333;
color: var(--body-fg);
}
.delete-confirmation form input[type="submit"] {
background: #ba2121;
background: var(--delete-button-bg);
border-radius: 4px;
padding: 10px 15px;
color: #fff;
color: var(--button-fg);
}
.delete-confirmation form input[type="submit"]:active,
.delete-confirmation form input[type="submit"]:focus,
.delete-confirmation form input[type="submit"]:hover {
background: #a41515;
background: var(--delete-button-hover-bg);
}
.delete-confirmation form .cancel-link {
@ -939,17 +1025,17 @@ table#change-history tbody th {
vertical-align: middle;
height: 15px;
line-height: 15px;
background: #ddd;
border-radius: 4px;
padding: 10px 15px;
color: #333;
color: var(--button-fg);
background: var(--close-button-bg);
margin: 0 0 0 10px;
}
.delete-confirmation form .cancel-link:active,
.delete-confirmation form .cancel-link:focus,
.delete-confirmation form .cancel-link:hover {
background: #ccc;
background: var(--close-button-hover-bg);
}
/* POPUP */

View File

@ -40,13 +40,13 @@
}
#changelist .toplinks {
border-bottom: 1px solid #ddd;
border-bottom: 1px solid var(--hairline-color);
}
#changelist .paginator {
color: #666;
border-bottom: 1px solid #eee;
background: #fff;
color: var(--body-quiet-color);
border-bottom: 1px solid var(--hairline-color);
background: var(--body-bg);
overflow: hidden;
}
@ -68,7 +68,7 @@
}
#changelist table tfoot {
color: #666;
color: var(--body-quiet-color);
}
/* TOOLBAR */
@ -76,22 +76,22 @@
#toolbar {
padding: 8px 10px;
margin-bottom: 15px;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
background: #f8f8f8;
color: #666;
border-top: 1px solid var(--hairline-color);
border-bottom: 1px solid var(--hairline-color);
background: var(--darkened-bg);
color: var(--body-quiet-color);
}
#toolbar form input {
border-radius: 4px;
font-size: 14px;
padding: 5px;
color: #333;
color: var(--body-fg);
}
#toolbar #searchbar {
height: 19px;
border: 1px solid #ccc;
border: 1px solid var(--border-color);
padding: 2px 5px;
margin: 0;
vertical-align: top;
@ -100,24 +100,24 @@
}
#toolbar #searchbar:focus {
border-color: #999;
border-color: var(--body-quiet-color);
}
#toolbar form input[type="submit"] {
border: 1px solid #ccc;
border: 1px solid var(--border-color);
font-size: 13px;
padding: 4px 8px;
margin: 0;
vertical-align: middle;
background: #fff;
background: var(--body-bg);
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
cursor: pointer;
color: #333;
color: var(--body-fg);
}
#toolbar form input[type="submit"]:focus,
#toolbar form input[type="submit"]:hover {
border-color: #999;
border-color: var(--body-quiet-color);
}
#changelist-search img {
@ -128,9 +128,9 @@
/* FILTER COLUMN */
#changelist-filter {
flex: 0 0 240px;
order: 1;
width: 240px;
background: #f8f8f8;
background: var(--darkened-bg);
border-left: none;
margin: 0 0 0 30px;
}
@ -146,7 +146,6 @@
#changelist-filter h3 {
font-weight: 400;
font-size: 14px;
padding: 0 15px;
margin-bottom: 10px;
}
@ -154,7 +153,7 @@
#changelist-filter ul {
margin: 5px 0;
padding: 0 15px 15px;
border-bottom: 1px solid #eaeaea;
border-bottom: 1px solid var(--hairline-color);
}
#changelist-filter ul:last-child {
@ -169,31 +168,31 @@
#changelist-filter a {
display: block;
color: #999;
color: var(--body-quiet-color);
text-overflow: ellipsis;
overflow-x: hidden;
}
#changelist-filter li.selected {
border-left: 5px solid #eaeaea;
border-left: 5px solid var(--hairline-color);
padding-left: 10px;
margin-left: -15px;
}
#changelist-filter li.selected a {
color: #5b80b2;
color: var(--link-selected-fg);
}
#changelist-filter a:focus, #changelist-filter a:hover,
#changelist-filter li.selected a:focus,
#changelist-filter li.selected a:hover {
color: #036;
color: var(--link-hover-color);
}
#changelist-filter #changelist-filter-clear a {
font-size: 13px;
padding-bottom: 10px;
border-bottom: 1px solid #eaeaea;
border-bottom: 1px solid var(--hairline-color);
}
/* DATE DRILLDOWN */
@ -214,12 +213,12 @@
}
.change-list ul.toplinks .date-back a {
color: #999;
color: var(--body-quiet-color);
}
.change-list ul.toplinks .date-back a:focus,
.change-list ul.toplinks .date-back a:hover {
color: #036;
color: var(--link-hover-color);
}
/* PAGINATOR */
@ -230,26 +229,26 @@
padding-bottom: 10px;
line-height: 22px;
margin: 0;
border-top: 1px solid #ddd;
border-top: 1px solid var(--hairline-color);
width: 100%;
}
.paginator a:link, .paginator a:visited {
padding: 2px 6px;
background: #79aec8;
background: var(--button-bg);
text-decoration: none;
color: #fff;
color: var(--button-fg);
}
.paginator a.showall {
border: none;
background: none;
color: #5b80b2;
color: var(--link-fg);
}
.paginator a.showall:focus, .paginator a.showall:hover {
background: none;
color: #036;
color: var(--link-hover-color);
}
.paginator .end {
@ -265,7 +264,7 @@
.paginator a:focus, .paginator a:hover {
color: white;
background: #036;
background: var(--link-hover-color);
}
/* ACTIONS */
@ -280,22 +279,22 @@
}
#changelist table tbody tr.selected {
background-color: #FFFFCC;
background-color: var(--selected-row);
}
#changelist .actions {
padding: 10px;
background: #fff;
background: var(--body-bg);
border-top: none;
border-bottom: none;
line-height: 24px;
color: #999;
color: var(--body-quiet-color);
width: 100%;
}
#changelist .actions.selected {
background: #fffccf;
border-top: 1px solid #fffee8;
#changelist .actions.selected { /* XXX Probably unused? */
background: var(--body-bg);
border-top: 1px solid var(--body-bg);
border-bottom: 1px solid #edecd6;
}
@ -305,7 +304,6 @@
#changelist .actions span.question {
font-size: 13px;
margin: 0 0.5em;
display: none;
}
#changelist .actions:last-child {
@ -315,9 +313,8 @@
#changelist .actions select {
vertical-align: top;
height: 24px;
background: none;
color: #000;
border: 1px solid #ccc;
color: var(--body-fg);
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 14px;
padding: 0 0 0 4px;
@ -326,7 +323,7 @@
}
#changelist .actions select:focus {
border-color: #999;
border-color: var(--body-quiet-color);
}
#changelist .actions label {
@ -337,18 +334,18 @@
#changelist .actions .button {
font-size: 13px;
border: 1px solid #ccc;
border: 1px solid var(--border-color);
border-radius: 4px;
background: #fff;
background: var(--body-bg);
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
cursor: pointer;
height: 24px;
line-height: 1;
padding: 4px 8px;
margin: 0;
color: #333;
color: var(--body-fg);
}
#changelist .actions .button:focus, #changelist .actions .button:hover {
border-color: #999;
border-color: var(--body-quiet-color);
}

View File

@ -6,7 +6,7 @@
overflow: hidden;
padding: 10px;
font-size: 13px;
border-bottom: 1px solid #eee;
border-bottom: 1px solid var(--hairline-color);
}
.form-row img, .form-row input {
@ -22,21 +22,17 @@ form .form-row p {
padding-left: 0;
}
.hidden {
display: none;
}
/* FORM LABELS */
label {
font-weight: normal;
color: #666;
color: var(--body-quiet-color);
font-size: 13px;
}
.required label, label.required {
font-weight: bold;
color: #333;
color: var(--body-fg);
}
/* RADIO BUTTONS */
@ -219,24 +215,24 @@ fieldset.collapsed h2, fieldset.collapsed {
}
fieldset.collapsed {
border: 1px solid #eee;
border: 1px solid var(--hairline-color);
border-radius: 4px;
overflow: hidden;
}
fieldset.collapsed h2 {
background: #f8f8f8;
color: #666;
background: var(--darkened-bg);
color: var(--body-quiet-color);
}
fieldset .collapse-toggle {
color: #fff;
color: var(--header-link-color);
}
fieldset.collapsed .collapse-toggle {
background: transparent;
display: inline;
color: #447e9b;
color: var(--link-fg);
}
/* MONOSPACE TEXTAREAS */
@ -250,8 +246,8 @@ fieldset.monospace textarea {
.submit-row {
padding: 12px 14px;
margin: 0 0 20px;
background: #f8f8f8;
border: 1px solid #eee;
background: var(--darkened-bg);
border: 1px solid var(--hairline-color);
border-radius: 4px;
text-align: right;
overflow: hidden;
@ -283,35 +279,35 @@ body.popup .submit-row {
.submit-row a.deletelink {
display: block;
background: #ba2121;
background: var(--delete-button-bg);
border-radius: 4px;
padding: 10px 15px;
height: 15px;
line-height: 15px;
color: #fff;
color: var(--button-fg);
}
.submit-row a.closelink {
display: inline-block;
background: #bbbbbb;
background: var(--close-button-bg);
border-radius: 4px;
padding: 10px 15px;
height: 15px;
line-height: 15px;
margin: 0 0 0 5px;
color: #fff;
color: var(--button-fg);
}
.submit-row a.deletelink:focus,
.submit-row a.deletelink:hover,
.submit-row a.deletelink:active {
background: #a41515;
background: var(--delete-button-hover-bg);
}
.submit-row a.closelink:focus,
.submit-row a.closelink:hover,
.submit-row a.closelink:active {
background: #aaaaaa;
background: var(--close-button-hover-bg);
}
/* CUSTOM FORM FIELDS */
@ -390,12 +386,12 @@ body.popup .submit-row {
.inline-related h3 {
margin: 0;
color: #666;
color: var(--body-quiet-color);
padding: 5px;
font-size: 13px;
background: #f8f8f8;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
background: var(--darkened-bg);
border-top: 1px solid var(--hairline-color);
border-bottom: 1px solid var(--hairline-color);
}
.inline-related h3 span.delete {
@ -409,7 +405,7 @@ body.popup .submit-row {
.inline-related fieldset {
margin: 0;
background: #fff;
background: var(--body-bg);
border: none;
width: 100%;
}
@ -421,7 +417,7 @@ body.popup .submit-row {
text-align: left;
font-weight: bold;
background: #bcd;
color: #fff;
color: var(--body-bg);
}
.inline-group .tabular fieldset.module {
@ -460,7 +456,7 @@ body.popup .submit-row {
overflow: hidden;
font-size: 9px;
font-weight: bold;
color: #666;
color: var(--body-quiet-color);
_width: 700px;
}
@ -477,15 +473,15 @@ body.popup .submit-row {
.inline-group div.add-row,
.inline-group .tabular tr.add-row td {
color: #666;
background: #f8f8f8;
color: var(--body-quiet-color);
background: var(--darkened-bg);
padding: 8px 10px;
border-bottom: 1px solid #eee;
border-bottom: 1px solid var(--hairline-color);
}
.inline-group .tabular tr.add-row td {
padding: 8px 10px;
border-bottom: 1px solid #eee;
border-bottom: 1px solid var(--hairline-color);
}
.inline-group ul.tools a.add,

View File

@ -1,7 +1,7 @@
/* LOGIN FORM */
.login {
background: #f8f8f8;
background: var(--darkened-bg);
height: auto;
}
@ -16,7 +16,7 @@
}
.login #header h1 a {
color: #fff;
color: var(--header-link-color);
}
.login #content {
@ -24,8 +24,8 @@
}
.login #container {
background: #fff;
border: 1px solid #eaeaea;
background: var(--body-bg);
border: 1px solid var(--hairline-color);
border-radius: 4px;
overflow: hidden;
width: 28em;
@ -34,44 +34,25 @@
height: auto;
}
.login #content-main {
width: 100%;
}
.login .form-row {
padding: 4px 0;
float: left;
width: 100%;
border-bottom: none;
}
.login .form-row label {
padding-right: 0.5em;
display: block;
line-height: 2em;
font-size: 1em;
clear: both;
color: #333;
}
.login .form-row #id_username, .login .form-row #id_password {
clear: both;
padding: 8px;
width: 100%;
box-sizing: border-box;
}
.login span.help {
font-size: 10px;
display: block;
}
.login .submit-row {
clear: both;
padding: 1em 0 0 9.4em;
padding: 1em 0 0 0;
margin: 0;
border: none;
background: none;
text-align: left;
text-align: center;
}
.login .password-reset-link {

View File

@ -12,22 +12,23 @@
justify-content: center;
flex: 0 0 23px;
width: 23px;
border-right: 1px solid #eaeaea;
background-color: #ffffff;
border: 0;
border-right: 1px solid var(--hairline-color);
background-color: var(--body-bg);
cursor: pointer;
font-size: 20px;
color: #447e9b;
color: var(--link-fg);
padding: 0;
}
[dir="rtl"] .toggle-nav-sidebar {
border-left: 1px solid #eaeaea;
border-left: 1px solid var(--hairline-color);
border-right: 0;
}
.toggle-nav-sidebar:hover,
.toggle-nav-sidebar:focus {
background-color: #f6f6f6;
background-color: var(--darkened-bg);
}
#nav-sidebar {
@ -36,13 +37,13 @@
left: -276px;
margin-left: -276px;
border-top: 1px solid transparent;
border-right: 1px solid #eaeaea;
background-color: #ffffff;
border-right: 1px solid var(--hairline-color);
background-color: var(--body-bg);
overflow: auto;
}
[dir="rtl"] #nav-sidebar {
border-left: 1px solid #eaeaea;
border-left: 1px solid var(--hairline-color);
border-right: 0;
left: 0;
margin-left: 0;
@ -91,12 +92,12 @@
#nav-sidebar .current-app .section:link,
#nav-sidebar .current-app .section:visited {
color: #ffc;
color: var(--header-color);
font-weight: bold;
}
#nav-sidebar .current-model {
background: #ffc;
background: var(--selected-row);
}
.main > #nav-sidebar + .content {

View File

@ -140,7 +140,7 @@ input[type="submit"], button {
}
#changelist .actions select {
background: #fff;
background: var(--body-bg);
}
#changelist .actions .button {
@ -157,7 +157,7 @@ input[type="submit"], button {
}
#changelist-filter {
width: 200px;
flex-basis: 200px;
}
.change-list .filtered .results,
@ -166,7 +166,7 @@ input[type="submit"], button {
.filtered .actions,
#changelist .paginator {
border-top-color: #eee;
border-top-color: var(--hairline-color); /* XXX Is this used at all? */
}
#changelist .results + .paginator {
@ -213,7 +213,7 @@ input[type="submit"], button {
fieldset .fieldBox + .fieldBox {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
border-top: 1px solid var(--hairline-color);
}
textarea {
@ -399,11 +399,11 @@ input[type="submit"], button {
.datetime .timezonewarning {
display: block;
font-size: 11px;
color: #999;
color: var(--body-quiet-color);
}
.datetimeshortcuts {
color: #ccc;
color: var(--border-color); /* XXX Redundant, .datetime span also sets #ccc */
}
.form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
@ -655,7 +655,7 @@ input[type="submit"], button {
margin-bottom: -3px;
}
form .aligned ul.radiolist li + li {
form .aligned ul.radiolist:not(.inline) li + li {
margin-top: 5px;
}
@ -740,7 +740,7 @@ input[type="submit"], button {
/* Inlines */
.inline-group[data-inline-type="stacked"] .inline-related {
border: 2px solid #eee;
border: 1px solid var(--hairline-color);
border-radius: 4px;
margin-top: 15px;
overflow: auto;
@ -750,18 +750,19 @@ input[type="submit"], button {
box-sizing: border-box;
}
.inline-group[data-inline-type="stacked"] .inline-related + .inline-related {
margin-top: 30px;
}
.inline-group[data-inline-type="stacked"] .inline-related .module {
padding: 0 10px;
}
.inline-group[data-inline-type="stacked"] .inline-related .module .form-row:last-child {
.inline-group[data-inline-type="stacked"] .inline-related .module .form-row {
border-top: 1px solid var(--hairline-color);
border-bottom: none;
}
.inline-group[data-inline-type="stacked"] .inline-related .module .form-row:first-child {
border-top: none;
}
.inline-group[data-inline-type="stacked"] .inline-related h3 {
padding: 10px;
border-top-width: 0;
@ -791,7 +792,7 @@ input[type="submit"], button {
.inline-group[data-inline-type="stacked"] div.add-row {
margin-top: 15px;
border: 1px solid #eee;
border: 1px solid var(--hairline-color);
border-radius: 4px;
}
@ -885,9 +886,7 @@ input[type="submit"], button {
}
.login .form-row label {
display: block;
margin: 0 0 5px;
padding: 0;
line-height: 1.2;
}
@ -895,7 +894,7 @@ input[type="submit"], button {
padding: 15px 0 0;
}
.login br, .login .submit-row label {
.login br {
display: none;
}
@ -963,7 +962,7 @@ input[type="submit"], button {
}
.timelist a {
background: #fff;
background: var(--body-bg);
padding: 4px;
}

View File

@ -1,25 +1,3 @@
body {
direction: rtl;
}
/* LOGIN */
.login .form-row {
float: right;
}
.login .form-row label {
float: right;
padding-left: 0.5em;
padding-right: 0;
text-align: left;
}
.login .submit-row {
clear: both;
padding: 1em 9.4em 0 0;
}
/* GLOBAL */
th {
@ -119,7 +97,7 @@ thead th.sorted .text {
border-left: none;
padding-left: 10px;
margin-left: 0;
border-right: 5px solid #eaeaea;
border-right: 5px solid var(--hairline-color);
padding-right: 10px;
margin-right: -15px;
}

View File

@ -22,26 +22,25 @@
}
.selector-available h2, .selector-chosen h2 {
border: 1px solid #ccc;
border: 1px solid var(--border-color);
border-radius: 4px 4px 0 0;
}
.selector-chosen h2 {
background: #79aec8;
color: #fff;
background: var(--primary);
color: var(--header-link-color);
}
.selector .selector-available h2 {
background: #f8f8f8;
color: #666;
background: var(--darkened-bg);
color: var(--body-quiet-color);
}
.selector .selector-filter {
background: white;
border: 1px solid #ccc;
border: 1px solid var(--border-color);
border-width: 0 1px;
padding: 8px;
color: #999;
color: var(--body-quiet-color);
font-size: 10px;
margin: 0;
text-align: left;
@ -66,7 +65,7 @@
.selector ul.selector-chooser {
float: left;
width: 22px;
background-color: #eee;
background-color: var(--selected-bg);
border-radius: 10px;
margin: 10em 5px 0 5px;
padding: 0;
@ -91,7 +90,7 @@
text-indent: -3000px;
overflow: hidden;
cursor: default;
opacity: 0.3;
opacity: 0.55;
}
.active.selector-add, .active.selector-remove {
@ -126,14 +125,14 @@ a.selector-chooseall, a.selector-clearall {
overflow: hidden;
font-weight: bold;
line-height: 16px;
color: #666;
color: var(--body-quiet-color);
text-decoration: none;
opacity: 0.3;
opacity: 0.55;
}
a.active.selector-chooseall:focus, a.active.selector-clearall:focus,
a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
color: #447e9b;
color: var(--link-fg);
}
a.active.selector-chooseall, a.active.selector-clearall {
@ -261,7 +260,7 @@ p.datetime {
line-height: 20px;
margin: 0;
padding: 0;
color: #666;
color: var(--body-quiet-color);
font-weight: bold;
}
@ -269,7 +268,7 @@ p.datetime {
white-space: nowrap;
font-weight: normal;
font-size: 11px;
color: #ccc;
color: var(--body-quiet-color);
}
.datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
@ -313,7 +312,7 @@ table p.datetime {
.timezonewarning {
font-size: 11px;
color: #999;
color: var(--body-quiet-color);
}
/* URL */
@ -322,7 +321,7 @@ p.url {
line-height: 20px;
margin: 0;
padding: 0;
color: #666;
color: var(--body-quiet-color);
font-size: 11px;
font-weight: bold;
}
@ -337,7 +336,7 @@ p.file-upload {
line-height: 20px;
margin: 0;
padding: 0;
color: #666;
color: var(--body-quiet-color);
font-size: 11px;
font-weight: bold;
}
@ -355,7 +354,7 @@ p.file-upload {
}
span.clearable-file-input label {
color: #333;
color: var(--body-fg);
font-size: 11px;
display: inline;
float: none;
@ -368,8 +367,9 @@ span.clearable-file-input label {
font-size: 12px;
width: 19em;
text-align: center;
background: white;
border: 1px solid #ddd;
background: var(--body-bg);
color: var(--body-fg);
border: 1px solid var(--hairline-color);
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
overflow: hidden;
@ -397,20 +397,20 @@ span.clearable-file-input label {
margin: 0;
text-align: center;
border-top: none;
background: #f5dd5d;
font-weight: 700;
font-size: 12px;
color: #333;
background: var(--accent);
}
.calendar th {
padding: 8px 5px;
background: #f8f8f8;
border-bottom: 1px solid #ddd;
background: var(--darkened-bg);
border-bottom: 1px solid var(--border-color);
font-weight: 400;
font-size: 12px;
text-align: center;
color: #666;
color: var(--body-quiet-color);
}
.calendar td {
@ -418,17 +418,17 @@ span.clearable-file-input label {
font-size: 12px;
text-align: center;
padding: 0;
border-top: 1px solid #eee;
border-top: 1px solid var(--hairline-color);
border-bottom: none;
}
.calendar td.selected a {
background: #79aec8;
color: #fff;
background: var(--primary);
color: var(--button-fg);
}
.calendar td.nonday {
background: #f8f8f8;
background: var(--darkened-bg);
}
.calendar td.today a {
@ -440,17 +440,17 @@ span.clearable-file-input label {
font-weight: 400;
padding: 6px;
text-decoration: none;
color: #444;
color: var(--body-quiet-color);
}
.calendar td a:focus, .timelist a:focus,
.calendar td a:hover, .timelist a:hover {
background: #79aec8;
background: var(--primary);
color: white;
}
.calendar td a:active, .timelist a:active {
background: #417690;
background: var(--header-bg);
color: white;
}
@ -464,16 +464,16 @@ span.clearable-file-input label {
.calendarnav a:link, #calendarnav a:visited,
#calendarnav a:focus, #calendarnav a:hover {
color: #999;
color: var(--body-quiet-color);
}
.calendar-shortcuts {
background: white;
background: var(--body-bg);
color: var(--body-quiet-color);
font-size: 11px;
line-height: 11px;
border-top: 1px solid #eee;
border-top: 1px solid var(--hairline-color);
padding: 8px 0;
color: #ccc;
}
.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
@ -511,8 +511,8 @@ span.clearable-file-input label {
padding: 4px 0;
font-size: 12px;
background: #eee;
border-top: 1px solid #ddd;
color: #333;
border-top: 1px solid var(--border-color);
color: var(--body-fg);
}
.calendar-cancel:focus, .calendar-cancel:hover {

View File

@ -13,6 +13,7 @@
redisplay: function(id) {
// Repopulate HTML select box from cache
const box = document.getElementById(id);
const scroll_value_from_top = box.scrollTop;
box.innerHTML = '';
for (const node of SelectBox.cache[id]) {
if (node.displayed) {
@ -22,6 +23,7 @@
box.appendChild(new_option);
}
}
box.scrollTop = scroll_value_from_top;
},
filter: function(id, text) {
// Redisplay the HTML select box, displaying only the choices containing ALL
@ -31,7 +33,7 @@
node.displayed = 1;
const node_text = node.text.toLowerCase();
for (const token of tokens) {
if (node_text.indexOf(token) === -1) {
if (!node_text.includes(token)) {
node.displayed = 0;
break; // Once the first token isn't found we're done
}

View File

@ -1,154 +1,201 @@
/*global gettext, interpolate, ngettext*/
'use strict';
{
const $ = django.jQuery;
let lastChecked;
function show(selector) {
document.querySelectorAll(selector).forEach(function(el) {
el.classList.remove('hidden');
});
}
$.fn.actions = function(opts) {
const options = $.extend({}, $.fn.actions.defaults, opts);
const actionCheckboxes = $(this);
let list_editable_changed = false;
const showQuestion = function() {
$(options.acrossClears).hide();
$(options.acrossQuestions).show();
$(options.allContainer).hide();
},
showClear = function() {
$(options.acrossClears).show();
$(options.acrossQuestions).hide();
$(options.actionContainer).toggleClass(options.selectedClass);
$(options.allContainer).show();
$(options.counterContainer).hide();
},
reset = function() {
$(options.acrossClears).hide();
$(options.acrossQuestions).hide();
$(options.allContainer).hide();
$(options.counterContainer).show();
},
clearAcross = function() {
reset();
$(options.acrossInput).val(0);
$(options.actionContainer).removeClass(options.selectedClass);
},
checker = function(checked) {
if (checked) {
showQuestion();
} else {
reset();
}
$(actionCheckboxes).prop("checked", checked)
.parent().parent().toggleClass(options.selectedClass, checked);
},
updateCounter = function() {
const sel = $(actionCheckboxes).filter(":checked").length;
// data-actions-icnt is defined in the generated HTML
// and contains the total amount of objects in the queryset
const actions_icnt = $('.action-counter').data('actionsIcnt');
$(options.counterContainer).html(interpolate(
ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
sel: sel,
cnt: actions_icnt
}, true));
$(options.allToggle).prop("checked", function() {
let value;
if (sel === actionCheckboxes.length) {
value = true;
showQuestion();
} else {
value = false;
clearAcross();
}
return value;
});
};
// Show counter by default
$(options.counterContainer).show();
// Check state of checkboxes and reinit state if needed
$(this).filter(":checked").each(function(i) {
$(this).parent().parent().toggleClass(options.selectedClass);
updateCounter();
if ($(options.acrossInput).val() === 1) {
showClear();
}
function hide(selector) {
document.querySelectorAll(selector).forEach(function(el) {
el.classList.add('hidden');
});
$(options.allToggle).show().on('click', function() {
checker($(this).prop("checked"));
updateCounter();
}
function showQuestion(options) {
hide(options.acrossClears);
show(options.acrossQuestions);
hide(options.allContainer);
}
function showClear(options) {
show(options.acrossClears);
hide(options.acrossQuestions);
document.querySelector(options.actionContainer).classList.remove(options.selectedClass);
show(options.allContainer);
hide(options.counterContainer);
}
function reset(options) {
hide(options.acrossClears);
hide(options.acrossQuestions);
hide(options.allContainer);
show(options.counterContainer);
}
function clearAcross(options) {
reset(options);
const acrossInputs = document.querySelectorAll(options.acrossInput);
acrossInputs.forEach(function(acrossInput) {
acrossInput.value = 0;
});
$("a", options.acrossQuestions).on('click', function(event) {
event.preventDefault();
$(options.acrossInput).val(1);
showClear();
document.querySelector(options.actionContainer).classList.remove(options.selectedClass);
}
function checker(actionCheckboxes, options, checked) {
if (checked) {
showQuestion(options);
} else {
reset(options);
}
actionCheckboxes.forEach(function(el) {
el.checked = checked;
el.closest('tr').classList.toggle(options.selectedClass, checked);
});
$("a", options.acrossClears).on('click', function(event) {
event.preventDefault();
$(options.allToggle).prop("checked", false);
clearAcross();
checker(0);
updateCounter();
});
lastChecked = null;
$(actionCheckboxes).on('click', function(event) {
if (!event) { event = window.event; }
const target = event.target ? event.target : event.srcElement;
if (lastChecked && $.data(lastChecked) !== $.data(target) && event.shiftKey === true) {
let inrange = false;
$(lastChecked).prop("checked", target.checked)
.parent().parent().toggleClass(options.selectedClass, target.checked);
$(actionCheckboxes).each(function() {
if ($.data(this) === $.data(lastChecked) || $.data(this) === $.data(target)) {
inrange = (inrange) ? false : true;
}
if (inrange) {
$(this).prop("checked", target.checked)
.parent().parent().toggleClass(options.selectedClass, target.checked);
}
});
}
$(target).parent().parent().toggleClass(options.selectedClass, target.checked);
lastChecked = target;
updateCounter();
});
$('form#changelist-form table#result_list tr').on('change', 'td:gt(0) :input', function() {
list_editable_changed = true;
});
$('form#changelist-form button[name="index"]').on('click', function(event) {
if (list_editable_changed) {
return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
}
});
$('form#changelist-form input[name="_save"]').on('click', function(event) {
let action_changed = false;
$('select option:selected', options.actionContainer).each(function() {
if ($(this).val()) {
action_changed = true;
}
});
if (action_changed) {
if (list_editable_changed) {
return confirm(gettext("You have selected an action, but you havent saved your changes to individual fields yet. Please click OK to save. Youll need to re-run the action."));
} else {
return confirm(gettext("You have selected an action, and you havent made any changes on individual fields. Youre probably looking for the Go button rather than the Save button."));
}
}
});
};
/* Setup plugin defaults */
$.fn.actions.defaults = {
}
function updateCounter(actionCheckboxes, options) {
const sel = Array.from(actionCheckboxes).filter(function(el) {
return el.checked;
}).length;
const counter = document.querySelector(options.counterContainer);
// data-actions-icnt is defined in the generated HTML
// and contains the total amount of objects in the queryset
const actions_icnt = Number(counter.dataset.actionsIcnt);
counter.textContent = interpolate(
ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
sel: sel,
cnt: actions_icnt
}, true);
const allToggle = document.getElementById(options.allToggleId);
allToggle.checked = sel === actionCheckboxes.length;
if (allToggle.checked) {
showQuestion(options);
} else {
clearAcross(options);
}
}
const defaults = {
actionContainer: "div.actions",
counterContainer: "span.action-counter",
allContainer: "div.actions span.all",
acrossInput: "div.actions input.select-across",
acrossQuestions: "div.actions span.question",
acrossClears: "div.actions span.clear",
allToggle: "#action-toggle",
allToggleId: "action-toggle",
selectedClass: "selected"
};
$(document).ready(function() {
const $actionsEls = $('tr input.action-select');
if ($actionsEls.length > 0) {
$actionsEls.actions();
window.Actions = function(actionCheckboxes, options) {
options = Object.assign({}, defaults, options);
let list_editable_changed = false;
let lastChecked = null;
let shiftPressed = false;
document.addEventListener('keydown', (event) => {
shiftPressed = event.shiftKey;
});
document.addEventListener('keyup', (event) => {
shiftPressed = event.shiftKey;
});
document.getElementById(options.allToggleId).addEventListener('click', function(event) {
checker(actionCheckboxes, options, this.checked);
updateCounter(actionCheckboxes, options);
});
document.querySelectorAll(options.acrossQuestions + " a").forEach(function(el) {
el.addEventListener('click', function(event) {
event.preventDefault();
const acrossInputs = document.querySelectorAll(options.acrossInput);
acrossInputs.forEach(function(acrossInput) {
acrossInput.value = 1;
});
showClear(options);
});
});
document.querySelectorAll(options.acrossClears + " a").forEach(function(el) {
el.addEventListener('click', function(event) {
event.preventDefault();
document.getElementById(options.allToggleId).checked = false;
clearAcross(options);
checker(actionCheckboxes, options, false);
updateCounter(actionCheckboxes, options);
});
});
function affectedCheckboxes(target, withModifier) {
const multiSelect = (lastChecked && withModifier && lastChecked !== target);
if (!multiSelect) {
return [target];
}
const checkboxes = Array.from(actionCheckboxes);
const targetIndex = checkboxes.findIndex(el => el === target);
const lastCheckedIndex = checkboxes.findIndex(el => el === lastChecked);
const startIndex = Math.min(targetIndex, lastCheckedIndex);
const endIndex = Math.max(targetIndex, lastCheckedIndex);
const filtered = checkboxes.filter((el, index) => (startIndex <= index) && (index <= endIndex));
return filtered;
};
Array.from(document.getElementById('result_list').tBodies).forEach(function(el) {
el.addEventListener('change', function(event) {
const target = event.target;
if (target.classList.contains('action-select')) {
const checkboxes = affectedCheckboxes(target, shiftPressed);
checker(checkboxes, options, target.checked);
updateCounter(actionCheckboxes, options);
lastChecked = target;
} else {
list_editable_changed = true;
}
});
});
document.querySelector('#changelist-form button[name=index]').addEventListener('click', function() {
if (list_editable_changed) {
const confirmed = confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
if (!confirmed) {
event.preventDefault();
}
}
});
const el = document.querySelector('#changelist-form input[name=_save]');
// The button does not exist if no fields are editable.
if (el) {
el.addEventListener('click', function(event) {
if (document.querySelector('[name=action]').value) {
const text = list_editable_changed
? gettext("You have selected an action, but you havent saved your changes to individual fields yet. Please click OK to save. Youll need to re-run the action.")
: gettext("You have selected an action, and you havent made any changes on individual fields. Youre probably looking for the Go button rather than the Save button.");
if (!confirm(text)) {
event.preventDefault();
}
}
});
}
};
// Call function fn when the DOM is loaded and ready. If it is already
// loaded, call the function now.
// http://youmightnotneedjquery.com/#ready
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
ready(function() {
const actionsEls = document.querySelectorAll('tr input.action-select');
if (actionsEls.length > 0) {
Actions(actionsEls);
}
});
}

View File

@ -28,8 +28,7 @@
timezoneWarningClass: 'timezonewarning', // class of the warning for timezone mismatch
timezoneOffset: 0,
init: function() {
const body = document.getElementsByTagName('body')[0];
const serverOffset = body.dataset.adminUtcOffset;
const serverOffset = document.body.dataset.adminUtcOffset;
if (serverOffset) {
const localOffset = new Date().getTimezoneOffset() * -60;
DateTimeShortcuts.timezoneOffset = localOffset - serverOffset;
@ -48,8 +47,7 @@
},
// Return the current time while accounting for the server timezone.
now: function() {
const body = document.getElementsByTagName('body')[0];
const serverOffset = body.dataset.adminUtcOffset;
const serverOffset = document.body.dataset.adminUtcOffset;
if (serverOffset) {
const localNow = new Date();
const localOffset = localNow.getTimezoneOffset() * -60;

View File

@ -7,13 +7,9 @@
function showAdminPopup(triggeringLink, name_regexp, add_popup) {
const name = triggeringLink.id.replace(name_regexp, '');
let href = triggeringLink.href;
const href = new URL(triggeringLink.href);
if (add_popup) {
if (href.indexOf('?') === -1) {
href += '?_popup=1';
} else {
href += '&_popup=1';
}
href.searchParams.set('_popup', 1);
}
const win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus();

View File

@ -7,7 +7,10 @@
data: function(params) {
return {
term: params.term,
page: params.page
page: params.page,
app_label: $element.data('app-label'),
model_name: $element.data('model-name'),
field_name: $element.data('field-name')
};
}
}

View File

@ -21,6 +21,20 @@ depends on core.js for utility functions like removeChildren or quickElement
gettext('November'),
gettext('December')
],
monthsOfYearAbbrev: [
pgettext('abbrev. month January', 'Jan'),
pgettext('abbrev. month February', 'Feb'),
pgettext('abbrev. month March', 'Mar'),
pgettext('abbrev. month April', 'Apr'),
pgettext('abbrev. month May', 'May'),
pgettext('abbrev. month June', 'Jun'),
pgettext('abbrev. month July', 'Jul'),
pgettext('abbrev. month August', 'Aug'),
pgettext('abbrev. month September', 'Sep'),
pgettext('abbrev. month October', 'Oct'),
pgettext('abbrev. month November', 'Nov'),
pgettext('abbrev. month December', 'Dec')
],
daysOfWeek: [
pgettext('one letter Sunday', 'S'),
pgettext('one letter Monday', 'M'),

View File

@ -14,10 +14,11 @@
ready(function() {
function handleClick(event) {
event.preventDefault();
if (window.location.search.indexOf('&_popup=1') === -1) {
window.history.back(); // Go back if not a popup.
const params = new URLSearchParams(window.location.search);
if (params.has('_popup')) {
window.close(); // Close the popup.
} else {
window.close(); // Otherwise, close the popup.
window.history.back(); // Otherwise, go back.
}
}

View File

@ -85,6 +85,12 @@ function findPosY(obj) {
return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds();
};
Date.prototype.getAbbrevMonthName = function() {
return typeof window.CalendarNamespace === "undefined"
? this.getTwoDigitMonth()
: window.CalendarNamespace.monthsOfYearAbbrev[this.getMonth()];
};
Date.prototype.getFullMonthName = function() {
return typeof window.CalendarNamespace === "undefined"
? this.getTwoDigitMonth()
@ -93,6 +99,7 @@ function findPosY(obj) {
Date.prototype.strftime = function(format) {
const fields = {
b: this.getAbbrevMonthName(),
B: this.getFullMonthName(),
c: this.toString(),
d: this.getTwoDigitDate(),

View File

@ -134,8 +134,7 @@
for (const lookup of ALL_DOWNCODE_MAPS) {
Object.assign(Downcoder.map, lookup);
}
Downcoder.chars = Object.keys(Downcoder.map);
Downcoder.regex = new RegExp(Downcoder.chars.join('|'), 'g');
Downcoder.regex = new RegExp(Object.keys(Downcoder.map).join('|'), 'g');
}
};
@ -149,23 +148,9 @@
function URLify(s, num_chars, allowUnicode) {
// changes, e.g., "Petty theft" to "petty-theft"
// remove all these words from the string before urlifying
if (!allowUnicode) {
s = downcode(s);
}
const hasUnicodeChars = /[^\u0000-\u007f]/.test(s);
// Remove English words only if the string contains ASCII (English)
// characters.
if (!hasUnicodeChars) {
const removeList = [
"a", "an", "as", "at", "before", "but", "by", "for", "from",
"is", "in", "into", "like", "of", "off", "on", "onto", "per",
"since", "than", "the", "this", "that", "to", "up", "via",
"with"
];
const r = new RegExp('\\b(' + removeList.join('|') + ')\\b', 'gi');
s = s.replace(r, '');
}
s = s.toLowerCase(); // convert to lowercase
// if downcode doesn't hit, the char will be stripped here
if (allowUnicode) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,9 +0,0 @@
.custom-article {
border-style: solid;
border-color: gray;
border-width: 1px;
padding: 5px;
border-left: 3px solid SteelBlue;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 B

View File

@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M19.07,4.93C17.22,3 14.66,1.96 12,2C9.34,1.96 6.79,3 4.94,4.93C3,6.78 1.96,9.34 2,12C1.96,14.66 3,17.21 4.93,19.06C6.78,21 9.34,22.04 12,22C14.66,22.04 17.21,21 19.06,19.07C21,17.22 22.04,14.66 22,12C22.04,9.34 21,6.78 19.07,4.93M17,12V18H13.5V13H10.5V18H7V12H5L12,5L19.5,12H17Z" /></svg>

Before

Width:  |  Height:  |  Size: 573 B

File diff suppressed because one or more lines are too long

View File

@ -1,48 +0,0 @@
$(function() {
$('.downmovie').click(function(event) {
var id = $(this).attr('id')
var href = $(this).attr('href')
var hc = window.location.href
var url = '../addcount/' + id
if(hc.search('director')){
url = '../../addcount/' + id
}
event.preventDefault();
$.ajax({
url: url,
success : function(data){
$('#down' + id).text(data);
}
})
var msg = 'Gracias al "honorable" poder legislativo de mi país, ya no es posible compartir nada, contactanos en nuestro canal de Telegram si tienes dudas: @mauflix'
alert(msg);
});
});
function director_change(){
var directors = document.getElementById('directors')
if(directors.selectedIndex){
name = directors.options[directors.selectedIndex].value
window.location.href = '/movies/director/' + name
}else{
window.location.href = '/movies'
}
}
function search_movie(){
var search = document.getElementById('search')
window.location.href = '/movies/' + search.value
}
function search_key_press(ev){
alert(ev.keyCode)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 434 KiB

View File

@ -1,6 +0,0 @@
/*!
Autosize 3.0.15
license: MIT
http://www.jacklmoore.com/autosize
*/
!function(e,t){if("function"==typeof define&&define.amd)define(["exports","module"],t);else if("undefined"!=typeof exports&&"undefined"!=typeof module)t(exports,module);else{var n={exports:{}};t(n.exports,n),e.autosize=n.exports}}(this,function(e,t){"use strict";function n(e){function t(){var t=window.getComputedStyle(e,null);p=t.overflowY,"vertical"===t.resize?e.style.resize="none":"both"===t.resize&&(e.style.resize="horizontal"),c="content-box"===t.boxSizing?-(parseFloat(t.paddingTop)+parseFloat(t.paddingBottom)):parseFloat(t.borderTopWidth)+parseFloat(t.borderBottomWidth),isNaN(c)&&(c=0),i()}function n(t){var n=e.style.width;e.style.width="0px",e.offsetWidth,e.style.width=n,p=t,f&&(e.style.overflowY=t),o()}function o(){var t=window.pageYOffset,n=document.body.scrollTop,o=e.style.height;e.style.height="auto";var i=e.scrollHeight+c;return 0===e.scrollHeight?void(e.style.height=o):(e.style.height=i+"px",v=e.clientWidth,document.documentElement.scrollTop=t,void(document.body.scrollTop=n))}function i(){var t=e.style.height;o();var i=window.getComputedStyle(e,null);if(i.height!==e.style.height?"visible"!==p&&n("visible"):"hidden"!==p&&n("hidden"),t!==e.style.height){var r=d("autosize:resized");e.dispatchEvent(r)}}var s=void 0===arguments[1]?{}:arguments[1],a=s.setOverflowX,l=void 0===a?!0:a,u=s.setOverflowY,f=void 0===u?!0:u;if(e&&e.nodeName&&"TEXTAREA"===e.nodeName&&!r.has(e)){var c=null,p=null,v=e.clientWidth,h=function(){e.clientWidth!==v&&i()},y=function(t){window.removeEventListener("resize",h,!1),e.removeEventListener("input",i,!1),e.removeEventListener("keyup",i,!1),e.removeEventListener("autosize:destroy",y,!1),e.removeEventListener("autosize:update",i,!1),r["delete"](e),Object.keys(t).forEach(function(n){e.style[n]=t[n]})}.bind(e,{height:e.style.height,resize:e.style.resize,overflowY:e.style.overflowY,overflowX:e.style.overflowX,wordWrap:e.style.wordWrap});e.addEventListener("autosize:destroy",y,!1),"onpropertychange"in e&&"oninput"in e&&e.addEventListener("keyup",i,!1),window.addEventListener("resize",h,!1),e.addEventListener("input",i,!1),e.addEventListener("autosize:update",i,!1),r.add(e),l&&(e.style.overflowX="hidden",e.style.wordWrap="break-word"),t()}}function o(e){if(e&&e.nodeName&&"TEXTAREA"===e.nodeName){var t=d("autosize:destroy");e.dispatchEvent(t)}}function i(e){if(e&&e.nodeName&&"TEXTAREA"===e.nodeName){var t=d("autosize:update");e.dispatchEvent(t)}}var r="function"==typeof Set?new Set:function(){var e=[];return{has:function(t){return Boolean(e.indexOf(t)>-1)},add:function(t){e.push(t)},"delete":function(t){e.splice(e.indexOf(t),1)}}}(),d=function(e){return new Event(e)};try{new Event("test")}catch(s){d=function(e){var t=document.createEvent("Event");return t.initEvent(e,!0,!1),t}}var a=null;"undefined"==typeof window||"function"!=typeof window.getComputedStyle?(a=function(e){return e},a.destroy=function(e){return e},a.update=function(e){return e}):(a=function(e,t){return e&&Array.prototype.forEach.call(e.length?e:[e],function(e){return n(e,t)}),e},a.destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],o),e},a.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],i),e}),t.exports=a});

View File

@ -1,250 +0,0 @@
Suit = {};
window.Suit = Suit;
(function ($) {
if (!$)
return;
Suit.$ = $;
// Register callbacks to perform after inline has been added
Suit.after_inline = function () {
var functions = {};
var register = function (fn_name, fn_callback) {
functions[fn_name] = fn_callback;
};
var run = function (inline_prefix, row) {
for (var fn_name in functions) {
functions[fn_name](inline_prefix, row);
}
};
return {
register: register,
run: run
};
}();
Suit.ListActionsToggle = function () {
var $topActions;
var init = function () {
$(document).ready(function () {
$topActions = $('.results').parent().find('.actions').eq(0);
if (!$topActions.length)
return;
$("tr input.action-select, #action-toggle").on('click', checkIfSelected);
});
};
var checkIfSelected = function () {
if ($('tr.selected').length) {
$topActions.slideDown('fast');
} else {
$topActions.slideUp('fast');
}
};
return {
init: init
}
}();
Suit.FixedBar = function () {
var didScroll = false, $fixedItem, $fixedItemParent, $win, $body,
itemOffset,
extraOffset = 0,
fixed = false;
function init(selector) {
$fixedItem = $(selector || '.submit-row');
if (!$fixedItem.length)
return;
$fixedItemParent = $fixedItem.parents('form');
itemOffset = $fixedItem.offset();
$win = $(window);
window.onscroll = onScroll;
window.onresize = onScroll;
onScroll();
setInterval(function () {
if (didScroll) {
didScroll = false;
}
}, 200);
}
function onScroll() {
didScroll = true;
var itemHeight = $fixedItem.height(),
scrollTop = $win.scrollTop();
if (scrollTop + $win.height() - itemHeight - extraOffset < itemOffset.top) {
if (!fixed) {
$fixedItem.addClass('fixed');
$fixedItemParent.addClass('fixed').css('padding-bottom', itemHeight + 'px');
fixed = true;
}
} else {
if (fixed) {
$fixedItem.removeClass('fixed');
$fixedItemParent.removeClass('fixed').css('padding-bottom', '');
fixed = false;
}
}
}
return {
init: init
};
}();
/**
* Avoids double-submit issues in the change_form.
*/
$.fn.suitFormDebounce = function () {
var $form = $(this),
$saveButtons = $form.find('.submit-row button, .submit-row input[type=button], .submit-row input[type=submit]'),
submitting = false;
$form.submit(function () {
if (submitting) {
return false;
}
submitting = true;
$saveButtons.addClass('disabled');
setTimeout(function () {
$saveButtons.removeClass('disabled');
submitting = false;
}, 5000);
});
};
/**
* Content tabs
*/
$.fn.suitFormTabs = function () {
var $tabs = $(this);
var tabPrefix = $tabs.data('tab-prefix');
if (!tabPrefix)
return;
var $tabLinks = $tabs.find('a');
function tabContents($link) {
return $('.' + tabPrefix + '-' + $link.attr('href').replace('#', ''));
}
function activateTabs() {
// Init tab by error, by url hash or init first tab
if (window.location.hash) {
var foundError;
$tabLinks.each(function () {
var $link = $(this);
if (tabContents($link).find('.error, .errorlist').length != 0) {
$link.addClass('has-error');
$link.trigger('click');
foundError = true;
}
});
!foundError && $($tabs).find('a[href=\\' + window.location.hash + ']').click();
} else {
$tabLinks.first().trigger('click');
}
}
$tabLinks.click(function () {
var $link = $(this),
showEvent = $.Event('shown.suit.tab', {
relatedTarget: $link,
tab: $link.attr('href').replace('#', '')
});
$link.parent().parent().find('.active').removeClass('active');
$link.addClass('active');
$('.' + tabPrefix).removeClass('show').addClass('hidden-xs-up');
tabContents($link).removeClass('hidden-xs-up').addClass('show');
$link.trigger(showEvent);
});
activateTabs();
};
/* Characters count for CharacterCountTextarea */
$.fn.suitCharactersCount = function () {
var $elements = $(this);
if (!$elements.length)
return;
$elements.each(function () {
var $el = $(this),
$countEl = $('<div class="suit-char-count"></div>');
$el.after($countEl);
$el.on('keyup', function (e) {
updateCount($(e.currentTarget));
});
updateCount($el);
});
function updateCount($el) {
var maxCount = $el.data('suit-maxcount'),
twitterCount = $el.data('suit-twitter-count'),
value = $el.val(),
len = twitterCount ? getTweetLength(value) : value.length,
count = maxCount ? maxCount - len : len;
if (count < 0)
count = '<span class="text-danger">' + count + '</span>';
$el.next().first().html(count);
}
function getTweetLength(input) {
var tmp = "";
for (var i = 0; i < 23; i++) {
tmp += "o"
}
return input.replace(/(http:\/\/[\S]*)/g, tmp).length;
}
};
/**
* Search filters - submit only changed fields
*/
$.fn.suitSearchFilters = function () {
$(this).change(function () {
var $field = $(this);
var $option = $field.find('option:selected');
var select_name = $option.data('name');
if (select_name) {
$field.attr('name', select_name);
} else {
$field.removeAttr('name');
}
// Handle additional values for date filters
var additional = $option.data('additional');
console.log($field, additional)
if (additional) {
var hiddenId = $field.data('name') + '_add';
var $hidden = $('#' + hiddenId);
if (!$hidden.length) {
$hidden = $('<input/>').attr('type', 'hidden').attr('id', hiddenId);
$field.after($hidden);
}
additional = additional.split('=');
$hidden.attr('name', additional[0]).val(additional[1])
}
});
$(this).trigger('change');
};
})(typeof django !== 'undefined' ? django.jQuery : undefined);

View File

@ -1,183 +0,0 @@
/**
* List sortables
*/
(function ($) {
$.fn.suit_list_sortable = function () {
var $inputs = $(this);
if (!$inputs.length)
return;
// Detect if this is normal or mptt table
var mptt_table = $inputs.first().closest('table').hasClass('table-mptt');
function performMove($arrow, $row) {
var $next, $prev;
$row.closest('table').find('tr.selected').removeClass('selected');
if (mptt_table) {
function getPadding($tr) {
return parseInt($tr.find('th:first').css('padding-left'));
}
function findWithChildren($tr) {
var padding = getPadding($tr);
return $tr.nextUntil(function () {
return getPadding($(this)) <= padding
}).andSelf();
}
var padding = getPadding($row);
var $rows_to_move = findWithChildren($row);
if ($arrow.data('dir') === 'down') {
$next = $rows_to_move.last().next();
if ($next.length && getPadding($next) === padding) {
var $after = findWithChildren($next).last();
if ($after.length) {
$rows_to_move.insertAfter($after).addClass('selected');
}
}
} else {
$prev = $row.prevUntil(function () {
return getPadding($(this)) <= padding
}).andSelf().first().prev();
if ($prev.length && getPadding($prev) === padding) {
$rows_to_move.insertBefore($prev).addClass('selected')
}
}
} else {
if ($arrow.data('dir') === 'down') {
$next = $row.next();
if ($next.is(':visible') && $next.length) {
$row.insertAfter($next).addClass('selected')
}
} else {
$prev = $row.prev();
if ($prev.is(':visible') && $prev.length) {
$row.insertBefore($prev).addClass('selected')
}
}
}
markLastInline($row.parent());
}
function onArrowClick(e) {
var $sortable = $(this);
var $row = $sortable.closest(
$sortable.hasClass('sortable-stacked') ? 'div.inline-related' : 'tr'
);
performMove($sortable, $row);
e.preventDefault();
}
function createLink(text, direction, on_click_func, is_stacked) {
return $('<a/>').attr('href', '#')
.addClass('sortable sortable-' + direction +
(is_stacked ? ' sortable-stacked' : ''))
.attr('data-dir', direction).html(text)
.on('click', on_click_func);
}
function markLastInline($rowParent) {
$rowParent.find(' > .last-sortable').removeClass('last-sortable');
$rowParent.find('tr.form-row:visible:last').addClass('last-sortable');
}
var $lastSortable;
$inputs.each(function () {
var $inline_sortable = $('<div class="inline-sortable"/>'),
icon = '<span class="fa fa-lg fa-arrow-up"></span>',
$sortable = $(this),
is_stacked = $sortable.hasClass('suit-sortable-stacked');
var $up_link = createLink(icon, 'up', onArrowClick, is_stacked),
$down_link = createLink(icon.replace('-up', '-down'), 'down', onArrowClick, is_stacked);
if (is_stacked) {
var $sortable_row = $sortable.closest('div.form-group'),
$stacked_block = $sortable.closest('div.inline-related'),
$links_span = $('<span/>').attr('class', 'stacked-inline-sortable');
// Add arrows to header h3, move order input and remove order field row
$links_span.append($up_link).append($down_link);
$links_span.insertAfter($stacked_block.find('.inline_label'));
$stacked_block.append($sortable);
$sortable_row.remove();
} else {
$sortable.parent().append($inline_sortable);
$inline_sortable.append($up_link);
$inline_sortable.append($down_link);
$lastSortable = $sortable;
}
});
$lastSortable && markLastInline($lastSortable.closest('.form-row').parent());
// Filters out unchanged checkboxes, selects and sortable field itself
function filter_unchanged(i, input) {
if (input.type == 'checkbox') {
if (input.defaultChecked == input.checked) {
return false;
}
} else if (input.type == 'select-one' || input.type == 'select-multiple') {
var options = input.options, option;
for (var j = 0; j < options.length; j++) {
option = options[j];
if (option.selected && option.selected == option.defaultSelected) {
return false;
}
}
} else if ($(input).hasClass('suit-sortable')) {
if (input.defaultValue == input.value && input.value == 0) {
return false;
}
}
return true;
}
// Update input count right before submit
if ($inputs && $inputs.length) {
var $last_input = $inputs.last();
var selector = $(this).selector;
$($last_input[0].form).submit(function (e) {
var i = 0, value;
// e.preventDefault();
$(selector).each(function () {
var $input = $(this);
var fieldset_id = $input.attr('name').split(/-\d+-/)[0];
// Check if any of new dynamic block values has been added
var $set_block = $input.closest('.dynamic-' + fieldset_id);
var $changed_fields = $set_block.find(":input[type!='hidden']:not(.suit-sortable)").filter(
function () {
return $(this).val() != "";
}).filter(filter_unchanged);
// console.log($changed_fields.length, $changed_fields);
var is_changelist = !$set_block.length;
if (is_changelist
|| $set_block.hasClass('has_original')
|| $changed_fields.serializeArray().length
// Since jQuery serialize() doesn't include type=file do additional check
|| $changed_fields.find(":input[type='file']").addBack().length) {
value = i++;
$input.val(value);
}
});
});
}
Suit.after_inline.register('bind_sortable_arrows', function (prefix, row) {
$(row).find('.suit-sortable').on('click', onArrowClick);
markLastInline($(row).parent());
});
};
$(function () {
$('.suit-sortable').suit_list_sortable();
});
}(django.jQuery));
// Call Suit.after_inline
django.jQuery(document).on('formset:added', function (e, row, prefix) {
Suit.after_inline.run(prefix, row);
});

View File

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block content %}
<div class="hero full">
<div class="hero-body">
<p class="title">Acerca de Mauflix</p>
<p>MauFlix es una videoteca personal. Esta no persigue <b>ningún tipo de
lucro</b> y solo cuenta con datos informativos de las películas. Aquí no
encontrarás enlaces de descarga.</p>
</div>
</div>
{% endblock %}

View File

@ -1,57 +1,26 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MauFlix</title>
{% load static %}
<link rel="shortcut icon" href="{% static 'img/favicon.png' %}">
<link rel="stylesheet" href="{% static 'css/bulma.min.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/main.css' %}" type="text/css">
<script type="text/javascript" src="{% static 'js/jquery-3.4.1.min.js' %}" ></script>
<script type="text/javascript" src="{% static 'js/main.js' %}" ></script>
{% block media %}{% endblock %}
<script>
$(document).ready(function() {
});
</script>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MauFlix</title>
{% load static %}
<link rel="shortcut icon" href="{% static 'img/favicon.png' %}">
<link rel="stylesheet" href="{% static 'css/bulma.min.css' %}">
<link rel="stylesheet" href="{% static 'css/bulma.darkly.min.css' %}">
<link rel="stylesheet" href="{% static 'css/main.css' %}">
<script type="text/javascript" src="{% static 'js/main.js' %}"></script>
</head>
</head>
<body>
<section class="hero is-light">
<div class="hero-body">
<div class="container">
<h2 class="title"> MauFlix </h1>
<p class="subtitle">
Otras plataformas tienen todas las películas, excepto las que nos gustan :)
</p>
</div>
</div>
</section>
<div id="movies">
{% block content %}{% endblock %}
<section class="hero is-small is-info">
<div class="hero-body has-text-centered">
<p>Mauflix. Otras plataformas tienen todas las películas, excepto las que nos gustan :)</p>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>
Site develop thanks to:
<a href="https://python.org" target='_blank'>Python</a>,
<a href="https://djangoproject.com/" target='_blank'>Django</a>,
<a href="https://bulma.io/" target='_blank'>Bulma</a>.
The website content is licensed <a href="http://creativecommons.org/licenses/by-nc-sa/4.0/" target='_blank'>CC BY NC SA 4.0</a>.
</p>
</div>
</footer>
</section>
{% include 'nav.html' %}
<section id="main">
{% block content %}{% endblock %}
</section>
{% include 'footer.html' %}
</body>
</html>

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block content %}
<b>TODO: Reporta un problema</b>
{% endblock %}

View File

@ -0,0 +1,16 @@
<footer class="footer">
<div class="content">
<p><b>G1</b>:&nbsp;<code>GwhvskhBj6B6JYQLo3E9S97PFfL6gw3LgmYG1cTsjQkF</code></p>
</div>
<div class="content has-text-centered">
<p>Te recomendamos <a href="https://www.videolan.org/vlc/" target="_blank">VLC</a> para ver tus películas.</p>
</div>
<div class="content has-text-centered">
<p>
Sitio desarrollado con
<a href="https://python.org" target='_blank'>Python</a>,
<a href="https://djangoproject.com/" target='_blank'>Django</a> y
<a href="https://bulma.io/" target='_blank'>Bulma</a>.
</p>
</div>
</footer>

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

@ -0,0 +1,121 @@
{% 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>
Las búsquedas ignoran mayúsculas, tildes, eñes y signos de puntuación,
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>
Las búsquedas no buscan palabras exactas, sino las palabras que comienzan
con el término de búsqueda, así que «<a href="{% url 'search' %}?q=perro">perro</a>»
también incluye los resultados de «perros».
</li>
<li>
Cuando una búsqueda encuentra más de cien películas,
los cien resultados son seleccionados y ordenados de manera aleatoria:
<u>nunca obtendrás los mismos resultados</u>.
</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 <u>t</u>í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 <u>o</u>riginal</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><u>y</u>ear</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 <u>p</u>aís «México»</td>
</tr>
<tr>
<td><a href="{% url 'search' %}?q=d:varda">d:varda</a></td>
<td>Busca películas <u>d</u>irigidas por «varda»</td>
</tr>
<tr>
<td><a href="{% url 'search' %}?q=a:uma">a:uma</a></td>
<td>Busca películas en donde <u>a</u>ctúe «uma»</td>
</tr>
<tr>
<td><a href="{% url 'search' %}?q=g:drama">g:drama</a></td>
<td>Busca películas del <u>g</u>é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>
<li>
La inexactitud en los resultados permite búsquedas más generales en los campos.
<ul>
<li>
Ejemplo 1: «<a href="{% url 'search' %}?q=y:192">y:192</a>» da como resultado
las películas publicadas en la década de 1920.
</li>
<li>
Ejemplo 2: «<a href="{% url 'search' %}?q=p:a">p:a</a>» da como resultado
las películas hechas en los países que comienzan con «a».
</li>
</ul>
</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 'api' %}?q=p:mexico+d:cuaron"><u>api</u>/?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 'api' %}?id=1596">api/?id=1596</a></code>.
</p>
<p><i>Happy hacking</i> 😎</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,31 +1,18 @@
{% extends "base.html" %}
{% block content %}
<section class="section">
<div class="container">
<p class="subtitle">
Te recomendamos usar <a href="https://www.videolan.org/vlc/" target="_blank">VLC</a>
para ver tus <a href="/movies">películas</a>.
</p>
</div>
</section>
<section class="section">
<div class="container">
<p class="">
<b>BCH</b>: &nbsp;<code>qztd3l00xle5tffdqvh2snvadkuau2ml0uqm4n875d</code>
</p>
<p class="">
<b>BTC</b>: &nbsp;<code>3FhiXcXmAesmQzrNEngjHFnvaJRhU1AGWV</code>
</p>
<p class="">
<b>LTC</b>: &nbsp;<code>MBcgQ3LQJA4W2wsXknTdm2fxRSysLaBJHS</code>
</p>
<p class="">
<b>ETH</b>: &nbsp;<code>0x61a4f614a30ff686445751ed8328b82b77ecfc69</code>
</p>
</div>
</section>
{% for section, content in sections.items %}
{% if section != "genders"%}
{% 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 %}
{% endfor %}
{% endif %}
{% endfor %}
{% endblock %}

View File

@ -1,13 +0,0 @@
{% extends "base.html" %}
{% block content %}
<script type='text/javascript' charset='utf-8'>
webix.ready(function(){
webix.ui(ui_main)
})
</script>
{% endblock %}

View File

@ -0,0 +1,76 @@
<div class="hero-body movie-body">
{% if request.get_full_path == "/" %}
<div class="stats">
<p class="stars">{{ movie.stars_icons }}</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">
<div class="columns is-desktop">
<div class="column">
<h2 class="subtitle">Cartel</h2>
<figure>
<img src="{{ movie.cartel }}">
</figure>
</div>
<div class="column">
<h2 class="subtitle">Ficha técnica</h2>
<table class="table infobox">
<tbody>
<tr><td>Título original</td><td>{{ movie.original_name }}<td></tr>
<tr><td>Año</td><td>
<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' %}?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' %}?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' %}?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' %}?q=g:{{ gender | slugify }}">
{{ gender }}</a>{% if not forloop.last %},{% endif %}
{% endfor %}
<td></tr>
</tbody>
</table>
</div>
</div>
{% 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ó 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_authenticated %}
<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>

View File

@ -0,0 +1,13 @@
<div class="hero-foot">
<nav class="tabs is-boxed is-fullwidth">
<div class="container {% if request.get_full_path == "/" %}extra-info{% endif %}">
<ul>
{% if request.get_full_path == "/" %}
<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>
</nav>
</div>

View File

@ -0,0 +1,12 @@
<div class="hero-head movie-head">
<p class="title">{{ movie.name }}</p>
{% if request.get_full_path != "/" %}
<p class="stats">
<span class="stars">{{ movie.stars_icons }}</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

@ -0,0 +1,5 @@
<section class="hero {% if request.get_full_path == "/" %}is-primary full{% else %}is-large{% endif%}">
{% include 'info-head.html' with movie=movie %}
{% include 'info-body.html' with movie=movie %}
{% include 'info-foot.html' with movie=movie %}
</section>

View File

@ -0,0 +1,4 @@
{% extends "base.html" %}
{% block content %}
{% include 'info.html' with movie=movie %}
{% endblock %}

View File

@ -1,87 +0,0 @@
{% extends "base.html" %}
{% block content %}
<BR>
<!-- Main container -->
<nav class="level">
<!-- Left side -->
<div class="level-left">
<div class="level-item">
<p class="subtitle is-6">
Mostrando <strong>{{count}}</strong> películas
</p>
</div>
<div class="level-item">
<div class="field has-addons">
<p class="control">
<input class="input" id='search' type="text" placeholder="Buscar por nombre">
</p>
<p class="control">
<button class="button is-info is-light" onclick='search_movie();'>Buscar</button>
</p>
</div>
</div>
<div class="level-item">
<div class="select is-info">
<select id='directors' onChange='director_change()'>
<option>Por director</option>
{% for director in directors %}
{% ifequal selected_director director %}
<option selected>{{director}}</option>
{% else %}
<option>{{director}}</option>
{% endifequal %}
{% endfor %}
</select>
</div>
</div>
</div>
<!-- Right side -->
<div class="level-right">
<p class="level-item">
{% if args %}
<button class="button is-info is-light"><a href="/movies/">Ultimas</a></button>
{% else %}
<button class="button is-info is-light"><a href="/movies/all">Ver Todas</a></button>
{% endif %}
</p>
</div>
</nav>
{% for row in movies %}
<div class="columns is-desktop is-variable is-1">
{% for cell in row %}
<div class="column" style="padding: 0.5rem;">
<article class="media has-background-light custom-article">
<figure class="media-left" style="padding-top: 0.75rem;">
<img src='{{ cell.image }}' height='150' width='100'>
</figure>
<div class="media-content">
<div class="content">
<p>
<strong><a class='downmovie' href='{{ cell.url }}' id={{ cell.id }}>{{ cell.name }}</a></strong>
<BR>
({{ cell.original_name }})
</p>
<p>
<strong>Director</strong>: {{ cell.director }} <br>
<strong>País</strong>: {{ cell.country }} <br>
<strong>Año</strong>: {{ cell.year }}, <strong>Duración</strong>: {{ cell.duration }} <br>
<strong>Descargas</strong>: <span id='down{{cell.id}}'>{{ cell.count }}</span>
</p>
</div>
</div>
</article>
</div>
{% endfor %}
</div>
{% endfor %}
<BR>
{% endblock %}

41
source/templates/nav.html Normal file
View File

@ -0,0 +1,41 @@
<nav id="nav" class="navbar" role="navigation" aria-label="main navigation">
{% load static %}
<div class="navbar-brand">
<a class="navbar-item" href="{% url 'home' %}">
<img src="{% static 'img/logo-mauflix.png' %}" width="112" height="28">
</a>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="menu">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="menu" class="navbar-menu force-display">
<div class="navbar-start">
{% if request.get_full_path != "/" %}
<a class="navbar-item" href="{% url 'home' %}">Inicio</a>
{% endif %}
{% 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>
<div class="navbar-end">
<div class="navbar-item">
<div class="buttons">
<a class="button is-primary" href="{% url 'admin:index' %}">Ingresa</a>
</div>
</div>
</div>
</div>
</nav>

View File

@ -0,0 +1,33 @@
{% extends "base.html" %}
{% block content %}
<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

@ -0,0 +1,40 @@
<section class="hero">
<div class="hero-body hero-cartels">
{% if request.get_full_path == "/" %}
<p class="title">
{% if gender %}
<a href="{% url 'search' %}?q=g:{{ section | slugify }}">
{% else %}
<a href="{% url 'search' %}?q=s:{{ section | slugify }}">
{% endif %}
{{ section }}<span class="arrows"/></a>
</p>
{% else %}
<p class="title">{{ section }}</p>
{% if request.path == "/search/" %}
<p class="subtitle">Visita la <a href="{% url 'help' %}">ayuda</a> para obtener mejores resultados.
{% endif %}
{% endif %}
<div class="cartels">
{% for movie in content %}
<div class="cartel tooltip">
<span class="tooltiptext">{{ movie.name }} ({{ movie.duration_formatted | safe }})</span>
<!--
Lo comentado activaría una ficha que se ve en lugar del cartel.
Una idea a futuro es que sirva para mostrar la ficha de la
pelicula como ventana flotante.
<input type="checkbox" class="toggle">
-->
<div class="info">
{% include 'info.html' with movie=movie %}
</div>
<figure class="image is-2by3">
<a href="/movie/{{ movie.id }}">
<img src="{{ movie.cartel }}">
</a>
</figure>
</div>
{% endfor %}
</div>
</div>
</section>