first commit

master
ifiguero 2023-02-27 16:21:22 -03:00
parent 7ee6857696
commit a1b84a93d6
227 changed files with 81618 additions and 0 deletions

View File

@ -0,0 +1,3 @@
FROM postgres:14
COPY Docker/init_db.sql /docker-entrypoint-initdb.d/

View File

@ -0,0 +1,3 @@
FROM postgres:14
COPY Docker/init_db_web.sql /docker-entrypoint-initdb.d/

View File

@ -0,0 +1,27 @@
FROM python:3-slim
# set a directory for the app
RUN groupadd app && useradd -m -g app app
# install dependencies
WORKDIR /srv
RUN pip3 install setuptools gunicorn
COPY front-static/requirements.txt /srv/webinterface/requirements.txt
RUN pip3 install --no-cache-dir -r webinterface/requirements.txt
# copy all the files to the container
COPY app.py /srv
COPY front-static /srv/webinterface
COPY static /srv/webinterface/static
COPY templates/layout.html templates/home.html templates/objetivos.html /srv/webinterface/templates
RUN chown -R app:app /srv
USER app
# define the port number the container should expose
EXPOSE 8000
# run the command
ENTRYPOINT ["gunicorn"]
CMD ["-b", "0.0.0.0:8000", "app:iapp"]

View File

@ -0,0 +1,25 @@
FROM python:3-slim
# set a directory for the app
RUN groupadd app && useradd -m -g app app
# install dependencies
WORKDIR /srv
RUN pip3 install setuptools gunicorn
COPY carga-gtfs/requirements.txt /srv/daemon/requirements.txt
RUN pip3 install --no-cache-dir -r daemon/requirements.txt
# copy all the files to the container
COPY daemon.py /srv
COPY carga-gtfs /srv/daemon
RUN chown -R app:app /srv
USER app
# define the port number the container should expose
EXPOSE 8000
# run the command
CMD ["python3", "daemon.py"]

View File

@ -0,0 +1,30 @@
FROM python:3-slim
# set a directory for the app
RUN groupadd app && useradd -m -g app app
# install dependencies
WORKDIR /srv
RUN pip3 install setuptools gunicorn
COPY login-mecanics/requirements.txt /srv/webinterface/requirements.txt
RUN pip3 install --no-cache-dir -r webinterface/requirements.txt
# copy all the files to the container
COPY app.py /srv
COPY login-mecanics /srv/webinterface
COPY static /srv/static
COPY templates/layout.html /srv/webinterface/templates
COPY models/__init__.py models/system.py models/gtfs_work.py models/gtfs_static.py /srv/webinterface/models
RUN chown -R app:app /srv
USER app
# define the port number the container should expose
EXPOSE 8000
# run the command
ENTRYPOINT ["gunicorn"]
CMD ["-b", "0.0.0.0:8000", "app:iapp"]

View File

@ -0,0 +1,28 @@
FROM python:3-slim
# set a directory for the app
RUN groupadd app && useradd -m -g app app
# install dependencies
WORKDIR /srv
RUN pip3 install setuptools gunicorn
COPY private-dynamic/requirements.txt /srv/webinterface/requirements.txt
RUN pip3 install --no-cache-dir -r webinterface/requirements.txt
# copy all the files to the container
COPY app.py /srv
COPY private-dynamic /srv/webinterface
COPY models/__init__.py models/system.py models/gtfs_work.py models/gtfs_static.py /srv/webinterface/models
RUN chown -R app:app /srv
USER app
# define the port number the container should expose
EXPOSE 8000
# run the command
ENTRYPOINT ["gunicorn"]
CMD ["-b", "0.0.0.0:8000", "app:iapp"]

View File

@ -0,0 +1,30 @@
FROM python:3-slim
# set a directory for the app
RUN groupadd app && useradd -m -g app app
# install dependencies
WORKDIR /srv
RUN pip3 install setuptools gunicorn
COPY public-dynamic/requirements.txt /srv/webinterface/requirements.txt
RUN pip3 install --no-cache-dir -r webinterface/requirements.txt
# copy all the files to the container
COPY app.py /srv
COPY public-dynamic /srv/webinterface
COPY static /srv/static
COPY templates/layout.html templates/home.html templates/objetivos.html /srv/webinterface/templates
COPY models/__init__.py models/system.py models/gtfs_work.py models/gtfs_static.py /srv/webinterface/models
RUN chown -R app:app /srv
USER app
# define the port number the container should expose
EXPOSE 8000
# run the command
ENTRYPOINT ["gunicorn"]
CMD ["-b", "0.0.0.0:8000", "app:iapp"]

View File

@ -0,0 +1,4 @@
CREATE USER docker superuser login password 'docker';
CREATE DATABASE docker;
GRANT ALL PRIVILEGES ON DATABASE docker TO docker;

View File

@ -0,0 +1,915 @@
--
-- PostgreSQL database dump
--
CREATE USER docker superuser login password 'docker';
CREATE DATABASE docker;
GRANT ALL PRIVILEGES ON DATABASE docker TO docker;
-- Dumped from database version 14.6 (Debian 14.6-1.pgdg110+1)
-- Dumped by pg_dump version 14.6 (Ubuntu 14.6-0ubuntu0.22.04.1)
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
--
-- Name: registros; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA registros;
--
-- Name: usuarios; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA usuarios;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- Name: conexiones; Type: TABLE; Schema: registros; Owner: -
--
CREATE TABLE registros.conexiones (
id bigint NOT NULL,
ipaddrid integer NOT NULL,
sesionid integer NOT NULL,
iniciada timestamp with time zone DEFAULT now(),
ultimo timestamp with time zone
);
--
-- Name: conexiones_id_seq; Type: SEQUENCE; Schema: registros; Owner: -
--
CREATE SEQUENCE registros.conexiones_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: conexiones_id_seq; Type: SEQUENCE OWNED BY; Schema: registros; Owner: -
--
ALTER SEQUENCE registros.conexiones_id_seq OWNED BY registros.conexiones.id;
--
-- Name: dispositivos; Type: TABLE; Schema: registros; Owner: -
--
CREATE TABLE registros.dispositivos (
id integer NOT NULL,
useragent character varying(1024) NOT NULL,
parsed_ua character varying(150),
dev character varying(50),
os character varying(50),
browser character varying(50)
);
--
-- Name: dispositivos_id_seq; Type: SEQUENCE; Schema: registros; Owner: -
--
CREATE SEQUENCE registros.dispositivos_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: dispositivos_id_seq; Type: SEQUENCE OWNED BY; Schema: registros; Owner: -
--
ALTER SEQUENCE registros.dispositivos_id_seq OWNED BY registros.dispositivos.id;
--
-- Name: ipaddrs; Type: TABLE; Schema: registros; Owner: -
--
CREATE TABLE registros.ipaddrs (
id integer NOT NULL,
ipaddr inet NOT NULL,
hostname character varying(100)
);
--
-- Name: ipaddrs_id_seq; Type: SEQUENCE; Schema: registros; Owner: -
--
CREATE SEQUENCE registros.ipaddrs_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: ipaddrs_id_seq; Type: SEQUENCE OWNED BY; Schema: registros; Owner: -
--
ALTER SEQUENCE registros.ipaddrs_id_seq OWNED BY registros.ipaddrs.id;
--
-- Name: registros; Type: TABLE; Schema: registros; Owner: -
--
CREATE TABLE registros.registros (
id bigint NOT NULL,
sitioid integer NOT NULL,
rutaid integer NOT NULL,
sesionid bigint NOT NULL,
ipaddrid integer NOT NULL,
tamano integer,
creado timestamp without time zone DEFAULT now()
);
--
-- Name: registros_id_seq; Type: SEQUENCE; Schema: registros; Owner: -
--
CREATE SEQUENCE registros.registros_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: registros_id_seq; Type: SEQUENCE OWNED BY; Schema: registros; Owner: -
--
ALTER SEQUENCE registros.registros_id_seq OWNED BY registros.registros.id;
--
-- Name: rutas; Type: TABLE; Schema: registros; Owner: -
--
CREATE TABLE registros.rutas (
id integer NOT NULL,
ruta character varying(100) NOT NULL
);
--
-- Name: rutas_id_seq; Type: SEQUENCE; Schema: registros; Owner: -
--
CREATE SEQUENCE registros.rutas_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: rutas_id_seq; Type: SEQUENCE OWNED BY; Schema: registros; Owner: -
--
ALTER SEQUENCE registros.rutas_id_seq OWNED BY registros.rutas.id;
--
-- Name: sesiones; Type: TABLE; Schema: registros; Owner: -
--
CREATE TABLE registros.sesiones (
id bigint NOT NULL,
identidadid bigint NOT NULL,
dispositivoid integer NOT NULL,
iniciada timestamp with time zone DEFAULT now(),
ultimo timestamp with time zone
);
--
-- Name: sesiones_id_seq; Type: SEQUENCE; Schema: registros; Owner: -
--
CREATE SEQUENCE registros.sesiones_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: sesiones_id_seq; Type: SEQUENCE OWNED BY; Schema: registros; Owner: -
--
ALTER SEQUENCE registros.sesiones_id_seq OWNED BY registros.sesiones.id;
--
-- Name: sitios; Type: TABLE; Schema: registros; Owner: -
--
CREATE TABLE registros.sitios (
id integer NOT NULL,
sitio character varying(100) NOT NULL
);
--
-- Name: sitios_id_seq; Type: SEQUENCE; Schema: registros; Owner: -
--
CREATE SEQUENCE registros.sitios_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: sitios_id_seq; Type: SEQUENCE OWNED BY; Schema: registros; Owner: -
--
ALTER SEQUENCE registros.sitios_id_seq OWNED BY registros.sitios.id;
--
-- Name: ubicaciones; Type: TABLE; Schema: registros; Owner: -
--
CREATE TABLE registros.ubicaciones (
id bigint NOT NULL,
ipaddrid integer NOT NULL,
identidadid integer NOT NULL,
descripcion character varying(200),
iniciada timestamp with time zone DEFAULT now(),
ultimo timestamp with time zone
);
--
-- Name: ubicaciones_id_seq; Type: SEQUENCE; Schema: registros; Owner: -
--
CREATE SEQUENCE registros.ubicaciones_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: ubicaciones_id_seq; Type: SEQUENCE OWNED BY; Schema: registros; Owner: -
--
ALTER SEQUENCE registros.ubicaciones_id_seq OWNED BY registros.ubicaciones.id;
--
-- Name: correos; Type: TABLE; Schema: usuarios; Owner: -
--
CREATE TABLE usuarios.correos (
id integer NOT NULL,
correo character varying(100) NOT NULL,
cuentaid bigint NOT NULL,
"default" boolean
);
--
-- Name: correos_id_seq; Type: SEQUENCE; Schema: usuarios; Owner: -
--
CREATE SEQUENCE usuarios.correos_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: correos_id_seq; Type: SEQUENCE OWNED BY; Schema: usuarios; Owner: -
--
ALTER SEQUENCE usuarios.correos_id_seq OWNED BY usuarios.correos.id;
--
-- Name: cuentas; Type: TABLE; Schema: usuarios; Owner: -
--
CREATE TABLE usuarios.cuentas (
id bigint NOT NULL,
clave character varying(60) NOT NULL,
sysadmin boolean,
ultimoacceso timestamp with time zone
);
--
-- Name: identidades; Type: TABLE; Schema: usuarios; Owner: -
--
CREATE TABLE usuarios.identidades (
id bigint NOT NULL,
login character varying(50) NOT NULL,
alias character varying(50),
creado timestamp with time zone DEFAULT now(),
modificado timestamp with time zone,
tipo character varying(20)
);
--
-- Name: identidades_id_seq; Type: SEQUENCE; Schema: usuarios; Owner: -
--
CREATE SEQUENCE usuarios.identidades_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: identidades_id_seq; Type: SEQUENCE OWNED BY; Schema: usuarios; Owner: -
--
ALTER SEQUENCE usuarios.identidades_id_seq OWNED BY usuarios.identidades.id;
--
-- Name: personas; Type: TABLE; Schema: usuarios; Owner: -
--
CREATE TABLE usuarios.personas (
id bigint NOT NULL,
nombres character varying(100) NOT NULL,
apellidop character varying(100) NOT NULL,
apellidom character varying(100),
rut character varying(20) NOT NULL,
telefono character varying(20),
foto character varying(50) NOT NULL
);
--
-- Name: conexiones id; Type: DEFAULT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.conexiones ALTER COLUMN id SET DEFAULT nextval('registros.conexiones_id_seq'::regclass);
--
-- Name: dispositivos id; Type: DEFAULT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.dispositivos ALTER COLUMN id SET DEFAULT nextval('registros.dispositivos_id_seq'::regclass);
--
-- Name: ipaddrs id; Type: DEFAULT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.ipaddrs ALTER COLUMN id SET DEFAULT nextval('registros.ipaddrs_id_seq'::regclass);
--
-- Name: registros id; Type: DEFAULT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.registros ALTER COLUMN id SET DEFAULT nextval('registros.registros_id_seq'::regclass);
--
-- Name: rutas id; Type: DEFAULT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.rutas ALTER COLUMN id SET DEFAULT nextval('registros.rutas_id_seq'::regclass);
--
-- Name: sesiones id; Type: DEFAULT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.sesiones ALTER COLUMN id SET DEFAULT nextval('registros.sesiones_id_seq'::regclass);
--
-- Name: sitios id; Type: DEFAULT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.sitios ALTER COLUMN id SET DEFAULT nextval('registros.sitios_id_seq'::regclass);
--
-- Name: ubicaciones id; Type: DEFAULT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.ubicaciones ALTER COLUMN id SET DEFAULT nextval('registros.ubicaciones_id_seq'::regclass);
--
-- Name: correos id; Type: DEFAULT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.correos ALTER COLUMN id SET DEFAULT nextval('usuarios.correos_id_seq'::regclass);
--
-- Name: identidades id; Type: DEFAULT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.identidades ALTER COLUMN id SET DEFAULT nextval('usuarios.identidades_id_seq'::regclass);
--
-- Data for Name: conexiones; Type: TABLE DATA; Schema: registros; Owner: -
--
COPY registros.conexiones (id, ipaddrid, sesionid, iniciada, ultimo) FROM stdin;
1 1 1 2023-02-27 01:18:31.243924+00 2023-02-27 01:18:31.24764+00
\.
--
-- Data for Name: dispositivos; Type: TABLE DATA; Schema: registros; Owner: -
--
COPY registros.dispositivos (id, useragent, parsed_ua, dev, os, browser) FROM stdin;
1 Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/110.0 PC / Linux / Firefox 110.0 PC Linux Firefox 110.0
\.
--
-- Data for Name: ipaddrs; Type: TABLE DATA; Schema: registros; Owner: -
--
COPY registros.ipaddrs (id, ipaddr, hostname) FROM stdin;
1 172.23.0.1 \N
\.
--
-- Data for Name: registros; Type: TABLE DATA; Schema: registros; Owner: -
--
COPY registros.registros (id, sitioid, rutaid, sesionid, ipaddrid, tamano, creado) FROM stdin;
1 1 1 1 1 0 2023-02-27 01:18:31.248091
\.
--
-- Data for Name: rutas; Type: TABLE DATA; Schema: registros; Owner: -
--
COPY registros.rutas (id, ruta) FROM stdin;
1 /favicon.ico
\.
--
-- Data for Name: sesiones; Type: TABLE DATA; Schema: registros; Owner: -
--
COPY registros.sesiones (id, identidadid, dispositivoid, iniciada, ultimo) FROM stdin;
1 2 1 2023-02-27 01:18:31.241528+00 2023-02-27 01:18:31.24764+00
\.
--
-- Data for Name: sitios; Type: TABLE DATA; Schema: registros; Owner: -
--
COPY registros.sitios (id, sitio) FROM stdin;
1 localhost:5003
\.
--
-- Data for Name: ubicaciones; Type: TABLE DATA; Schema: registros; Owner: -
--
COPY registros.ubicaciones (id, ipaddrid, identidadid, descripcion, iniciada, ultimo) FROM stdin;
\.
--
-- Data for Name: correos; Type: TABLE DATA; Schema: usuarios; Owner: -
--
COPY usuarios.correos (id, correo, cuentaid, "default") FROM stdin;
\.
--
-- Data for Name: cuentas; Type: TABLE DATA; Schema: usuarios; Owner: -
--
COPY usuarios.cuentas (id, clave, sysadmin, ultimoacceso) FROM stdin;
4 $2b$12$Xa.mKurU3IVc54Pirzs9ReAjI4AivIFu/EGUelx59/xorVN1Jkb5i f \N
5 $2b$12$2f6Zjld4.4lMz77r9gncNe4O2nDNVs1lV0CkpbRDOW3b8876h3wyC f \N
\.
--
-- Data for Name: identidades; Type: TABLE DATA; Schema: usuarios; Owner: -
--
COPY usuarios.identidades (id, login, alias, creado, modificado, tipo) FROM stdin;
1 Internet \N 2023-02-27 01:18:31.225317+00 \N Identidad
2 Intranet \N 2023-02-27 01:18:31.225317+00 \N Identidad
3 Buscador \N 2023-02-27 01:18:31.225317+00 \N Identidad
4 ifiguero \N 2023-02-27 01:18:31.225317+00 \N Persona
5 tpmc \N 2023-02-27 01:18:31.225317+00 \N Persona
\.
--
-- Data for Name: personas; Type: TABLE DATA; Schema: usuarios; Owner: -
--
COPY usuarios.personas (id, nombres, apellidop, apellidom, rut, telefono, foto) FROM stdin;
4 Israel Figueroa \N 13955977-0 \N default.jpg
5 Transporte Público \N tpmc \N default.jpg
\.
--
-- Name: conexiones_id_seq; Type: SEQUENCE SET; Schema: registros; Owner: -
--
SELECT pg_catalog.setval('registros.conexiones_id_seq', 1, true);
--
-- Name: dispositivos_id_seq; Type: SEQUENCE SET; Schema: registros; Owner: -
--
SELECT pg_catalog.setval('registros.dispositivos_id_seq', 1, true);
--
-- Name: ipaddrs_id_seq; Type: SEQUENCE SET; Schema: registros; Owner: -
--
SELECT pg_catalog.setval('registros.ipaddrs_id_seq', 1, true);
--
-- Name: registros_id_seq; Type: SEQUENCE SET; Schema: registros; Owner: -
--
SELECT pg_catalog.setval('registros.registros_id_seq', 1, true);
--
-- Name: rutas_id_seq; Type: SEQUENCE SET; Schema: registros; Owner: -
--
SELECT pg_catalog.setval('registros.rutas_id_seq', 1, true);
--
-- Name: sesiones_id_seq; Type: SEQUENCE SET; Schema: registros; Owner: -
--
SELECT pg_catalog.setval('registros.sesiones_id_seq', 1, true);
--
-- Name: sitios_id_seq; Type: SEQUENCE SET; Schema: registros; Owner: -
--
SELECT pg_catalog.setval('registros.sitios_id_seq', 1, true);
--
-- Name: ubicaciones_id_seq; Type: SEQUENCE SET; Schema: registros; Owner: -
--
SELECT pg_catalog.setval('registros.ubicaciones_id_seq', 1, false);
--
-- Name: correos_id_seq; Type: SEQUENCE SET; Schema: usuarios; Owner: -
--
SELECT pg_catalog.setval('usuarios.correos_id_seq', 1, false);
--
-- Name: identidades_id_seq; Type: SEQUENCE SET; Schema: usuarios; Owner: -
--
SELECT pg_catalog.setval('usuarios.identidades_id_seq', 5, true);
--
-- Name: ubicaciones _una_ip_identidad_uc; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.ubicaciones
ADD CONSTRAINT _una_ip_identidad_uc UNIQUE (ipaddrid, identidadid);
--
-- Name: conexiones _una_ip_sesion_uc; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.conexiones
ADD CONSTRAINT _una_ip_sesion_uc UNIQUE (ipaddrid, sesionid);
--
-- Name: conexiones conexiones_pkey; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.conexiones
ADD CONSTRAINT conexiones_pkey PRIMARY KEY (id);
--
-- Name: dispositivos dispositivos_pkey; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.dispositivos
ADD CONSTRAINT dispositivos_pkey PRIMARY KEY (id);
--
-- Name: dispositivos dispositivos_useragent_key; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.dispositivos
ADD CONSTRAINT dispositivos_useragent_key UNIQUE (useragent);
--
-- Name: ipaddrs ipaddrs_ipaddr_key; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.ipaddrs
ADD CONSTRAINT ipaddrs_ipaddr_key UNIQUE (ipaddr);
--
-- Name: ipaddrs ipaddrs_pkey; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.ipaddrs
ADD CONSTRAINT ipaddrs_pkey PRIMARY KEY (id);
--
-- Name: registros registros_pkey; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.registros
ADD CONSTRAINT registros_pkey PRIMARY KEY (id);
--
-- Name: rutas rutas_pkey; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.rutas
ADD CONSTRAINT rutas_pkey PRIMARY KEY (id);
--
-- Name: rutas rutas_ruta_key; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.rutas
ADD CONSTRAINT rutas_ruta_key UNIQUE (ruta);
--
-- Name: sesiones sesiones_pkey; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.sesiones
ADD CONSTRAINT sesiones_pkey PRIMARY KEY (id);
--
-- Name: sitios sitios_pkey; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.sitios
ADD CONSTRAINT sitios_pkey PRIMARY KEY (id);
--
-- Name: sitios sitios_sitio_key; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.sitios
ADD CONSTRAINT sitios_sitio_key UNIQUE (sitio);
--
-- Name: ubicaciones ubicaciones_pkey; Type: CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.ubicaciones
ADD CONSTRAINT ubicaciones_pkey PRIMARY KEY (id);
--
-- Name: correos correos_correo_key; Type: CONSTRAINT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.correos
ADD CONSTRAINT correos_correo_key UNIQUE (correo);
--
-- Name: correos correos_pkey; Type: CONSTRAINT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.correos
ADD CONSTRAINT correos_pkey PRIMARY KEY (id);
--
-- Name: cuentas cuentas_pkey; Type: CONSTRAINT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.cuentas
ADD CONSTRAINT cuentas_pkey PRIMARY KEY (id);
--
-- Name: identidades identidades_login_key; Type: CONSTRAINT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.identidades
ADD CONSTRAINT identidades_login_key UNIQUE (login);
--
-- Name: identidades identidades_pkey; Type: CONSTRAINT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.identidades
ADD CONSTRAINT identidades_pkey PRIMARY KEY (id);
--
-- Name: personas personas_pkey; Type: CONSTRAINT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.personas
ADD CONSTRAINT personas_pkey PRIMARY KEY (id);
--
-- Name: personas personas_rut_key; Type: CONSTRAINT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.personas
ADD CONSTRAINT personas_rut_key UNIQUE (rut);
--
-- Name: conexiones conexiones_ipaddrid_fkey; Type: FK CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.conexiones
ADD CONSTRAINT conexiones_ipaddrid_fkey FOREIGN KEY (ipaddrid) REFERENCES registros.ipaddrs(id);
--
-- Name: conexiones conexiones_sesionid_fkey; Type: FK CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.conexiones
ADD CONSTRAINT conexiones_sesionid_fkey FOREIGN KEY (sesionid) REFERENCES registros.sesiones(id);
--
-- Name: registros registros_ipaddrid_fkey; Type: FK CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.registros
ADD CONSTRAINT registros_ipaddrid_fkey FOREIGN KEY (ipaddrid) REFERENCES registros.ipaddrs(id);
--
-- Name: registros registros_rutaid_fkey; Type: FK CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.registros
ADD CONSTRAINT registros_rutaid_fkey FOREIGN KEY (rutaid) REFERENCES registros.rutas(id);
--
-- Name: registros registros_sesionid_fkey; Type: FK CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.registros
ADD CONSTRAINT registros_sesionid_fkey FOREIGN KEY (sesionid) REFERENCES registros.sesiones(id);
--
-- Name: registros registros_sitioid_fkey; Type: FK CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.registros
ADD CONSTRAINT registros_sitioid_fkey FOREIGN KEY (sitioid) REFERENCES registros.sitios(id);
--
-- Name: sesiones sesiones_dispositivoid_fkey; Type: FK CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.sesiones
ADD CONSTRAINT sesiones_dispositivoid_fkey FOREIGN KEY (dispositivoid) REFERENCES registros.dispositivos(id);
--
-- Name: sesiones sesiones_identidadid_fkey; Type: FK CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.sesiones
ADD CONSTRAINT sesiones_identidadid_fkey FOREIGN KEY (identidadid) REFERENCES usuarios.identidades(id);
--
-- Name: ubicaciones ubicaciones_identidadid_fkey; Type: FK CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.ubicaciones
ADD CONSTRAINT ubicaciones_identidadid_fkey FOREIGN KEY (identidadid) REFERENCES usuarios.identidades(id);
--
-- Name: ubicaciones ubicaciones_ipaddrid_fkey; Type: FK CONSTRAINT; Schema: registros; Owner: -
--
ALTER TABLE ONLY registros.ubicaciones
ADD CONSTRAINT ubicaciones_ipaddrid_fkey FOREIGN KEY (ipaddrid) REFERENCES registros.ipaddrs(id);
--
-- Name: correos correos_cuentaid_fkey; Type: FK CONSTRAINT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.correos
ADD CONSTRAINT correos_cuentaid_fkey FOREIGN KEY (cuentaid) REFERENCES usuarios.cuentas(id);
--
-- Name: cuentas cuentas_id_fkey; Type: FK CONSTRAINT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.cuentas
ADD CONSTRAINT cuentas_id_fkey FOREIGN KEY (id) REFERENCES usuarios.identidades(id);
--
-- Name: personas personas_id_fkey; Type: FK CONSTRAINT; Schema: usuarios; Owner: -
--
ALTER TABLE ONLY usuarios.personas
ADD CONSTRAINT personas_id_fkey FOREIGN KEY (id) REFERENCES usuarios.cuentas(id);
--
-- PostgreSQL database dump complete
--

6
app.py 100644
View File

@ -0,0 +1,6 @@
from webinterface import create_app
iapp = create_app()
if __name__ == '__main__':
iapp.run()

View File

View File

@ -0,0 +1,6 @@
import logging
log = logging.getLogger()
logging.getLogger("carga_gtfs").setLevel(
logging.INFO
) # Prevent debug messages from peewee lib

View File

View File

@ -0,0 +1,147 @@
import os
import io
import csv
import traceback
from datetime import datetime
def parse_time(strinput):
try:
h, m, s = strinput.split(":")
h = int(h)
d = 0
while h > 23:
d += 1
h -= 24
return datetime.strptime("{}:{}:{}".format(h,m,s), '%H:%M:%S').time(), d
except:
current_app.logger.debug('Traceback {}'.format(traceback.format_exc()))
current_app.logger.warning("Hora incorrecta => {}".format(strinput))
return datetime.strptime("01:01:01", '%H:%M:%S').time(), 0
def parse_paraderoid(strinput):
primerodenero2022 = 44562
if strinput.find("-") > 0:
current_app.logger.error("Error en el dato de la parada: {}".format(strinput))
try:
whatdate= datetime.strptime('{}-{}'.format(strinput, 2022), '%d-%b-%Y').date()
idparadero = primerodenero2022 + whatdate.timetuple().tm_yday
current_app.logger.warning("Calculado el Identificador en base al año 2022 => {}".format(idparadero))
return idparadero
except:
return 0
else:
return int(strinput)
def completa_dataset():
iparaderos = 0
for paradero in Paradero.query.all():
iparaderos += 1
cparadero = 0
lparadas = 0
sparadas = 0
dparadas = 0
lineas = []
lparada = []
for parada in Parada.query.filter(Parada.paraderoid==paradero.id).all():
cparadero += 1
if cparadero % 1000 == 0:
current_app.logger.debug("Paradero {}, {} detenciones. {} lineas".format(iparaderos, cparadero, len(lineas)))
elemento = (parada.servicio.lineaid, parada.servicio.direccionid)
if parada.servicio.dotw == 'S':
sparadas += 1
elif parada.servicio.dotw == 'D':
dparadas += 1
else:
lparadas += 1
if elemento in lineas:
continue
lineas.append(elemento)
if parada.servicio.direccionid:
lparada.append("{} ({} ida)".format(parada.servicio.linea.codigo, parada.secuencia))
else:
lparada.append("{} ({} regreso)".format(parada.servicio.linea.codigo, parada.secuencia))
lparada.sort()
paradero.recorridos = " - ".join(lparada)
paradero.paradas = 5*lparadas+sparadas+dparadas
paradero.resumen = "{} paradas a la semana, {} lineas".format(paradero.paradas, len(lineas))
paradero.paradasl = lparadas
paradero.paradass = sparadas
paradero.paradasd = dparadas
paradero.lineas = len(lineas)
current_app.logger.info("Paradero {}, {} detenciones a la semana en {} lineas".format(iparaderos, paradero.paradas, len(lineas)))
if iparaderos % 10 == 0:
db.session.commit()
db.session.commit()
def agrega_paraderos():
from bs4 import BeautifulSoup
from zipfile import ZipFile
# print(os.getcwd())
current_app.logger.info("Archivo de paraderos, kmz")
with ZipFile('webinterface/models/datos/PARADEROS_FORMALES.kmz', 'r') as kmz:
kml = kmz.open(kmz.filelist[0].filename, 'r').read()
soup = BeautifulSoup(kml, 'xml')
existen = 0
nuevo = 0
for paradero in soup.find_all("kml:Placemark"):
if (existen+nuevo) % 1000 == 0:
current_app.logger.debug("Leidos {} paraderos existentes y {} nuevos".format(existen, nuevo))
lat = int(paradero.find("kml:SimpleData", {"name": "LATITUDE"}).text)/1000000
lng = int(paradero.find("kml:SimpleData", {"name": "LONGITUDE"}).text)/1000000
codigo = paradero.find("kml:SimpleData", {"name": "CODIGO_INT"}).text
comuna = paradero.find("kml:SimpleData", {"name": "COMUNA"}).text
recorridos = paradero.find("kml:SimpleData", {"name": "RECORRIDOS"}).text
tamano = paradero.find("kml:SimpleData", {"name": "SUPERFICIE"}).text
capacidad = paradero.find("kml:SimpleData", {"name": "CAPACIDAD"}).text
materialpiso = paradero.find("kml:SimpleData", {"name": "RADIER_MAT"}).text
materialpared = paradero.find("kml:SimpleData", {"name": "PARED_POS2"}).text
materialtecho = paradero.find("kml:SimpleData", {"name": "TECHUMBRE2"}).text
match = None
for radio in [0.00000001, 0.00000002, 0.00000003, 0.00000004, 0.00000005, 0.000000075, 0.0000001, 0.0000002]:
match = Paradero.query.filter(((Paradero.latitud-lat)*(Paradero.latitud-lat)+(Paradero.longitud-lng)*(Paradero.longitud-lng)) < radio).first()
if match is not None:
break
if match is None:
nuevo += 1
nombre = "{} cerca {}".format(paradero.find("kml:SimpleData", {"name": "NOMBRE_DE_"}).text, paradero.find("kml:SimpleData", {"name": "CALLE_CERC"}).text)
match = Paradero(id=codigo, codigo=codigo, nombre=nombre, latitud=lat, longitud=lng)
db.session.add(match)
db.session.commit()
else:
existen += 1
match.codigo = codigo
match.comuna = comuna
match.recorridos2 = recorridos
match.tamano = tamano
match.capacidad = capacidad
match.techo = materialtecho
match.pared = materialpared
match.piso = materialpiso
db.session.commit()
current_app.logger.info("Cargados {} paraderos existentes y {} nuevos".format(existen, nuevo))

View File

@ -0,0 +1,172 @@
from sqlalchemy.sql import func
from sqlalchemy.dialects import postgresql
from webinterface import db
from sqlalchemy.ext.declarative import declarative_base
Model = declarative_base()
engine = create_engine(os.environ.get('SQLALCHEMY_GTFSDB_URI'), echo=False)
Session = sessionmaker(bind=engine, autocommit=False, autoflush=True)
session = Session()
class sArchivosGTFS(Model):
__tablename__ = 'gtfs'
__bind_key__ = 'gtfs_static'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
descripcion = Column(String)
file = Column(String)
hash = Column(String)
size = Column(Integer)
loaded = Column(Integer, default=0)
ts = Column(Datetime, default=func.now())
#agency_id,agency_name,agency_url,agency_timezone,agency_lang,agency_phone,agency_fare_url
#DTPR,División de Transporte Público Regional,http://www.dtpr.gob.cl/,America/Santiago,es,+562 2421 3580,
class sAgencia(Model):
__tablename__ = 'agency'
__bind_key__ = 'gtfs_static'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
agency_id = Column(String)
agency_name = Column(String)
agency_url = Column(String)
agency_timezone = Column(String)
agency_lang = Column(String)
agency_phone = Column(String)
agency_fare_url = Column(String)
#service_id,start_date,end_date,monday,tuesday,wednesday,thursday,friday,saturday,sunday
#S,20210101,20311231,0,0,0,0,0,1,0
#D,20210101,20311231,0,0,0,0,0,0,1
#L,20210101,20311231,1,1,1,1,1,0,0
class sCalendario(Model):
__tablename__ = 'calendar'
__bind_key__ = 'gtfs_static'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
service_id = Column(String)
start_date = Column(String)
end_date = Column(String)
monday = Column(Integer)
tuesday = Column(Integer)
wednesday = Column(Integer)
thursday = Column(Integer)
friday = Column(Integer)
saturday = Column(Integer)
sunday = Column(Integer)
#feed_publisher_name,feed_publisher_url,feed_lang,feed_start_date,feed_end_date,feed_version
#División de Transporte Público Regional,http://www.dtpr.gob.cl/,es,20210101,20311231,Gran Concepción20220616
class sFeedInfo(Model):
__tablename__ = 'feed_info'
__bind_key__ = 'gtfs_static'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
feed_publisher_name = Column(String)
feed_publisher_url = Column(String)
feed_lang = Column(String)
feed_start_date = Column(String)
feed_end_date = Column(String)
feed_version = Column(String)
#route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
#625,DTPR,70KH,Nonguén - Parque Tumbes,,3,,0d7215,ffffff
#600,DTPR,41CR,Parque Empresarial Biobío - Terminal Collao,,3,,ad0101,ffffff
class sRuta(Model):
__tablename__ = 'routes'
__bind_key__ = 'gtfs_static'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
route_id = Column(String)
agency_id = Column(String)
route_short_name = Column(String)
route_long_name = Column(String)
route_desc = Column(String)
route_type = Column(Integer)
route_url = Column(String)
route_color = Column(String)
route_text_color = Column(String)
#shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled
#1136979693,-36.843,-73.00984,1
#1136979693,-36.843,-73.00984,2
class sFormas(Model):
__tablename__ = 'shapes'
__bind_key__ = 'gtfs_static'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
shape_id = Column(String)
shape_pt_lat = Column(Double)
shape_pt_lon = Column(Double)
shape_pt_sequence = Column(Integer)
shape_dist_traveled = Column(Double)
#stop_id,stop_code,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,location_type,parent_station,wheelchair_boarding
#40921,,Pque Zoologico Concepcion Poniente,,-36.8400453,-73.00696914,,,,,
#40808,,Cno. Nonguen esq Las Vertientes,,-36.83675878,-73.00343935,,,,,
class sParadas(Model):
__tablename__ = 'stops'
__bind_key__ = 'gtfs_static'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
stop_id = Column(String)
stop_code = Column(String)
stop_name = Column(String)
stop_desc = Column(String)
stop_lat = Column(Double)
stop_lon = Column(Double)
zone_id = Column(String)
stop_url = Column(String)
location_type = Column(Integer)
parent_station = Column(String)
wheelchair_boarding = Column(Integer)
#trip_id,arrival_time,departure_time,stop_id,stop_sequence, timepoint,shape_dist_traveled
#trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,timepoint
#c8b17d5f-4-76aabf89-b,05:01:00,05:01:00,40439,1,,,,1
#c8b17d5f-4-76aabf89-b,05:02:00,05:02:00,40440,2,,,,0
class sDetenciones(Model):
__tablename__ = 'stop_times'
__bind_key__ = 'gtfs_static'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
trip_id = Column(String)
arrival_time = Column(String)
departure_time = Column(String)
stop_id = Column(String)
stop_sequence = Column(Integer)
stop_headsign = Column(String)
pickup_type = Column(Integer)
drop_off_type = Column(Integer)
timepoint = Column(Integer)
#,,,,,,
#route_id,service_id,trip_id,trip_headsign, direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed
#route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed
#625,S,c8b17d5f-4-76aabf89-b,Nonguén,,1,,1136979694,,
#625,S,4d018d35-7-76aabf89-b,Parque Tumbes,,0,,1136979693,,
#ac48288b-1-56195eaf-8,b1910f5b,1,0,1168259538,,0,0,1-1,1
#d9e94be9-f-56195eaf-8,b1910f5b,1,0,1168259538,,0,0,1-1,2
class sServicio(Model):
__tablename__ = 'trips'
__bind_key__ = 'gtfs_static'
id = Column(String, primary_key=True, nullable=False)
route_id = Column(String) # Ruta.rutaid
service_id = Column(String)
trip_id = Column(String)
trip_headsign = Column(String)
trip_short_name = Column(String)
direction_id = Column(Integer)
block_id = Column(String)
shape_id = Column(String)
wheelchair_accessible = Column(Integer)
bikes_allowed = Column(Integer)
# 4cc08782-c-76aabf89-b

View File

@ -0,0 +1,176 @@
from sqlalchemy.sql import func
from sqlalchemy.dialects import postgresql
from sqlalchemy import Integer, BigInteger, Double, Float, String, Time, Column, ForeignKey, UniqueConstraint
Model = declarative_base()
engine = create_engine(os.environ.get('SQLALCHEMY_HOTDB_URI'), echo=False)
Session = sessionmaker(bind=engine, autocommit=False, autoflush=True)
session = Session()
class ArchivosGTFS(Model):
__tablename__ = 'archivos'
__bind_key__ = 'gtfs_work'
id = Column(Integer, primary_key=True, nullable=False)
analisis = Column(Integer, default=0)
terminado = Column(Integer, default=0)
activo = Column(Integer, default=0)
class Agencia(Model):
__tablename__ = 'agency'
__bind_key__ = 'gtfs_work'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
agency_id = Column(String, unique=True)
agency_name = Column(String)
agency_url = Column(String)
agency_timezone = Column(String)
agency_lang = Column(String)
agency_phone = Column(String)
agency_fare_url = Column(String)
class Calendario(Model):
__tablename__ = 'calendars'
__bind_key__ = 'gtfs_work'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
label = Column(String, unique=True)
monday = Column(Integer, default=0)
tuesday = Column(Integer, default=0)
wednesday = Column(Integer, default=0)
thursday = Column(Integer, default=0)
friday = Column(Integer, default=0)
saturday = Column(Integer, default=0)
sunday = Column(Integer, default=0)
#shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled
#1136979693,-36.843,-73.00984,1
#1136979693,-36.843,-73.00984,2
class Formas(Model):
__tablename__ = 'shapes'
__bind_key__ = 'gtfs_work'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
shape_1pt_lat = Column(Double, nullable=False)
shape_1pt_lon = Column(Double, nullable=False)
shape_num_pt = Column(Integer, nullable=False)
shape_length = Column(Double)
class Segmentos(Model):
__tablename__ = 'shapes_point'
__bind_key__ = 'gtfs_work'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
pt_start_lat = Column(Double, nullable=False)
pt_start_lon = Column(Double, nullable=False)
pt_end_lat = Column(Double, nullable=False)
pt_end_lon = Column(Double, nullable=False)
shape_pt_sequence = Column(Integer, nullable=False)
shape_slope = Column(Double)
shape_dist_traveled = Column(Double)
shape_id = Column(Integer, ForeignKey('shapes.id'), nullable=False)
__table_args__ = (UniqueConstraint('shape_id', 'shape_pt_sequence', name='_shapeid_sequence_uc'), )
class Comunas(Model):
__tablename__ = 'comuna'
__bind_key__ = 'gtfs_work'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
name = Column(String, unique=True)
#stop_id,stop_code,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,location_type,parent_station,wheelchair_boarding
#40921,,Pque Zoologico Concepcion Poniente,,-36.8400453,-73.00696914,,,,,
#40808,,Cno. Nonguen esq Las Vertientes,,-36.83675878,-73.00343935,,,,,
class Paradas(Model):
__tablename__ = 'stops'
__bind_key__ = 'gtfs_work'
id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True)
stop_code = Column(String, unique=True, nullable=True)
stop_name = Column(String)
stop_desc = Column(String)
stop_lat = Column(Double, nullable=False)
stop_lon = Column(Double, nullable=False)
comuna_id = Column(Integer, ForeignKey('comuna.id'), nullable=False)
stop_url = Column(String)
parent_station = Column(BigInteger)
wheelchair_boarding = Column(Integer, default=0)
#route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
#625,DTPR,70KH,Nonguén - Parque Tumbes,,3,,0d7215,ffffff
#600,DTPR,41CR,Parque Empresarial Biobío - Terminal Collao,,3,,ad0101,ffffff
class Lineas(Model):
__tablename__ = 'routes'
__bind_key__ = 'gtfs_work'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
agency_id = Column(Integer, ForeignKey('agency.id'), nullable=False)
route_short_name = Column(String)
route_long_name = Column(String)
route_desc = Column(String)
route_url = Column(String)
route_color = Column(Integer, default=0)
route_text_color = Column(Integer, default=0)
#route_id,service_id,trip_id,trip_headsign, direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed
#route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed
#625,S,c8b17d5f-4-76aabf89-b,Nonguén,,1,,1136979694,,
#625,S,4d018d35-7-76aabf89-b,Parque Tumbes,,0,,1136979693,,
#ac48288b-1-56195eaf-8,b1910f5b,1,0,1168259538,,0,0,1-1,1
#d9e94be9-f-56195eaf-8,b1910f5b,1,0,1168259538,,0,0,1-1,2
class Servicio(Model):
__tablename__ = 'trips'
__bind_key__ = 'gtfs_work'
id = Column(Integer, primary_key=True, nullable=False)
route_id = Column(Integer, ForeignKey('routes.id'), nullable=False) # A que linea corresponde
calendar_id = Column(Integer, ForeignKey('calendars.id'), nullable=False)
gtfs_id = Column(Integer, ForeignKey('archivos.id'), nullable=False) # Identifiador unico del servicio
shape_id = Column(Integer, ForeignKey('shapes.id'), nullable=False) # shapes.id
direction_id = Column(Integer, nullable=False) # 0=ida, 1=vuelta
trip_headsign = Column(String)
trip_short_name = Column(String)
wheelchair_accessible = Column(Integer, default=0)
bikes_allowed = Column(Integer, default=0)
#trip_id,arrival_time,departure_time,stop_id,stop_sequence, timepoint,shape_dist_traveled
#trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,timepoint
#c8b17d5f-4-76aabf89-b,05:01:00,05:01:00,40439,1,,,,1
#c8b17d5f-4-76aabf89-b,05:02:00,05:02:00,40440,2,,,,0
class Detenciones(Model):
__tablename__ = 'stop_times'
__bind_key__ = 'gtfs_work'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
trip_id = Column(Integer, ForeignKey('trips.id'), nullable=False)
stop_sequence = Column(Integer, nullable=False)
stop_id = Column(BigInteger, ForeignKey('stops.id'), nullable=False)
departure_time = Column(String(9), index=True)
arrival_time = Column(String(9))
dist_traveled = Column(Integer, default=0)
stop_headsign = Column(String)
pickup_type = Column(Integer, default=0)
drop_off_type = Column(Integer, default=0)
timepoint = Column(Integer, default=1)
__table_args__ = (UniqueConstraint('trip_id', 'stop_sequence', name='_trip_sequence_uc'), )
# 4cc08782-c-76aabf89-b

View File

View File

@ -0,0 +1,49 @@
import time
from daemon import log
from daemon.models.gtfs_work import session as dbhot
from daemon.models.gtfs_static import session as dbstatic
if not os.environ.get('HEARTBEAT'):
hb = 60 * 15 # 15 minutos
else:
hb = int(os.environ.get('HEARTBEAT'))
def main_loop(shouldIrun):
log.info("Iniciando el loop principal")
doki=int(time.time()) + hb
while shouldIrun:
if check_for_work():
ingest_waiting_gtfs()
time.sleep(1)
i = int(time.time())
if i >= doki:
doki = i + hb
log.info('Heartbeat')
def check_for_work():
result = dbstatic.query(sArchivosGTFS).filter(sArchivosGTFS.loaded==0).first()
if result is not None:
return True
return False
def ingest_waiting_gtfs():
pass
def load_dataset(nombrearchivo):
import zipfile
import tempfile
with tempfile.TemporaryDirectory() as tmpdirname:
with zipfile.ZipFile(nombrearchivo, 'r') as zip_ref:
zip_ref.extractall(tmpdirname)

22
daemon.py 100644
View File

@ -0,0 +1,22 @@
from daemon.service import main_loop
from daemon.log import log
import os
import time
import signal
shouldIrun = True
def main():
def programCleanup(_signo, _stack_frame):
log.info('Recibida la señal de salida')
shouldIrun = False
signal.signal(signal.SIGTERM, programCleanup)
run(shouldIrun)
log.info('Terminando Correctamente')
if __name__ == '__main__':
main()

View File

@ -0,0 +1,155 @@
version: '3.2'
services:
## Bases de datos
static_gtfs:
image: docker.ilab.cl/tpmc_static_gtfs:devel
restart: "no"
environment:
- DEBUG=False
- POSTGRES_PASSWORD=docker
ports:
- target: 5432
published: 5401
deploy:
mode: replicated
replicas: 1
work_gtfs:
image: docker.ilab.cl/tpmc_work_gtfs:devel
restart: "no"
volumes:
- pgdata_hot_gtfs:/var/lib/postgresql/data
environment:
- DEBUG=False
- POSTGRES_PASSWORD=docker
ports:
- target: 5432
published: 5402
deploy:
mode: replicated
replicas: 1
system_db:
image: docker.ilab.cl/tpmc_system_db:devel
restart: "no"
volumes:
- pgdata_web:/var/lib/postgresql/data
environment:
- DEBUG=False
- POSTGRES_PASSWORD=docker
ports:
- target: 5432
published: 5403
deploy:
mode: replicated
replicas: 1
## Contenedor de sitios estáticos
front-static:
image: docker.ilab.cl/tpmc_front_static:devel
restart: "no"
environment:
- DEBUG=False
- SECRET_KEY=4d6f45a5fc12445dbac2f59c3b6c7cb2
ports:
- target: 8000
published: 5001
deploy:
mode: replicated
replicas: 1
## Contenedor del proceso de inicio de session
login-logic:
image: docker.ilab.cl/tpmc_login_logic:devel
restart: "no"
depends_on:
- system_db
- work_gtfs
- static_gtfs
environment:
- DEBUG=False
- SECRET_KEY=4d6f45a5fc12445dbac2f59c3b6c7cb2
- COOKIE_DOMAIN=tpmc.ilab.cl
- SQLALCHEMY_WEBDB_URI=postgresql://docker:docker@system_db/docker
- SQLALCHEMY_HOTDB_URI=postgresql://docker:docker@work_gtfs/docker
- SQLALCHEMY_GTFSDB_URI=postgresql://docker:docker@static_gtfs/docker
ports:
- target: 8000
published: 5002
deploy:
mode: replicated
replicas: 1
## Contenedor de contenido dinamico
public-dynamic:
image: docker.ilab.cl/tpmc_public_dynamic:devel
restart: "no"
depends_on:
- system_db
- work_gtfs
- static_gtfs
volumes:
- static_gtfs_volume:/srv/webinterface/static/gtfs_static
environment:
- DEBUG=False
- SECRET_KEY=4d6f45a5fc12445dbac2f59c3b6c7cb2
- SQLALCHEMY_WEBDB_URI=postgresql://docker:docker@system_db/docker
- SQLALCHEMY_HOTDB_URI=postgresql://docker:docker@work_gtfs/docker
- SQLALCHEMY_GTFSDB_URI=postgresql://docker:docker@static_gtfs/docker
- COOKIE_DOMAIN=tpmc.ilab.cl
ports:
- target: 8000
published: 5003
deploy:
mode: replicated
replicas: 1
## Contenedor de contenido dinamico
private-app:
image: docker.ilab.cl/tpmc_private_app:devel
restart: "no"
depends_on:
- system_db
- work_gtfs
- static_gtfs
volumes:
- static_gtfs_volume:/srv/webinterface/static/gtfs_static
environment:
- DEBUG=False
- SECRET_KEY=4d6f45a5fc12445dbac2f59c3b6c7cb2
- SQLALCHEMY_WEBDB_URI=postgresql://docker:docker@system_db/docker
- SQLALCHEMY_HOTDB_URI=postgresql://docker:docker@work_gtfs/docker
- SQLALCHEMY_GTFSDB_URI=postgresql://docker:docker@static_gtfs/docker
- COOKIE_DOMAIN=tpmc.ilab.cl
ports:
- target: 8000
published: 5004
deploy:
mode: replicated
replicas: 1
## Carga GTFS en la DB
load-gtfs:
image: docker.ilab.cl/tpmc_load_gtfs:devel
restart: "no"
depends_on:
- work_gtfs
- static_gtfs
volumes:
- static_gtfs_volume:/srv/webinterface/static/gtfs_static
environment:
- DEBUG=False
- SQLALCHEMY_HOTDB_URI=postgresql://docker:docker@work_gtfs/docker
- SQLALCHEMY_GTFSDB_URI=postgresql://docker:docker@static_gtfs/docker
deploy:
mode: replicated
replicas: 1
volumes:
static_gtfs_volume:
external: true
pgdata_web:
external: true
pgdata_hot_gtfs:
external: true

150
docker-compose.yml 100644
View File

@ -0,0 +1,150 @@
version: '3.2'
services:
## Bases de datos
static_gtfs:
build:
dockerfile: Docker/Dockerfile.db_base
context: .
image: docker.ilab.cl/tpmc_static_gtfs:devel
restart: "no"
environment:
- DEBUG=False
- POSTGRES_PASSWORD=docker
ports:
- 5401:5432
work_gtfs:
build:
dockerfile: Docker/Dockerfile.db_base
context: .
image: docker.ilab.cl/tpmc_work_gtfs:devel
restart: "no"
volumes:
- pgdata_hot_gtfs:/var/lib/postgresql/data
environment:
- DEBUG=False
- POSTGRES_PASSWORD=docker
ports:
- 5402:5432
system_db:
build:
dockerfile: Docker/Dockerfile.db_web
context: .
image: docker.ilab.cl/tpmc_system_db:devel
restart: "no"
volumes:
- pgdata_web:/var/lib/postgresql/data
environment:
- DEBUG=False
- POSTGRES_PASSWORD=docker
ports:
- 5403:5432
## Contenedor de sitios estáticos
front-static:
build:
dockerfile: Docker/Dockerfile.front_static
context: .
image: docker.ilab.cl/tpmc_front_static:devel
restart: "no"
environment:
- DEBUG=False
- SECRET_KEY=4d6f45a5fc12445dbac2f59c3b6c7cb2
ports:
- 5001:8000
## Contenedor del proceso de inicio de session
login-logic:
build:
dockerfile: Docker/Dockerfile.login_system
context: .
image: docker.ilab.cl/tpmc_login_logic:devel
restart: "no"
depends_on:
- system_db
- work_gtfs
- static_gtfs
environment:
- DEBUG=False
- SECRET_KEY=4d6f45a5fc12445dbac2f59c3b6c7cb2
- COOKIE_DOMAIN=tpmc.ilab.cl
- SQLALCHEMY_WEBDB_URI=postgresql://docker:docker@system_db/docker
- SQLALCHEMY_HOTDB_URI=postgresql://docker:docker@work_gtfs/docker
- SQLALCHEMY_GTFSDB_URI=postgresql://docker:docker@static_gtfs/docker
ports:
- 5002:8000
## Contenedor de contenido dinamico
public-dynamic:
build:
dockerfile: Docker/Dockerfile.public_dynamic
context: .
image: docker.ilab.cl/tpmc_public_dynamic:devel
restart: "no"
depends_on:
- system_db
- work_gtfs
- static_gtfs
volumes:
- static_gtfs_volume:/srv/webinterface/static/gtfs_static
environment:
- DEBUG=False
- SECRET_KEY=4d6f45a5fc12445dbac2f59c3b6c7cb2
- SQLALCHEMY_WEBDB_URI=postgresql://docker:docker@system_db/docker
- SQLALCHEMY_HOTDB_URI=postgresql://docker:docker@work_gtfs/docker
- SQLALCHEMY_GTFSDB_URI=postgresql://docker:docker@static_gtfs/docker
- COOKIE_DOMAIN=tpmc.ilab.cl
ports:
- 5003:8000
## Contenedor de contenido dinamico
private-app:
build:
dockerfile: Docker/Dockerfile.private_dynamic
context: .
image: docker.ilab.cl/tpmc_private_app:devel
restart: "no"
depends_on:
- system_db
- work_gtfs
- static_gtfs
volumes:
- static_gtfs_volume:/srv/webservice/static/gtfs_static
environment:
- DEBUG=False
- SECRET_KEY=4d6f45a5fc12445dbac2f59c3b6c7cb2
- SQLALCHEMY_WEBDB_URI=postgresql://docker:docker@system_db/docker
- SQLALCHEMY_HOTDB_URI=postgresql://docker:docker@work_gtfs/docker
- SQLALCHEMY_GTFSDB_URI=postgresql://docker:docker@static_gtfs/docker
- COOKIE_DOMAIN=tpmc.ilab.cl
ports:
- 5004:8000
## Carga GTFS en la DB
load-gtfs:
build:
dockerfile: Docker/Dockerfile.load_gtfs
context: .
image: docker.ilab.cl/tpmc_load_gtfs:devel
restart: "no"
depends_on:
- work_gtfs
- static_gtfs
volumes:
- static_gtfs_volume:/srv/webinterface/static/gtfs_static
environment:
- DEBUG=False
- SQLALCHEMY_HOTDB_URI=postgresql://docker:docker@work_gtfs/docker
- SQLALCHEMY_GTFSDB_URI=postgresql://docker:docker@static_gtfs/docker
volumes:
static_gtfs_volume:
external: true
pgdata_web:
external: true
pgdata_hot_gtfs:
external: true

View File

