feat Presentar memoria

parent 94dccafe
Pipeline #634 failed with stage
in 0 seconds
......@@ -9,12 +9,22 @@ ENV PYTHONDONTWRITEBYTECODE 1 \
PYTHONUNBUFFERED 1
# Install packages needed to run your application (not build deps):
# fonts-ebgaramond-extra -- EB Garamond 12
# fonts-liberation -- Fonts with the same metrics as Times, Arial and Courier
# fonts-sil-gentium -- Gentium
# fonts-texgyre -- TeX Gyre Bonum (~ URW Bookman L) and TeX Gyre Pagella (~ URW Palladio L)
# libgs9-common -- URWBookman
# libmariadbclient-client -- for running database commands
# libpcre3 -- for uWSGI internal routing support
# xmlsec1 -- required for SAML auth
# mime-support -- for mime types when serving static files
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
# fonts-ebgaramond-extra \
# fonts-liberation \
# fonts-sil-gentium \
fonts-texgyre \
# libgs9-common \
libmariadb3 \
libpcre3 \
libxmlsec1-openssl \
......
......@@ -25,7 +25,6 @@ mysqlclient = "*"
pypandoc = "*"
python-magic = "*"
python3-saml = "*"
redis = "*"
social-auth-app-django = "*"
social-auth-core = {extras = ["saml"],version = "*"}
weasyprint = "*"
......
{
"_meta": {
"hash": {
"sha256": "3ad454271654c4566145855f3a454eeab86f6a570fe8a55da41d1748f02a52fd"
"sha256": "3768825800c8fd649e6af10ae5d279085823bb8b5f2ac62d7a453fe30c6b99b8"
},
"pipfile-spec": 6,
"requires": {
......@@ -422,14 +422,6 @@
],
"version": "==2021.1"
},
"redis": {
"hashes": [
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
"sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
],
"index": "pypi",
"version": "==3.5.3"
},
"requests": {
"hashes": [
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
......
......@@ -15,7 +15,7 @@ Requisitos
3. [pipenv](https://github.com/pypa/pipenv) para crear un entorno virtual para Python y facilitar el trabajo.
Se puede instalar con `sudo -H pip3 install pipenv`.
4. Paquetes `libxmlsec1-dev`, `pandoc` y `pkg-config`.
4. Paquetes `fonts-texgyre`, `libxmlsec1-dev`, `pandoc` y `pkg-config`.
5. Un servidor de bases de datos aceptado por Django (vg PostgreSQL o MariaDB).
En Debian/Ubuntu:
......@@ -84,6 +84,7 @@ Servidor web para desarrollo
```shell
pipenv shell
nohup ./manage.py run_huey &
./manage.py runserver [<IP>[:<puerto>]]
```
......
......@@ -7,4 +7,7 @@ if [ "x$DJANGO_MANAGEPY_MIGRATE" = 'xon' ]; then
python manage.py migrate --noinput
fi
# Ejecutar el worker de Huey en segundo plano - ¿Sería mejor crear un contenedor independiente?
nohup python manage.py run_huey &
exec "$@"
# Generated by Django 3.2 on 2021-04-08 11:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('indo', '0017_auto_20210211_0909'),
]
operations = [
migrations.AlterField(
model_name='proyecto',
name='estado',
field=models.CharField(
choices=[
('ANULADO', 'Solicitud anulada'),
('BORRADOR', 'Solicitud en preparación'),
('SOLICITADO', 'Solicitud presentada'),
('DENEGADO', 'Denegado por la comisión evaluadora'),
('APROBADO', 'Aprobado por la comisión evaluadora'),
('RECHAZADO', 'Rechazado por el coordinador'),
('ACEPTADO', 'Aceptado por el coordinador'),
('MEM_PRESENTADA', 'Memoria presentada'),
('MEM_NO_ADMITIDA', 'Memoria no admitida por el corrector'),
('MEM_ADMITIDA', 'Memoria admitida por el corrector'),
],
default='BORRADOR',
max_length=63,
),
),
]
......@@ -270,6 +270,9 @@ class Proyecto(models.Model):
('APROBADO', 'Aprobado por la comisión evaluadora'),
('RECHAZADO', 'Rechazado por el coordinador'),
('ACEPTADO', 'Aceptado por el coordinador'),
('MEM_PRESENTADA', 'Memoria presentada'),
('MEM_NO_ADMITIDA', 'Memoria no admitida por el corrector'),
('MEM_ADMITIDA', 'Memoria admitida por el corrector'),
),
default='BORRADOR',
max_length=63,
......
from pathlib import Path
from huey.contrib.djhuey import task
from weasyprint import HTML # https://weasyprint.org/
@task()
def generar_pdf(url_origen, pdf_destino):
"""Obtiene una página web y la guarda en formato PDF en la ruta indicada."""
html = HTML(url_origen)
# Si no existe el directorio del PDF destino, crearlo recursivamente.
Path(pdf_destino).parent.mkdir(parents=True, exist_ok=True)
html.write_pdf(pdf_destino)
# Standard library
import csv
import json
import os
# import magic
from datetime import date
......@@ -32,6 +33,7 @@ from django.forms.models import modelform_factory
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse, reverse_lazy
from django.utils.formats import localize
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from django.views import View
......@@ -66,6 +68,7 @@ from .tables import (
ProyectosEvaluadosTable,
ProyectosTable,
)
from .tasks import generar_pdf
class ChecksMixin(UserPassesTestMixin):
......@@ -611,9 +614,11 @@ class ProyectoAnularView(LoginRequiredMixin, ChecksMixin, RedirectView):
return self.es_coordinador(self.kwargs['pk'])
class MemoriaDetailView(LoginRequiredMixin, ChecksMixin, TemplateView):
class MemoriaDetailView(TemplateView):
"""Muestra la memoria del proyecto indicado."""
# TODO: ¿Limitar el acceso al coordinador, corrector y generador PDF?
template_name = 'memoria/detail.html'
def get_context_data(self, **kwargs):
......@@ -671,22 +676,63 @@ class MemoriaDetailView(LoginRequiredMixin, ChecksMixin, TemplateView):
return redirect('proyecto_detail', proyecto.id)
"""
def test_func(self):
# TODO: Comprobar fecha y estado
return self.es_coordinador(self.kwargs['pk']) # TODO: Permitir a los correctores?
class MemoriaPresentarView(LoginRequiredMixin, ChecksMixin, RedirectView):
"""Presenta la memoria final de proyecto.
El proyecto pasa de estado «Aceptado» a estado «Memoria presentada».
Se genera (en segundo plano) un documento PDF que podrá archivarse en Zaguán.
TODO: ¿Enviar correos al corrector y al coordinador?
"""
pass
def get_redirect_url(self, *args, **kwargs):
return reverse_lazy('proyecto_detail', args=[kwargs.get('pk')])
def post(self, request, *args, **kwargs):
proyecto_id = kwargs.get('pk')
proyecto = get_object_or_404(Proyecto, pk=proyecto_id)
if proyecto.estado != 'ACEPTADO':
messages.error(
request,
_(
f'El estado actual del proyecto ({proyecto.get_estado_display()})'
' no permite presentar memorias.'
),
)
return super().post(request, *args, **kwargs)
fecha_limite = proyecto.convocatoria.fecha_max_memorias
if date.today() > fecha_limite:
fecha_limite_str = localize(fecha_limite)
messages.error(
request,
_(f'Se ha superado la fecha límite ({fecha_limite_str}) para presentar memorias.'),
)
return super().post(request, *args, **kwargs)
proyecto.estado = 'MEM_PRESENTADA'
proyecto.save()
# url_origen = request.build_absolute_uri().removesuffix('presentar/') # Python 3.9
url_origen = request.build_absolute_uri()[: -len('presentar/')]
# Generar ruta tipo `BASE_DIR/media/2021/PIIDUZ_42.pdf`
pdf_destino = os.path.join(
settings.MEDIA_ROOT,
'memoria',
str(proyecto.convocatoria_id),
f'{proyecto.programa.nombre_corto}_{proyecto_id}.pdf',
)
generar_pdf(url_origen, pdf_destino)
messages.success(
request, _('La memoria de su proyecto ha sido presentada para su corrección.')
)
return super().post(request, *args, **kwargs)
def test_func(self):
# TODO: Comprobar fecha y estado
return self.es_coordinador(self.kwargs['pk'])
......@@ -1037,8 +1083,7 @@ class ProyectoPresentarView(LoginRequiredMixin, ChecksMixin, RedirectView):
)
except Exception as err: # smtplib.SMTPAuthenticationError etc
messages.warning(
request,
_(f'No se envió por correo la solicitud de Visto Bueno del centro: {err}'),
request, _(f'No se envió por correo la solicitud de Visto Bueno del centro: {err}')
)
def _is_email_valid(self, email):
......
'''
"""
Django settings for manhattan_project project.
Generated by 'django-admin startproject' using Django 2.1.
......@@ -8,13 +8,17 @@ https://docs.djangoproject.com/en/2.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
'''
"""
import os
from datetime import date
from django.urls import reverse_lazy
# from huey import RedisHuey
# from redis import ConnectionPool
from huey import SqliteHuey
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
......@@ -61,6 +65,7 @@ INSTALLED_APPS = [
'crispy_forms', # https://github.com/django-crispy-forms/django-crispy-forms
'django_summernote', # https://github.com/summernote/django-summernote
'django_tables2', # https://github.com/jieter/django-tables2
'huey.contrib.djhuey', # https://github.com/coleifer/huey
'social_django', # https://github.com/python-social-auth/social-app-django
]
......@@ -169,8 +174,8 @@ AUTHENTICATION_BACKENDS = (
# SOCIAL_AUTH_POSTGRES_JSONFIELD = True
# Identifier of the SP entity (must be a URI)
SOCIAL_AUTH_SAML_SP_ENTITY_ID = 'https://manhattan.local/accounts/metadata'
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = '''Spam, ham and eggs'''
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = '''Spam, sausages and bacon'''
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = """Spam, ham and eggs"""
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = """Spam, sausages and bacon"""
SOCIAL_AUTH_SAML_ORG_INFO = {
'en-US': {
'name': 'manhattan',
......@@ -224,7 +229,6 @@ CRISPY_TEMPLATE_PACK = 'bootstrap4'
# SUMMERNOTE
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
SUMMERNOTE_THEME = 'bs4'
SUMMERNOTE_CONFIG = {
......@@ -367,3 +371,8 @@ PASS_VINCULACIONES = os.environ.get('PASS_VINCULACIONES')
# Titular actual del Vicerrectorado de Política Académica
VICERRECTOR = os.environ.get('VICERRECTOR')
# Tareas en segundo plano
# pool = ConnectionPool(host='localhost', port=6379, max_connections=20)
# HUEY = RedisHuey('manhattan', connection_pool=pool)
HUEY = SqliteHuey()
......@@ -44,7 +44,6 @@ python-magic==0.4.22
python3-openid==3.2.0; python_version >= '3.0'
python3-saml==1.10.1
pytz==2021.1
redis==3.5.3
requests-file==1.5.1
requests-oauthlib==1.3.0
requests-toolbelt==0.9.1
......
......@@ -260,4 +260,82 @@ h2 > small {
@page {
size: A4;
}
html {
margin: 0px;
padding: 0px;
width: 17cm;
/* position: absolute; */
}
body {
background: none;
font-size: 12pt;
/* Debian packages: fonts-texgyre libgs9-common fonts-sil-gentium fonts-ebgaramond-extra fonts-liberation */
font-family: 'TeX Gyre Bonum', 'URW Bookman L', 'Bookman Old Style', 'Bookman', 'TeX Gyre Pagella', 'URW Palladio L', Palatino, 'Gentium', 'EB Garamond 12', 'Garamond', 'Liberation Serif', 'Times New Roman', Times, serif;
line-height: normal;
margin: 0cm;
padding: 0px;
width: 17cm;
}
.container-blanco {
padding: 0px;
margin: 0px;
width: 17cm;
}
.listado { margin-left: -3rem; }
.listado li, .listado li::before { font-size: 1.2rem; }
.listado li::before { top: 0rem; left: -1rem; }
h1, h2, h3, h4, table, table {
font-family: 'LatoLatinWeb', sans-serif;
font-size: 80%;
}
h1 { font-size: 20pt; }
h2 { font-size: 18pt; color: #000080; }
h3 { font-size: 16pt; color: #000080; }
h4 { font-size: 14pt; }
.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {
float: left;
}
.col-sm-12 {
width: 100%;
}
.col-sm-11 {
width: 91.66666667%;
}
.col-sm-10 {
width: 83.33333333%;
}
.col-sm-9 {
width: 75%;
}
.col-sm-8 {
width: 66.66666667%;
}
.col-sm-7 {
width: 58.33333333%;
}
.col-sm-6 {
width: 50%;
}
.col-sm-5 {
width: 41.66666667%;
}
.col-sm-4 {
width: 33.33333333%;
}
.col-sm-3 {
width: 25%;
}
.col-sm-2 {
width: 16.66666667%;
}
.col-sm-1 {
width: 8.33333333%;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment