From 6697c60c7f6035f11595eecc8551245968c0a6b9 Mon Sep 17 00:00:00 2001 From: msinkec Date: Tue, 8 Jun 2021 08:00:18 +0200 Subject: [PATCH] solar update --- app.py | 171 +++++++++++++++--- ...fc7f06_changed_user_institution_corpus_.py | 66 +++++++ portal/base.py | 97 +++++++++- portal/model.py | 36 ++-- portal/solar.py | 82 ++++++--- static/style.css | 14 ++ templates/solar-admin.html | 110 +++++++++++ templates/solar-institution-managment.html | 13 ++ templates/solar-oddaja.html | 1 + templates/solar-pogodbe.html | 49 +++-- templates/solar-zgodovina.html | 1 + 11 files changed, 555 insertions(+), 85 deletions(-) create mode 100644 migrations/versions/5ba116fc7f06_changed_user_institution_corpus_.py create mode 100644 templates/solar-admin.html create mode 100644 templates/solar-institution-managment.html diff --git a/app.py b/app.py index a8de9d3..c9e589c 100644 --- a/app.py +++ b/app.py @@ -9,7 +9,7 @@ from flask import Flask, render_template, request, redirect, flash, safe_join, s from flask_dropzone import Dropzone from flask_migrate import Migrate, MigrateCommand from flask_script import Manager -from flask_login import LoginManager, login_required, login_user, current_user +from flask_login import LoginManager, login_required, login_user, current_user, logout_user from portal.model import db, RegisteredUser import portal.base @@ -193,7 +193,8 @@ def index_corpus(corpus_name): @login_manager.user_loader def load_user(user_id): - return RegisteredUser.query.get(int(user_id)) + user = RegisteredUser.query.get(int(user_id)) + return user @app.route('/solar/login') @@ -204,7 +205,7 @@ def login_get(): @app.route('//login', methods=['POST']) def login_post(corpus_name): if corpus_name not in ENABLED_CORPUSES or corpus_name not in CORPUSES_LOGIN_REQUIRED: - return 404 + return '', 404 email = request.form.get('email') password = request.form.get('password') @@ -222,23 +223,32 @@ def login_post(corpus_name): # Check if user is authorized to log into this corpus. Admins are an exception. if not portal.base.has_user_corpus_access(user.id, corpus_name): - flash('Nimate dostop do tega korpusa.') + flash('Nimate dostopa do tega korpusa.') return redirect('/{}/login'.format(corpus_name)) + #portal.base.add_user_session(user.id) login_user(user, remember=remember) if corpus_name == 'solar': return redirect('/solar/oddaja') - return 404 + return '', 404 # TODO: Move solar stuff to seperate file using Flask blueprints. # TODO: Better routing logic. + +@app.route('/solar/logout') +@login_required +def logout(): + logout_user() + return redirect('/solar/login') + + @app.route('/solar/') @login_required def solar(text): if not portal.base.has_user_corpus_access(current_user.id, 'solar'): - return 404 + return '', 404 if text.startswith('oddaja/') or text == 'oddaja': return render_template('solar-oddaja.html') elif text.startswith('zgodovina/') or text == 'zgodovina': @@ -256,51 +266,162 @@ def solar(text): institution_names=institution_names) elif text.startswith('pogodbe/') or text == 'pogodbe': # Check for ownload contract request. - match = re.match('^pogodbe/[a-z0-9_]+\.pdf$', text) + match = re.match('^pogodbe/([a-z0-9_]+\.pdf)$', text) if match: filename = match.group(1) - safe_path = safe_join(str(upload_handler_solar.get_uploads_subdir('contracts')), filename) + if len(filename) < 10: + return '', 404 + prefix = filename[:2] + suffix = filename[2:] + + safe_path = safe_join(str(upload_handler_solar.get_uploads_subdir('contracts')), prefix, suffix) try: return send_file(safe_path, as_attachment=True) except FileNotFoundError: - return 404 + return '', 404 user_obj = portal.base.get_user_obj(current_user.get_id()) - institution_id = user_obj.institution - + institutions = portal.base.get_user_institutions(user_obj.id) contracts_students = [] - contract_school = None - show_upload_form = True - if institution_id: - contracts_students = portal.solar.get_institution_student_contracts(institution_id) - contract_school = portal.solar.get_institution_contract(institution_id) - else: - show_upload_form = False + contract_school = [] + enable_upload_school_contract = False + show_upload_form = False + if len(institutions) > 0: + show_upload_form = True + institution = portal.base.get_user_institutions(user_obj.id)[0] + contracts_students = portal.solar.get_institution_student_contracts(institution.id) + contract_school = portal.solar.get_institution_contract(institution.id) + + if portal.base.is_institution_moderator(user_obj.id, institution.id): + enable_upload_school_contract = True return render_template('solar-pogodbe.html', contracts_students=contracts_students, - contract_school=contract_school, show_upload_form=show_upload_form) + contract_school=contract_school, + enable_upload_school_contract=enable_upload_school_contract, + show_upload_form=show_upload_form) elif text.startswith('admin/') or text == 'admin': + solar_users = portal.base.get_all_active_users() + solar_institutions = portal.solar.get_all_institutions() if current_user.role == 'admin': - return render_template('solar-admin.html') - return 404 + return render_template('solar-admin.html', users=solar_users, institutions=solar_institutions) + return '', 404 @app.route('/solar/pogodbe', methods=['POST']) @login_required def solar_upload_contract(): if not portal.base.has_user_corpus_access(current_user.id, 'solar'): - return 404 + return '', 404 + return upload_handler_solar.handle_contract_upload(request, current_user.get_id()) + +@app.route('//adduser', methods=['POST']) +@login_required +def solar_add_user(corpus_name): + if not portal.base.is_admin(current_user.id): + return '', 404 + if not corpus_name in ENABLED_CORPUSES: + return '', 404 + + name = request.form['name'] + email = request.form['email'] + password = request.form['password'] + + if not name: + return 'Prazno polje za ime.' + if len(name) > 100: + return 'Predolgo ime.' + + if not email: + return 'Prazno polje za elektronsko pošto.' + if len(email) > 100: + return 'Predolgi email naslov' + elif not re.search(portal.base.REGEX_EMAIL, email): + return 'Email napačnega formata.' + + if not password: + return 'Prazno polje za geslo.' + if len(password) > 100: + return 'Predolgo geslo.' + + portal.base.register_new_user(name, email, password) + + return 'Uporabnik je bil dodan.' + + +@app.route('/solar/deluser', methods=['POST']) +@login_required +def solar_del_user(): + # TODO: check if user is institution moderator for the added users institution or is an admin + # TODO: delete from "user", "user_institution_mapping", update "institution_contract" set user to NULL + return '', 404 + +@app.route('//addinstitution', methods=['POST']) +@login_required +def add_institution(corpus_name): + if not portal.base.is_admin(current_user.id): + return '', 404 + if not corpus_name in ENABLED_CORPUSES: + return '', 404 + + name = request.form['name'] + region = request.form['region'] + + if not name: + return 'Prazno polje za ime.' + if len(name) > 100: + return 'Predolgo ime.' + + if not region: + return 'Prazno polje za regijo.' + if len(region) > 100: + return 'Predolgi niz za regijo.' + + institution_id = portal.base.add_institution(name, region) + portal.base.grant_institution_corpus_access(institution_id, corpus_name) + return 'Institucija je bila dodana.' + +@app.route('//addusertoinstitution', methods=['POST']) +@login_required +def add_user_institution_mapping(corpus_name): + if not portal.base.is_admin(current_user.id): + return '', 404 + if not corpus_name in ENABLED_CORPUSES: + return '', 404 + + user_id = request.form['user_id'] + institution_id = request.form['institution_id'] + role = request.form['role'] + if role not in ['moderator', 'user']: + return '', 404 + + # TODO: remove this restriction + if len(portal.base.get_user_institutions(user_id)) > 0: + return 'Uporabnik je že dodeljen instituciji. Dodeljevanje večim institucijam '\ + 'zaenkrat ni implementirano.' + + portal.base.add_user_to_institution(user_id, institution_id, role) + return 'Uporabnik je bil dodeljen instituciji.' + +@app.route('//delinstitution', methods=['POST']) +@login_required +def del_institution(corpus_name): + # TODO: check if valid corpus_name + # TODO: check if user is admin + # TODO: delete cascade - institution, user_institution_mapping, corpus_access, institution_contract + return '', 404 + + @app.route('//upload', methods=['POST']) def handle_upload(corpus_name): if corpus_name not in ENABLED_CORPUSES: - return 404 + return '', 404 if corpus_name == 'solar': if not current_user.is_authenticated: - return 404 + return '', 404 if not portal.base.has_user_corpus_access(current_user.id, corpus_name): - return 404 + return '', 404 return upload_handler_solar.handle_upload(request, current_user.get_id()) elif corpus_name == 'predavanja': return upload_handler_predavanja.handle_upload(request) diff --git a/migrations/versions/5ba116fc7f06_changed_user_institution_corpus_.py b/migrations/versions/5ba116fc7f06_changed_user_institution_corpus_.py new file mode 100644 index 0000000..414e506 --- /dev/null +++ b/migrations/versions/5ba116fc7f06_changed_user_institution_corpus_.py @@ -0,0 +1,66 @@ +"""Changed user<->institution<->corpus relational mapping. + +Revision ID: 5ba116fc7f06 +Revises: 7d6db184b8fc +Create Date: 2021-06-07 13:02:42.900168 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5ba116fc7f06' +down_revision = '7d6db184b8fc' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('institution_contract', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('institution', sa.Integer(), nullable=False), + sa.Column('corpus', sa.String(), nullable=False), + sa.Column('timestamp', sa.DateTime(), nullable=False), + sa.Column('file_contract', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['institution'], ['institution.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user_institution_mapping', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user', sa.Integer(), nullable=False), + sa.Column('institution', sa.Integer(), nullable=False), + sa.Column('role', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['institution'], ['institution.id'], ), + sa.ForeignKeyConstraint(['user'], ['registered_user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.add_column('corpus_access', sa.Column('institution', sa.Integer(), nullable=False)) + op.drop_constraint('user_id_fkey', 'corpus_access', type_='foreignkey') + op.create_foreign_key(None, 'corpus_access', 'institution', ['institution'], ['id']) + op.drop_column('corpus_access', 'user_id') + op.create_unique_constraint(None, 'institution', ['name']) + op.drop_column('institution', 'file_contract') + op.create_unique_constraint(None, 'registered_user', ['email']) + op.drop_constraint('registered_user_institution_fkey', 'registered_user', type_='foreignkey') + op.drop_column('registered_user', 'institution_moderator') + op.drop_column('registered_user', 'institution') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('registered_user', sa.Column('institution', sa.INTEGER(), autoincrement=False, nullable=True)) + op.add_column('registered_user', sa.Column('institution_moderator', sa.BOOLEAN(), autoincrement=False, nullable=True)) + op.create_foreign_key('registered_user_institution_fkey', 'registered_user', 'institution', ['institution'], ['id']) + op.drop_constraint(None, 'registered_user', type_='unique') + op.add_column('institution', sa.Column('file_contract', sa.TEXT(), autoincrement=False, nullable=True)) + op.drop_constraint(None, 'institution', type_='unique') + op.add_column('corpus_access', sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False)) + op.drop_constraint(None, 'corpus_access', type_='foreignkey') + op.create_foreign_key('user_id_fkey', 'corpus_access', 'registered_user', ['user_id'], ['id']) + op.drop_column('corpus_access', 'institution') + op.drop_table('user_institution_mapping') + op.drop_table('institution_contract') + # ### end Alembic commands ### diff --git a/portal/base.py b/portal/base.py index 68f5d36..cb9e3e4 100644 --- a/portal/base.py +++ b/portal/base.py @@ -3,6 +3,7 @@ import time import ssl import traceback import re +import logging from pathlib import Path from datetime import datetime @@ -19,7 +20,9 @@ from email.mime.application import MIMEApplication import pdfkit from jinja2 import Environment, FileSystemLoader -from . model import db, UploadRegular, UploadSolar, RegisteredUser, CorpusAccess, Institution +from werkzeug.security import generate_password_hash + +from . model import db, UploadRegular, UploadSolar, RegisteredUser, UserInstitutionMapping, Institution, InstitutionContract, CorpusAccess #REGEX_EMAIL = re.compile('^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$') @@ -131,7 +134,7 @@ class UploadHandler: return res @staticmethod - def store_model(self, model_obj): + def store_model(model_obj): try: db.session.add(model_obj) db.session.commit() @@ -234,20 +237,100 @@ class UploadHandler: return None - @staticmethod - def get_user_institution(user_id): - match = db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).one() - return match.institution + +def get_user_institutions(user_id): + return UserInstitutionMapping.query.filter_by(user=user_id).all() def has_user_corpus_access(user_id, corpus_name): user = RegisteredUser.query.filter_by(id=user_id).first() + + # TODO: check if user even is active? + + # Admins always have access to everything. + if user.role == 'admin': + return True + + # Check if user belongs to an institution, that has access to this corpus. + institutions = get_user_institutions(user_id) + has_access = False + for institution in institutions: + row = CorpusAccess.query.filter_by(institution=institution.id, corpus=corpus_name).first() + if row: + has_access = True + break + return has_access + + +def is_admin(user_id): + user = RegisteredUser.query.filter_by(id=user_id).first() if user.role == 'admin': return True - return CorpusAccess.query.filter_by(user_id=user.id, corpus=corpus_name).first() is not None + return False + def get_user_obj(user_id): return RegisteredUser.query.filter_by(id=user_id).first() + def get_institution_obj(institution_id): return Institution.query.filter_by(id=institution_id).first() + + +def register_new_user(name, email, password, active=True, admin=False): + model_obj = RegisteredUser( + name=name, + email=email, + role='admin' if admin else 'user', + pass_hash=generate_password_hash(password), + active=active, + registered=datetime.now() + ) + db.session.add(model_obj) + db.session.commit() + return model_obj.id + + +def add_institution(name, region): + model_obj = Institution( + name=name, + region=region + ) + db.session.add(model_obj) + db.session.commit() + return model_obj.id + + +def grant_institution_corpus_access(institution_id, corpus_name): + model_obj = CorpusAccess( + institution=institution_id, + corpus=corpus_name + ) + db.session.add(model_obj) + db.session.commit() + return model_obj.id + + +def add_user_to_institution(user_id, institution_id, role): + model_obj = UserInstitutionMapping( + user=user_id, + institution=institution_id, + role=role + ) + db.session.add(model_obj) + db.session.commit() + return model_obj.id + + +def get_all_active_users(): + return RegisteredUser.query.filter_by(active=True).all() + + +def is_institution_moderator(user_id, institution_id): + user_inst_mapping = UserInstitutionMapping.query.filter_by(user=user_id).first() + if not user_inst_mapping: + return False + if user_inst_mapping.role != 'moderator': + return False + return True + diff --git a/portal/model.py b/portal/model.py index e5eee45..0cae17d 100644 --- a/portal/model.py +++ b/portal/model.py @@ -83,31 +83,45 @@ class ContractsSolar(db.Model): contract_type = db.Column(db.String, nullable=False) -class CorpusAccess(db.Model): - __tablename__ = 'corpus_access' - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, sqlalchemy.ForeignKey('registered_user.id'), nullable=False) - corpus = db.Column(db.String, nullable=False) - - class RegisteredUser(UserMixin, db.Model): __tablename__ = 'registered_user' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, nullable=False) - email = db.Column(db.String, nullable=False) + email = db.Column(db.String, nullable=False, unique=True) role = db.Column(db.String, nullable=False) pass_hash = db.Column(db.String, nullable=False) active = db.Column(db.Boolean, nullable=True) last_login = db.Column(db.DateTime, nullable=True) registered = db.Column(db.DateTime, nullable=True) - institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=True) - institution_moderator = db.Column(db.Boolean, default=False) + + +class UserInstitutionMapping(db.Model): + __tablename__ = 'user_institution_mapping' + id = db.Column(db.Integer, primary_key=True) + user = db.Column(db.Integer, sqlalchemy.ForeignKey('registered_user.id'), nullable=False) + institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=False) + role =db.Column(db.String, nullable=False) class Institution(db.Model): __tablename__ = 'institution' id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String, nullable=False) + name = db.Column(db.String, nullable=False, unique=True) region = db.Column(db.String, nullable=False) + + +class CorpusAccess(db.Model): + __tablename__ = 'corpus_access' + id = db.Column(db.Integer, primary_key=True) + institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=False) + corpus = db.Column(db.String, nullable=False) + + +class InstitutionContract(db.Model): + __tablename__ = 'institution_contract' + id = db.Column(db.Integer, primary_key=True) + institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=False) + corpus = db.Column(db.String, nullable=False) + timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) file_contract = db.Column(db.String, nullable=True) diff --git a/portal/solar.py b/portal/solar.py index f3ccd92..ec5e231 100644 --- a/portal/solar.py +++ b/portal/solar.py @@ -3,10 +3,10 @@ import re import traceback import hashlib from datetime import datetime -from sqlalchemy import desc +from sqlalchemy import desc, exists -from portal.base import UploadHandler -from portal.model import db, UploadSolar, ContractsSolar, RegisteredUser, Institution +from portal.base import UploadHandler, get_user_institutions, has_user_corpus_access +from portal.model import db, UploadSolar, ContractsSolar, RegisteredUser, Institution, InstitutionContract, UserInstitutionMapping, CorpusAccess VALID_PROGRAMS = {'OS', 'SSG', 'MGP', 'ZG', 'NPI', 'SPI', 'SSI', 'PTI'} @@ -27,7 +27,8 @@ class UploadHandlerSolar(UploadHandler): sorted_f_hashes = list(file_hashes.values()) sorted_f_hashes.sort() - institution_id = UploadHandler.get_user_institution(user_id) + # If user is mapped to multiple institutions, let him chose in name of which one he makes the upload. + institution_id = get_user_institutions(user_id)[0].id model_obj = UploadSolar( upload_user = user_id, @@ -44,7 +45,7 @@ class UploadHandlerSolar(UploadHandler): grammar_corrections=form_data['jezikovni-popravki'], upload_file_hashes=sorted_f_hashes ) - self.store_model(model_obj) + UploadHandler.store_model(model_obj) def handle_upload(self, request, user_id): err = self.check_upload_request(request) @@ -73,9 +74,9 @@ class UploadHandlerSolar(UploadHandler): return 'Uspešno ste oddali datotek(e). Št. datotek: {}'.format(len(request.files)) def handle_contract_upload(self, request, user_id): - contracts_type = request.form['tip-pogodbe'] + contract_type = request.form['tip-pogodbe'] - if contracts_type not in ['sola', 'ucenci-starsi']: + if contract_type not in ['sola', 'ucenci-starsi']: return 'Neveljaven tip pogodbe.' f_obj = None @@ -92,35 +93,46 @@ class UploadHandlerSolar(UploadHandler): return 'Niste naložili nobene datoteke.' base = self.get_uploads_subdir('contracts') - f_hash = hashlib.md5(f_obj.read()) + f_hash = hashlib.md5(f_obj.read()).hexdigest() + f_obj.seek(0, 0) # First byte used for indexing, similarly like git does for example. sub_dir = base / f_hash[:2] if not sub_dir.exists(): sub_dir.mkdir() - path = sub_dir / f_hash[2:] + '.pdf' + path = sub_dir / (f_hash[2:] + '.pdf') f_obj.save(path) + timestamp = datetime.now() user_obj = RegisteredUser.query.filter_by(id=user_id).one() - institution_id = user_obj.institution - is_institution_moderator = user_obj.institution_moderator - - if institution_id is None: + user_institution_mapping = UserInstitutionMapping.query.filter_by(user=user_id).first() + if user_institution_mapping is None: return 'Vaš uporabnik ni dodeljen nobeni inštituciji.' + institution_id = user_institution_mapping.institution + is_institution_moderator = True if user_institution_mapping.role == 'moderator' else False - if contracts_type == 'sola': + if contract_type == 'sola': if not is_institution_moderator: return 'Vaš uporabnik nima pravic za nalaganje pogodbe s šolo.' - Institution.update().values(file_contract=f_hash).where(id=institution_id) - - model_obj = ContractsSolar( - institution=institution_id, - upload_user=user_id, - file_contract=f_hash, - contracts_type=contracts_type, - ) - self.store_model(model_obj) + # TODO: insert institution contract + model_obj = InstitutionContract( + institution=institution_id, + corpus='solar', + timestamp=timestamp, + file_contract=f_hash + ) + self.store_model(model_obj) + else: + model_obj = ContractsSolar( + institution=institution_id, + upload_user=user_id, + timestamp=timestamp, + file_contract=f_hash, + contract_type=contract_type, + ) + + self.store_model(model_obj) return 'Nalaganje pogodbe je bilo uspešno.' @staticmethod @@ -154,12 +166,30 @@ def get_upload_history(user_id, n=20): return UploadSolar.query.filter_by(upload_user=user_id).order_by(desc(UploadSolar.timestamp)).limit(n).all() +def get_all_institutions(): + # TODO: do filtering purely within an SQL query + res = [] + for institution in Institution.query.all(): + row = CorpusAccess.query.filter_by(institution=institution.id, corpus='solar').first() + if row: + res.append(institution) + return res + + def get_institution_student_contracts(institution_id): - return ContractsSolar.query.filter_by(id=institution_id, contract_type='ucenci-starsi').all() + return ContractsSolar.query.filter_by(institution=institution_id, contract_type='ucenci-starsi').all() def get_institution_contract(institution_id): - return ContractsSolar.query.filter_by(id=institution_id, contract_type='sola').first() - + return InstitutionContract.query.filter_by(institution=institution_id, corpus='solar').order_by(desc(InstitutionContract.timestamp)).first() + +def get_all_active_users(): + # TODO: do filtering purely within an SQL query + res = [] + active_users = RegisteredUser.query.filter_by(active=True).all() + for user in active_users: + if has_user_corpus_access(user.id, 'solar'): + res.append(user) + return res diff --git a/static/style.css b/static/style.css index dd9a02d..6163efe 100644 --- a/static/style.css +++ b/static/style.css @@ -84,6 +84,20 @@ html { color: #006cb7; } +.section-desc { + font-family: Roboto; + font-style: bold; + font-weight: 320; + font-size: 16px; + text-align: center; + line-height: 20px; + margin-block-start: 0.4em; + border-bottom: 3px solid #006cb7; + margin-bottom: 10px; + padding-bottom: 10px; + color: #006cb7; +} + label { font-family: Roboto; font-style: normal; diff --git a/templates/solar-admin.html b/templates/solar-admin.html new file mode 100644 index 0000000..7a90129 --- /dev/null +++ b/templates/solar-admin.html @@ -0,0 +1,110 @@ + + + + + Admin panel - Šolar + + + +

Uporabniki

+

Dodaj uporabnika

+
+
+
+
+
+
+
+ +
+ +

Seznam vseh aktivnih uporabnikov

+
+ + + + + + + + + + {% for item in users %} + + + + + + {% endfor %} +
IDIme in priimekEmail
{{item.id}}{{item.name}}{{item.email}}
+
+

Dodeli uporabnika instituciji

+
+ +
+ +
+ + + +
+
+

Institucije

+

Dodaj institucijo

+
+ +
+ +
+ +
+

Seznam vseh instituticij

+
+ + + + + + + + + + {% for item in institutions %} + + + + + + {% endfor %} +
IDNazivRegija
{{item.id}}{{item.name}}{{item.region}}
+
+ diff --git a/templates/solar-institution-managment.html b/templates/solar-institution-managment.html new file mode 100644 index 0000000..336deb2 --- /dev/null +++ b/templates/solar-institution-managment.html @@ -0,0 +1,13 @@ + + + + + Admin panel - Šolar + + +

Uporabniki

+ TODO: odobri registracije +
+
+ + diff --git a/templates/solar-oddaja.html b/templates/solar-oddaja.html index f0b915f..309929b 100644 --- a/templates/solar-oddaja.html +++ b/templates/solar-oddaja.html @@ -18,6 +18,7 @@ + Odjavi se
diff --git a/templates/solar-pogodbe.html b/templates/solar-pogodbe.html index e7ded68..786917a 100644 --- a/templates/solar-pogodbe.html +++ b/templates/solar-pogodbe.html @@ -6,6 +6,7 @@ + Odjavi se
@@ -22,33 +23,49 @@
{% if contract_school %} -
-
...
-
Pogodba o prenosu lastništva
-
DODANO {{contract_school.date}}
- PRENESI
-
-
+
+
{% endif %} {% for item in contracts_students %}
-
...
+
Pogodba o prenosu lastništva
DODANO {{item.date}}
- PRENESI
+ PRENESI

{% endfor %}
+
+
{% if show_upload_form %} -
- -
- -
- - +
+
Oddaj pogodbo
+ + {% if enable_upload_school_contract %} +
+ + +
+ {% else %} +
+ + +
+ {% endif %} +
+ + +
+ + +
{% endif %}
diff --git a/templates/solar-zgodovina.html b/templates/solar-zgodovina.html index 5e1b79a..d75b827 100644 --- a/templates/solar-zgodovina.html +++ b/templates/solar-zgodovina.html @@ -6,6 +6,7 @@ + Odjavi se