@ -0,0 +1,39 @@
# coding: utf-8
from flask import Flask
from flask.logging import default_handler
import logging
import sys
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
DEBUG = os.environ.get('DEBUG')
SESSION_COOKIE_DOMAIN = os.environ.get('COOKIE_DOMAIN')
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI')
MAIL_DEBUG = int(os.environ.get('DEBUG') == True)
#if sys.version_info.major < 3:
# reload(sys)
#sys.setdefaultencoding('utf8')
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
if app.debug:
app.logger.setLevel(logging.DEBUG)
else:
app.logger.setLevel(logging.INFO)
from webinterface.content.main import main
app.register_blueprint(main)
return app

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
import os
from flask import Flask, Blueprint, abort, current_app, request, render_template, send_from_directory
from datetime import datetime, date
main = Blueprint('main', __name__)
@main.route('/repositorio')
def repositorio():
return render_template('repositorio.html')
@main.route('/objetivos')
def about():
return render_template('objetivos.html')
@main.route('/')
def home():
return render_template('home.html')
@main.route('/static/<path:path>')
def static(path):
return send_from_directory('static', path)
@main.route('/fonts/<path:path>')
def static_fonts(path):
return send_from_directory('static/fonts', path)
@main.route('/favicon.ico')
def favicon():
return send_from_directory(os.path.join(current_app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon')

View File

@ -0,0 +1,6 @@
Flask
gunicorn
Werkzeug
ua-parser

View File

@ -0,0 +1,61 @@
# coding: utf-8
from flask import Flask
from flask.logging import default_handler
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_mail import Mail
from flask_bcrypt import Bcrypt
import logging
import sys
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
DEBUG = os.environ.get('DEBUG')
SESSION_COOKIE_DOMAIN = os.environ.get('COOKIE_DOMAIN')
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_WEBDB_URI')
SQLALCHEMY_BINDS = {
'system': os.environ.get('SQLALCHEMY_WEBDB_URI'),
'gtfs_work': os.environ.get('SQLALCHEMY_HOTDB_URI'),
'gtfs_static': os.environ.get('SQLALCHEMY_GTFSDB_URI'),
}
MAIL_DEBUG = int(os.environ.get('DEBUG') == True)
#if sys.version_info.major < 3:
# reload(sys)
#sys.setdefaultencoding('utf8')
bcrypt = Bcrypt()
mail = Mail()
login_manager = LoginManager()
login_manager.login_view = 'main.login'
login_manager.login_message = u'Para continuar, ingrese su nombre de usuario y contraseña.'
login_manager.login_message_category = 'info'
db = SQLAlchemy()
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
login_manager.init_app(app)
if app.debug:
app.logger.setLevel(logging.DEBUG)
else:
app.logger.setLevel(logging.INFO)
db.init_app(app)
from webinterface.content.main import main
app.register_blueprint(main)
return app

View File

@ -0,0 +1,55 @@
# coding: utf-8
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from flask_login import current_user
from webinterface.models.system import Persona
import pwnedpasswords
class LoginForm(FlaskForm):
login = StringField('Rut', validators=[DataRequired()])
clave = PasswordField('Clave', validators=[DataRequired()])
remember = BooleanField('Recordarme')
submit = SubmitField('Ingresar')
class RequestResetForm(FlaskForm):
correo = StringField('Correo',
validators=[DataRequired(), Email()])
submit = SubmitField(u'Solicitud de recuperación de clave')
class ResetPasswordForm(FlaskForm):
password = PasswordField('Ingresa una Clave de Acceso', validators=[DataRequired()])
confirm_password = PasswordField(u'Confirma tu Clave',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField(u'Actualizar Contraseña')
def validate_password(self, clave):
if pwnedpasswords.check(clave.data):
raise ValidationError(u'La Clave ingresada es insegura. Verifíquelo en <a href="https://haveibeenpwned.com/Passwords">\';--have i been pwned?</a>')
class RegistrationForm(FlaskForm):
login = StringField('Usuario',
validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Correo',
validators=[DataRequired(), Email()])
password = PasswordField('Clave', validators=[DataRequired()])
confirm_password = PasswordField('Confirma clave',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Registrar')
def validate_login(self, login):
user = Persona.query.filter_by(login=login.data).first()
if user:
raise ValidationError(u'El nombre de usuario ya existe, elige otro por favor.')
def validate_email(self, email):
correo = Correo.query.filter_by(correo=email.data).first()
if correo:
raise ValidationError(u'El correo electrónico ya exite. ¿Olvidaste tu clave?')
def validate_password(self, clave):
if pwnedpasswords.check(clave.data):
raise ValidationError(u'La clave ingresada es insegura. Verifíquelo en <a href="https://haveibeenpwned.com/Passwords">\';--have i been pwned?</a>')

View File

@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
import os
from flask import Flask, Blueprint, abort, session, g, current_app, request, render_template, send_from_directory, jsonify, flash, redirect
from flask_login import login_user, current_user, logout_user, login_required
from user_agents import parse
from webinterface import db, bcrypt
from sqlalchemy import func
from datetime import datetime, date
from webinterface.models.system import Persona, Ipaddr, Dispositivo, Identidad, Sesion, Registro, Conexion, Ruta, Sitio, Correo
from webinterface.content.forms import RegistrationForm, LoginForm, RequestResetForm, ResetPasswordForm
from webinterface.content.utils import clean_str, es_local
main = Blueprint('main', __name__)
if os.environ.get('COOKIE_DOMAIN'):
systemuri = "https://app.{}/".format(os.environ.get('COOKIE_DOMAIN'))
else:
systemuri = 'https://app.tpmc.ilab.cl/'
@main.route("/system/login", methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.dashboard'))
form = LoginForm()
if form.validate_on_submit():
form.login.data = clean_str(form.login.data.lower().strip())
user = Persona.query.filter(Persona.rut==form.login.data).first()
if user and bcrypt.check_password_hash(user.clave, form.clave.data):
login_user(user, remember=form.remember.data)
g.user = user
g.sesion.identidad = user
db.session.commit()
next_page = request.args.get('next')
current_app.logger.info('Acceso exitoso: IP({}), USER({}), UA({})'.format(g.ip.ipaddr,form.login.data, g.dispositivo.parsed_ua))
return redirect(next_page) if next_page else redirect(systemuri)
else:
flash(u'El nombre de usuario o la contraseña son incorrectos.', 'danger')
current_app.logger.warning('Intento de ingreso fallido: IP({}), USER({}), UA({})'.format(g.ip.ipaddr,form.login.data, g.dispositivo.parsed_ua))
return render_template('system/login.html', title='Ingreso de Usuarios del Sistema', form=form)
@main.route("/system/logout")
def logout():
logout_user()
session.pop('sid', None)
return redirect('https://tpmc.ilab.cl/')
@main.route("/me")
@login_required
def me():
image_file = url_for('static', filename='profile_pics/' + current_user.foto)
return render_template('system/me.html', title=u'¿Quién soy?',
image_file=image_file)
@main.before_app_request
def registra_sesion():
if request.headers.getlist("X-Forwarded-For"):
remote_ip = request.headers.getlist("X-Forwarded-For")[0]
else:
remote_ip = request.environ['REMOTE_ADDR']
uadata = parse(request.user_agent.string)
ip = Ipaddr.query.filter_by(ipaddr=remote_ip).one_or_none()
if ip is None:
ip = Ipaddr(ipaddr=remote_ip)
db.session.add(ip)
db.session.commit()
if 'sid' in session:
sesion = Sesion.query.filter_by(id=session['sid']).one_or_none()
if sesion is None:
session.pop('sid', None)
else:
dispositivo = Dispositivo.query.filter_by(id=sesion.dispositivoid, parsed_ua=str(uadata)).one_or_none()
if dispositivo is None:
session.pop('sid', None)
else:
conexion = Conexion.query.filter_by(ipaddrid=ip.id, sesionid=sesion.id).one_or_none()
Ident = sesion.identidad
if conexion is None:
conexion = Conexion(ipaddr=ip, sesion=sesion)
db.session.add(conexion)
db.session.commit()
if 'sid' not in session:
dispositivo = Dispositivo.query.filter_by(useragent=request.user_agent.string).one_or_none()
if dispositivo is None:
dev = uadata.is_pc and "PC" or uadata.device.family
os = ("%s %s" % (uadata.os.family, uadata.os.version_string)).strip()
browser = ("%s %s" % (uadata.browser.family, uadata.browser.version_string)).strip()
dispositivo = Dispositivo(useragent=request.user_agent.string, dev=dev , os=os , browser=browser, parsed_ua=str(uadata))
db.session.add(dispositivo)
db.session.commit()
if uadata.is_bot:
Ident = Identidad.query.filter_by(login='Buscador').one()
elif es_local(remote_ip):
Ident = Identidad.query.filter_by(login='Intranet').one()
else:
Ident = Identidad.query.filter_by(login='Internet').one()
sesion = Sesion(identidad=Ident, dispositivo=dispositivo)
db.session.add(sesion)
db.session.commit()
conexion = Conexion(ipaddr=ip, sesion=sesion)
db.session.add(conexion)
db.session.commit()
# agregamos el registro asociado a la solicitud actual
# a la sesion que esta actualmente cargada. Dejamos la sesion guardada en
# la variable global g.
if '/reset_password' in str(request.path):
rpth = '/reset_password'
else:
rpth = str(request.path)
solicitud = Ruta.query.filter_by(ruta=rpth).first()
if solicitud is None:
solicitud = Ruta(ruta=str(request.path))
db.session.add(solicitud)
db.session.commit()
host = Sitio.query.filter_by(sitio=request.host).first()
if host is None:
host = Sitio(sitio=request.host)
db.session.add(host)
db.session.commit()
tamano = int(request.headers.get('Content-Length') or 0)
now = datetime.now()
registro = Registro(sitio=host, ruta=solicitud, sesion=sesion, ipaddr=ip, tamano=tamano)
db.session.add(registro)
db.session.commit()
conexion.ultimo = now
sesion.ultimo = now
db.session.commit()
g.ip = ip
g.user = Ident
g.sesion = sesion
g.conexion = conexion
g.registro = registro
g.dispositivo = dispositivo
session['sid'] = g.sesion.id

View File

@ -0,0 +1,35 @@
# coding: utf-8
import os
from itertools import cycle
def es_local(ip='127.0.0.1'):
from netaddr import IPAddress
return IPAddress(ip).is_private()
def digito_verificador(rut):
reversed_digits = map(int, reversed(str(rut)))
factors = cycle(range(2, 8))
s = sum(d * f for d, f in zip(reversed_digits, factors))
return (-s) % 11 if (-s) % 11 != 10 else 'k'
def send_reset_email(correo):
from flask import url_for, current_app
from flask_mail import Message
from ilab_app import mail
token = correo.cuenta.get_reset_token()
msg = Message('Solicitud de recuperación de cuenta', sender='noreply@ilab.cl', recipients=[correo.correo])
msg.body = '''Para recuperar su cuenta, ingrese a la siguiente URL: {}
En caso que usted *no* haya realizado esta solicitud, simplemente ignore este correo y no se realizará ningún cambio.'''.format(url_for('main.reset_token', token=token, _external=True))
current_app.logger.debug('Sending Dev({})'.format(msg.body))
mail.send(msg)
def clean_telefono(mystr):
import unidecode
return unidecode.unidecode(mystr).replace(" ", "").replace("\t", "").replace("-", "").replace("'", "").replace(".", "").replace("\"", "").replace("%", "")
def clean_str(mystr):
import unidecode
return unidecode.unidecode(mystr).replace(" ", "").replace("\t", "").replace("'", "").replace(".", "").replace("\"", "").replace("%", "")

View File

@ -0,0 +1,18 @@
Flask
Flask-Bcrypt
Flask-Login
Flask-Mail
Flask-SQLAlchemy
Flask-WTF
flask_migrate
psycopg2-binary
SQLAlchemy
gunicorn
Werkzeug
ua-parser
user-agents
MarkupSafe
pwnedpasswords
email_validator
netaddr
unidecode

View File

@ -0,0 +1,44 @@
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Ingreso</legend>
<div class="form-group">
{{ form.login.label(class="form-control-label") }}
{% if form.login.errors %}
{{ form.login(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.login.errors %}
<span>{{ error.decode('utf-8') }}</span>
{% endfor %}
</div>
{% else %}
{{ form.login(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.clave.label(class="form-control-label") }}
{% if form.clave.errors %}
{{ form.clave(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.clave.errors %}
<span>{{ error.decode('utf-8') }}</span>
{% endfor %}
</div>
{% else %}
{{ form.clave(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-check">
{{ form.remember(class="form-check-input") }}
{{ form.remember.label(class="form-check-label") }}
</div>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</fieldset>
</form>
</div>
{% endblock content %}

View File

@ -0,0 +1,43 @@
{% extends "layout.html" %}
{% block content %}
<div class="card-header bg-primary"><h4 class="text-white"><i class="fas fa-user-circle mr-2"></i>{{ title }}</h4></div>
<div class="content-section">
<div class="media">
<img class="rounded-circle account-img" src="{{ image_file }}">
<div class="media-body">
{% if not current_user.alias %}
<h2 class="account-heading">{{ current_user.nombrecompleto }}</h2>
{% else %}
<h2 class="account-heading">{{ current_user.alias }}</h2>
{% endif %}
<p class="text-secondary">{{ current_user.correodefecto.correo }}</p>
</div>
</div>
<fieldset class="form-group">
<legend class="border-bottom mb-4">Información de la Cuenta</legend>
<div class="form-group">
Nombre:
{{ current_user.nombrecompleto }}
</div>
<div class="form-group">
Login:
{{ current_user.login }}
</div>
<div class="form-group">
Correo:
{{ current_user.correodefecto.correo }}
</div>
<legend class="border-bottom mb-4">Personalización</legend>
<div class="form-group">
Alias:
{{ current_user.alias }}
</div>
<div class="form-group">
</div>
</fieldset>
</form>
</div>
<a href="{{ url_for('main.dashboard') }}" class="btn btn-info btn-lg btn-block mt-2"><i class="fas fa-undo-alt mr-2"></i>Volver</a>
{% endblock content %}

View File

@ -0,0 +1,13 @@
{% extends "layout.html" %}
{% block content %}
<article class="media content-section">
<div class="media-body">
<legend class="border-bottom mb-4">Registro de Usuarios</legend>
<p class="article-content">El registro de cuentas se encuentra <strong>desactivado</strong>.</p>
<p class="article-content">Si desea obtener una cuenta, debe solicitarla personalmente para evaluar su factibilidad.</p>
</div>
</article>
{% endblock content %}

View File

@ -0,0 +1,27 @@
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">{{ title }}</legend>
<div class="form-group">
{{ form.correo.label(class="form-control-label") }}
{% if form.correo.errors %}
{{ form.correo(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.correo.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.correo(class="form-control form-control-lg") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}

View File

@ -0,0 +1,41 @@
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">{{ title }}</legend>
<div class="form-group">Su nombre de usuario es: <b>{{user.login}}</b></div>
<div class="form-group">
{{ form.password.label(class="form-control-label") }}
{% if form.password.errors %}
{{ form.password(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.password.errors %}
<span>{{ error|safe }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.confirm_password.label(class="form-control-label") }}
{% if form.confirm_password.errors %}
{{ form.confirm_password(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.confirm_password.errors %}
<span>{{ error|safe }}</span>
{% endfor %}
</div>
{% else %}
{{ form.confirm_password(class="form-control form-control-lg") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}

View File

View File

@ -0,0 +1,328 @@
import os
import io
import csv
import traceback
from datetime import datetime
from flask import current_app
from webinterface import db
from webinterface.models.gtfs_static import Agencia, Servicio, Paradero, Parada, Ruta, Linea
def parse_time(strinput):
try:
h, m, s = strinput.split(":")
h = int(h)
d = 0
while h > 23:
d += 1
h -= 24
return datetime.strptime("{}:{}:{}".format(h,m,s), '%H:%M:%S').time(), d
except:
current_app.logger.debug('Traceback {}'.format(traceback.format_exc()))
current_app.logger.warning("Hora incorrecta => {}".format(strinput))
return datetime.strptime("01:01:01", '%H:%M:%S').time(), 0
def parse_paraderoid(strinput):
primerodenero2022 = 44562
if strinput.find("-") > 0:
current_app.logger.error("Error en el dato de la parada: {}".format(strinput))
try:
whatdate= datetime.strptime('{}-{}'.format(strinput, 2022), '%d-%b-%Y').date()
idparadero = primerodenero2022 + whatdate.timetuple().tm_yday
current_app.logger.warning("Calculado el Identificador en base al año 2022 => {}".format(idparadero))
return idparadero
except:
return 0
else:
return int(strinput)
def completa_dataset():
iparaderos = 0
for paradero in Paradero.query.all():
iparaderos += 1
cparadero = 0
lparadas = 0
sparadas = 0
dparadas = 0
lineas = []
lparada = []
for parada in Parada.query.filter(Parada.paraderoid==paradero.id).all():
cparadero += 1
if cparadero % 1000 == 0:
current_app.logger.debug("Paradero {}, {} detenciones. {} lineas".format(iparaderos, cparadero, len(lineas)))
elemento = (parada.servicio.lineaid, parada.servicio.direccionid)
if parada.servicio.dotw == 'S':
sparadas += 1
elif parada.servicio.dotw == 'D':
dparadas += 1
else:
lparadas += 1
if elemento in lineas:
continue
lineas.append(elemento)
if parada.servicio.direccionid:
lparada.append("{} ({} ida)".format(parada.servicio.linea.codigo, parada.secuencia))
else:
lparada.append("{} ({} regreso)".format(parada.servicio.linea.codigo, parada.secuencia))
lparada.sort()
paradero.recorridos = " - ".join(lparada)
paradero.paradas = 5*lparadas+sparadas+dparadas
paradero.resumen = "{} paradas a la semana, {} lineas".format(paradero.paradas, len(lineas))
paradero.paradasl = lparadas
paradero.paradass = sparadas
paradero.paradasd = dparadas
paradero.lineas = len(lineas)
current_app.logger.info("Paradero {}, {} detenciones a la semana en {} lineas".format(iparaderos, paradero.paradas, len(lineas)))
if iparaderos % 10 == 0:
db.session.commit()
db.session.commit()
def agrega_paraderos():
from bs4 import BeautifulSoup
from zipfile import ZipFile
# print(os.getcwd())
current_app.logger.info("Archivo de paraderos, kmz")
with ZipFile('webinterface/models/datos/PARADEROS_FORMALES.kmz', 'r') as kmz:
kml = kmz.open(kmz.filelist[0].filename, 'r').read()
soup = BeautifulSoup(kml, 'xml')
existen = 0
nuevo = 0
for paradero in soup.find_all("kml:Placemark"):
if (existen+nuevo) % 1000 == 0:
current_app.logger.debug("Leidos {} paraderos existentes y {} nuevos".format(existen, nuevo))
lat = int(paradero.find("kml:SimpleData", {"name": "LATITUDE"}).text)/1000000
lng = int(paradero.find("kml:SimpleData", {"name": "LONGITUDE"}).text)/1000000
codigo = paradero.find("kml:SimpleData", {"name": "CODIGO_INT"}).text
comuna = paradero.find("kml:SimpleData", {"name": "COMUNA"}).text
recorridos = paradero.find("kml:SimpleData", {"name": "RECORRIDOS"}).text
tamano = paradero.find("kml:SimpleData", {"name": "SUPERFICIE"}).text
capacidad = paradero.find("kml:SimpleData", {"name": "CAPACIDAD"}).text
materialpiso = paradero.find("kml:SimpleData", {"name": "RADIER_MAT"}).text
materialpared = paradero.find("kml:SimpleData", {"name": "PARED_POS2"}).text
materialtecho = paradero.find("kml:SimpleData", {"name": "TECHUMBRE2"}).text
match = None
for radio in [0.00000001, 0.00000002, 0.00000003, 0.00000004, 0.00000005, 0.000000075, 0.0000001, 0.0000002]:
match = Paradero.query.filter(((Paradero.latitud-lat)*(Paradero.latitud-lat)+(Paradero.longitud-lng)*(Paradero.longitud-lng)) < radio).first()
if match is not None:
break
if match is None:
nuevo += 1
nombre = "{} cerca {}".format(paradero.find("kml:SimpleData", {"name": "NOMBRE_DE_"}).text, paradero.find("kml:SimpleData", {"name": "CALLE_CERC"}).text)
match = Paradero(id=codigo, codigo=codigo, nombre=nombre, latitud=lat, longitud=lng)
db.session.add(match)
db.session.commit()
else:
existen += 1
match.codigo = codigo
match.comuna = comuna
match.recorridos2 = recorridos
match.tamano = tamano
match.capacidad = capacidad
match.techo = materialtecho
match.pared = materialpared
match.piso = materialpiso
db.session.commit()
current_app.logger.info("Cargados {} paraderos existentes y {} nuevos".format(existen, nuevo))
def load_dataset():
from bs4 import BeautifulSoup
from zipfile import ZipFile
with ZipFile('GC.zip', 'r') as dataset:
current_app.logger.debug("Abriendo archivo GC.zip")
#Carga las agencias:
with io.TextIOWrapper(dataset.open('agency.txt'), encoding='utf8') as agencias_csv:
current_app.logger.debug("Leyendo agency.txt")
agencias = csv.reader(agencias_csv, delimiter=',')
elementos = 0
for item in agencias:
if elementos > 0:
agencia = Agencia.query.filter(Agencia.id==item[0]).one_or_none()
if agencia is None:
agencia = Agencia(id=item[0], nombre=item[1], url=item[2], fono=item[5])
db.session.add(agencia)
else:
agencia.nombre = item[1]
agencia.url = item[2]
agencia.fono = item[3]
elementos += 1
db.session.commit()
current_app.logger.info("Se cargaron {} agencias".format(elementos))
#Carga las agencias
with io.TextIOWrapper(dataset.open('stops.txt'), encoding='utf8') as paraderos_csv:
current_app.logger.debug("Leyendo stops.txt")
paraderos = csv.reader(paraderos_csv, delimiter=',')
elementos = 0
for item in paraderos:
if elementos > 0:
idparadero = item[0]
paradero = Paradero.query.filter(Paradero.id==idparadero).one_or_none()
if len(item[1]) > 0:
codigo = item[1]
else:
codigo = None
if paradero is None:
paradero = Paradero(id=idparadero, codigo=codigo, nombre=item[2], latitud=item[4], longitud=item[5])
db.session.add(paradero)
else:
paradero.codigo = codigo
paradero.nombre = item[2]
paradero.latitud = item[4]
paradero.longitud = item[5]
elementos += 1
if elementos % 1000 == 0:
current_app.logger.info("Se han cargado {} elementos".format(elementos))
db.session.commit()
current_app.logger.info("Se cargaron {} paradas".format(elementos))
with io.TextIOWrapper(dataset.open('routes.txt'), encoding='utf8') as lineas_csv:
current_app.logger.debug("Leyendo routes.txt")
lineas = csv.reader(lineas_csv, delimiter=',')
elementos = 0
for item in lineas:
if elementos > 0:
variante = Linea.query.filter(Linea.id==item[0]).one_or_none()
if variante is None:
variante = Linea(id=item[0], agenciaid=item[1], codigo=item[2], nombre=item[3], color=item[7])
db.session.add(variante)
else:
variante.agenciaid = item[1]
variante.codigo = item[2]
variante.nombre = item[3]
variante.color = item[7]
elementos += 1
if elementos % 1000 == 0:
current_app.logger.info("Se han cargado {} elementos".format(elementos))
db.session.commit()
current_app.logger.info("Se cargaron {} Linea".format(elementos))
with io.TextIOWrapper(dataset.open('trips.txt'), encoding='utf8') as rutas_csv:
current_app.logger.debug("Leyendo Servicios.txt")
rutas = csv.reader(rutas_csv, delimiter=',')
elementos = 0
for item in rutas:
if elementos > 0:
itinerario = Servicio.query.filter(Servicio.id==item[2], Servicio.lineaid==item[0]).one_or_none()
if itinerario is None:
itinerario = Servicio(id=item[2], lineaid=item[0], direccionid=item[5], destino=item[3], dotw=item[1], rutaid=item[7])
db.session.add(itinerario)
else:
itinerario.destino = item[3]
itinerario.direccionid = item[5]
itinerario.rutaid = item[7]
itinerario.dotw = item[1]
elementos += 1
if elementos % 1000 == 0:
db.session.commit()
current_app.logger.info("Se han cargado {} elementos".format(elementos))
db.session.commit()
current_app.logger.info("Se cargaron {} servicios".format(elementos))
with io.TextIOWrapper(dataset.open('shapes.txt'), encoding='utf8') as shapes_csv:
current_app.logger.debug("Leyendo rutas.txt")
shapes = csv.reader(shapes_csv, delimiter=',')
elementos = 0
for item in shapes:
if elementos > 0:
shape = Ruta.query.filter(Ruta.rutaid==item[0], Ruta.secuencia==item[3]).one_or_none()
if shape is None:
shape = Ruta(rutaid=item[0], secuencia=item[3], latitud=item[1], longitud=item[2])
db.session.add(shape)
else:
shape.latitud = item[1]
shape.longitud = item[2]
elementos += 1
if elementos % 1000 == 0:
db.session.commit()
current_app.logger.info("Se han cargado {} elementos".format(elementos))
db.session.commit()
current_app.logger.info("Se cargaron {} puntos".format(elementos))
with io.TextIOWrapper(dataset.open('stop_times.txt'), encoding='utf8') as paradas_csv:
current_app.logger.debug("Leyendo paradas.txt")
paradas = csv.reader(paradas_csv, delimiter=',')
elementos = 0
for item in paradas:
if elementos > 0:
idparadero = item[3]
parada = Parada.query.filter(Parada.servicioid==item[0], Parada.paraderoid==idparadero, Parada.secuencia==item[4]).one_or_none()
llegada, _ = parse_time(item[1])
salida, otrodia = parse_time(item[2])
if parada is None:
parada = Parada(servicioid=item[0], paraderoid=idparadero, secuencia=item[4], tipo=item[8], llegada=llegada, salida=salida, otrodia=otrodia)
db.session.add(parada)
else:
parada.llegada = llegada
parada.salida = salida
parada.otrodia = otrodia
parada.tipo = item[8]
elementos += 1
if elementos % 1000 == 0:
db.session.commit()
current_app.logger.info("Se han cargado {} elementos".format(elementos))
db.session.commit()
current_app.logger.info("Se cargaron {} paradas".format(elementos))

View File

@ -0,0 +1,164 @@
from sqlalchemy.sql import func
from sqlalchemy.dialects import postgresql
from webinterface import db
class sArchivosGTFS(db.Model):
__tablename__ = 'gtfs'
__bind_key__ = 'gtfs_static'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
descripcion = db.Column(db.String)
file = db.Column(db.String)
hash = db.Column(db.String)
size = db.Column(db.Integer)
loaded = db.Column(db.Integer, default=0)
ts = db.Column(db.Datetime, default=func.now())
#agency_id,agency_name,agency_url,agency_timezone,agency_lang,agency_phone,agency_fare_url
#DTPR,División de Transporte Público Regional,http://www.dtpr.gob.cl/,America/Santiago,es,+562 2421 3580,
class sAgencia(db.Model):
__tablename__ = 'agency'
__bind_key__ = 'gtfs_static'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
agency_id = db.Column(db.String)
agency_name = db.Column(db.String)
agency_url = db.Column(db.String)
agency_timezone = db.Column(db.String)
agency_lang = db.Column(db.String)
agency_phone = db.Column(db.String)
agency_fare_url = db.Column(db.String)
#service_id,start_date,end_date,monday,tuesday,wednesday,thursday,friday,saturday,sunday
#S,20210101,20311231,0,0,0,0,0,1,0
#D,20210101,20311231,0,0,0,0,0,0,1
#L,20210101,20311231,1,1,1,1,1,0,0
class sCalendario(db.Model):
__tablename__ = 'calendar'
__bind_key__ = 'gtfs_static'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
service_id = db.Column(db.String)
start_date = db.Column(db.String)
end_date = db.Column(db.String)
monday = db.Column(db.Integer)
tuesday = db.Column(db.Integer)
wednesday = db.Column(db.Integer)
thursday = db.Column(db.Integer)
friday = db.Column(db.Integer)
saturday = db.Column(db.Integer)
sunday = db.Column(db.Integer)
#feed_publisher_name,feed_publisher_url,feed_lang,feed_start_date,feed_end_date,feed_version
#División de Transporte Público Regional,http://www.dtpr.gob.cl/,es,20210101,20311231,Gran Concepción20220616
class sFeedInfo(db.Model):
__tablename__ = 'feed_info'
__bind_key__ = 'gtfs_static'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
feed_publisher_name = db.Column(db.String)
feed_publisher_url = db.Column(db.String)
feed_lang = db.Column(db.String)
feed_start_date = db.Column(db.String)
feed_end_date = db.Column(db.String)
feed_version = db.Column(db.String)
#route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
#625,DTPR,70KH,Nonguén - Parque Tumbes,,3,,0d7215,ffffff
#600,DTPR,41CR,Parque Empresarial Biobío - Terminal Collao,,3,,ad0101,ffffff
class sRuta(db.Model):
__tablename__ = 'routes'
__bind_key__ = 'gtfs_static'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
route_id = db.Column(db.String)
agency_id = db.Column(db.String)
route_short_name = db.Column(db.String)
route_long_name = db.Column(db.String)
route_desc = db.Column(db.String)
route_type = db.Column(db.Integer)
route_url = db.Column(db.String)
route_color = db.Column(db.String)
route_text_color = db.Column(db.String)
#shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled
#1136979693,-36.843,-73.00984,1
#1136979693,-36.843,-73.00984,2
class sFormas(db.Model):
__tablename__ = 'shapes'
__bind_key__ = 'gtfs_static'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
shape_id = db.Column(db.String)
shape_pt_lat = db.Column(db.Double)
shape_pt_lon = db.Column(db.Double)
shape_pt_sequence = db.Column(db.Integer)
shape_dist_traveled = db.Column(db.Double)
#stop_id,stop_code,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,location_type,parent_station,wheelchair_boarding
#40921,,Pque Zoologico Concepcion Poniente,,-36.8400453,-73.00696914,,,,,
#40808,,Cno. Nonguen esq Las Vertientes,,-36.83675878,-73.00343935,,,,,
class sParadas(db.Model):
__tablename__ = 'stops'
__bind_key__ = 'gtfs_static'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
stop_id = db.Column(db.String)
stop_code = db.Column(db.String)
stop_name = db.Column(db.String)
stop_desc = db.Column(db.String)
stop_lat = db.Column(db.Double)
stop_lon = db.Column(db.Double)
zone_id = db.Column(db.String)
stop_url = db.Column(db.String)
location_type = db.Column(db.Integer)
parent_station = db.Column(db.String)
wheelchair_boarding = db.Column(db.Integer)
#trip_id,arrival_time,departure_time,stop_id,stop_sequence, timepoint,shape_dist_traveled
#trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,timepoint
#c8b17d5f-4-76aabf89-b,05:01:00,05:01:00,40439,1,,,,1
#c8b17d5f-4-76aabf89-b,05:02:00,05:02:00,40440,2,,,,0
class sDetenciones(db.Model):
__tablename__ = 'stop_times'
__bind_key__ = 'gtfs_static'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
trip_id = db.Column(db.String)
arrival_time = db.Column(db.String)
departure_time = db.Column(db.String)
stop_id = db.Column(db.String)
stop_sequence = db.Column(db.Integer)
stop_headsign = db.Column(db.String)
pickup_type = db.Column(db.Integer)
drop_off_type = db.Column(db.Integer)
timepoint = db.Column(db.Integer)
#,,,,,,
#route_id,service_id,trip_id,trip_headsign, direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed
#route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed
#625,S,c8b17d5f-4-76aabf89-b,Nonguén,,1,,1136979694,,
#625,S,4d018d35-7-76aabf89-b,Parque Tumbes,,0,,1136979693,,
#ac48288b-1-56195eaf-8,b1910f5b,1,0,1168259538,,0,0,1-1,1
#d9e94be9-f-56195eaf-8,b1910f5b,1,0,1168259538,,0,0,1-1,2
class sServicio(db.Model):
__tablename__ = 'trips'
__bind_key__ = 'gtfs_static'
id = db.Column(db.String, primary_key=True, nullable=False)
route_id = db.Column(db.String) # Ruta.rutaid
service_id = db.Column(db.String)
trip_id = db.Column(db.String)
trip_headsign = db.Column(db.String)
trip_short_name = db.Column(db.String)
direction_id = db.Column(db.Integer)
block_id = db.Column(db.String)
shape_id = db.Column(db.String)
wheelchair_accessible = db.Column(db.Integer)
bikes_allowed = db.Column(db.Integer)
# 4cc08782-c-76aabf89-b

168
models/gtfs_work.py 100644
View File

@ -0,0 +1,168 @@
from sqlalchemy.sql import func
from sqlalchemy.dialects import postgresql
from webinterface import db
class ArchivosGTFS(db.Model):
__tablename__ = 'archivos'
__bind_key__ = 'gtfs_work'
id = db.Column(db.Integer, primary_key=True, nullable=False)
analisis = db.Column(db.Integer, default=0)
terminado = db.Column(db.Integer, default=0)
activo = db.Column(db.Integer, default=0)
class Agencia(db.Model):
__tablename__ = 'agency'
__bind_key__ = 'gtfs_work'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
agency_id = db.Column(db.String, unique=True)
agency_name = db.Column(db.String)
agency_url = db.Column(db.String)
agency_timezone = db.Column(db.String)
agency_lang = db.Column(db.String)
agency_phone = db.Column(db.String)
agency_fare_url = db.Column(db.String)
class Calendario(db.Model):
__tablename__ = 'calendars'
__bind_key__ = 'gtfs_work'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
label = db.Column(db.String, unique=True)
monday = db.Column(db.Integer, default=0)
tuesday = db.Column(db.Integer, default=0)
wednesday = db.Column(db.Integer, default=0)
thursday = db.Column(db.Integer, default=0)
friday = db.Column(db.Integer, default=0)
saturday = db.Column(db.Integer, default=0)
sunday = db.Column(db.Integer, default=0)
#shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled
#1136979693,-36.843,-73.00984,1
#1136979693,-36.843,-73.00984,2
class Formas(db.Model):
__tablename__ = 'shapes'
__bind_key__ = 'gtfs_work'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
shape_1pt_lat = db.Column(db.Double, nullable=False)
shape_1pt_lon = db.Column(db.Double, nullable=False)
shape_num_pt = db.Column(db.Integer, nullable=False)
shape_length = db.Column(db.Double)
class Segmentos(db.Model):
__tablename__ = 'shapes_point'
__bind_key__ = 'gtfs_work'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
pt_start_lat = db.Column(db.Double, nullable=False)
pt_start_lon = db.Column(db.Double, nullable=False)
pt_end_lat = db.Column(db.Double, nullable=False)
pt_end_lon = db.Column(db.Double, nullable=False)
shape_pt_sequence = db.Column(db.Integer, nullable=False)
shape_dist_traveled = db.Column(db.Double)
shape_id = db.Column(db.Integer, db.ForeignKey('shapes.id'), nullable=False)
__table_args__ = (db.UniqueConstraint('shape_id', 'shape_pt_sequence', name='_shapeid_sequence_uc'), )
class Comunas(db.Model):
__tablename__ = 'comuna'
__bind_key__ = 'gtfs_work'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
name = db.Column(db.String, unique=True)
#stop_id,stop_code,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,location_type,parent_station,wheelchair_boarding
#40921,,Pque Zoologico Concepcion Poniente,,-36.8400453,-73.00696914,,,,,
#40808,,Cno. Nonguen esq Las Vertientes,,-36.83675878,-73.00343935,,,,,
class Paradas(db.Model):
__tablename__ = 'stops'
__bind_key__ = 'gtfs_work'
id = db.Column(db.BigInteger, primary_key=True, nullable=False, autoincrement=True)
stop_code = db.Column(db.String, unique=True, nullable=True)
stop_name = db.Column(db.String)
stop_desc = db.Column(db.String)
stop_lat = db.Column(db.Double, nullable=False)
stop_lon = db.Column(db.Double, nullable=False)
comuna_id = db.Column(db.Integer, db.ForeignKey('comuna.id'), nullable=False)
stop_url = db.Column(db.String)
parent_station = db.Column(db.BigInteger)
wheelchair_boarding = db.Column(db.Integer, default=0)
#route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
#625,DTPR,70KH,Nonguén - Parque Tumbes,,3,,0d7215,ffffff
#600,DTPR,41CR,Parque Empresarial Biobío - Terminal Collao,,3,,ad0101,ffffff
class Lineas(db.Model):
__tablename__ = 'routes'
__bind_key__ = 'gtfs_work'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
agency_id = db.Column(db.Integer, db.ForeignKey('agency.id'), nullable=False)
route_short_name = db.Column(db.String)
route_long_name = db.Column(db.String)
route_desc = db.Column(db.String)
route_url = db.Column(db.String)
route_color = db.Column(db.Integer, default=0)
route_text_color = db.Column(db.Integer, default=0)
#route_id,service_id,trip_id,trip_headsign, direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed
#route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed
#625,S,c8b17d5f-4-76aabf89-b,Nonguén,,1,,1136979694,,
#625,S,4d018d35-7-76aabf89-b,Parque Tumbes,,0,,1136979693,,
#ac48288b-1-56195eaf-8,b1910f5b,1,0,1168259538,,0,0,1-1,1
#d9e94be9-f-56195eaf-8,b1910f5b,1,0,1168259538,,0,0,1-1,2
class Servicio(db.Model):
__tablename__ = 'trips'
__bind_key__ = 'gtfs_work'
id = db.Column(db.Integer, primary_key=True, nullable=False)
route_id = db.Column(db.Integer, db.ForeignKey('routes.id'), nullable=False) # A que linea corresponde
calendar_id = db.Column(db.Integer, db.ForeignKey('calendars.id'), nullable=False)
gtfs_id = db.Column(db.Integer, db.ForeignKey('archivos.id'), nullable=False) # Identifiador unico del servicio
shape_id = db.Column(db.Integer, db.ForeignKey('shapes.id'), nullable=False) # shapes.id
direction_id = db.Column(db.Integer, nullable=False) # 0=ida, 1=vuelta
trip_headsign = db.Column(db.String)
trip_short_name = db.Column(db.String)
wheelchair_accessible = db.Column(db.Integer, default=0)
bikes_allowed = db.Column(db.Integer, default=0)
#trip_id,arrival_time,departure_time,stop_id,stop_sequence, timepoint,shape_dist_traveled
#trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,timepoint
#c8b17d5f-4-76aabf89-b,05:01:00,05:01:00,40439,1,,,,1
#c8b17d5f-4-76aabf89-b,05:02:00,05:02:00,40440,2,,,,0
class Detenciones(db.Model):
__tablename__ = 'stop_times'
__bind_key__ = 'gtfs_work'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
trip_id = db.Column(db.Integer, db.ForeignKey('trips.id'), nullable=False)
stop_sequence = db.Column(db.Integer, nullable=False)
stop_id = db.Column(db.BigInteger, db.ForeignKey('stops.id'), nullable=False)
departure_time = db.Column(db.String(9), index=True)
arrival_time = db.Column(db.String(9))
dist_traveled = db.Column(db.Integer, default=0)
stop_headsign = db.Column(db.String)
pickup_type = db.Column(db.Integer, default=0)
drop_off_type = db.Column(db.Integer, default=0)
timepoint = db.Column(db.Integer, default=1)
__table_args__ = (db.UniqueConstraint('trip_id', 'stop_sequence', name='_trip_sequence_uc'), )
# 4cc08782-c-76aabf89-b

262
models/system.py 100644
View File

@ -0,0 +1,262 @@
#from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
#from datetime import datetime
#from flask import current_app
from flask_login import UserMixin
from sqlalchemy.sql import func
from sqlalchemy.dialects import postgresql, sqlite
from webinterface import db, login_manager
@login_manager.user_loader
def load_user(user_id):
return Persona.query.filter(Persona.rut==user_id).one_or_none()
class Identidad(db.Model):
__tablename__ = 'identidades'
__table_args__ = { 'schema': 'usuarios' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), primary_key=True, autoincrement=True)
login = db.Column(db.String(50), unique=True, nullable=False)
alias = db.Column(db.String(50))
creado = db.Column(db.DateTime(timezone=True), server_default=func.now())
modificado = db.Column(db.DateTime(timezone=True), server_onupdate=func.now())
sesiones = db.relationship('Sesion', back_populates='identidad', lazy=True)
ubicaciones = db.relationship('Ubicacion', back_populates='identidad', lazy=True)
tipo = db.Column(db.String(20))
__mapper_args__ = {
'polymorphic_identity':'Identidad',
'polymorphic_on':tipo
}
class Cuenta(Identidad):
__tablename__ = 'cuentas'
__table_args__ = { 'schema': 'usuarios' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), db.ForeignKey('usuarios.identidades.id'), primary_key=True)
clave = db.Column(db.String(60), nullable=False)
sysadmin = db.Column(db.Boolean, default=0)
ultimoacceso = db.Column(db.DateTime(timezone=True))
__mapper_args__ = {
'polymorphic_identity':'Cuenta'
}
correos = db.relationship('Correo', back_populates='cuenta', lazy=True)
def set_default(self, correo):
for item in self.correos:
if correo == item:
correo.default = True
else:
correo.default = False
@property
def correodefecto(self):
last = None
for item in self.correos:
if item.default == True:
return item
elif last is None:
last = item
return last
@correodefecto.setter
def correodefecto(self, correo):
if correo.cuenta != self:
pass
for item in self.correos:
if item == correo:
item.default = True
else:
item.default = False
class Persona(Cuenta,UserMixin):
__tablename__ = 'personas'
__table_args__ = { 'schema': 'usuarios' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), db.ForeignKey('usuarios.cuentas.id'), primary_key=True)
nombres = db.Column(db.String(100), nullable=False)
apellidop = db.Column(db.String(100), nullable=False)
apellidom = db.Column(db.String(100))
rut = db.Column(db.String(20), unique=True, nullable=False)
telefono = db.Column(db.String(20), nullable=True)
foto = db.Column(db.String(50), nullable=False, default='default.jpg')
__mapper_args__ = {
'polymorphic_identity':'Persona'
}
def get_id(self):
return self.rut
@property
def nombrecompleto(self):
if self.apellidom is not None:
return u"{} {} {}.".format(self.nombres.partition(' ')[0], self.apellidop, self.apellidom[0])
else:
return u"{} {}.".format(self.nombres.partition(' ')[0], self.apellidop)
@property
def safe_nombrecompleto(self):
return "{}".format(self.nombrecompleto.encode('utf-8'))
@property
def ascii_nombrecompleto(self):
return unidecode.unidecode(self.nombrecompleto)
def is_in(self, comision=0):
return True
class Correo(db.Model):
__tablename__ = 'correos'
__table_args__ = { 'schema': 'usuarios' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
correo = db.Column(db.String(100), unique=True, nullable=False)
cuentaid = db.Column(db.BigInteger(), db.ForeignKey('usuarios.cuentas.id'), nullable=False)
cuenta = db.relationship('Cuenta', back_populates='correos')
default = db.Column(db.Boolean, default=0)
class Sesion(db.Model):
__tablename__ = 'sesiones'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), primary_key=True)
identidadid = db.Column(db.BigInteger(), db.ForeignKey('usuarios.identidades.id'), nullable=False)
identidad = db.relationship('Identidad', back_populates='sesiones')
dispositivoid = db.Column(db.Integer, db.ForeignKey('registros.dispositivos.id'), nullable=False)
dispositivo = db.relationship('Dispositivo')
conexiones = db.relationship('Conexion')
iniciada = db.Column(db.DateTime(timezone=True), server_default=func.now())
ultimo = db.Column(db.DateTime(timezone=True), server_onupdate=func.now())
def __repr__(self):
return u"Session ('{}','{}','{}')".format(self.identidad.login, self.ipaddr.ipaddr, self.useragent.parsed_ua)
class Conexion(db.Model):
__tablename__ = 'conexiones'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), primary_key=True)
ipaddrid = db.Column(db.Integer, db.ForeignKey('registros.ipaddrs.id'), nullable=False)
ipaddr = db.relationship('Ipaddr')
sesionid = db.Column(db.Integer, db.ForeignKey('registros.sesiones.id'), nullable=False)
sesion = db.relationship('Sesion', back_populates='conexiones', lazy=True)
iniciada = db.Column(db.DateTime(timezone=True), server_default=func.now())
ultimo = db.Column(db.DateTime(timezone=True), server_onupdate=func.now())
__table_args__ = (db.UniqueConstraint('ipaddrid', 'sesionid', name='_una_ip_sesion_uc'),{ 'schema': 'registros' })
class Ubicacion(db.Model):
__tablename__ = 'ubicaciones'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), primary_key=True)
ipaddrid = db.Column(db.Integer, db.ForeignKey('registros.ipaddrs.id'), nullable=False)
ipaddr = db.relationship('Ipaddr')
identidadid = db.Column(db.Integer, db.ForeignKey('usuarios.identidades.id'), nullable=False)
identidad = db.relationship('Identidad', back_populates='ubicaciones', lazy=True)
descripcion = db.Column(db.String(200), nullable=True)
iniciada = db.Column(db.DateTime(timezone=True), server_default=func.now())
ultimo = db.Column(db.DateTime(timezone=True), server_onupdate=func.now())
__table_args__ = (db.UniqueConstraint('ipaddrid', 'identidadid', name='_una_ip_identidad_uc'),{ 'schema': 'registros' })
class Ipaddr(db.Model):
__tablename__ = 'ipaddrs'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
ipaddr = db.Column(db.String(15).with_variant(postgresql.INET(), 'postgresql'), unique=True, nullable=False)
hostname = db.Column(db.String(100), nullable=True)
def __repr__(self):
return "IpAddr('{}','{}')".format(self.ipaddr, self.hostname)
class Dispositivo(db.Model):
__tablename__ = 'dispositivos'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
useragent = db.Column(db.String(1024), unique=True, nullable=False)
parsed_ua = db.Column(db.String(150))
dev = db.Column(db.String(50))
os = db.Column(db.String(50))
browser = db.Column(db.String(50))
def __repr__(self):
return "Useragent('{}')".format(self.parsed_ua)
class Ruta(db.Model):
__tablename__ = 'rutas'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
ruta = db.Column(db.String(100), unique=True, nullable=False)
def __repr__(self):
return "Url('{}')".format(self.ruta)
class Registro(db.Model):
__tablename__ = 'registros'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), primary_key=True)
sitioid = db.Column(db.Integer, db.ForeignKey('registros.sitios.id'), nullable=False)
sitio = db.relationship('Sitio')
rutaid = db.Column(db.Integer, db.ForeignKey('registros.rutas.id'), nullable=False)
ruta = db.relationship('Ruta')
sesionid = db.Column(db.BigInteger().with_variant(sqlite.INTEGER(), 'sqlite'), db.ForeignKey('registros.sesiones.id'), nullable=False)
sesion = db.relationship('Sesion')
ipaddrid = db.Column(db.Integer, db.ForeignKey('registros.ipaddrs.id'), nullable=False)
ipaddr = db.relationship('Ipaddr')
tamano = db.Column(db.Integer)
creado = db.Column(db.DateTime, server_default=func.now())
def __repr__(self):
return "Registro('{}')".format(self.ruta.ruta)
class Sitio(db.Model):
__tablename__ = 'sitios'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
sitio = db.Column(db.String(100), unique=True, nullable=False)

View File

@ -0,0 +1,59 @@
# coding: utf-8
from flask import Flask
from flask.logging import default_handler
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_mail import Mail
from flask_bcrypt import Bcrypt
import logging
import sys
from .config import Config
#if sys.version_info.major < 3:
# reload(sys)
#sys.setdefaultencoding('utf8')
db = SQLAlchemy()
bcrypt = Bcrypt()
mail = Mail()
login_manager = LoginManager()
login_manager.login_view = 'main.login'
login_manager.login_message = u'Para continuar, ingrese su nombre de usuario y contraseña.'
login_manager.login_message_category = 'info'
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
if app.debug:
app.logger.setLevel(logging.DEBUG)
else:
app.logger.setLevel(logging.INFO)
db.init_app(app)
login_manager.init_app(app)
mail.init_app(app)
from webinterface.content.errors.handlers import errors
app.register_blueprint(errors)
from webinterface.content.main import main
app.register_blueprint(main)
from webinterface.content.gestion.routes import gestion
app.register_blueprint(gestion)
from webinterface.content.gtfs.routes import gtfs
app.register_blueprint(gtfs)
from webinterface.content.docs.routes import docs
app.register_blueprint(docs)
return app

View File

@ -0,0 +1,19 @@
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
DEBUG = os.environ.get('DEBUG')
SESSION_COOKIE_DOMAIN = os.environ.get('COOKIE_DOMAIN')
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_WEBDB_URI')
SQLALCHEMY_BINDS = {
'system': os.environ.get('SQLALCHEMY_WEBDB_URI'),
'gtfs_work': os.environ.get('SQLALCHEMY_HOTDB_URI'),
'gtfs_static': os.environ.get('SQLALCHEMY_GTFSDB_URI'),
}
MAIL_DEBUG = int(os.environ.get('DEBUG') == True)
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT = os.environ.get('MAIL_PORT')

View File

@ -0,0 +1,89 @@
# coding: utf-8
from flask import send_file, render_template, flash, redirect, url_for, request, Blueprint, current_app, abort, g
from flask_login import login_required, current_user
from webinterface import db
from webinterface.models.documentos import Documento
from webinterface.models.gestion import Evidencia, AnexosActa
from .utils import ingest_file
from werkzeug.utils import secure_filename
import os
docs = Blueprint('docs', __name__)
@docs.before_request
@login_required
def verifica_permisos():
pass
def touchContainer(filehash, oldhash):
try:
if 'actividadid' in request.form and int(request.form['actividadid']) > 0:
actividadid = request.form['actividadid']
current_app.logger.debug(u'Actividad {} detectada' .format(actividadid))
inode = Evidencia.query.filter(Evidencia.actividadid==actividadid, Evidencia.documentohash==oldhash).one_or_none()
if inode is not None:
db.session.delete(inode)
inode = Evidencia.query.filter(Evidencia.actividadid==actividadid, Evidencia.documentohash==filehash).one_or_none()
if inode is None:
inode = Evidencia(actividadid=actividadid, documentohash=filehash)
db.session.add(inode)
db.session.commit()
return inode
elif 'actaid' in request.form and int(request.form['actaid']) > 0:
actaid = request.form['actaid']
current_app.logger.debug(u'AnexosActa {} detectado' .format(actaid))
inode = AnexosActa.query.filter(AnexosActa.actaid==actaid, AnexosActa.documentohash==oldhash).one_or_none()
if inode is not None:
db.session.delete(inode)
inode = AnexosActa.query.filter(AnexosActa.actaid==actaid, AnexosActa.documentohash==filehash).one_or_none()
if inode is None:
inode = AnexosActa(actaid=actaid, documentohash=filehash)
db.session.add(inode)
db.session.commit()
return inode
else:
return None
except:
db.session.rollback()
return None
@docs.route("/docs/upload", methods=['POST'])
def upload():
(item, previtem) = ingest_file()
if item is None or (previtem is not None and touchContainer(item.hash, previtem.hash) is None) or (previtem is None and touchContainer(item.hash, "00000000000000000000000000000000") is None):
current_app.logger.debug(u'Item is {}, previtem is {}'.format(item, previtem))
flash(u"Ocurrió un error al subir el documento", "danger")
else:
flash(u"El documento fue almacenado con éxito", "success")
nombre, extension = os.path.splitext(item.nombre.lower())
if extension not in ['.pdf', '.jpg', '.jpeg', '.png']:
flash(u"El formato '{}' no es compatible con los reportes automáticos. Si desea que se incluya como evidencia en los reportes súbalo como PDF (tamaño carta) o imágen compatible.".format(extension), "warning")
return redirect(request.form['redirect'])
@docs.route("/docs/<string:filehash>")
def get(filehash=0):
doc = Documento.query.get(filehash)
if doc.tamano == 0:
return redirect(doc.nombre)
else:
try:
return send_file(os.path.abspath(doc.get_file()), as_attachment=True, download_name=doc.nombre, last_modified=doc.creado)
except FileNotFoundError:
abort(404)
@docs.route("/docs/evidencia", methods=['POST'])
def evidencias():
return upload()

View File

@ -0,0 +1,102 @@
# coding: utf-8
from flask import send_file, render_template, flash, redirect, url_for, request, Blueprint, current_app, abort, g
from flask_login import login_required, current_user
from webinterface import db
from webinterface.models.documentos import Documento
from werkzeug.utils import secure_filename
def ingest_file():
import datetime
import hashlib
import os
from shutil import copyfile
from tempfile import NamedTemporaryFile
# for key, values in request.form.items():
# current_app.logger.info('request-form: {}: {}'.format(key, values))
#
# for key, values in request.files.items():
# current_app.logger.info('request-file: {}: {}'.format(key, values))
uuid_nill = "00000000000000000000000000000000"
try:
if len(request.form['currenthash']) < 32:
currenthash = uuid_nill
else:
currenthash = request.form['currenthash']
except Exception as e:
currenthash = uuid_nill
previtem = Documento.query.filter_by(hash=currenthash).one_or_none()
sha1 = hashlib.sha1()
newdocs = None
if 'documento' in request.files and len(request.files['documento'].filename) > 0:
uploaded_file = request.files['documento']
# current_app.logger.info('Archivo subido: procesando {}'.format(uploaded_file.filename))
fsize = 0
with NamedTemporaryFile() as tmp:
try:
BUF_SIZE = 65536
uploaded_file.save(tmp.name)
fsize = os.path.getsize(tmp.name)
# current_app.logger.debug('Archivo subido: Tamaño {} bytes'.format(fsize))
with open(tmp.name, 'rb') as f:
while True:
data = f.read(BUF_SIZE)
if not data:
break
sha1.update(data)
# current_app.logger.debug('Archivo subido: hash {}'.format(sha1.hexdigest()[:32]))
except Exception as e:
# current_app.logger.error('Error leyendo el archivo: Name({}), Error({})'.format(uploaded_file.filename, str(e)))
return (None, None)
else:
item = Documento.query.filter_by(hash=sha1.hexdigest()[:32]).one_or_none()
if item is None:
name = secure_filename(uploaded_file.filename)
item = Documento(hash=sha1.hexdigest()[:32], nombre=name, descripcion=request.form['descripcion'].strip(), tamano=fsize, autorid=current_user.id, reemplazahash=previtem.hash)
db.session.add(item)
db.session.commit()
try:
# current_app.logger.debug('Directorio: {}'.format(os.path.abspath(item.get_dir())))
os.makedirs(os.path.abspath(item.get_dir()), exist_ok=True)
# current_app.logger.debug('Archivo: {}'.format(os.path.abspath(item.get_file())))
copyfile(tmp.name, os.path.abspath(item.get_file()))
except Exception as e:
return (None, None)
else:
if not os.path.exists(os.path.abspath(item.get_file())):
try:
# current_app.logger.info('Archivo: {}'.format(os.path.abspath(item.get_file())))
item.name = secure_filename(uploaded_file.filename)
item.reemplazahash = previtem.hash
copyfile(tmp.name, os.path.abspath(item.get_file()))
db.session.commit()
except Exception as e:
db.session.rollback()
current_app.logger.error('Error al guardar el archivo: Name({}), Error({})'.format(uploaded_file.filename, str(e)))
return (None, None)
# current_app.logger.info('Archivo subido: guardado {}'.format(item.get_file()))
elif previtem.tamano == 0 and len(request.form['url']) > 5:
sha1.update(request.form['url'].strip().encode('utf-8'))
item = Documento.query.filter_by(hash=sha1.hexdigest()[:32]).one_or_none()
if item is None:
item = Documento(hash=sha1.hexdigest()[:32], nombre=request.form['url'].strip(), descripcion=request.form['descripcion'].strip(), tamano=0, autorid=current_user.id, reemplazahash=previtem.hash)
db.session.add(item)
else:
item.descripcion=request.form['descripcion'].strip()
db.session.commit()
elif currenthash != uuid_nill:
previtem.descripcion = request.form['descripcion']
db.session.commit()
return (previtem, None)
else:
return (None, None)
return (item, previtem)

View File

@ -0,0 +1,22 @@
from flask import Blueprint, render_template
from flask_login import login_required
errors = Blueprint('errors', __name__)
@errors.app_errorhandler(404)
@login_required
def error_404(error):
return render_template('errors/404.html'), 404
@errors.app_errorhandler(403)
@login_required
def error_403(error):
return render_template('errors/403.html'), 403
@errors.app_errorhandler(500)
def error_500(error):
return render_template('errors/500.html'), 500

View File

@ -0,0 +1,171 @@
# coding: utf-8
from flask import send_file, render_template, flash, redirect, url_for, request, Blueprint, current_app, abort, g
from flask_login import login_required, current_user
from ilab_app import db
from ilab_app.models.gestion import Evidencia, Documento, Actividad, Objetivo
from werkzeug.utils import secure_filename
evidencia = Blueprint('evidencia', __name__)
@evidencia.before_request
@login_required
def verifica_permisos():
pass
@evidencia.route("/evidencia/subir", methods=['POST'])
def subir():
import datetime
import hashlib
for key, values in request.form.items():
current_app.logger.debug('request-form: {}: {}'.format(key, values))
for key, values in request.files.items():
current_app.logger.debug('request-file: {}: {}'.format(key, values))
uuid_nill = "00000000000000000000000000000000"
try:
if len(request.form['currenthash']) < 32:
currenthash = uuid_nill
else:
currenthash = request.form['currenthash']
except Exception as e:
currenthash = uuid_nill
previtem = Documento.query.filter_by(hash=currenthash).one_or_none()
item = None
sha1 = hashlib.sha1()
if 'documento' in request.files and len(request.files['documento'].filename) > 0:
from tempfile import NamedTemporaryFile
import os
uploaded_file = request.files['documento']
current_app.logger.debug('Archivo subido: procesando {}'.format(uploaded_file.filename))
fsize = 0
with NamedTemporaryFile() as tmp:
try:
BUF_SIZE = 65536
uploaded_file.save(tmp.name)
fsize = os.path.getsize(tmp.name)
current_app.logger.debug('Archivo subido: Tamaño {} bytes'.format(fsize))
with open(tmp.name, 'rb') as f:
while True:
data = f.read(BUF_SIZE)
if not data:
break
sha1.update(data)
current_app.logger.debug('Archivo subido: hash {}'.format(sha1.hexdigest()[:32]))
except Exception as e:
current_app.logger.error('Error leyendo el archivo: Name({}), Error({})'.format(uploaded_file.filename, str(e)))
item = None
else:
item = Documento.query.filter_by(hash=sha1.hexdigest()[:32]).one_or_none()
if item is None:
name = secure_filename(uploaded_file.filename)
item = Documento(hash=sha1.hexdigest()[:32], nombre=name, descripcion=request.form['descripcion'].strip(), tamano=fsize, autorid=current_user.id, reemplazahash=previtem.hash)
db.session.add(item)
actid = int(request.form['aid'])
newevidencia = Evidencia(actividadid=actid, documentohash=item.hash)
db.session.add(newevidencia)
db.session.commit()
try:
from shutil import copyfile
current_app.logger.debug('Directorio: {}'.format(os.path.abspath(item.get_dir())))
os.makedirs(os.path.abspath(item.get_dir()))
current_app.logger.debug('Archivo: {}'.format(os.path.abspath(item.get_file())))
copyfile(tmp.name, os.path.abspath(item.get_file()))
except Exception as e:
current_app.logger.error('Error al guardar el archivo: Name({}), Error({})'.format(uploaded_file.filename, str(e)))
item = None
else:
current_app.logger.debug('Archivo subido: guardado {}'.format(item.get_file()))
else:
item.descripcion=request.form['descripcion'].strip()
db.session.commit()
elif previtem.tamano == 0 and len(request.form['url']) > 5:
sha1.update(request.form['url'].strip().encode('utf-8'))
item = Documento.query.filter_by(hash=sha1.hexdigest()[:32]).one_or_none()
if item is None:
item = Documento(hash=sha1.hexdigest()[:32], nombre=request.form['url'].strip(), descripcion=request.form['descripcion'].strip(), tamano=0, autorid=current_user.id, reemplazahash=previtem.hash)
db.session.add(item)
actid = int(request.form['aid'])
newevidencia = Evidencia(actividadid=actid, documentohash=item.hash)
db.session.add(newevidencia)
db.session.commit()
else:
item.descripcion=request.form['descripcion'].strip()
db.session.commit()
elif currenthash != uuid_nill:
previtem.descripcion = request.form['descripcion']
db.session.commit()
item = None
if item != None and item.hash != previtem.hash and not previtem.placeholder:
from ilab_app.models.system import get_comisiones
actid = int(request.form['aid'])
comisiones = get_comisiones()
evidencias = Evidencia.query.filter(Evidencia.documentohash==previtem.hash).\
join(Evidencia.actividad).join(Actividad.objetivo).\
filter(Objetivo.estado==0, Objetivo.responsableid.in_(comisiones)).all()
for evidencia in evidencias:
if evidencia.actividadid != actid:
newevidencia = Evidencia(actividadid=evidencia.actividadid, documentohash=item.hash)
db.session.add(newevidencia)
db.session.delete(evidencia)
db.session.commit()
elif item != None and item.hash != previtem.hash:
actid = int(request.form['aid'])
prevev = Evidencia.query.filter_by(actividadid=actid, documentohash=previtem.hash).one_or_none()
if prevev != None:
db.session.delete(prevev)
db.session.commit()
return redirect(url_for(request.form['redirect']))
@evidencia.route("/evidencia/<string:filehash>")
def get(filehash=0):
from ilab_app.models.system import get_comisiones
from sqlalchemy import or_
from sqlalchemy.orm import aliased
Hijo = aliased(Objetivo)
comisiones = get_comisiones()
cani = Objetivo.query.join(Hijo, Objetivo.hijos).\
filter(or_(Objetivo.responsableid.in_(comisiones), Hijo.responsableid.in_(comisiones), Hijo.invitadosid.in_(comisiones))).\
join(Hijo.actividades).join(Actividad.evidencias).\
filter(Evidencia.documentohash==filehash).first()
if cani is None:
abort(403)
else:
doc = Documento.query.get(filehash)
if doc.placeholder:
return render_template('system/placeholder.html', doc=doc)
elif doc.tamano == 0:
return redirect(doc.nombre)
else:
try:
import os
return send_file(os.path.abspath(doc.get_file()), as_attachment=True, download_name=doc.nombre, last_modified=doc.creado)
except FileNotFoundError:
abort(404)

View File

@ -0,0 +1,735 @@
# coding: utf-8
import os
import math
from PIL import Image
from fpdf import FPDF
from flask import current_app
from flask_login import current_user
from tempfile import NamedTemporaryFile
from functools import cache
from webinterface.models.documentos import Documento
from datetime import datetime, timedelta, date
from PyPDF2 import PdfFileReader, PdfFileMerger
from webinterface.models.gestion import Objetivo, Accion, Evidencia, Comision
@cache
def logoyencabezado(Nodo):
uuid_nill = "00000000000000000000000000000000"
responsable = Nodo.responsable
while responsable.creado > 1:
responsable = responsable.supervisor
titulo = None
logo = None
docLogo = Documento.query.filter(Documento.hash==responsable.logo).one_or_none()
if docLogo is not None:
logoFile = docLogo.get_file()
if logoFile is not None:
logo = os.path.abspath(logoFile)
docTitulo = Documento.query.filter(Documento.hash==responsable.titulo).one_or_none()
if docTitulo is not None:
tituloFile = docTitulo.get_file()
if tituloFile is not None:
titulo = os.path.abspath(tituloFile)
return (logo, titulo)
def ReportePDF(Archivo, Nodo, Fini=None, Ffin=None):
logo, titulo = logoyencabezado(Nodo)
maindoc = plantillaReporte(Titulo=Nodo.nombre, Logo=logo, Encabezado=titulo)
maindoc.set_title(u"Reporte de Gestión: {}".format(Nodo.nombre))
maindoc.set_subject(Nodo.nombre)
maindoc.set_author("iLab Gestion: {}".format(maindoc.serial))
maindoc.set_creator(current_user.ascii_nombrecompleto)
Horachilena=datetime.today() - timedelta(hours=4)
Horachilenastr = Horachilena.strftime('Generado el %d/%m/%Y a las %H horas')
if isinstance(Fini,date) and isinstance(Ffin, date) and Fini < Ffin:
maindoc.Tapa('Reporte de Gestión desde {:%d/%m/%Y} hasta {:%d/%m/%Y}.'.format(Fini, Ffin), Linea2=Nodo.nombre, Linea3=Horachilenastr, Logo=True)
else:
maindoc.Tapa('Reporte de Gestión', Linea2=Nodo.nombre, Linea3=Horachilenastr, Logo=True)
maindoc, _, _ = ReporteNodo(maindoc, Nodo, True, Fini, Ffin)
paginas_en_blanco = maindoc.paginas_en_blanco
anexos_pdf = maindoc.anexospdf
paginas_reporte = maindoc.page_no()
current_app.logger.debug(u'PDF original tiene {} páginas'.format(paginas_reporte))
with NamedTemporaryFile() as tmp:
maindoc.output(name=tmp.name, dest='F')
merger = PdfFileMerger(strict=False)
pagina = 0
while pagina < paginas_reporte:
if pagina not in paginas_en_blanco:
merger.append(tmp.name, pages=(pagina, pagina+1))
pagina = pagina + 1
else:
archivo_pdf, largo = anexos_pdf.pop(0)
merger.append(archivo_pdf)
pagina = pagina + largo
merger.write(Archivo)
merger.close()
return "reporte_gestion-{}-{}.pdf".format(Nodo.id, maindoc.serial)
def ListaActaPDF(Archivo, Acta):
logo, titulo = logoyencabezado(Acta.objetivo)
maindoc = plantillaReporte(Titulo='Acta {}: {}'.format(Acta.secuencia, Acta.objetivo.nombre), Logo=logo, Encabezado=titulo)
maindoc.add_page()
maindoc.PutLogoCenter()
maindoc.Titulo1(u'{} - Acta {}'.format(Acta.objetivo.nombre, Acta.secuencia), align='C', newpage=False)
maindoc.Titulo2(u"Listado de Asistentes", align='C', Tight=True)
maindoc.Titulo2(u"Fecha: {}".format(Acta.horaini.strftime('%d/%m/%Y')), align='C', Tight=True)
count = 0
w = maindoc.AnchoFirma(Acta.asistentes)
for asistente in Acta.asistentes:
if count>0 and count % 11 == 0:
maindoc.add_page()
maindoc.ln(25)
count = count + 1
# maindoc.ln(5)
maindoc.Firma("{}) {}".format(count, asistente),Ancho=w)
maindoc.output(name=Archivo, dest='F')
return "Lista acta {}-{} {}.pdf".format(Acta.secuencia, Acta.objetivo.nombre, maindoc.serial)
def ActaPDF(Archivo, Acta):
logo, titulo = logoyencabezado(Acta.objetivo)
maindoc = plantillaReporte(Titulo='Acta {}: {}'.format(Acta.secuencia, Acta.objetivo.nombre), Logo=logo, Encabezado=titulo)
maindoc = ReporteActa(maindoc, Acta)
paginas_en_blanco = maindoc.paginas_en_blanco
anexos_pdf = maindoc.anexospdf
paginas_reporte = maindoc.page_no()
current_app.logger.debug(u'PDF original tiene {} páginas'.format(paginas_reporte))
with NamedTemporaryFile() as tmp:
maindoc.output(name=tmp.name, dest='F')
merger = PdfFileMerger(strict=False)
pagina = 0
while pagina < paginas_reporte:
if pagina not in paginas_en_blanco:
merger.append(tmp.name, pages=(pagina, pagina+1))
pagina = pagina + 1
else:
archivo_pdf, largo = anexos_pdf.pop(0)
merger.append(archivo_pdf)
pagina = pagina + largo
merger.write(Archivo)
merger.close()
return "acta {}-{} {}.pdf".format(Acta.secuencia, Acta.objetivo.nombre, maindoc.serial)
def ReporteActa(documento, Acta):
documento.add_page()
documento.PutLogoCenter()
documento.Titulo1(u'Acta {}'.format(Acta.secuencia), align='C', newpage=False)
documento.Titulo1(Acta.objetivo.nombre, align='C', newpage=False)
documento.LugaryFecha(Acta.lugar, Acta.horaini, Acta.horafin)
documento.Asistentes(Acta.asistentes)
documento.TituloParrafo(u'Temas', Acta.temas)
if len(Acta.desarrollo):
documento.TituloParrafo(u'Desarrollo', Acta.desarrollo)
if len(Acta.acuerdos):
documento.TituloParrafo(u'Acuerdos', Acta.acuerdos)
links = []
fotos = []
pdfs = []
evidencias = 0
for evidencia in Acta.anexos:
if evidencia.documento.nombre[:7] == 'http://' or evidencia.documento.nombre[:8] == 'https://':
# current_app.logger.debug(u'Link detectado: {}'.format(evidencia.documento.nombre))
links.append((evidencia.documento.nombre, evidencia.documento.descripcion))
evidencias = evidencias + 1
elif evidencia.documento.tamano > 0:
nombre, extension = os.path.splitext(evidencia.documento.nombre.lower())
if extension in ['.jpg', '.jpeg', '.png']:
evidencias = evidencias + 1
fotos.append(evidencia)
if extension in ['.pdf']:
evidencias = evidencias + 1
pdfs.append(evidencia)
if evidencias > 0:
if len(links) > 0:
documento.Titulo2('Anexos')
for link in links:
documento.Link(link)
if len(fotos) > 0:
documento.FotoEvidencia(fotos)
if len(pdfs) > 0:
largo = 0
for evidencia in pdfs:
archivo = PdfFileReader(open(evidencia.documento.get_file(), "rb"))
largo = largo + archivo.getNumPages()
documento.anexospdf = (evidencia.documento.get_file(), archivo.getNumPages())
documento.pagina_en_blanco(largo)
return documento
def ReporteNodo(documento, Nodo, Recursive=True, Fini=None, Ffin=None, anexoi=0, anexos={}):
documento.Titulo1(Nodo.nombre)
if isinstance(Fini,date) and isinstance(Ffin, date) and Fini < Ffin:
rango = True
DTini = datetime(Fini.year, Fini.month, Fini.day, 0, 0, 0)
DTfin = datetime(Ffin.year, Ffin.month, Ffin.day, 23, 59, 59)
else:
rango = False
if Nodo.sub_HDS < 1:
documento.Item(Sujeto='Dedicación al objetivo:',Predicado= u'{} Horas cronológicas'.format(round(Nodo.sub_horas,1)), Ancho=55)
else:
documento.Item(Sujeto='Dedicación al objetivo:',Predicado= u'{} Horas cronológicas ({} Horas de dedicación semanal)'.format(round(Nodo.sub_horas,1), Nodo.sub_HDS), Ancho=55)
if len(Nodo.descripcion)>0:
documento.Titulo3("Descripción del Objetivo")
documento.Parrafo(Nodo.descripcion)
# indicadores
eactas = 0
for Acta in Nodo.actasr:
if rango and (Acta.horaini < DTini or Acta.horaini > DTfin):
continue
if eactas == 0:
documento.Titulo2("Actas")
documento.Indicador(Glosa="Acta/Lugar", vIni="Fecha", vAct="Hora", vFin="Fin", Titulo=True, ancho=25)
eactas = eactas + 1
documento.Indicador(Glosa="Acta {}: {}".format(Acta.secuencia, Acta.lugar), vIni=Acta.horaini.strftime('%d/%m/%Y'), vAct=Acta.horaini.strftime('%H:%M'), vFin=Acta.horafin.strftime('%H:%M'), ancho=25)
# indicadores
eind = 0
for Indicador in Nodo.indicadores:
if eind == 0:
documento.Titulo2("Indicadores")
documento.Indicador(Glosa="Indicador", vIni="Inicial", vAct="Actual", vFin="Meta", Titulo=True)
eind = eind + 1
documento.Indicador(Glosa=Indicador.nombre, vIni=Indicador.inicial, vAct=Indicador.valor, vFin=Indicador.meta)
# Actividades
if Recursive is True:
anexos = {}
eact = 0
for Accion in Nodo.iactividades:
if rango and (Accion.fecha < Fini or Accion.fecha > Ffin):
continue
if eact == 0:
documento.Titulo2("Actividades Realizadas", border='b', Tight=False)
eact = eact + 1
documento.Titulo2(u"Actividad {}: {}".format(eact, Accion.nombre))
documento.Item(Sujeto='Fecha:',Predicado= u'{}'.format(Accion.fecha.strftime('%d/%m/%Y')), Ancho=20)
if Accion.dedicacion < 26:
documento.Item(Sujeto='Dedicación:',Predicado= u'{} Horas cronológicas'.format(Accion.dedicacion), Ancho=30)
else:
documento.Item(Sujeto='Dedicación:',Predicado= u'{} Horas cronológicas ({} Horas de dedicación semanal)'.format(Accion.dedicacion, round(Accion.dedicacion/26,2)), Ancho=30)
if len(Accion.descripcion)>0:
documento.Titulo3(u"Descripción de la Actividad")
documento.Parrafo(Accion.descripcion)
evidencias = 0
links = []
for evidencia in Accion.evidencias:
if evidencia.documento.nombre[:7] == 'http://' or evidencia.documento.nombre[:8] == 'https://':
links.append((evidencia.documento.nombre, evidencia.documento.descripcion))
elif evidencia.documento.tamano > 0:
nombre, extension = os.path.splitext(evidencia.documento.nombre.lower())
if extension in ['.jpg', '.jpeg', '.png', '.pdf']:
evidencias = evidencias + 1
if evidencias > 0 or len(links) > 0:
documento.Titulo3('Evidencias')
if len(links)>0:
for link in links:
documento.Link(link)
if evidencias > 0:
if anexoi > 25:
v1 = chr(ord('A') + int(anexoi/26) - 1)
v2 = chr(ord('A') + anexoi%26)
v = '{}{}'.format(v1,v2)
else:
v = chr(ord('A') + anexoi)
anexoi = anexoi + 1
anexos[v] = Accion
documento.Parrafo(u'Ver Anexo {}.'.format(v))
if Recursive is True:
for hijo in Nodo.hijos:
documento, anexoi, anexos = ReporteNodo(documento, hijo, Recursive=False, Fini=Fini, Ffin=Ffin, anexoi=anexoi, anexos=anexos)
else:
return (documento, anexoi, anexos)
eacta = 0
for Acta in Nodo.actasr:
if rango and (Acta.horaini < DTini or Acta.horaini > DTfin):
continue
if eacta == 0:
eacta = 1
documento.Tapa('Anexo Actas')
documento = ReporteActa(documento, Acta)
if Recursive is True:
for hijo in Nodo.hijos:
for Acta in hijo.actas:
if rango and (Acta.horaini < DTini or Acta.horaini > DTfin):
continue
if eacta == 0:
eacta = 1
documento.Tapa('Anexo Actas')
documento = ReporteActa(documento, Acta)
for letra, anexo in anexos.items():
documento.Tapa('Anexo {}'.format(letra))
fotos = []
pdfs = []
for evidencia in anexo.evidencias:
nombre, extension = os.path.splitext(evidencia.documento.nombre.lower())
if extension in ['.jpg', '.jpeg', '.png']:
fotos.append(evidencia)
if extension in ['.pdf']:
pdfs.append(evidencia)
if len(fotos) > 0:
documento.FotoEvidencia(fotos)
if len(pdfs) > 0:
largo = 0
for evidencia in pdfs:
archivo = PdfFileReader(open(evidencia.documento.get_file(), "rb"))
largo = largo + archivo.getNumPages()
documento.anexospdf = (evidencia.documento.get_file(), archivo.getNumPages())
documento.pagina_en_blanco(largo)
return (documento, anexoi, anexos)
class plantillaReporte(FPDF):
def __init__(self, Titulo = None, Logo = None, Encabezado = None):
super(plantillaReporte, self).__init__('P', 'mm', 'Letter')
self.set_compression(True)
self.add_font('dejavu', '', fname='/srv/font/DejaVuSerif.ttf', uni=True)
self.add_font('dejavu', 'B', fname='/srv/font/DejaVuSerif-Bold.ttf', uni=True)
self.add_font('dejavu', 'I', fname='/srv/font/DejaVuSerif-Italic.ttf', uni=True)
self.add_font('dejavu', 'BI', fname='/srv/font/DejaVuSerif-BoldItalic.ttf', uni=True)
self.__titulo = Titulo
self.__ancho = 215.9 # 190 / 185
self.__alto = 279.4 # 125 / 120
self.__columna = 90
self.__limiteT2 = 0.8 * self.__alto
self.__limiteT3 = 0.85 * self.__alto
self.__codigo = datetime.today().strftime('%Y%m%d%H.%M%S')
self.__blanks = []
self.__pdf = []
self.__subtitulo = None
self.__h1 = 14
self.__h2 = 12
self.__h3 = 10
self.__logo = Logo
self.__encabezado = Encabezado
# self.__fecha = datetime.today().strftime('%d/%m/%Y a las %H horas')
# self.__titulos = []
# self.__ptitulos = []
# Encabezado
def header(self):
if self.__titulo is not None and self.page_no() > 1:
self.set_y(1)
self.cell(0, 5+self.__h1, u'', border='B', ln=1, align='C')
if self.__encabezado is not None:
imLogo = Image.open(os.path.abspath(self.__encabezado))
w, h = imLogo.size
ancho = self.__h2 * w / h
_, extension = os.path.splitext(self.__logo)
self.image(self.__encabezado, x=10, y=6, w=ancho, type=extension[1:])
else:
self.set_font('dejavu', 'B', self.__h2)
self.set_y(5)
self.cell(110, self.__h1, self.__titulo, border=0, ln=0, align='L')
# if self.__subtitulo is not None:
# self.cell(0, 10, self.__subtitulo, border=0, ln=0, align='R')
self.ln(self.__h2)
# Pie de Página
def footer(self):
# Position at 1.5 cm from bottom
if self.page_no() > 1:
self.set_y(-15)
self.cell(0, 8, u'', border='T', ln=1, align='C')
self.set_y(-15)
self.set_font('dejavu', '', 3)
self.cell(60, self.__h3, u'Versión: {}'.format(self.__codigo), border=0, ln=0, align='L')
self.set_font('dejavu', 'I', self.__h3)
self.cell(0, self.__h3, u'Página {}'.format(self.page_no()), border=0, ln=0, align='R')
def pagina_en_blanco(self, Num = 1):
for i in range(Num):
self.Tapa(u"Esta página fue dejada intencionalmente en blanco")
self.__blanks.append(self.page_no()-1)
@property
def anexospdf(self):
return self.__pdf
@anexospdf.setter
def anexospdf(self, valor):
self.__pdf.append(valor)
@property
def paginas_en_blanco(self):
return self.__blanks
@property
def serial(self):
return self.__codigo
def Tapa(self, Titulo, Linea2=None, Linea3=None, Logo=False):
if self.page_no() > 1:
self.__subtitulo = Titulo
self.add_page()
if self.__logo is not None and Logo is True:
self.ln(self.__h1)
self.ln(self.__h2)
self.PutLogoCenter()
if Linea2 or Linea3:
self.set_y((self.__alto-40)/2)
else:
self.set_y((self.__alto-20)/2)
self.set_font('dejavu', 'B', self.__h1+2)
self.multi_cell(0, self.__h1, Titulo, border=0, align='C')
if Linea2 is not None:
self.set_font('dejavu', 'B', self.__h1)
self.multi_cell(0, self.__h2, Linea2, border=0, align='C')
if Linea3 is not None:
self.set_font('dejavu', '', self.__h2)
self.multi_cell(0, self.__h3, Linea3, border=0, align='C')
def PutLogoCenter(self):
if self.__logo is not None:
yini = self.get_y()
imLogo = Image.open(os.path.abspath(self.__logo))
w, h = imLogo.size
y = self.__columna * h / w
xLogo = (self.__ancho - self.__columna) / 2
_, extension = os.path.splitext(self.__logo)
# current_app.logger.debug(u'Logo {} {} {} {} {}'.format(xLogo,w,h,y,extension))
self.image(self.__logo, x=xLogo, y=yini, w=self.__columna, type=extension[1:])
yfin = yini + y
self.set_y(yfin)
def Titulo1(self, Titulo, align='L', newpage=True):
if newpage:
self.add_page()
# self.__titulos.append(Titulo)
# self.__ptitulos.append(self.page_no())
self.set_font('dejavu', 'B', self.__h1)
self.multi_cell(0, self.__h1, Titulo, border=0, align=align)
# self.cell(0, self.__h1, Titulo, border=0, ln=0, align=align)
# self.ln(self.__h1)
def Titulo2(self, Titulo, border=0, align='L', Tight=True):
if not Tight:
self.ln(self.__h3)
if self.get_y() > self.__limiteT2:
self.add_page()
self.set_font('dejavu', 'B', self.__h2)
self.multi_cell(0, self.__h2, Titulo, border=0, align=align)
# self.cell(0, self.__h2, Titulo, border=border, ln=0, align=align)
# self.ln(self.__h2)
def Titulo3(self, Titulo):
if self.get_y() > self.__limiteT3:
self.add_page()
self.set_font('dejavu', 'B', self.__h3)
self.multi_cell(0, self.__h3, Titulo, border=0, align='L')
# self.cell(0, self.__h3, Titulo, border=0, ln=0, align='L')
# self.ln(self.__h3)
def Parrafo(self, Texto):
self.Normal()
self.multi_cell(0, self.__h3, Texto, align='J')
# self.ln(self.__h3)
def Normal(self):
self.set_font('dejavu', '', self.__h3)
def LimpiaColordeFondo(self):
self.set_fill_color(1, 1, 1)
def ColordeFondo(self):
self.set_fill_color(230, 230, 230)
def TituloParrafo(self, Titulo, Parrafo):
self.set_font('dejavu', 'B', self.__h2)
self.cell(0, self.__h2, Titulo, border=0, ln=1, align='L')
self.ColordeFondo()
self.Normal()
self.multi_cell(0, self.__h3, Parrafo, align='J', fill=True)
self.ln(self.__h3)
def LugaryFecha(self, lugar, horaini, horafin):
self.ColordeFondo()
self.set_font('dejavu', 'B', self.__h2)
self.cell(0, self.__h2, "Lugar", border=0, ln=1, align='L')
self.set_font('dejavu', '', self.__h2)
self.cell(0, self.__h2, lugar, border=0, ln=1, align='L', fill = True)
self.set_font('dejavu', 'B', self.__h2)
self.cell(67, self.__h2, 'Fecha', border=0, ln=0, align='L')
self.cell(67, self.__h2, 'Inicio', border=0, ln=0, align='L')
self.cell(61, self.__h2, u'Término', border=0, ln=1, align='L')
self.set_font('dejavu', '', self.__h2)
self.cell(61, self.__h2, horaini.strftime('%d/%m/%Y'), border=0, ln=0, align='C', fill = True)
self.cell(6, self.__h2, '', border=0, ln=0, align='L')
self.cell(61, self.__h2, horaini.strftime('%H:%M'), border=0, ln=0, align='C', fill = True)
self.cell(6, self.__h2, '', border=0, ln=0, align='L')
self.cell(61, self.__h2, horafin.strftime('%H:%M'), border=0, ln=1, align='C', fill = True)
def Asistentes(self, asistentes):
self.ColordeFondo()
self.set_font('dejavu', 'B', self.__h2)
self.cell(0, self.__h2, "Convocados", border=0, ln=1, align='L')
count = 0
for asistente in asistentes:
self.set_font('dejavu', '', self.__h3)
rcells = math.ceil(self.get_string_width(asistente)/61)
if rcells == 1:
w = 61
elif rcells == 2:
w = 128
else:
w = 195
if ((count + rcells) % 3 <= count % 3):
self.set_font('dejavu', '', self.__h3)
self.cell(w, self.__h3, asistente, border=0, ln=1, align='C', fill = True)
else:
self.set_font('dejavu', '', self.__h3)
self.cell(w, self.__h3, asistente, border=0, ln=0, align='C', fill = True)
self.cell(6, self.__h3, '', border=0, ln=0, align='L')
count = count + rcells
if count == 0:
self.set_font('dejavu', '', self.__h3)
self.cell(0, self.__h3, 'No existen convocados', border=0, ln=1, align='L')
elif count % 3 != 0:
self.ln(self.__h2)
def Indicador(self, Glosa, vIni, vAct, vFin, Titulo = False, ancho=16):
if Titulo:
self.set_font('dejavu', 'B', self.__h3)
else:
self.set_font('dejavu', '', self.__h3)
top = self.get_y()
anchoglosa = 195 - 3*ancho
self.multi_cell(anchoglosa, self.__h3, str(Glosa), border=1, align='J')
bottom = self.get_y()
if bottom < top: # si el final está en la siguiente página, partimos la celda desde arriba.
top = 20
altura = bottom - top # 125-45 = 80 | 125-36 = 89
self.set_y(top)
self.set_x(anchoglosa+10)
# current_app.logger.debug(u'Indicador: top:{} bottom:{} altura:{} anchoglosa:{}'.format(top,bottom,altura,anchoglosa))
self.cell(ancho, altura, str(vIni), border=1, ln=0, align='C')
self.cell(ancho, altura, str(vAct), border=1, ln=0, align='C')
self.cell(ancho, altura, str(vFin), border=1, ln=1, align='C')
self.set_y(bottom)
def AnchoFirma(self, asistentes):
w = 0
self.set_font('dejavu', '', self.__h1)
for asistente in asistentes:
w = max(w, self.get_string_width(asistente))
return w+10
def Firma(self, Sujeto, Ancho=80, Titulo=False):
if Titulo:
self.set_font('dejavu', 'B', self.__h1)
else:
self.set_font('dejavu', '', self.__h1)
self.cell(Ancho, self.__h1, Sujeto , ln=0, align='L')
self.cell(0 , self.__h1, '' , border='B', ln=1)
def Item(self, Sujeto, Predicado, Ancho=80, borde=0, ln=0, alineacion='L'):
self.set_font('dejavu', 'I', self.__h2)
self.cell(Ancho, self.__h3, Sujeto, border=borde, ln=0, align=alineacion)
self.set_font('dejavu', '', self.__h2)
self.cell(0, self.__h3, Predicado, border=borde, ln=1, align=alineacion)
def Link(self, linktupla):
link, descripcion = linktupla
self.set_font('dejavu', '', self.__h2)
self.cell(35, self.__h3, u'Link Externo:', border=0, ln=0, align='L')
self.set_text_color(r=0, g=0, b=255)
if len(descripcion) > 60:
self.cell(0, self.__h3, u"{}...".format(descripcion[:60]), border=0, ln=1, align='L', link=link)
elif len(descripcion) > 0:
self.cell(0, self.__h3, descripcion, border=0, ln=1, align='L', link=link)
elif len(link) > 60:
self.cell(0, self.__h3, u"{}...".format(link[:60]), border=0, ln=1, align='L', link=link)
else:
self.cell(0, self.__h3, link, border=0, ln=1, align='L', link=link)
self.set_text_color(r=0, g=0, b=0)
def FotoEvidencia(self, fotos):
self.add_page()
nfotos = len(fotos)
filas = int(nfotos/2)
sobran = nfotos % 2
seq = 0
self.set_font('dejavu', '', self.__h2)
for fila in range(filas):
primera = seq
segunda = primera + 1
seq = seq + 2
# Obtenemos el tamaño de la imagen
im1 = Image.open(os.path.abspath(fotos[primera].documento.get_file()))
w1, h1 = im1.size
im2 = Image.open(os.path.abspath(fotos[segunda].documento.get_file()))
w2, h2 = im2.size
#Calculamos la altura a medio espacio
y1 = self.__columna * h1 / w1
y2 = self.__columna * h2 / w2
# Vemos si tenemos suficiente espacio en la hoja para poner ambas imagenes
ymax = max(y1, y2)
if ymax + self.get_y() > self.__limiteT3:
self.add_page()
# Guardamos la posición inicial antes de pegar las imagenes y sus captions
posicionYinicial = self.get_y()
## Primera imagen
yImagenCentrada = posicionYinicial + ymax - y1
xColumna1 = 10
_, extension = os.path.splitext(fotos[primera].documento.nombre)
self.image(fotos[primera].documento.get_file(), x=xColumna1, y=yImagenCentrada, w=self.__columna, type=extension[1:])
yImagenCentrada = posicionYinicial + ymax - y2
xColumna2 = 25 + self.__columna
_, extension = os.path.splitext(fotos[segunda].documento.nombre)
self.image(fotos[segunda].documento.get_file(), x=xColumna2, y=yImagenCentrada, w=self.__columna, type=extension[1:])
# Captions de las imagenes
self.set_x(10)
self.set_y(posicionYinicial + ymax + self.__h3)
self.multi_cell(w=self.__columna, h=self.__h3, txt=fotos[primera].documento.descripcion or fotos[primera].documento.nombre, border=0, align='C')
y1 = self.get_y()
self.set_y(posicionYinicial + ymax + self.__h3)
self.set_x(xColumna2)
self.multi_cell(w=self.__columna, h=self.__h3, txt=fotos[segunda].documento.descripcion or fotos[segunda].documento.nombre, border=0, align='C')
y2 = self.get_y()
# Dejamos el cursor bajo ambos captions
self.set_y(max(y1,y2))
self.ln(self.__h3)
if sobran > 0:
seq = nfotos-1
im1 = Image.open(os.path.abspath(fotos[seq].documento.get_file()))
w1, h1 = im1.size
y1 = self.__columna * h1 / w1
if y1 + self.get_y() > self.__limiteT2:
self.add_page()
# Guardamos la posición inicial antes de pegar las imagenes y sus captions
posicionYinicial = self.get_y()
xColumna1 = 10 + self.__columna / 2
_, extension = os.path.splitext(fotos[seq].documento.nombre)
self.image(fotos[seq].documento.get_file(), x=xColumna1, y=posicionYinicial, w=self.__columna, type=extension[1:])
self.set_y(posicionYinicial + y1 + self.__h3)
self.set_x(xColumna1)
self.multi_cell(w=self.__columna, h=self.__h3, txt=fotos[seq].documento.descripcion or fotos[seq].documento.nombre, border=0, align='C')

View File

@ -0,0 +1,887 @@
# coding: utf-8
from flask import render_template, flash, redirect, send_file, url_for, request, Blueprint, current_app, abort, g
from flask_login import login_required, current_user
from webinterface import db
from sqlalchemy.sql import func
from webinterface.models.gestion import Objetivo, Comision, Miembro, Avance, Accion, AccesoGestion, ModuloGestion, AccesosModuloGestion, Acta, AnexosActa, MiembrosAsistentes, InvitadosAsistentes
import datetime
gestion = Blueprint('gestion', __name__)
@gestion.before_request
@login_required
def verifica_permisos():
pass
# @gestion.route("/gestion/nuevoobjetivo", methods=['POST'])
# def newsingle():
# cid = int(request.form['responsable'])
# fid = int(request.form['id'])
# nombre = request.form['nombre'].strip()
# if fid > 0 and cid > 0 and nombre:
# fobj = Objetivo.query.get(fid)
# nobj = Objetivo(nombre=nombre, tipo=fobj.tipo, parentid=fid, estado=0, responsableid=cid)
# db.session.add(nobj)
# db.session.commit()
# flash(u'Elemento "{}" generado con exito.'.format(nombre),'success')
# else:
# flash(u'Los datos ingresados son inválidos.','danger')
#
# return redirect(url_for('gestion.manager'))
@gestion.route("/objetivos/editmodulo", methods=['POST'])
def editmodulo():
# for key, values in request.form.items():
# current_app.logger.debug('request: {}: {}'.format(key, values))
try:
mid = int(request.form['id'])
except:
mid = 0
uri = request.form['uri'].strip().lower()
if not uri:
flash("Los datos ingresados son incorrectos: uri vacio", 'danger')
abort(400)
existeA = ModuloGestion.query.filter(ModuloGestion.id==mid).one_or_none()
existeB = ModuloGestion.query.filter(ModuloGestion.uri==uri).one_or_none()
if existeA is None and existeB is None:
flash("Creado el modulo: {}".format(request.form['nombre'].strip()), 'success')
nodobase = Objetivo(nombre=request.form['nombre'].strip(), tipo=request.form['icon'].strip().lower(), descripcion='gestion.{}'.format(uri), parentid=1, estado=0, responsableid=1)
db.session.add(nodobase)
if mid==0:
existe = ModuloGestion(nombre=request.form['nombre'].strip(), etiqueta=request.form['etiqueta'].strip().lower().capitalize(), uri=uri, nodobase=nodobase)
else:
existe = ModuloGestion(id=mid, nombre=request.form['nombre'].strip(), etiqueta=request.form['etiqueta'].strip().lower().capitalize(), uri=uri, nodobase=nodobase)
db.session.add(existe)
for value in request.form.getlist('comisiones'):
acceso = AccesosModuloGestion(comisionid=int(value), modulo=existe)
db.session.add(acceso)
# current_app.logger.debug('Acceso comision: {}'.format(value))
db.session.commit()
elif existeA is not None:
flash("Modificada el acceso del modulo: {}".format(existeA.nombre), 'success')
for exmiembro in AccesosModuloGestion.query.filter(AccesosModuloGestion.moduloid==mid).all():
# current_app.logger.debug('quita comision: {}'.format(exmiembro.comisionid))
db.session.delete(exmiembro)
db.session.commit()
existeA.nombre = request.form['nombre'].strip()
existeA.etiqueta = request.form['etiqueta'].strip().lower().capitalize()
existeA.uri = uri
existeA.nodobase.nombre = request.form['nombre'].strip()
existeA.nodobase.tipo = request.form['icon'].strip().lower()
existeA.nodobase.descripcion = 'gestion.{}'.format(uri)
for value in request.form.getlist('comisiones'):
acceso = AccesosModuloGestion(comisionid=int(value), modulo=existeA)
db.session.add(acceso)
# current_app.logger.debug('Acceso comision: {}'.format(value))
db.session.commit()
existe = existeA
elif existeB is not None and mid > 0:
flash("Modificada el acceso del modulo: {}".format(existeB.nombre), 'success')
for exmiembro in AccesosModuloGestion.query.filter(AccesosModuloGestion.moduloid==existeB.id).all():
# current_app.logger.debug('quita comision: {}'.format(exmiembro.comisionid))
db.session.delete(exmiembro)
db.session.commit()
existeB.nombre = request.form['nombre'].strip()
existeB.etiqueta = request.form['etiqueta'].strip().lower().capitalize()
existeB.id = mid
existeB.nodobase.nombre = request.form['nombre'].strip()
existeB.nodobase.tipo = request.form['icon'].strip().lower()
existeB.nodobase.descripcion = 'gestion.{}'.format(uri)
for value in request.form.getlist('comisiones'):
acceso = AccesosModuloGestion(comisionid=int(value), modulo=existeB)
db.session.add(acceso)
# current_app.logger.debug('Acceso comision: {}'.format(value))
db.session.commit()
existe = existeB
else:
flash("Los datos ingresados son incorrectos", 'danger')
abort(400)
############################################################################
############# Mantenimieto de las accesos grupales #############
############################################################################
administradores = []
conacceso = existe.mvector()
for item in Comision.query.filter(Comision.id>999, Comision.creado==1).all():
administradores.append(item.id)
for administrador in administradores:
acceso = Objetivo.query.filter(Objetivo.parentid==existe.nodobase.id, Objetivo.responsableid==administrador).one_or_none()
if acceso is None and administrador in conacceso:
responsable = Comision.query.get(administrador)
acceso = Objetivo(nombre="{} {}".format(request.form['nombre'].strip(), responsable.nombre), descripcion=existe.nodobase.descripcion, tipo=existe.nodobase.tipo, responsableid=administrador, parentid=existe.nodobase.id, estado=0)
db.session.add(acceso)
elif acceso is not None:
acceso.tipo = existe.nodobase.tipo
acceso.descripcion = existe.nodobase.descripcion
if administrador in conacceso:
acceso.estado = 0
else:
acceso.estado = 100
db.session.commit()
return redirect(url_for('gestion.modulos'))
@gestion.route("/objetivos/nuevacomision", methods=['POST'])
def newcomision():
cid = int(request.form['id'])
if cid == 0:
ncomision = Comision(nombre=request.form['nombre'].strip(), creado=int(request.form['admin']))
db.session.add(ncomision)
for value in request.form.getlist('miembros'):
miembro = Miembro(personaid=int(value), comision=ncomision)
db.session.add(miembro)
# current_app.logger.debug('crea miembro: {}'.format(value))
db.session.commit()
else:
for exmiembro in Miembro.query.filter_by(comisionid=cid).all():
# current_app.logger.debug('quita miembro: {}'.format(exmiembro.personaid))
db.session.delete(exmiembro)
db.session.commit()
ncomision = Comision.query.get(cid)
ncomision.nombre=request.form['nombre'].strip()
for value in request.form.getlist('miembros'):
miembro = Miembro(personaid=int(value), comision=ncomision)
db.session.add(miembro)
# current_app.logger.debug('agrega miembro: {}'.format(value))
db.session.commit()
if ncomision.creado==1 and cid==0:
return redirect(url_for('gestion.manager', admin=1))
elif ncomision.creado==1 and cid>0:
return redirect(url_for('gestion.manager', admin=cid))
else:
return redirect(url_for('gestion.manager', admin=ncomision.creado))
@gestion.route("/objetivos/delcomision/<int:comisionid>", methods=['GET'])
def delcomision(comisionid):
todel = Comision.query.filter(Comision.id == comisionid).first()
if todel is None:
abort(404)
if todel.creado == 1:
abort(403)
canI = Miembro.query.filter(Miembro.personaid==current_user.id, Miembro.comisionid==todel.creado).first()
if canI is None:
abort(403)
responsable = todel.creado
nombre = todel.nombre
for item in Objetivo.query.filter(Objetivo.responsableid==todel.id).all():
item.responsableid = responsable
for item in Objetivo.query.filter(Objetivo.invitadosid==todel.id).all():
item.invitadosid = None
for item in Miembro.query.filter(Miembro.comisionid==todel.id).all():
db.session.delete(item)
db.session.delete(todel)
db.session.commit()
flash(u"Comision '{}' eliminada con éxito".format(nombre), 'success')
return redirect(url_for('gestion.manager', admin=responsable))
@gestion.route("/objetivos/syscomision", methods=['POST'])
def syscomision():
cid = int(request.form['id'])
new = Comision.query.filter(Comision.id == cid).first()
if cid == 0:
ncomision = Comision(nombre=request.form['nombre'].strip(), creado=1)
db.session.add(ncomision)
for value in request.form.getlist('miembros'):
miembro = Miembro(personaid=int(value), comision=ncomision)
db.session.add(miembro)
# current_app.logger.debug('crea miembro: {}'.format(value))
db.session.commit()
elif new is None:
ncomision = Comision(id=cid, nombre=request.form['nombre'].strip(), creado=1)
db.session.add(ncomision)
for value in request.form.getlist('miembros'):
miembro = Miembro(personaid=int(value), comision=ncomision)
db.session.add(miembro)
# current_app.logger.debug('crea miembro: {}'.format(value))
db.session.commit()
else:
for exmiembro in Miembro.query.filter_by(comisionid=cid).all():
# current_app.logger.debug('quita miembro: {}'.format(exmiembro.personaid))
db.session.delete(exmiembro)
db.session.commit()
ncomision = Comision.query.get(cid)
ncomision.nombre=request.form['nombre'].strip()
for value in request.form.getlist('miembros'):
miembro = Miembro(personaid=int(value), comision=ncomision)
db.session.add(miembro)
# current_app.logger.debug('agrega miembro: {}'.format(value))
db.session.commit()
############################################################################
############# Mantenimieto del acceso alas tareas personales #############
############################################################################
for item in Objetivo.query.filter(Objetivo.parentid==2, Objetivo.estado<100).all():
item.estado=100
db.session.commit()
personal = []
for item in Miembro.query.filter(Miembro.comisionid==2).all():
personal.append(item.personaid)
for personaid in personal:
from ilab_app.models.system import Persona
theuser = Persona.query.get(personaid)
comisionpersonal = Comision.query.filter(Comision.nombre=="Personal {}".format(theuser.login), Comision.creado==2).one_or_none()
if comisionpersonal is None:
comisionpersonal = Comision(nombre="Personal {}".format(theuser.login), creado=2)
db.session.add(comisionpersonal)
db.session.commit()
miembrocomisonpersoanal = Miembro.query.filter(Miembro.comisionid==comisionpersonal.id).first()
if miembrocomisonpersoanal is None:
miembrocomisonpersoanal = Miembro(comisionid=comisionpersonal.id, personaid=personaid)
db.session.add(miembrocomisonpersoanal)
objetivopersonal = Objetivo.query.filter(Objetivo.responsableid==comisionpersonal.id, Objetivo.parentid==2).one_or_none()
if objetivopersonal is None:
objetivopersonal = Objetivo(tipo='user-lock', nombre="{}".format(theuser.nombrecompleto), responsableid=comisionpersonal.id, parentid=2, estado=0)
db.session.add(objetivopersonal)
else:
objetivopersonal.estado=0
db.session.commit()
return redirect(url_for('gestion.manager', admin=1))
@gestion.route("/gestion/actividad", methods=['POST'])
def actividad():
# for key, values in request.form.items():
# current_app.logger.debug('request: {}: {}'.format(key, values))
try:
oid = int(request.form['oid'])
pid = int(request.form['pid'])
fecha = datetime.datetime.strptime(request.form['fecha'], '%d/%m/%Y').date()
except Exception as e:
flash("Los datos ingresados son incorrectos: {}".format(str(e)), 'danger')
abort(400)
try:
dedicacion = int(request.form['dedicacion'])
except Exception as e:
dedicacion = 0
if oid > 0:
item = Accion.query.get(oid)
item.nombre = request.form['nombre'].strip()[:100]
item.descripcion = request.form['descripcion'].strip()
item.dedicacion = dedicacion
item.fecha = fecha
item.modificadopor = current_user.id
# item.modificado = datetime.datetime.now()
db.session.commit()
elif pid > 0:
item = Accion(objetivoid=pid, nombre=request.form['nombre'].strip()[:100], descripcion=request.form['descripcion'].strip(),
fecha=fecha, estado=0, dedicacion=dedicacion)
item.creadopor = current_user.id
# item.creado = datetime.datetime.now()
item.modificadopor = current_user.id
# item.modificado = datetime.datetime.now()
db.session.add(item)
db.session.commit()
return redirect(url_for('gestion.show', objetivoid=item.objetivoid))
@gestion.route("/gestion/acta", methods=['POST'])
def acta():
# for key, values in request.form.items():
# current_app.logger.debug('request: {}: {}'.format(key, values))
try:
aid = int(request.form['aid'])
pid = int(request.form['pid'])
except Exception as e:
import traceback
current_app.logger.error('Traceback {}'.format(traceback.format_exc()))
flash("Los datos ingresados son incorrectos: {}".format(str(e)), 'danger')
abort(400)
try:
lugar = request.form['lugar'].strip()
d, m, y = request.form['fecha'].split('/',2)
hi, mi = request.form['hini'].split(':',2)
hf, mf = request.form['hfin'].split(':',2)
except Exception as e:
import traceback
current_app.logger.error('Traceback {}'.format(traceback.format_exc()))
abort(400)
horaini = datetime.datetime.strptime('{}/{}/{} {}:{}'.format(d,m,y,hi,mi), "%d/%m/%Y %H:%M")
horafin = datetime.datetime.strptime('{}/{}/{} {}:{}'.format(d,m,y,hf,mf), "%d/%m/%Y %H:%M")
if aid > 0:
item = Acta.query.filter(Acta.id==aid).one()
item.lugar = lugar
item.horaini = horaini
item.horafin = horafin
item.temas = request.form['temario']
item.desarrollo = request.form['desarrollo']
item.acuerdos = request.form['acuerdos']
item.modificadopor = current_user.id
# item.modificado = datetime.datetime.now()
# db.session.commit()
elif pid > 0:
cantidad = db.session.query(func.count(Acta.id)).filter(Acta.objetivoid==pid).scalar()
current_app.logger.debug('Contador {}'.format(cantidad))
seq = 1+int(cantidad)
item = Acta(objetivoid=pid, secuencia=seq, lugar=lugar, horaini=horaini, horafin=horafin, temas=request.form['temario'], desarrollo=request.form['desarrollo'], acuerdos=request.form['acuerdos'])
item.creadopor = current_user.id
# item.creado = datetime.datetime.now()
item.modificadopor = current_user.id
# item.modificado = datetime.datetime.now()
db.session.add(item)
else:
current_app.logger.error('aid = 0 y pid = 0')
abort(400)
db.session.commit()
miembros = []
for ma in MiembrosAsistentes.query.filter(MiembrosAsistentes.actaid==item.id).all():
miembros.append(ma.personaid)
for value in request.form.getlist('asistentes'):
v = int(value)
if v in miembros:
miembros.remove(v)
else:
db.session.add( MiembrosAsistentes(personaid=v, actaid=item.id) )
for v in miembros:
db.session.delete( MiembrosAsistentes.query.filter(MiembrosAsistentes.actaid==item.id, MiembrosAsistentes.personaid==v).one() )
invitados = []
for ia in InvitadosAsistentes.query.filter(InvitadosAsistentes.actaid==item.id).all():
invitados.append(ia.correo)
for value in request.form['invitados'].splitlines():
value = value.replace('\t', ' ')
try:
correo, nombre = value.strip().split(' ', 1)
except:
correo = value.strip()
nombre = ''
kcorreo = correo.strip().lower()
if kcorreo in invitados:
invitados.remove(kcorreo)
ikc = InvitadosAsistentes.query.filter(InvitadosAsistentes.actaid==item.id, InvitadosAsistentes.correo==kcorreo).one()
ikc.nombre = nombre.strip().title()
else:
db.session.add( InvitadosAsistentes(correo=kcorreo, actaid=item.id, nombre=nombre.strip().title()) )
for v in invitados:
db.session.delete( InvitadosAsistentes.query.filter(InvitadosAsistentes.actaid==item.id, InvitadosAsistentes.correo==v).one() )
db.session.commit()
flash(u"El Acta fue ingresado con éxito con el correlativo: {}".format(item.secuencia), 'success')
return redirect(url_for('gestion.show', objetivoid=item.objetivoid))
@gestion.route("/gestion/carpeta", methods=['POST'])
def carpeta():
# for key, values in request.form.items():
# current_app.logger.debug('request: {}: {}'.format(key, values))
try:
oid = int(request.form['oid'])
pid = int(request.form['pid'])
except Exception as e:
flash("Los datos ingresados son incorrectos: {}".format(str(e)), 'danger')
abort(400)
try:
dedicacion = int(request.form['dedicacion'])
except Exception as e:
dedicacion = 0
fecha = datetime.datetime.strptime(request.form['fecha'], '%d/%m/%Y').date()
if oid > 0:
item = Accion.query.get(oid)
item.nombre = request.form['nombre'].strip()[:100]
item.descripcion = request.form['descripcion'].strip()
item.dedicacion = dedicacion
item.fecha = fecha
item.modificadopor = current_user.id
# item.modificado = datetime.datetime.now()
db.session.commit()
elif pid > 0:
item = Accion(objetivoid=pid, nombre=request.form['nombre'].strip()[:100], descripcion=request.form['descripcion'].strip(),
fecha=fecha, estado=0, dedicacion=dedicacion)
item.creadopor = current_user.id
# item.creado = datetime.datetime.now()
item.modificadopor = current_user.id
# item.modificado = datetime.datetime.now()
db.session.add(item)
db.session.commit()
return redirect(url_for('gestion.documentos'))
@gestion.route("/gestion/edit", methods=['POST'])
def modificar():
# for key, values in request.form.items():
# current_app.logger.debug('request: {}: {}'.format(key, values))
try:
oid = int(request.form['oid'])
pid = int(request.form['pid'])
rid = int(request.form['responsable'])
except Exception as e:
flash("Los datos ingresados son incorrectos: {}".format(str(e)), 'danger')
abort(400)
try:
iid = int(request.form['invitado'])
except:
iid = None
finally:
if iid == 0:
iid = None
if oid > 0:
item = Objetivo.query.get(oid)
item.nombre = request.form['nombre'].strip()[:200]
item.descripcion = request.form['descripcion'].strip()
item.responsableid = rid
item.invitadosid = iid
item.modificadopor = current_user.id
# item.modificado = datetime.datetime.now()
db.session.commit()
elif pid > 0:
item = Objetivo(parentid=pid, nombre=request.form['nombre'].strip()[:200], descripcion = request.form['descripcion'].strip(),
responsableid = rid, invitadosid=iid, estado=0)
item.creadopor = current_user.id
item.modificadopor = current_user.id
# item.creado = datetime.datetime.now()
# item.modificado = datetime.datetime.now()
db.session.add(item)
db.session.commit()
if request.form['redirect']:
return redirect(request.form['redirect'])
else:
return redirect(url_for('gestion.show', objetivoid=item.id))
@gestion.route("/gestion/indicador", methods=['POST'])
def indicador():
# for key, values in request.form.items():
# current_app.logger.debug('request: {}: {}'.format(key, values))
try:
oid = int(request.form['oid'])
pid = int(request.form['pid'])
except Exception as e:
flash("Los datos ingresados son incorrectos: {}".format(str(e)), 'danger')
abort(400)
try:
ini = int(request.form['inicial'])
val = int(request.form['valor'])
tot = int(request.form['meta'])
except:
ini = 0
val = 0
tot = 100
if oid > 0:
item = Avance.query.get(oid)
item.nombre = request.form['nombre'].strip()[:100]
item.inicial = ini
item.valor = val
item.meta = tot
db.session.commit()
elif pid > 0:
item = Avance(objetivoid=pid, nombre=request.form['nombre'].strip()[:100],
inicial = ini, valor = val, meta = tot)
db.session.add(item)
db.session.commit()
return redirect(url_for('gestion.show', objetivoid=item.objetivoid))
@gestion.route("/gestion/subelogo", methods=['POST'])
def subelogo():
# for key, values in request.form.items():
# current_app.logger.debug('request: {}: {}'.format(key, values))
try:
cid = int(request.form['cid'])
tipologo = int(request.form['tipologo'])
except Exception as e:
flash(u"Los datos ingresados son incorrectos: {}".format(str(e)), 'danger')
abort(400)
comision = Comision.query.filter(Comision.id==cid).one_or_none()
if comision is None:
abort(404)
if comision.creado > 1:
abort(403)
permiso = Miembro.query.filter(Miembro.comisionid==comision.id, Miembro.personaid==current_user.id).one_or_none()
if permiso is None:
abort(403)
if 'documento' in request.files and len(request.files['documento'].filename) > 0:
from werkzeug.utils import secure_filename
import os
uploaded_file = request.files['documento']
name = secure_filename(uploaded_file.filename)
nombre, extension = os.path.splitext(name.lower())
if extension not in ['.png']:
flash(u"Solo se aceptan imágenes formato PNG", 'warning')
else:
from ilab_app.content.docs.utils import ingest_file
(item, previtem) = ingest_file()
if tipologo == 1:
flash(u"El logo fue subido con éxito", 'success')
comision.logo = item.hash
elif tipologo == 2:
flash(u"El encabezado de página fue subido con éxito", 'success')
comision.titulo = item.hash
db.session.commit()
return redirect(url_for('gestion.manager', admin=comision.id))
@gestion.route("/gestion/admin/<int:admin>")
@gestion.route("/gestion/admin/")
def manager(admin=0):
admindata = AccesoGestion()
current_app.logger.info('Admin Access: IP({}), USER({})'.format(g.ip.ipaddr, current_user.login))
if admin==0 or (admin not in admindata.admin):
adming = next(admindata.administrador())
return redirect(url_for('gestion.manager', admin=adming.id))
else:
return render_template('gestion/comisiones.html', title=u'Gestión', admindata=admindata, admin=admin, grupo='Responsabilidades', tag='gestion.manager')
@gestion.route("/gestion/modulos/")
def modulos():
admindata = AccesoGestion()
if not admindata.sysadmin:
current_app.logger.warning('Modulo Access Denied: IP({}), USER({})'.format(g.ip.ipaddr, current_user.login))
abort(403)
current_app.logger.info('Modulo Access: IP({}), USER({})'.format(g.ip.ipaddr, current_user.login))
return render_template('gestion/modulos.html', title=u'Módulos de Gestión', admindata=admindata, grupo='Responsabilidades', tag='gestion.modulos')
@gestion.route("/gestion/documentos/")
def documentos():
uri = "documentos"
modulo = ModuloGestion.query.filter(ModuloGestion.uri==uri).one_or_none()
if modulo is None:
current_app.logger.info('Acceso main {}: 404 IP({}), USER({})'.format(uri, g.ip.ipaddr, current_user.login))
abort(404)
accessdata = AccesoGestion()
if modulo not in accessdata.usuario:
current_app.logger.info('Acceso main {}: 403 IP({}), USER({})'.format(uri, g.ip.ipaddr, current_user.login))
abort(403)
return render_template('gestion/documentos.html', title=u'Almacen de {}'.format(modulo.nombre), root=modulo.nodobase, label=modulo.etiqueta, grupo='Responsabilidades', tag='gestion.{}'.format(modulo.uri), uri=uri)
@gestion.route("/gestion/<uri>")
def main(uri):
modulo = ModuloGestion.query.filter(ModuloGestion.uri==uri).one_or_none()
if modulo is None:
current_app.logger.info('Acceso main {}: 404 IP({}), USER({})'.format(uri, g.ip.ipaddr, current_user.login))
abort(404)
accessdata = AccesoGestion()
if modulo not in accessdata.usuario:
current_app.logger.info('Acceso main {}: 403 IP({}), USER({})'.format(uri, g.ip.ipaddr, current_user.login))
abort(403)
return render_template('gestion/resumenes.html', title=u'Tareas {}'.format(modulo.nombre), root=modulo.nodobase, label=modulo.etiqueta, grupo='Responsabilidades', tag='gestion.{}'.format(modulo.uri), uri=uri)
@gestion.route("/gestion/ver/<int:objetivoid>")
def show(objetivoid):
root = Objetivo.query.filter(Objetivo.id==objetivoid).one_or_none()
if root is None:
abort(404)
if not root.visible:
abort(403)
return render_template('gestion/objetivo.html', title=u'Tareas personales', subtitulo='Vista en Detalle', root=root, label=root.modulo.etiqueta, grupo='Responsabilidades', tag='gestion.{}'.format(root.modulo.uri))
@gestion.route("/gestion/pdf/listaacta/<int:actaid>")
def listapdf(actaid):
root = Acta.query.filter(Acta.id==actaid).one_or_none()
if root is None:
abort(404)
if not root.objetivo.visible:
abort(403)
from .reporte import ListaActaPDF
from tempfile import NamedTemporaryFile
import traceback
with NamedTemporaryFile() as tmp:
try:
nombre_reporte = ListaActaPDF(tmp.name, root)
with NamedTemporaryFile() as tmp2:
import subprocess
cmd = subprocess.Popen([u"/usr/bin/ps2pdf {} {}".format(tmp.name, tmp2.name)], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
cmd.wait()
return send_file(tmp2.name, as_attachment=True, download_name=nombre_reporte)
except:
current_app.logger.critical('Error al generar reporte: Objetivoid = {}'.format(actaid))
current_app.logger.debug('Traceback {}'.format(traceback.format_exc()))
abort(500)
@gestion.route("/gestion/pdf/acta/<int:actaid>")
def actapdf(actaid):
root = Acta.query.filter(Acta.id==actaid).one_or_none()
if root is None:
abort(404)
if not root.objetivo.visible:
abort(403)
from .reporte import ActaPDF
from tempfile import NamedTemporaryFile
import traceback
with NamedTemporaryFile() as tmp:
try:
nombre_reporte = ActaPDF(tmp.name, root)
with NamedTemporaryFile() as tmp2:
import subprocess
cmd = subprocess.Popen([u"/usr/bin/ps2pdf {} {}".format(tmp.name, tmp2.name)], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
cmd.wait()
return send_file(tmp2.name, as_attachment=True, download_name=nombre_reporte)
except:
current_app.logger.critical('Error al generar reporte: Objetivoid = {}'.format(actaid))
current_app.logger.debug('Traceback {}'.format(traceback.format_exc()))
abort(500)
@gestion.route("/gestion/xls/reporte/<int:objetivoid>", methods=['GET'])
def reportexls(objetivoid):
root = Objetivo.query.filter(Objetivo.id==objetivoid).one_or_none()
if root is None:
abort(404)
if not root.visible:
abort(403)
from openpyxl import Workbook
from ilab_app.models.system import Persona
from openpyxl.utils import get_column_letter
from tempfile import NamedTemporaryFile
wb = Workbook()
ws = wb.active
ws.title = root.nombre[:20]
ws.cell(column=1, row=1, value="Fecha")
ws.cell(column=2, row=1, value="Dedicación (Horas)")
ws.cell(column=3, row=1, value="Responsable")
ws.cell(column=4, row=1, value="Nombre")
ws.cell(column=5, row=1, value="Descripcion")
i = 1
for Accion in root.iactividades:
i = i + 1
ws.cell(column=1, row=i, value=Accion.fecha)
ws.cell(column=2, row=i, value=Accion.dedicacion)
if Accion.creadopor is not None:
creador = Persona.query.filter(Persona.id==Accion.creadopor).one_or_none()
ws.cell(column=3, row=i, value=creador.nombrecompleto)
ws.cell(column=4, row=i, value=Accion.nombre)
ws.cell(column=5, row=i, value=Accion.descripcion)
with NamedTemporaryFile() as tmp:
wb.save(tmp.name)
return send_file(tmp.name, as_attachment=True, download_name="Reporte_{}.xls".format(root.nombre[:20]))
@gestion.route("/gestion/pdf/reporte", methods=['POST'])
@gestion.route("/gestion/pdf/reporte/<int:objetivoid>", methods=['GET'])
def reporte(objetivoid=None):
if objetivoid==None:
try:
objetivoid = int(request.form['oid'])
except:
objetivoid = None
root = Objetivo.query.filter(Objetivo.id==objetivoid).one_or_none()
if root is None:
abort(404)
if not root.visible:
abort(403)
try:
Fini = datetime.datetime.strptime(request.form['fini'], '%d/%m/%Y').date()
Fend = datetime.datetime.strptime(request.form['fend'], '%d/%m/%Y').date()
current_app.logger.info('Se especifica rango de {} hasta {} para el reporte'.format(Fini, Fend))
except Exception as e:
import traceback
Fini = None
Fend = None
current_app.logger.info('No se especifica rango de fechas (valido) para el reporte')
from .reporte import ReportePDF
from tempfile import NamedTemporaryFile
import traceback
with NamedTemporaryFile() as tmp:
try:
nombre_reporte = ReportePDF(tmp.name, root, Fini, Fend)
# return send_file(tmp.name, as_attachment=True, download_name=nombre_reporte)
with NamedTemporaryFile() as tmp2:
import subprocess
cmd = subprocess.Popen([u"/usr/bin/ps2pdf {} {}".format(tmp.name, tmp2.name)], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
cmd.wait()
return send_file(tmp2.name, as_attachment=True, download_name=nombre_reporte)
except:
current_app.logger.critical('Error al generar reporte: Objetivoid = {}'.format(objetivoid))
current_app.logger.debug('Traceback {}'.format(traceback.format_exc()))
abort(500)
@gestion.route("/gestion/pdf/acta/<int:actaid>/<string:masivo>")
@gestion.route("/gestion/pdf/acta/<int:actaid>/")
def acta_copiame(actaid, masivo=None):
root = Acta.query.filter(Acta.id==actaid).one_or_none()
if root is None:
abort(404)
if not root.objetivo.visible:
abort(403)
from flask_mail import Message
from ilab_app import mail
from .reporte import ActaPDF
from tempfile import NamedTemporaryFile
import traceback
with NamedTemporaryFile() as tmp:
try:
nombre_reporte = ActaPDF(tmp.name, root)
with NamedTemporaryFile() as tmp2:
import subprocess
cmd = subprocess.Popen([u"/usr/bin/ps2pdf {} {}".format(tmp.name, tmp2.name)], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
cmd.wait()
timestamp = datetime.datetime.today().strftime('%Y%m%d%H.%M%S')
if masivo is None:
msg = Message('Copia de Acta {}, {}'.format(root.secuencia, root.objetivo.nombre), sender='noreply@ilab.cl', recipients=[current_user.correodefecto.correo])
else:
msg = Message('Copia de Acta {}, {}'.format(root.secuencia, root.objetivo.nombre), sender='noreply@ilab.cl', recipients=root.correosasistentes)
msg.body = '''Ésta acta corresponde a la reunión {},
Fecha : {} desde las {} hasta las {} horas
Ubicación: {}
El detalle de los temas a tratar y los citados a asistir se encuentra en el documento adjunto.
Favor no contestar este mensaje, fue generado automáticamente por: {} a las {}'''.format(root.secuencia, root.horaini.strftime('%d/%m/%Y'), root.horaini.strftime('%H:%M'), root.horafin.strftime('%H:%M'), root.lugar, current_user.nombrecompleto, timestamp)
with open(tmp.name, 'rb') as fp:
msg.attach(filename=nombre_reporte, content_type="application/pdf", data=fp.read(), disposition=None, headers=None)
mail.send(msg)
return redirect(url_for('gestion.show', objetivoid=root.objetivoid))
except:
current_app.logger.critical('Error al generar reporte: Objetivoid = {}'.format(actaid))
current_app.logger.debug('Traceback {}'.format(traceback.format_exc()))
abort(500)

View File

@ -0,0 +1,21 @@
# coding: utf-8
from flask import render_template, flash, redirect, send_file, url_for, request, Blueprint, current_app, abort, g
from flask_login import login_required, current_user
from webinterface import db
from sqlalchemy.sql import func
from webinterface.models.gtfs_work import ArchivosGTFS
import datetime
gtfs = Blueprint('gtfs', __name__)
@gtfs.before_request
@login_required
def verifica_permisos():
pass
@gtfs.route("/gtfs/resumen", methods=['GET'])
def resumen():
listado_gtfs = ArchivosGTFS.query.all()
return render_template('gtfs/resumen.html', listado_gtfs=listado_gtfs)

View File

@ -0,0 +1,217 @@
# coding: utf-8
from flask import render_template, flash, redirect, url_for, request, Blueprint, session as httpsession, g, current_app
from flask_login import login_user, current_user, logout_user, login_required
from user_agents import parse
from webinterface import db, bcrypt
from webinterface.models.system import Ipaddr, Dispositivo, Sesion, Identidad, Ruta, Registro, Conexion, Correo, Sitio, Persona
from webinterface.models.documentos import Documento
from webinterface.models.gestion import Comision, Miembro, Objetivo
from .utils import es_local
import datetime
## Core provee la funcionalidad basica de autentificación usando las credenciales de iLab
main = Blueprint('main', __name__)
@main.route("/")
@main.route("/home")
def home():
if current_user.is_authenticated:
return redirect(url_for('main.dashboard'))
else:
return redirect(url_for('main.login'))
@main.route("/dashboard")
@login_required
def dashboard():
return render_template('dashboard.html', title='Dashboard')
@main.route("/about")
def about():
return render_template('about.html', title='About')
@main.route("/login", methods=['GET', 'POST'])
def login():
return redirect('https://tpmc.ilab.cl/system/login')
@main.route("/logout")
def logout():
return redirect('https://tpmc.ilab.cl/system/logout')
@main.route("/me")
@login_required
def me():
image_file = url_for('static', filename='profile_pics/' + current_user.foto)
return render_template('system/me.html', title=u'¿Quién soy?',
image_file=image_file)
@main.before_app_first_request
def init_database():
try:
db.drop_all()
db.create_all()
id_internet = Identidad.query.filter(Identidad.login=='Internet').one_or_none()
if id_internet is None:
id_internet = Identidad(login='Internet')
db.session.add(id_internet)
id_intranet = Identidad.query.filter(Identidad.login=='Intranet').one_or_none()
if id_intranet is None:
id_intranet = Identidad(login='Intranet')
db.session.add(id_intranet)
id_buscador = Identidad.query.filter(Identidad.login=='Buscador').one_or_none()
if id_buscador is None:
id_buscador = Identidad(login='Buscador')
db.session.add(id_buscador)
clave_persona = bcrypt.generate_password_hash('hola1234').decode('utf-8')
ifiguero_persona = Persona.query.filter(login=='ifiguero').one_or_none()
if ifiguero_persona is None:
ifiguero_persona = Persona(login='ifiguero', clave=clave_persona, rut='13955977-0', nombres='Israel', apellidop='Figueroa')
db.session.add(ifiguero_persona)
else:
ifiguero_persona.clave=clave_persona
clave_tpmc = bcrypt.generate_password_hash('tpmc').decode('utf-8')
tpmc_persona = Persona.query.filter(login=='tpmc').one_or_none()
if tpmc_persona is None:
tpmc_persona = Persona(login='tpmc', clave=clave_tpmc, rut='tpmc', nombres='Transporte', apellidop='Público')
db.session.add(tpmc_persona)
else:
tpmc_persona.clave=clave_tpmc
db.session.commit()
uuid_nill = "00000000000000000000000000000000"
root_doc = Documento.query.filter(Documento.hash==uuid_nill).one_or_none()
if root_doc is None:
root_doc = Documento(hash=uuid_nill, nombre='Root', autorid=ifiguero_persona.id)
db.session.add(root_doc)
root_comision = Comision.query.filter(id==1).one_or_none()
if root_comision is None:
root_comision = Comision(id=1, creado=1, nombre='root')
db.session.add(root_comision)
db.session.commit()
ifiguero_comision = Miembro.query.filter(Miembro.personaid==ifiguero_persona.id, Miembro.comisionid==1).one_or_none()
if ifiguero_comision is None:
ifiguero_comision = Miembro(personaid=ifiguero_persona.id, comisionid=1)
db.session.add(ifiguero_comision)
root_object = Objetivo.query.filter(Objetivo.id==1).one_or_none()
if root_object is None:
root_object = Objetivo(id=1, parentid=1, responsableid=1, tipo='root', nombre='root')
db.session.add(root_object)
db.session.commit()
except:
import traceback
current_app.logger.debug(traceback.format_exc())
@main.before_app_request
def registra_sesion():
if request.headers.getlist("X-Forwarded-For"):
remote_ip = request.headers.getlist("X-Forwarded-For")[0]
else:
remote_ip = request.environ['REMOTE_ADDR']
uadata = parse(request.user_agent.string)
ip = Ipaddr.query.filter_by(ipaddr=remote_ip).one_or_none()
if ip is None:
ip = Ipaddr(ipaddr=remote_ip)
db.session.add(ip)
db.session.commit()
if 'sid' in httpsession:
sesion = Sesion.query.filter_by(id=httpsession['sid']).one_or_none()
if sesion is None:
httpsession.pop('sid', None)
else:
dispositivo = Dispositivo.query.filter_by(id=sesion.dispositivoid, parsed_ua=str(uadata)).one_or_none()
if dispositivo is None:
httpsession.pop('sid', None)
else:
conexion = Conexion.query.filter_by(ipaddrid=ip.id, sesionid=sesion.id).one_or_none()
Ident = sesion.identidad
if conexion is None:
conexion = Conexion(ipaddr=ip, sesion=sesion)
db.session.add(conexion)
db.session.commit()
if 'sid' not in httpsession:
dispositivo = Dispositivo.query.filter_by(useragent=request.user_agent.string).one_or_none()
if dispositivo is None:
dev = uadata.is_pc and "PC" or uadata.device.family
os = ("%s %s" % (uadata.os.family, uadata.os.version_string)).strip()
browser = ("%s %s" % (uadata.browser.family, uadata.browser.version_string)).strip()
dispositivo = Dispositivo(useragent=request.user_agent.string, dev=dev , os=os , browser=browser, parsed_ua=str(uadata))
db.session.add(dispositivo)
db.session.commit()
if uadata.is_bot:
Ident = Identidad.query.filter_by(login='Buscador').one()
elif es_local(remote_ip):
Ident = Identidad.query.filter_by(login='Intranet').one()
else:
Ident = Identidad.query.filter_by(login='Internet').one()
sesion = Sesion(identidad=Ident, dispositivo=dispositivo)
db.session.add(sesion)
db.session.commit()
conexion = Conexion(ipaddr=ip, sesion=sesion)
db.session.add(conexion)
db.session.commit()
# agregamos el registro asociado a la solicitud actual
# a la sesion que esta actualmente cargada. Dejamos la sesion guardada en
# la variable global g.
if '/reset_password' in str(request.path):
rpth = '/reset_password'
else:
rpth = str(request.path)
solicitud = Ruta.query.filter_by(ruta=rpth).first()
if solicitud is None:
solicitud = Ruta(ruta=str(request.path))
db.session.add(solicitud)
db.session.commit()
host = Sitio.query.filter_by(sitio=request.host).first()
if host is None:
host = Sitio(sitio=request.host)
db.session.add(host)
db.session.commit()
tamano = int(request.headers.get('Content-Length') or 0)
now = datetime.datetime.now()
registro = Registro(sitio=host, ruta=solicitud, sesion=sesion, ipaddr=ip, tamano=tamano)
db.session.add(registro)
db.session.commit()
conexion.ultimo = now
sesion.ultimo = now
db.session.commit()
g.ip = ip
g.user = Ident
g.sesion = sesion
g.conexion = conexion
g.registro = registro
g.dispositivo = dispositivo
httpsession['sid'] = g.sesion.id

View File

@ -0,0 +1,8 @@
# coding: utf-8
import os
from itertools import cycle
def es_local(ip='127.0.0.1'):
from netaddr import IPAddress
return IPAddress(ip).is_private()

View File

@ -0,0 +1,77 @@
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
from .system import Persona
from webinterface import db
class Documento(db.Model):
__tablename__ = 'archivos'
__table_args__ = { 'schema': 'doc' }
__bind_key__ = 'system'
hash = db.Column(UUID(as_uuid=True), primary_key=True, nullable=False, autoincrement=False)
nombre = db.Column(db.String, nullable=False)
descripcion = db.Column(db.Text)
tamano = db.Column(db.Integer, default=0)
reemplazahash = db.Column(UUID(as_uuid=True), db.ForeignKey('doc.archivos.hash'), nullable=True)
creado = db.Column(db.DateTime, server_default=func.now())
autorid = db.Column(db.BigInteger, db.ForeignKey('usuarios.personas.id'))
reemplaza = db.relationship('Documento')
# evidenciade = db.relationship('Evidencia')
autor = db.relationship('Persona')
@property
def humansize(self):
if self.tamano == 0:
return "link"
if self.tamano < 1000:
return "{} B".format(self.tamano)
elif self.tamano < 1000000:
return "{:.2f} KB".format(self.tamano/1024)
elif self.tamano < 1000000000:
return "{:.2f} MB".format(self.tamano/1048576)
else:
return "{:.2fs} GB".format(self.tamano/1073741824)
def get_file(self):
if self.tamano == 0:
return None
else:
hashstr = str(self.hash)
return 'ilab_app/archivos/{}/{}-{}'.format(hashstr[:2],hashstr, self.nombre[:150])
def get_dir(self):
if self.tamano == 0:
return None
else:
hashstr = str(self.hash)
return 'ilab_app/archivos/{}'.format(hashstr[:2])
@property
def placeholder(self):
if self.tamano > 0:
return False
elif self.nombre[:7] == 'http://' or self.nombre[:8] == 'https://':
return False
else:
return True
# @property
# def objetivos(self):
# for evidencia in self.evidenciade:
# yield evidencia.actividad.objetivo
#
# @property
# def visible(self):
# from flask import current_app
# from ilab_app.models.system import pertenece
# comisiones = []
# for objetivo in self.objetivos:
# comisiones = comisiones + [objetivo.responsableid, objetivo.invitadosid]
# return pertenece(0, comisiones)

View File

@ -0,0 +1,622 @@
# coding: utf-8
#from datetime import datetime
from sqlalchemy.sql import func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.dialects import sqlite
from webinterface import db
from flask_login import current_user
from flask import current_app, g
import datetime
import unidecode
from webinterface.models.system import Persona
from webinterface.models.documentos import Documento
class Comision(db.Model):
__tablename__ = 'comisiones'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
nombre = db.Column(db.String(100), nullable=False)
descripcion = db.Column(db.String(100))
creado = db.Column(db.Integer, db.ForeignKey('gestion.comisiones.id'), nullable=False)
logo = db.Column(UUID(as_uuid=True), nullable=True, autoincrement=False)
titulo = db.Column(UUID(as_uuid=True), nullable=True, autoincrement=False)
__table_args__ = (db.UniqueConstraint('nombre', 'creado', name='nodupesComisiones'), { 'schema': 'gestion' })
@property
def supervisor(self):
return Comision.query.filter(Comision.id==self.creado).one_or_none()
@property
def quorum(self):
miembro = Miembro.query.filter(Miembro.comisionid==self.id).count()
if miembro == 0:
return False
else:
return miembro
@property
def miembros(self):
for miembro in Miembro.query.filter(Miembro.comisionid==self.id).all():
yield miembro.persona
def mvector(self):
vector = []
for persona in self.miembros:
vector.append(persona.id)
return vector
class Miembro(db.Model):
__tablename__ = 'miembros'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
personaid = db.Column(db.BigInteger, db.ForeignKey('usuarios.personas.id'))
comisionid = db.Column(db.Integer, db.ForeignKey('gestion.comisiones.id'))
persona = db.relationship('Persona')
comision = db.relationship('Comision')
class Objetivo(db.Model):
__tablename__ = 'objetivos'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
tipo = db.Column(db.String(100), nullable=False, default=u'Estratégico')
nombre = db.Column(db.String(200), nullable=False)
descripcion = db.Column(db.Text)
parentid = db.Column(db.Integer, db.ForeignKey('gestion.objetivos.id'), nullable=False)
responsableid = db.Column(db.Integer, db.ForeignKey('gestion.comisiones.id'), nullable=False)
invitadosid = db.Column(db.Integer, db.ForeignKey('gestion.comisiones.id'), nullable=True)
# indicadores = db.relationship('Avance', back_populates="objetivo", order_by='Accion.id.desc()')
actas = db.relationship('Acta', back_populates="objetivo", order_by='Acta.secuencia.desc()')
actividades = db.relationship('Accion', back_populates="objetivo", order_by='Accion.fecha.desc()')
responsable = db.relationship('Comision', foreign_keys=[responsableid])
invitados = db.relationship('Comision', foreign_keys=[invitadosid])
estado = db.Column(db.SmallInteger, default=100)
## registros de auditoria
creado = db.Column(db.DateTime, default=func.now())
creadopor = db.Column(db.Integer, db.ForeignKey('usuarios.personas.id'), nullable=True)
modificado = db.Column(db.DateTime, default=func.now(), onupdate=func.now())
modificadopor = db.Column(db.Integer, db.ForeignKey('usuarios.personas.id'), nullable=True)
@property
def actasr(self):
for item in Acta.query.filter(Acta.objetivoid==self.id).order_by(Acta.secuencia.asc()).all():
yield item
@property
def sub_HDS(self):
return round(self.sub_horas/26,2)
@property
def modulo(self):
return ModuloGestion.query.filter(ModuloGestion.basenodeid==self.get_root().parentid).one_or_none()
@property
def modulo_raw(self):
return ModuloGestion.query.filter(ModuloGestion.basenodeid==self.id).one_or_none()
@property
def indicadores(self):
for item in Avance.query.filter(Avance.objetivoid==self.id).order_by(Avance.id.asc()).all():
yield item
@property
def iactividades(self):
for item in Accion.query.filter(Accion.objetivoid==self.id).order_by(Accion.fecha.asc()).all():
yield item
@property
def hijos(self):
for item in Objetivo.query.filter(Objetivo.parentid==self.id, Objetivo.estado<100).order_by(Objetivo.id.asc()).all():
yield item
for item in Objetivo.query.filter(Objetivo.parentid==self.id, Objetivo.estado>=100).order_by(Objetivo.id.asc()).all():
yield item
@property
def padre(self):
return Objetivo.query.get(self.parentid)
@property
def icon_class(self):
return self.get_root().tipo
@property
def sub_actividades(self):
i = 0
for item in self.hijos:
i += item.sub_actividades
for item in self.actividades:
i += 1
return i
@property
def sub_horas(self):
h = 0
for item in self.hijos:
h += item.sub_horas
for item in self.actividades:
h += item.dedicacion
for item in self.actas:
h += item.dedicacion
return h
@property
def sub_HDS(self):
return round(self.sub_horas/26,2)
@property
def sub_avanceNormalizado(self):
base = 0
numBase = 0
for item in self.hijos:
base += item.sub_avanceNormalizado
numBase += 1
for item in self.indicadores:
base += item.avanceNormalizado
numBase += 1
return base/numBase if numBase else 0
@property
def sub_avance(self):
return round(100*self.sub_avanceNormalizado)
def is_root(self):
if self.padre.parentid == 1:
return True
else:
return False
def get_root(self):
node = self
while node.padre.parentid > 1:
node = node.padre
return node
@property
def comisiones_validas(self):
node = self.get_root()
yield node.responsable
for comision in Comision.query.filter(Comision.creado == node.responsableid).all():
yield comision
@property
def miembros_responsables(self):
for miembro in self.responsable.miembros:
yield miembro
@property
def miembros_habilitados(self):
vistos = []
for miembro in self.responsable.miembros:
if miembro not in vistos:
vistos.append(miembro)
yield miembro
if self.invitados is not None:
for miembro in self.invitados.miembros:
if miembro not in vistos:
vistos.append(miembro)
yield miembro
@property
def archivado(self):
if self.estado < 100:
return False
else:
return True
def lineage(self):
ancestrys=[]
node = self.padre
while node.parentid > 1:
ancestrys.append(node)
node = node.padre
for node in reversed(ancestrys):
yield node
def get_comisiones(self, lvl=1, archived=False):
if self.invitadosid is None:
comisiones = [self.responsableid]
else:
comisiones = [self.responsableid, self.invitadosid]
if lvl > 0:
lvl = lvl - 1
for objetivo in self.hijos:
if objetivo.estado is None or objetivo.estado<100 or archived:
comisiones = comisiones + objetivo.get_comisiones(lvl=lvl, archived=archived)
return comisiones
def visible(self, lvl=5, archived=False):
comisiones = self.get_comisiones(lvl=lvl, archived=archived)
if len(comisiones)>0:
comisiones = list(set(comisiones))
try:
comisiones.remove(1)
except:
pass
existe = Miembro.query.filter(Miembro.comisionid.in_(comisiones), Miembro.personaid==current_user.id).first()
if existe is None:
# current_app.logger.debug('[No-Visble] Nodo {}: {} ({})'.format(self.nombre, current_user.login, comisiones))
return False
else:
# current_app.logger.debug('[Visible] Nodo {}: {} ({})'.format(self.nombre, current_user.login, comisiones))
return True
else:
return False
@property
def is_manager(self):
comisiones = list(set( [ self.responsableid, self.padre.responsableid ] ))
existe = Miembro.query.filter(Miembro.comisionid.in_(comisiones), Miembro.personaid==current_user.id).first()
if existe is None:
return False
else:
return True
class Avance(db.Model):
__tablename__ = 'indicadores'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
objetivoid = db.Column(db.Integer, db.ForeignKey('gestion.objetivos.id'))
nombre = db.Column(db.String(200), nullable=False)
inicial = db.Column(db.Integer, default=0)
valor = db.Column(db.Integer)
meta = db.Column(db.Integer, default=100)
ponderacion = db.Column(db.Integer, default=1)
objetivo = db.relationship('Objetivo')
@property
def progreso(self):
if self.valor>self.inicial:
return self.valor-self.inicial
else:
return 0
@property
def previo(self):
return int(100.0*self.inicial/self.meta)
@property
def avanceNormalizado(self):
return min(self.progreso/(self.meta-self.inicial), 1)
@property
def avance(self):
return int(100.0*self.progreso/self.meta)
class Acta(db.Model):
__tablename__ = 'actas'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
objetivoid = db.Column(db.Integer, db.ForeignKey('gestion.objetivos.id'))
secuencia = db.Column(db.Integer)
horaini = db.Column(db.DateTime)
horafin = db.Column(db.DateTime)
lugar = db.Column(db.String, nullable=False)
temas = db.Column(db.Text)
desarrollo = db.Column(db.Text)
acuerdos = db.Column(db.Text)
objetivo = db.relationship('Objetivo', back_populates='actas')
miembros = db.relationship('MiembrosAsistentes', back_populates='acta')
invitados = db.relationship('InvitadosAsistentes', back_populates='acta')
anexos = db.relationship('AnexosActa', back_populates='acta')
## registros de auditoria
creado = db.Column(db.DateTime, default=func.now())
creadopor = db.Column(db.Integer, db.ForeignKey('usuarios.personas.id'), nullable=True)
modificado = db.Column(db.DateTime, default=func.now(), onupdate=func.now())
modificadopor = db.Column(db.Integer, db.ForeignKey('usuarios.personas.id'), nullable=True)
@property
def documentos(self):
for evidencia in self.anexos:
yield evidencia.documento
@property
def dedicacion(self):
import datetime
now = datetime.datetime.now()
if self.horaini < now and self.horafin < now:
timedelta = self.horafin - self.horaini
secondsdelta = timedelta.total_seconds()
horasdelta = secondsdelta / 3600.0
else:
horasdelta = 0
return horasdelta
@property
def correosasistentes(self):
asistentes = []
for persona in self.miembros:
asistentes.append( (persona.persona.nombrecompleto, persona.persona.correodefecto.correo) )
for persona in self.invitados:
asistentes.append( (persona.nombre, persona.correo) )
return asistentes
@property
def asistentes(self):
asistentes = []
for persona in self.miembros:
asistentes.append( persona.persona.nombrecompleto )
for persona in self.invitados:
asistentes.append( persona.nombre )
return asistentes
@property
def txtasistentes(self):
asistentes = []
for persona in self.miembros:
asistentes.append( persona.persona.nombrecompleto )
for persona in self.invitados:
asistentes.append( u"{} ({})".format(persona.nombre, persona.correo) )
return ", ".join(asistentes)
@property
def miembrosid(self):
listado = []
for persona in self.miembros:
listado.append( persona.personaid )
return listado
@property
def invitadostextarea(self):
listado = []
for persona in self.invitados:
listado.append( u"{} {}".format(persona.correo, persona.nombre))
return "\n".join(listado)
@property
def documentos(self):
for anexo in self.anexos:
yield anexo.documento
class AnexosActa(db.Model):
__tablename__ = 'acta_anexos'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
actaid = db.Column(db.Integer, db.ForeignKey('gestion.actas.id'))
documentohash = db.Column(UUID(as_uuid=True), db.ForeignKey('doc.archivos.hash'))
__table_args__ = (db.UniqueConstraint('actaid', 'documentohash', name='nodupesAnexosActa'), { 'schema': 'gestion' })
acta = db.relationship('Acta', back_populates='anexos')
documento = db.relationship('Documento')
class MiembrosAsistentes(db.Model):
__tablename__ = 'acta_asistentes'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
personaid = db.Column(db.BigInteger, db.ForeignKey('usuarios.personas.id'))
actaid = db.Column(db.Integer, db.ForeignKey('gestion.actas.id'))
acta = db.relationship('Acta', back_populates='miembros')
persona = db.relationship('Persona')
__table_args__ = (db.UniqueConstraint('personaid', 'actaid', name='nodupesMiembrosActas'), { 'schema': 'gestion' })
class InvitadosAsistentes(db.Model):
__tablename__ = 'acta_invitados'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
correo = db.Column(db.String, nullable=False)
nombre = db.Column(db.String, nullable=False)
actaid = db.Column(db.Integer, db.ForeignKey('gestion.actas.id'))
acta = db.relationship('Acta', back_populates='invitados')
__table_args__ = (db.UniqueConstraint('correo', 'actaid', name='nodupesInvitadosActas'), { 'schema': 'gestion' })
class Accion(db.Model):
__tablename__ = 'actividades'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
objetivoid = db.Column(db.Integer, db.ForeignKey('gestion.objetivos.id'))
nombre = db.Column(db.String(200), nullable=False)
descripcion = db.Column(db.Text)
fecha = db.Column(db.Date)
estado = db.Column(db.SmallInteger, default=100)
dedicacion = db.Column(db.Integer, default=0)
objetivo = db.relationship('Objetivo', back_populates='actividades')
evidencias = db.relationship('Evidencia', back_populates='actividad')
## registros de auditoria
creado = db.Column(db.DateTime, default=func.now())
creadopor = db.Column(db.Integer, db.ForeignKey('usuarios.personas.id'), nullable=True)
modificado = db.Column(db.DateTime, default=func.now(), onupdate=func.now())
modificadopor = db.Column(db.Integer, db.ForeignKey('usuarios.personas.id'), nullable=True)
@property
def documentos(self):
for evidencia in self.evidencias:
yield evidencia.documento
class Evidencia(db.Model):
__tablename__ = 'evidencias'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
actividadid = db.Column(db.Integer, db.ForeignKey('gestion.actividades.id'))
documentohash = db.Column(UUID(as_uuid=True), db.ForeignKey('doc.archivos.hash'))
__table_args__ = (db.UniqueConstraint('actividadid', 'documentohash', name='nodupesEvidencia'), { 'schema': 'gestion' })
actividad = db.relationship('Accion', back_populates='evidencias')
documento = db.relationship('Documento')
class ModuloGestion(db.Model):
__tablename__ = 'modulo'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
basenodeid = db.Column(db.Integer, db.ForeignKey('gestion.objetivos.id'))
nombre = db.Column(db.String(100), nullable=False)
uri = db.Column(db.String(100), nullable=False, unique=True)
etiqueta = db.Column(db.String(100), nullable=False)
nodobase = db.relationship('Objetivo')
@property
def icon(self):
return self.nodobase.tipo
@property
def quorum(self):
return AccesosModuloGestion.query.filter(AccesosModuloGestion.moduloid==self.id).count()
def mvector(self):
vector = []
for acceso in AccesosModuloGestion.query.filter(AccesosModuloGestion.moduloid==self.id).all():
vector.append(acceso.comisionid)
return vector
class AccesosModuloGestion(db.Model):
__tablename__ = 'acceso'
__table_args__ = { 'schema': 'gestion' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
moduloid = db.Column(db.Integer, db.ForeignKey('gestion.modulo.id'))
comisionid = db.Column(db.Integer, db.ForeignKey('gestion.comisiones.id'))
__table_args__ = (db.UniqueConstraint('moduloid', 'comisionid', name='no_dupes_acceso_gestion'), { 'schema': 'gestion' })
modulo = db.relationship('ModuloGestion')
comision = db.relationship('Comision')
class AccesoGestion():
def __init__(self):
if 'accesogestion' not in g:
self.sysadmin = False
self.admin = []
self.usuario = []
for modulo in self.get_modulos():
if modulo.nodobase.visible():
self.usuario.append(modulo)
for comision in Comision.query.join(Miembro).filter(Miembro.personaid==current_user.id, Comision.creado==1).all():
if comision.id > 999:
self.admin.append(comision.id)
elif comision.id == 1:
self.sysadmin = True
self.admin.append(comision.id)
g.accesogestion = {
'sysadmin': self.sysadmin,
'admin': self.admin,
'usuario': self.usuario
}
else:
self.sysadmin = g.accesogestion['sysadmin']
self.admin = g.accesogestion['admin']
self.usuario = g.accesogestion['usuario']
def get_nodes(self):
for modulo in self.usuario:
yield modulo
def get_modulos(self):
for modulo in ModuloGestion.query.order_by(ModuloGestion.id.asc()).all():
yield modulo
def in_gestion(self):
if len(self.usuario) > 0:
return True
else:
return self.sysadmin
def is_sysadmin(self):
return self.sysadmin
def is_admin(self):
if len(self.admin)>0:
return True
else:
return False
def administrador(self):
for item in self.admin:
yield Comision.query.get(item)
def comisiones(self, admin):
if admin > 1:
yield Comision.query.get(admin)
for item in Comision.query.filter(Comision.creado.in_(self.admin), Comision.creado==admin).all():
yield item
elif self.sysadmin:
for item in Comision.query.filter(Comision.creado==1).order_by(Comision.id.asc()).all():
yield item
def valid_users(self, admin):
if admin > 1:
for item in Miembro.query.join(Comision).filter(Comision.creado==1).distinct(Miembro.personaid).all():
yield Persona.query.get(item.personaid)
elif self.sysadmin:
for item in Persona.query.order_by(Persona.nombres.asc(), Persona.apellidop.asc()).all():
yield item

View File

@ -0,0 +1,281 @@
#from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
#from datetime import datetime
#from flask import current_app
from flask_login import UserMixin
from sqlalchemy.sql import func
from sqlalchemy.dialects import postgresql, sqlite
from webinterface import db
@login_manager.user_loader
def load_user(user_id):
return Persona.query.filter(Persona.rut==user_id).one_or_none()
class Persona(Cuenta,UserMixin):
__tablename__ = 'personas'
__table_args__ = { 'schema': 'usuarios' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), db.ForeignKey('usuarios.cuentas.id'), primary_key=True)
nombres = db.Column(db.String(100), nullable=False)
apellidop = db.Column(db.String(100), nullable=False)
apellidom = db.Column(db.String(100))
rut = db.Column(db.String(20), unique=True, nullable=False)
telefono = db.Column(db.String(20), nullable=True)
foto = db.Column(db.String(50), nullable=False, default='default.jpg')
__mapper_args__ = {
'polymorphic_identity':'Persona'
}
def get_id(self):
return self.rut
@property
def nombrecompleto(self):
if self.apellidom is not None:
return u"{} {} {}.".format(self.nombres.partition(' ')[0], self.apellidop, self.apellidom[0])
else:
return u"{} {}.".format(self.nombres.partition(' ')[0], self.apellidop)
@property
def safe_nombrecompleto(self):
return "{}".format(self.nombrecompleto.encode('utf-8'))
@property
def ascii_nombrecompleto(self):
return unidecode.unidecode(self.nombrecompleto)
def is_in(self, comision=0):
return True
@property
def sidebar_menu(self):
import json
from .gestion import AccesoGestion
# Primer menu, de carrera Visión de jefe de carrera
amGestion = AccesoGestion()
if amGestion.in_gestion():
cat = u"Gestión"
links = []
for modulo in amGestion.get_nodes():
links.append((modulo.nodobase.nombre, modulo.nodobase.tipo, modulo.nodobase.descripcion, url_for('gestion.main', uri=modulo.uri) ))
class Identidad(db.Model):
__tablename__ = 'identidades'
__table_args__ = { 'schema': 'usuarios' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), primary_key=True, autoincrement=True)
login = db.Column(db.String(50), unique=True, nullable=False)
alias = db.Column(db.String(50))
creado = db.Column(db.DateTime(timezone=True), server_default=func.now())
modificado = db.Column(db.DateTime(timezone=True), server_onupdate=func.now())
sesiones = db.relationship('Sesion', back_populates='identidad', lazy=True)
ubicaciones = db.relationship('Ubicacion', back_populates='identidad', lazy=True)
tipo = db.Column(db.String(20))
__mapper_args__ = {
'polymorphic_identity':'Identidad',
'polymorphic_on':tipo
}
class Cuenta(Identidad):
__tablename__ = 'cuentas'
__table_args__ = { 'schema': 'usuarios' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), db.ForeignKey('usuarios.identidades.id'), primary_key=True)
clave = db.Column(db.String(60), nullable=False)
sysadmin = db.Column(db.Boolean, default=0)
ultimoacceso = db.Column(db.DateTime(timezone=True))
__mapper_args__ = {
'polymorphic_identity':'Cuenta'
}
correos = db.relationship('Correo', back_populates='cuenta', lazy=True)
tarjetas = db.relationship('Tarjeta', back_populates='cuenta', lazy=True)
# credenciales = db.relationship('Credencial', back_populates='cuenta', lazy=True)
def set_default(self, correo):
for item in self.correos:
if correo == item:
correo.default = True
else:
correo.default = False
@property
def correodefecto(self):
last = None
for item in self.correos:
if item.default == True:
return item
elif last is None:
last = item
return last
@correodefecto.setter
def correodefecto(self, correo):
if correo.cuenta != self:
pass
for item in self.correos:
if item == correo:
item.default = True
else:
item.default = False
class Correo(db.Model):
__tablename__ = 'correos'
__table_args__ = { 'schema': 'usuarios' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
correo = db.Column(db.String(100), unique=True, nullable=False)
cuentaid = db.Column(db.BigInteger(), db.ForeignKey('usuarios.cuentas.id'), nullable=False)
cuenta = db.relationship('Cuenta', back_populates='correos')
default = db.Column(db.Boolean, default=0)
class Sesion(db.Model):
__tablename__ = 'sesiones'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), primary_key=True)
identidadid = db.Column(db.BigInteger(), db.ForeignKey('usuarios.identidades.id'), nullable=False)
identidad = db.relationship('Identidad', back_populates='sesiones')
dispositivoid = db.Column(db.Integer, db.ForeignKey('registros.dispositivos.id'), nullable=False)
dispositivo = db.relationship('Dispositivo')
conexiones = db.relationship('Conexion')
iniciada = db.Column(db.DateTime(timezone=True), server_default=func.now())
ultimo = db.Column(db.DateTime(timezone=True), server_onupdate=func.now())
def __repr__(self):
return u"Session ('{}','{}','{}')".format(self.identidad.login, self.ipaddr.ipaddr, self.useragent.parsed_ua)
class Conexion(db.Model):
__tablename__ = 'conexiones'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), primary_key=True)
ipaddrid = db.Column(db.Integer, db.ForeignKey('registros.ipaddrs.id'), nullable=False)
ipaddr = db.relationship('Ipaddr')
sesionid = db.Column(db.Integer, db.ForeignKey('registros.sesiones.id'), nullable=False)
sesion = db.relationship('Sesion', back_populates='conexiones', lazy=True)
iniciada = db.Column(db.DateTime(timezone=True), server_default=func.now())
ultimo = db.Column(db.DateTime(timezone=True), server_onupdate=func.now())
__table_args__ = (db.UniqueConstraint('ipaddrid', 'sesionid', name='_una_ip_sesion_uc'),{ 'schema': 'registros' })
class Ubicacion(db.Model):
__tablename__ = 'ubicaciones'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), primary_key=True)
ipaddrid = db.Column(db.Integer, db.ForeignKey('registros.ipaddrs.id'), nullable=False)
ipaddr = db.relationship('Ipaddr')
identidadid = db.Column(db.Integer, db.ForeignKey('usuarios.identidades.id'), nullable=False)
identidad = db.relationship('Identidad', back_populates='ubicaciones', lazy=True)
descripcion = db.Column(db.String(200), nullable=True)
iniciada = db.Column(db.DateTime(timezone=True), server_default=func.now())
ultimo = db.Column(db.DateTime(timezone=True), server_onupdate=func.now())
__table_args__ = (db.UniqueConstraint('ipaddrid', 'identidadid', name='_una_ip_identidad_uc'),{ 'schema': 'registros' })
class Ipaddr(db.Model):
__tablename__ = 'ipaddrs'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
ipaddr = db.Column(db.String(15).with_variant(postgresql.INET(), 'postgresql'), unique=True, nullable=False)
hostname = db.Column(db.String(100), nullable=True)
def __repr__(self):
return "IpAddr('{}','{}')".format(self.ipaddr, self.hostname)
class Dispositivo(db.Model):
__tablename__ = 'dispositivos'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
useragent = db.Column(db.String(1024), unique=True, nullable=False)
parsed_ua = db.Column(db.String(150))
dev = db.Column(db.String(50))
os = db.Column(db.String(50))
browser = db.Column(db.String(50))
def __repr__(self):
return "Useragent('{}')".format(self.parsed_ua)
class Ruta(db.Model):
__tablename__ = 'rutas'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
ruta = db.Column(db.String(100), unique=True, nullable=False)
def __repr__(self):
return "Url('{}')".format(self.ruta)
class Registro(db.Model):
__tablename__ = 'registros'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.BigInteger(), primary_key=True)
sitioid = db.Column(db.Integer, db.ForeignKey('registros.sitios.id'), nullable=False)
sitio = db.relationship('Sitio')
rutaid = db.Column(db.Integer, db.ForeignKey('registros.rutas.id'), nullable=False)
ruta = db.relationship('Ruta')
sesionid = db.Column(db.BigInteger().with_variant(sqlite.INTEGER(), 'sqlite'), db.ForeignKey('registros.sesiones.id'), nullable=False)
sesion = db.relationship('Sesion')
ipaddrid = db.Column(db.Integer, db.ForeignKey('registros.ipaddrs.id'), nullable=False)
ipaddr = db.relationship('Ipaddr')
tamano = db.Column(db.Integer)
creado = db.Column(db.DateTime, server_default=func.now())
def __repr__(self):
return "Registro('{}')".format(self.ruta.ruta)
class Sitio(db.Model):
__tablename__ = 'sitios'
__table_args__ = { 'schema': 'registros' }
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
sitio = db.Column(db.String(100), unique=True, nullable=False)

View File

@ -0,0 +1,28 @@
Flask
Flask-Bcrypt
Flask-Login
Flask-Mail
Flask-SQLAlchemy
Flask-WTF
flask_migrate
psycopg2-binary
email_validator
itsdangerous
Jinja2
MarkupSafe
SQLAlchemy
Werkzeug
WTForms
PyYAML
ua-parser
user-agents
netaddr
unidecode
blinker
certifi
gunicorn
bcrypt
openpyxl
fpdf
Pillow
PyPDF2<3

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(a){a.fn.datepicker.dates.es={days:["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"],daysShort:["Dom","Lun","Mar","Mié","Jue","Vie","Sáb"],daysMin:["Do","Lu","Ma","Mi","Ju","Vi","Sa"],months:["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],monthsShort:["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"],today:"Hoy",monthsTitle:"Meses",clear:"Borrar",weekStart:1,format:"dd/mm/yyyy"}}(jQuery);

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 one or more lines are too long

File diff suppressed because it is too large Load Diff

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 one or more lines are too long

View File

@ -0,0 +1,325 @@
/*!
* Bootstrap Reboot v4.6.1 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus:not(:focus-visible) {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([class]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
-ms-overflow-style: scrollbar;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
[role="button"] {
cursor: pointer;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v4.6.1 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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 one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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 one or more lines are too long

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 one or more lines are too long

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="63pt" height="63pt" viewBox="0 0 63 39" version="1.1">
<circle cx="32" cy="20" r="25" stroke="black" stroke-width="2" fill="white" />
<defs>
<g>
<symbol overflow="visible" id="glyph0-0">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph0-1">
<path style="stroke:none;" d="M 7.90625 -15.4375 C 7.90625 -15.90625 7.5625 -16.1875 7.140625 -16.1875 C 6.6875 -16.1875 6.046875 -15.78125 6.046875 -15.09375 C 6.046875 -14.546875 6.4375 -14.34375 6.8125 -14.34375 C 7.3125 -14.34375 7.90625 -14.796875 7.90625 -15.4375 Z M 3.34375 -2.796875 C 3.140625 -2.25 3.140625 -1.9375 3.140625 -1.703125 C 3.140625 -0.265625 4.109375 0.25 4.9375 0.25 C 7.21875 0.25 8 -3.4375 8 -3.546875 C 8 -3.765625 7.78125 -3.765625 7.6875 -3.765625 C 7.390625 -3.765625 7.359375 -3.6875 7.265625 -3.328125 C 7.03125 -2.484375 6.390625 -0.25 4.984375 -0.25 C 4.703125 -0.25 4.40625 -0.375 4.40625 -0.96875 C 4.40625 -1.609375 4.765625 -2.5 5.203125 -3.71875 L 6.671875 -7.734375 C 6.890625 -8.34375 6.9375 -8.546875 6.9375 -9 C 6.9375 -10.265625 6.125 -10.921875 5.15625 -10.921875 C 2.875 -10.921875 2.0625 -7.265625 2.0625 -7.140625 C 2.0625 -6.90625 2.28125 -6.90625 2.40625 -6.90625 C 2.703125 -6.90625 2.71875 -6.984375 2.828125 -7.328125 C 3.015625 -8.125 3.671875 -10.4375 5.109375 -10.4375 C 5.65625 -10.4375 5.65625 -9.96875 5.65625 -9.6875 C 5.65625 -9.046875 5.5 -8.625 5.328125 -8.171875 Z M 3.34375 -2.796875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-0">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph1-1">
<path style="stroke:none;" d="M 6.46875 -1.609375 C 6.171875 -1.609375 5.875 -1.59375 5.578125 -1.59375 L 4.265625 -1.59375 L 4.265625 -17.203125 L 2.1875 -17.203125 L 2.1875 0 L 11.625 0 L 11.625 -1.609375 Z M 6.46875 -1.609375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-2">
<path style="stroke:none;" d="M 9.3125 -7.015625 C 9.3125 -9.34375 7.65625 -10.953125 5.421875 -10.953125 C 4.34375 -10.953125 3.140625 -10.75 1.78125 -9.96875 L 1.9375 -8.328125 C 2.546875 -8.765625 3.625 -9.484375 5.40625 -9.484375 C 6.671875 -9.484375 7.4375 -8.53125 7.4375 -6.984375 L 7.4375 -6.046875 C 3.46875 -5.921875 0.984375 -4.78125 0.984375 -2.828125 C 0.984375 -1.8125 1.609375 0.203125 3.671875 0.203125 C 4.046875 0.203125 6.046875 0.15625 7.484375 -0.921875 L 7.484375 0 L 9.3125 0 Z M 7.4375 -3.265625 C 7.4375 -2.828125 7.4375 -2.234375 6.6875 -1.78125 C 6.046875 -1.359375 5.234375 -1.3125 4.875 -1.3125 C 3.640625 -1.3125 2.796875 -2 2.796875 -2.875 C 2.796875 -4.609375 6.671875 -4.78125 7.4375 -4.8125 Z M 7.4375 -3.265625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-3">
<path style="stroke:none;" d="M 3.6875 -17.203125 L 1.859375 -17.203125 L 1.859375 0 L 3.734375 0 L 3.734375 -1.109375 C 4.6875 -0.203125 5.828125 0.203125 6.84375 0.203125 C 9.21875 0.203125 11.15625 -2.203125 11.15625 -5.359375 C 11.15625 -8.234375 9.671875 -10.859375 7.453125 -10.859375 C 5.890625 -10.859375 4.515625 -10.203125 3.6875 -9.546875 Z M 3.734375 -8.078125 C 4.28125 -8.765625 5.0625 -9.34375 6.140625 -9.34375 C 7.609375 -9.34375 9.265625 -8.28125 9.265625 -5.359375 C 9.265625 -2.234375 7.28125 -1.3125 5.921875 -1.3125 C 5.421875 -1.3125 4.90625 -1.4375 4.40625 -1.859375 C 3.734375 -2.453125 3.734375 -2.828125 3.734375 -3.171875 Z M 3.734375 -8.078125 "/>
</symbol>
</g>
</defs>
<g id="surface1">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-1" x="8.151" y="27.906"/>
</g>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="18.068" y="27.906"/>
<use xlink:href="#glyph1-2" x="30.679676" y="27.906"/>
<use xlink:href="#glyph1-3" x="41.85618" y="27.906"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

Some files were not shown because too many files have changed in this diff Show More