diff --git a/Dockerfile b/Dockerfile index a171a94..6198724 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,6 @@ WORKDIR /usr/src/portal-webapp RUN apt-get update && apt-get -y install wkhtmltopdf && \ rm -rf /var/lib/apt/lists/* -RUN pip3 install --no-cache-dir pdfkit flask==1.1.4 flask-dropzone flask-log-request-id flask-login Flask-SQLAlchemy alembic flask-migrate==2.7.0 Flask-script psycopg2 gunicorn pdfkit Werkzeug==1.0.1, PyJWT +RUN pip3 install --no-cache-dir pdfkit flask==1.1.4 flask-dropzone flask-log-request-id flask-login Flask-SQLAlchemy alembic flask-migrate==2.7.0 Flask-script psycopg2 gunicorn pdfkit Werkzeug==1.0.1 PyJWT ENTRYPOINT ["./entrypoint.sh"] diff --git a/app.py b/app.py index b0a2b6e..bd48562 100644 --- a/app.py +++ b/app.py @@ -123,7 +123,8 @@ upload_handler_regular = portal.regular.UploadHandlerRegular( MAIL_SUBJECT=MAIL_SUBJECT, MAIL_BODY=MAIL_BODY, CONTRACT_CLIENT_CONTACT=CONTRACT_CLIENT_CONTACT, - MAX_FILES_PER_UPLOAD=MAX_FILES_PER_UPLOAD + MAX_FILES_PER_UPLOAD=MAX_FILES_PER_UPLOAD, + APP_SECRET_KEY=APP_SECRET_KEY ) upload_handler_solar = portal.solar.UploadHandlerSolar( @@ -136,7 +137,8 @@ upload_handler_solar = portal.solar.UploadHandlerSolar( MAIL_SUBJECT=MAIL_SUBJECT, MAIL_BODY=MAIL_BODY, CONTRACT_CLIENT_CONTACT=CONTRACT_CLIENT_CONTACT, - MAX_FILES_PER_UPLOAD=MAX_FILES_PER_UPLOAD + MAX_FILES_PER_UPLOAD=MAX_FILES_PER_UPLOAD, + APP_SECRET_KEY=APP_SECRET_KEY ) @@ -217,6 +219,9 @@ def solar_register_post(): name = request.form.get('name') email = request.form.get('email') password = request.form.get('password') + institution_name = request.form.get('institution') + institution_role = request.form.get('role') + institution = portal.base.get_institution_obj_by_name(institution_name) user = RegisteredUser.query.filter_by(email=email).first() @@ -248,7 +253,17 @@ def solar_register_post(): flash('Predolgo geslo.') return redirect('/solar/register') - portal.base.register_new_user(name, email, password, active=False) + if not institution: + flash('Institucija ne obstaja.') + return redirect('/solar/register') + + if institution_role not in ['coordinator', 'mentor', 'other']: + flash('Neveljavna vloga v instituciji.') + return redirect('/solar/register') + + + user_id = portal.base.register_new_user(name, email, password, active=False) + portal.base.add_user_to_institution(user_id, institution.id, institution_role) flash('Uspešna registracija.') return redirect('/solar/login') @@ -270,13 +285,20 @@ def logout(): def solar(text): is_admin = current_user.role == 'admin' current_user_institution = portal.base.get_user_institution(current_user.id) + current_user_obj = portal.base.get_user_obj(current_user.get_id()) + institution_contract = None if current_user_institution: - current_user_institution_moderator = portal.base.is_institution_moderator(current_user.id, current_user_institution.id) + current_user_institution_coordinator = portal.base.is_institution_coordinator(current_user.id, current_user_institution.id) + institution_contract = portal.base.get_institution_contract(current_user_institution.id) else: - current_user_institution_moderator = False + current_user_institution_coordinator = False if text.startswith('oddaja/') or text == 'oddaja': - return render_template('solar-oddaja.html', is_admin=is_admin, is_institution_moderator=current_user_institution_moderator) + return render_template('solar-oddaja.html', + is_admin=is_admin, + institution=current_user_institution, + institution_contract=institution_contract, + is_institution_coordinator=current_user_institution_coordinator) elif text.startswith('zgodovina/') or text == 'zgodovina': upload_items = portal.solar.get_upload_history(current_user.id) uploader_names = [] @@ -289,58 +311,66 @@ def solar(text): else: institution_names.append(institution.name) return render_template('solar-zgodovina.html', upload_history=upload_items, uploader_names=uploader_names, - institution_names=institution_names, is_admin=is_admin, is_institution_moderator=current_user_institution_moderator) - elif text.startswith('pogodbe/') or text == 'pogodbe': + institution_names=institution_names, is_admin=is_admin, is_institution_coordinator=current_user_institution_coordinator) + elif text.startswith('pogodbe-institucije/') or text.startswith('pogodbe-ucencistarsi/'): # Check for ownload contract request. - match = re.match('^pogodbe/([a-z0-9_]+\.pdf)$', text) + match = re.match('^pogodbe-(institucije|ucencistarsi)/([a-z0-9_]+\.pdf)$', text) if match: - filename = match.group(1) + contract_type = match.group(1) + filename = match.group(2) if len(filename) < 10: return '', 404 prefix = filename[:2] suffix = filename[2:] + f_hash = filename.split('.')[0] + + if contract_type == 'institucije': + actual_filename = portal.base.get_actual_institution_contract_filename(f_hash) + else: + actual_filename = portal.base.get_actual_studentparent_contract_filename(f_hash) safe_path = safe_join(str(upload_handler_solar.get_uploads_subdir('contracts')), prefix, suffix) try: - return send_file(safe_path, as_attachment=True) + return send_file(safe_path, attachment_filename=actual_filename, as_attachment=True) except FileNotFoundError: return '', 404 - - user_obj = portal.base.get_user_obj(current_user.get_id()) - institution = portal.base.get_user_institution(user_obj.id) + elif text.startswith('pogodbe/') or text == 'pogodbe': contracts_students = [] contract_school = [] enable_upload_school_contract = False show_upload_form = False collaborators = [] - if institution: - collaborators = portal.base.get_all_active_institution_users(institution.id) + cooperation_history = dict() + if current_user_institution: + collaborators = portal.base.get_all_active_institution_users(current_user_institution.id) show_upload_form = True - contract_school = portal.solar.get_institution_contract(institution.id) - if portal.base.is_institution_moderator(user_obj.id, institution.id): - contracts_students = portal.solar.get_institution_student_contracts(institution.id) + contract_school = portal.solar.get_institution_contract(current_user_institution.id) + cooperation_history = portal.base.get_institution_cooperation_history(current_user_institution.id) + if portal.base.is_institution_coordinator(current_user_obj.id, current_user_institution.id): + contracts_students = portal.solar.get_institution_student_contracts(current_user_institution.id) enable_upload_school_contract = True else: - contracts_students = portal.solar.get_institution_student_contracts(institution.id, user_obj.id) + contracts_students = portal.solar.get_institution_student_contracts(current_user_institution.id, current_user_obj.id) return render_template('solar-pogodbe.html', contracts_students=contracts_students, contract_school=contract_school, enable_upload_school_contract=enable_upload_school_contract, show_upload_form=show_upload_form, collaborators=collaborators, - is_admin=is_admin, is_institution_moderator=current_user_institution_moderator) + cooperation_history=cooperation_history, + is_admin=is_admin, is_institution_coordinator=current_user_institution_coordinator) elif text.startswith('admin/') or text == 'admin': - users = portal.base.get_all_active_users_join_institutions() - inactive_users = portal.base.get_all_inactive_users() + users = portal.base.get_all_users_join_institutions() + inactive_users = portal.base.get_all_users_join_institutions(active=False) solar_institutions = portal.solar.get_all_institutions() + cooperation_history = portal.base.get_cooperation_history() if is_admin: - return render_template('solar-admin.html', users=users, + return render_template('solar-admin.html', users=users, user_cooperation_history=cooperation_history, institutions=solar_institutions, inactive_users=inactive_users) elif text.startswith('manage-institution/') or text == 'manage-institution': - institution = portal.base.get_user_institution(current_user.id) - if portal.base.is_institution_moderator(current_user.id, institution.id): + if portal.base.is_institution_coordinator(current_user.id, current_user_institution.id): solar_users = portal.base.get_all_active_users() - institution_users = portal.base.get_all_active_institution_users(institution.id) + institution_users = portal.base.get_all_active_institution_users(current_user_institution.id) return render_template('solar-manage-institution.html', users=solar_users, institution_users=institution_users) return '', 404 @@ -420,7 +450,7 @@ def solar_forgotpass(): def solar_sendresetpass(): email = request.form.get('email') - portal.base.send_resetpass_mail(email, upload_handler_regular.config) + portal.base.send_resetpass_mail(email, upload_handler_solar.config) flash('Povezava za ponastavitev gesla je bila poslana na vpisan email naslov.') return redirect(redirect_url()) @@ -428,7 +458,7 @@ def solar_sendresetpass(): @app.route('/solar/resetpass/') def solar_resetpass(token): - user = portal.base.verify_reset_token(token) + user = portal.base.verify_reset_token(token, upload_handler_solar.config['APP_SECRET_KEY']) if not user: return '', 404 @@ -439,7 +469,7 @@ def solar_resetpass(token): @app.route('/solar/resetpass/', methods=['POST']) def solar_resetpass_post(token): new_password = request.form.get('new_password') - user = portal.base.verify_reset_token(token) + user = portal.base.verify_reset_token(token, upload_handler_solar.config['APP_SECRET_KEY']) if not user: return '', 404 @@ -448,7 +478,8 @@ def solar_resetpass_post(token): if rowcount == 0: return '', 404 - return 'Ponastavitev gesla uspešna.' + flash('Ponastavitev gesla je bila uspešna.') + return redirect('/solar/login') @app.route('/solar/topuploads') @@ -460,9 +491,12 @@ def solar_topuploads_srednje(): @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 + if not portal.base.is_admin(current_user.id): + return '', 404 + user_id = request.form.get('user_id') + portal.base.del_user(user_id) + flash('Uporabnik je bil odstranjen.') + return redirect(redirect_url()) @app.route('//addinstitution', methods=['POST']) @login_required @@ -482,11 +516,8 @@ def add_institution(corpus_name): flash('Predolgo ime.') return redirect(redirect_url()) - if not region: - flash('Prazno polje za regijo.') - return redirect(redirect_url()) - if len(region) > 100: - flash('Predolgi niz za regijo.') + if not region in portal.solar.VALID_REGIONS: + flash('Neveljavna vrednost za regijo.') return redirect(redirect_url()) institution_id = portal.base.add_institution(name, region) @@ -506,37 +537,34 @@ def add_user_institution_mapping(corpus_name): if institution: institution_id = institution.id - if not (portal.base.is_admin(current_user.id) or portal.base.is_institution_moderator(current_user.id, institution_id)): + if not (portal.base.is_admin(current_user.id) or portal.base.is_institution_coordinator(current_user.id, institution_id)): return '', 404 user_id = request.form['user_id'] role = request.form['role'] - if role not in ['moderator', 'user']: + if role not in ['coordinator', 'mentor', 'other']: return '', 404 if portal.base.get_user_institution(user_id): - flash('Uporabnik je že dodeljen instituciji. Dodeljevanje večim institucijam '\ - 'zaenkrat ni implementirano.') + flash('Uporabnik je že dodeljen instituciji.') return redirect(redirect_url()) portal.base.add_user_to_institution(user_id, institution_id, role) flash('Uporabnik je bil dodeljen instituciji.') return redirect(redirect_url()) -@app.route('//deluserfrominstitution', methods=['POST']) +@app.route('/solar/deluserfrominstitution', methods=['POST']) @login_required -def del_user_institution_mapping(corpus_name): - institution = portal.base.get_user_institution(current_user.id) - if not portal.base.is_admin(current_user.id) \ - and not portal.base.is_institution_moderator(current_user.id, institution.id): - return '', 404 - if not corpus_name in ENABLED_CORPUSES: - return '', 404 - +def del_user_institution_mapping(): user_id = request.form['user_id'] + institution = portal.base.get_user_institution(user_id) + if not institution: + flash('Uporabnik ni član nobene institucije.') + return redirect(redirect_url()) - if not portal.base.is_institution_member(user_id, institution.id): - flash('Uporabnik ni član vaše institucije.') + if not portal.base.is_admin(current_user.id) \ + and not portal.base.is_institution_coordinator(current_user.id, institution.id): + flash('Nimate ustreznih pravic za odstranitev uporabnika iz institucije.') return redirect(redirect_url()) portal.base.del_user_from_institution(user_id, institution.id) diff --git a/migrations/versions/4c132220432f_added_usercooperationhistory.py b/migrations/versions/4c132220432f_added_usercooperationhistory.py new file mode 100644 index 0000000..892a877 --- /dev/null +++ b/migrations/versions/4c132220432f_added_usercooperationhistory.py @@ -0,0 +1,37 @@ +"""Added UserCooperationHistory. + +Revision ID: 4c132220432f +Revises: ff356b1ef661 +Create Date: 2021-09-01 09:25:43.144096 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4c132220432f' +down_revision = 'ff356b1ef661' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('user_cooperation', + 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.Column('badge_text', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['institution'], ['institution.id'], ), + sa.ForeignKeyConstraint(['user'], ['registered_user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('user_cooperation') + # ### end Alembic commands ### diff --git a/migrations/versions/c60b7bfaaf85_added_school_year_column_to_user_history.py b/migrations/versions/c60b7bfaaf85_added_school_year_column_to_user_history.py new file mode 100644 index 0000000..2d1d29a --- /dev/null +++ b/migrations/versions/c60b7bfaaf85_added_school_year_column_to_user_history.py @@ -0,0 +1,28 @@ +"""Added school year column to user history. + +Revision ID: c60b7bfaaf85 +Revises: 4c132220432f +Create Date: 2021-09-02 08:36:41.711040 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c60b7bfaaf85' +down_revision = '4c132220432f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('user_cooperation', sa.Column('school_year', sa.String(), nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('user_cooperation', 'school_year') + # ### end Alembic commands ### diff --git a/migrations/versions/ff356b1ef661_dropped_out_predavanje_stuff_added_.py b/migrations/versions/ff356b1ef661_dropped_out_predavanje_stuff_added_.py new file mode 100644 index 0000000..b679a08 --- /dev/null +++ b/migrations/versions/ff356b1ef661_dropped_out_predavanje_stuff_added_.py @@ -0,0 +1,58 @@ +"""Dropped out predavanje stuff, added contract initial file name. + +Revision ID: ff356b1ef661 +Revises: 5ba116fc7f06 +Create Date: 2021-08-26 12:45:56.036966 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'ff356b1ef661' +down_revision = '5ba116fc7f06' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('stamps_solar') + op.drop_table('upload_predavanja') + op.add_column('contracts_solar', sa.Column('original_filename', sa.String(), nullable=True)) + op.add_column('institution_contract', sa.Column('original_filename', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('institution_contract', 'original_filename') + op.drop_column('contracts_solar', 'original_filename') + op.create_table('upload_predavanja', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('upload_hash', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('timestamp', postgresql.TIMESTAMP(), autoincrement=False, nullable=False), + sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('address', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('subject', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('faculty', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('email', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('phone', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('keywords', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('file_contract', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('upload_file_hashes', postgresql.ARRAY(sa.VARCHAR()), autoincrement=False, nullable=True), + sa.Column('agree_publish_future', sa.TEXT(), autoincrement=False, nullable=False), + sa.Column('agree_machine_translation', sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.Column('agree_news_cjvt', sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.PrimaryKeyConstraint('id', name='upload_predavanja_pkey') + ) + op.create_table('stamps_solar', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('institution', sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column('name', sa.TEXT(), autoincrement=False, nullable=False), + sa.Column('file_logo', sa.TEXT(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['institution'], ['institution.id'], name='stamps_institution_fkey'), + sa.PrimaryKeyConstraint('id', name='stamps_pkey') + ) + # ### end Alembic commands ### diff --git a/portal/base.py b/portal/base.py index 8ef5de7..384dd59 100644 --- a/portal/base.py +++ b/portal/base.py @@ -25,7 +25,7 @@ import jwt from werkzeug.security import generate_password_hash -from . model import db, UploadRegular, UploadSolar, RegisteredUser, UserInstitutionMapping, Institution, InstitutionContract, CorpusAccess +from . model import * #REGEX_EMAIL = re.compile('^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$') @@ -248,6 +248,33 @@ def get_user_institution(user_id): return None +def get_institution_contract(institution_id): + return InstitutionContract.query.filter_by(institution=institution_id).first() + + +def get_institution_cooperation_history(institution_id): + #return CooperationToken.query.join(UserCooperationTokenMapping, +# UserCooperationTokenMapping.cooperation_token == CooperationToken.id).filter(UserCooperationTokenMapping.user == user_id).all() +# + res = dict() + + uch_rows = UserCooperationHistory.query.filter_by(institution=institution_id).order_by(UserCooperationHistory.school_year.desc()).all() + for row in uch_rows: + if row.user not in res: + res[row.user] = { + 'coordinator': [], + 'moderator': [], + 'other': [] + } + res[row.user][row.role].append(row.school_year) + + return res + + +def get_cooperation_history(): + return UserCooperationHistory.query.all() + + def has_user_corpus_access(user_id, corpus_name): user = RegisteredUser.query.filter_by(id=user_id).first() @@ -281,6 +308,10 @@ def get_institution_obj(institution_id): return Institution.query.filter_by(id=institution_id).first() +def get_institution_obj_by_name(institution_name): + return Institution.query.filter_by(name=institution_name).first() + + def register_new_user(name, email, password, active=True, admin=False): model_obj = RegisteredUser( name=name, @@ -334,11 +365,18 @@ def activate_user(user_id): def update_user_password(user_id, new_password): phash = generate_password_hash(new_password) - rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'pass_hash': pass_hash}) + rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'pass_hash': phash}) db.session.commit() return rowcount +def del_user(user_id): + db.session.query(UserCooperationHistory).filter(UserCooperationHistory.user == user_id).delete() + db.session.query(UserInstitutionMapping).filter(UserInstitutionMapping.user == user_id).delete() + db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).delete() + db.session.commit() + + def del_user_from_institution(user_id, institution_id): db.session.query(UserInstitutionMapping).filter(UserInstitutionMapping.institution == institution_id).filter(UserInstitutionMapping.user == user_id).delete() db.session.commit() @@ -347,24 +385,28 @@ def del_user_from_institution(user_id, institution_id): def get_all_active_users(): return RegisteredUser.query.filter_by(active=True).order_by(RegisteredUser.id).all() + def get_all_inactive_users(): return RegisteredUser.query.filter_by(active=False).order_by(RegisteredUser.id).all() -def get_all_active_users_join_institutions(): + +def get_all_users_join_institutions(active=True): #return RegisteredUser.query.filter_by(active=True).order_by(RegisteredUser.id).all() return db.session.query(RegisteredUser, UserInstitutionMapping).outerjoin(UserInstitutionMapping, - RegisteredUser.id == UserInstitutionMapping.user).order_by(RegisteredUser.id).all() + RegisteredUser.id == UserInstitutionMapping.user).filter(RegisteredUser.active == active).order_by(RegisteredUser.id).all() + + def get_all_active_institution_users(institution_id): return RegisteredUser.query.filter_by(active=True).join(UserInstitutionMapping, RegisteredUser.id == UserInstitutionMapping.user).filter(UserInstitutionMapping.institution == institution_id).all() -def is_institution_moderator(user_id, institution_id): +def is_institution_coordinator(user_id, institution_id): user_inst_mapping = UserInstitutionMapping.query.filter_by(user=user_id).filter_by(institution=institution_id).first() if not user_inst_mapping: return False - if user_inst_mapping.role != 'moderator': + if user_inst_mapping.role != 'coordinator': return False return True @@ -376,15 +418,34 @@ def is_institution_member(user_id, institution_id): return True -def get_password_reset_token(email, expires=500): +def get_actual_institution_contract_filename(f_hash): + contract = InstitutionContract.query.filter_by(file_contract=f_hash).first() + if contract: + return contract.original_filename + return None + + +def get_actual_studentparent_contract_filename(f_hash): + contract = ContractsSolar.query.filter_by(file_contract=f_hash).first() + if contract: + return contract.original_filename + return None + + +def get_password_reset_token(email, key, expires=600): return jwt.encode({'reset_password': email, - 'exp': time() + expires}, - key=os.getenv('APP_SECRET_KEY'), algorithm='HS256') + 'exp': int(time.time()) + expires}, + key=key, algorithm='HS256') -def verify_reset_token(token): +def verify_reset_token(token, key): try: - email = jwt.decode(token, - key=os.getenv('APP_SECRET_KEY'), algorithms=["HS256"])['reset_password'] + message = jwt.decode(token, + key=key, algorithms=["HS256"]) + email = message['reset_password'] + exp = message['exp'] + if int(time.time()) >= exp: + # Token timed out + return None except Exception as e: logging.error(e) return @@ -392,13 +453,12 @@ def verify_reset_token(token): def send_resetpass_mail(email, config): - jwt_token = get_password_reset_token(email) + jwt_token = get_password_reset_token(email, config['APP_SECRET_KEY']) text = ''' Zahtevali ste ponastavitev gesla vašega uporabniškega računa. - Geslo lahko ponastavite na naslednji povezavi: https://zbiranje.slovenscina.eu/solar/resetpass/{}'''.format( - 'https://zbiranje.slovenscina.eu/solar/resetpass/{}'.format(jwt_token)) + Geslo lahko ponastavite na naslednji povezavi: https://zbiranje.slovenscina.eu/solar/resetpass/{}'''.format(jwt_token) # Create a secure SSL context context = ssl.create_default_context() diff --git a/portal/model.py b/portal/model.py index 0cae17d..30f7a11 100644 --- a/portal/model.py +++ b/portal/model.py @@ -28,25 +28,6 @@ class UploadRegular(db.Model): corpus_name = db.Column(db.String, nullable=False) -class UploadPredavanja(db.Model): - __tablename__ = 'upload_predavanja' - id = db.Column(db.Integer, primary_key=True) - upload_hash = db.Column(db.String, nullable=False) - timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) - name = db.Column(db.String, nullable=False) - address = db.Column(db.String, nullable=False) - subject = db.Column(db.String, nullable=False) - faculty = db.Column(db.String, nullable=False) - email = db.Column(db.String, nullable=False) - phone = db.Column(db.String, nullable=True) - keywords = db.Column(db.String, nullable=False) - agree_publish_future = db.Column(db.String, nullable=False) - agree_machine_translation = db.Column(db.Boolean, default=False, nullable=False) - agree_news_cjvt = db.Column(db.Boolean, default=False, nullable=False) - file_contract = db.Column(db.String, nullable=True) - upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String), nullable=True) - - class UploadSolar(db.Model): __tablename__ = 'upload_solar' id = db.Column(db.Integer, primary_key=True) @@ -65,14 +46,6 @@ class UploadSolar(db.Model): upload_file_hashes = db.Column(sqlalchemy.types.ARRAY(db.String), nullable=True) -class StampsSolar(db.Model): - __tablename__ = 'stamps_solar' - id = db.Column(db.Integer, primary_key=True) - institution = db.Column(db.Integer, sqlalchemy.ForeignKey('institution.id'), nullable=True) - name = db.Column(db.String, nullable=False) - file_logo = db.Column(db.String, nullable=True) - - class ContractsSolar(db.Model): __tablename__ = 'contracts_solar' id = db.Column(db.Integer, primary_key=True) @@ -80,6 +53,7 @@ class ContractsSolar(db.Model): upload_user = db.Column(db.Integer, sqlalchemy.ForeignKey('registered_user.id'), nullable=True) timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) file_contract = db.Column(db.String, nullable=False) + original_filename = db.Column(db.String, nullable=True) contract_type = db.Column(db.String, nullable=False) @@ -100,7 +74,29 @@ class UserInstitutionMapping(db.Model): 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) + role = db.Column(db.String, nullable=False) + + +#class CooperationToken(db.Model): +# __tablename__ = 'cooperation_token' +# id = db.Column(db.Integer, primary_key=True) +# name = db.Column(db.String, nullable=False) +# +# +#class UserCooperationTokenMapping(db.Model): +# __tablename__ = 'user_cooperation_token_mapping' +# id = db.Column(db.Integer, primary_key=True) +# user = db.Column(db.Integer, sqlalchemy.ForeignKey('registered_user.id'), nullable=False) +# cooperation_token = db.Column(db.Integer, sqlalchemy.ForeignKey('cooperation_token.id'), nullable=False) + +class UserCooperationHistory(db.Model): + __tablename__ = 'user_cooperation' + 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) + school_year = db.Column(db.String, nullable=False) + badge_text = db.Column(db.String, nullable=True) class Institution(db.Model): @@ -124,4 +120,5 @@ class InstitutionContract(db.Model): 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) + original_filename = db.Column(db.String, nullable=True) diff --git a/portal/solar.py b/portal/solar.py index 18afa54..5e92d07 100644 --- a/portal/solar.py +++ b/portal/solar.py @@ -13,6 +13,7 @@ VALID_PROGRAMS = {'OS', 'SSG', 'MGP', 'ZG', 'NPI', 'SPI', 'SSI', 'PTI'} VALID_SUBJECTS = {'slo', 'drug-jez', 'drug-druz', 'drug-narav', 'drug-strok', 'drug-izb'} VALID_TEXT_TYPES = {'esej-spis', 'prakticno', 'solski-test', 'delo-v-razredu'} VALID_GRAMMAR_CORRECTIONS = {'popr-ne', 'brez-popr', 'popr-da'} +VALID_REGIONS = {'CE', 'GO', 'KK', 'KP', 'KR', 'LJ', 'MB', 'MS', 'NM', 'PO', 'SG'} MAXLEN_FORM = 150 @@ -109,17 +110,18 @@ class UploadHandlerSolar(UploadHandler): 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 + is_institution_coordinator = True if user_institution_mapping.role == 'coordinator' else False if contract_type == 'sola': - if not is_institution_moderator: + if not is_institution_coordinator: return 'Vaš uporabnik nima pravic za nalaganje pogodbe s šolo.' # TODO: insert institution contract model_obj = InstitutionContract( institution=institution_id, corpus='solar', timestamp=timestamp, - file_contract=f_hash + file_contract=f_hash, + original_filename=f_obj.filename ) self.store_model(model_obj) else: @@ -129,6 +131,7 @@ class UploadHandlerSolar(UploadHandler): timestamp=timestamp, file_contract=f_hash, contract_type=contract_type, + original_filename=f_obj.filename ) self.store_model(model_obj) diff --git a/static/image/contract.png b/static/image/contract.png new file mode 100644 index 0000000..4f33bbb Binary files /dev/null and b/static/image/contract.png differ diff --git a/static/image/password.png b/static/image/password.png new file mode 100644 index 0000000..3c52c25 Binary files /dev/null and b/static/image/password.png differ diff --git a/static/image/register.png b/static/image/register.png new file mode 100644 index 0000000..29538b1 Binary files /dev/null and b/static/image/register.png differ diff --git a/static/image/showpass.png b/static/image/showpass.png new file mode 100644 index 0000000..cc3890a Binary files /dev/null and b/static/image/showpass.png differ diff --git a/static/image/star.png b/static/image/star.png new file mode 100644 index 0000000..c3aeadb Binary files /dev/null and b/static/image/star.png differ diff --git a/static/image/user.png b/static/image/user.png new file mode 100644 index 0000000..7d81b47 Binary files /dev/null and b/static/image/user.png differ diff --git a/static/style.css b/static/style.css index 7ef53db..83addb0 100644 --- a/static/style.css +++ b/static/style.css @@ -142,33 +142,50 @@ label { } .contract-item { - height: 50px; + height: 65px; margin: 5px; border: 0px; border-bottom: 2px solid #c4c4c4; } +.contract-item-icon { + height: 50%; + margin-top: 15px; + margin-left: 15px; + margin-right: 15px; + float: left; +} + .contract-item-title { font-family: Roboto; font-style: normal; font-weight: normal; font-size: 16px; color: #46535b; + padding-top: 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .contract-item-button { font-family: Roboto; font-style: normal; font-weight: normal; - font-size: 16px; + font-size: 14px; + float: right; + margin-top: -5px; } -.contract-item-date { +.contract-item-data { font-family: Roboto; font-style: normal; font-weight: normal; font-size: 10px; color: #46535b; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } #collaborators-container { @@ -200,6 +217,33 @@ label { margin: 25px; padding:5px; border-radius: 10px; + text-align: center; +} + +.warning { + background: #ffcc00; + border-radius: 15px; + border: 0px; + font-family: Roboto; + font-style: normal; + font-weight: normal; + font-size: 16px; + margin: 20px; + padding: 10px; + text-align: center; +} + +.message-notification { + background: #99cc33; + border-radius: 15px; + border: 0px; + font-family: Roboto; + font-style: normal; + font-weight: normal; + font-size: 16px; + margin: 20px; + padding: 10px; + text-align: center; } #button-submit { @@ -554,6 +598,10 @@ select { left: 25% !important; } +.history-item { + border-bottom: 1px solid grey; +} + .history-item-date { font-family: Roboto; font-style: normal; @@ -598,6 +646,7 @@ select { font-weight: normal; font-size: 12px; line-height: 14px; + margin-bottom: 5px; color: #848C91; } diff --git a/templates/basic.html b/templates/basic.html index 658b5b3..f6a6047 100644 --- a/templates/basic.html +++ b/templates/basic.html @@ -47,7 +47,7 @@ -
*Po kliku na gumb “Oddaj” se bo prikazala vsebina pogodobe o odstopu avtorskih pravic. Če se z vsebino strinjate, kliknite gumb “Pošlji”, da podatke posredujete v korpus, po e-pošti pa boste prejeli svoj izvod pogodbe.
+
*Po kliku na gumb “Oddaj” se bo prikazala vsebina pogodbe o odstopu avtorskih pravic. Če se z vsebino strinjate, kliknite gumb “Pošlji”, da podatke posredujete v korpus, po e-pošti pa boste prejeli svoj izvod pogodbe.
diff --git a/templates/solar-admin.html b/templates/solar-admin.html index 737d9f2..e5185da 100644 --- a/templates/solar-admin.html +++ b/templates/solar-admin.html @@ -5,8 +5,8 @@ Admin panel - Šolar @@ -45,13 +48,13 @@
- -

Seznam vseh aktivnih uporabnikov

+ +

Aktivni uporabniki

@@ -60,6 +63,7 @@ + @@ -69,6 +73,7 @@ + {% endfor %}
Ime in priimek Email ID institucijeVloga v instituciji
{{item[0].name}} {{item[0].email}} {{item[1].institution}}{{item[1].role}}
@@ -80,11 +85,18 @@
+

Odstrani uporabnika iz institucije

+
+ +
+ +

Institucije

Dodaj institucijo

@@ -92,7 +104,19 @@
-
+

Seznam vseh instituticij

@@ -115,7 +139,7 @@ {% endfor %} -

Seznam uporabnikov, ki niso aktivirani

+

Čakajoči uporabniki

@@ -123,14 +147,18 @@ + + {% for item in inactive_users %} - - - + + + + + {% endfor %}
ID Ime in priimek EmailInstitucijaVloga v instituciji
{{item.id}}{{item.name}}{{item.email}}{{item[0].id}}{{item[0].name}}{{item[0].email}}{{item[1].institution}}{{item[1].role}}
@@ -141,4 +169,30 @@
+

Zgodovina sodelovanja

+
+ + + + + + + + + + + + + {% for item in user_cooperation_history %} + + + + + + + + + {% endfor %} +
IDID uporabnikaID institucijeVlogaŠolsko letoBesedilo značke
{{item.id}}{{item.user}}{{item.institution}}{{item.role}}{{item.school_year}}{{item.badge_text}}
+
diff --git a/templates/solar-forgotpass.html b/templates/solar-forgotpass.html index bf0eb4e..c9c4222 100644 --- a/templates/solar-forgotpass.html +++ b/templates/solar-forgotpass.html @@ -28,9 +28,8 @@
- + - Registracija diff --git a/templates/solar-login.html b/templates/solar-login.html index 37a38ae..8419109 100644 --- a/templates/solar-login.html +++ b/templates/solar-login.html @@ -8,11 +8,17 @@
-
+
logo
-

Prijava - ŠOLAR

+

Portal za oddajanje besedil

+

Korpus Šolar

+ +
Zbiranje besedil za korpus Šolar poteka po naslednjem postopku, ki prinaša tudi točke za napredovanje.
+ +

Prijava

+
{% with messages = get_flashed_messages() %} {% if messages %} @@ -24,22 +30,32 @@
- + user +
- + user +
-
- - -
- +
- Registracija + Pozabljeno geslo + user + Registracija
Še nimate uporabniškega računa? Registrirajte se!
diff --git a/templates/solar-manage-institution.html b/templates/solar-manage-institution.html index a8f2f9e..3adfc32 100644 --- a/templates/solar-manage-institution.html +++ b/templates/solar-manage-institution.html @@ -5,8 +5,8 @@ Upravljanje institucije - Šolar {% with messages = get_flashed_messages() %} {% if messages %} -
+
{{ messages[0] }}
{% endif %} @@ -76,14 +79,15 @@

-

Dodeli uporabnika instituciji

+

Dodaj uporabnika instituciji


diff --git a/templates/solar-oddaja.html b/templates/solar-oddaja.html index d330a76..a507840 100644 --- a/templates/solar-oddaja.html +++ b/templates/solar-oddaja.html @@ -19,12 +19,13 @@ Odjavi se - {% if is_institution_moderator %} + {% if is_institution_coordinator %}
Upravljaj z institucijo {% endif %} {% if is_admin %}
Administracijski meni {% endif %} +
Pomoč
@@ -41,6 +42,13 @@
+ {% if not institution %} +
Niste član nobene institucije!
+ {% elif not institution_contract %} +
Pogodba s šolo še ni naložena!
+ {% endif %} + +
+ +
+
+ +
+
+ + +
diff --git a/templates/solar-resetpass.html b/templates/solar-resetpass.html index 37a38ae..45f2256 100644 --- a/templates/solar-resetpass.html +++ b/templates/solar-resetpass.html @@ -12,7 +12,7 @@
logo
-

Prijava - ŠOLAR

+

Ponastavitev gesla - ŠOLAR

{% with messages = get_flashed_messages() %} {% if messages %} @@ -21,25 +21,14 @@
{% endif %} {% endwith %} -
+
- +
- -
-
- -
-
-
- - -
- +
- Registracija diff --git a/templates/solar-zgodovina.html b/templates/solar-zgodovina.html index e8a5771..73f6da4 100644 --- a/templates/solar-zgodovina.html +++ b/templates/solar-zgodovina.html @@ -8,12 +8,13 @@ Odjavi se - {% if is_institution_moderator %} + {% if is_institution_coordinator %}
Upravljaj z institucijo {% endif %} {% if is_admin %}
Administracijski meni {% endif %} +
Pomoč
@@ -35,43 +36,45 @@
{{ uploader_names[loop.index - 1] }}
Št. datotek: {{ item.upload_file_hashes|length }}
+ [ {% set began = False %} {% if institution_names[loop.index - 1] %} - {% if began %}|{% endif %} {{ institution_names[loop.index - 1] }} + {% if began %}, {% endif %} {{ institution_names[loop.index - 1] }} {% set began = True %} {% endif %} {% if item.program %} - {% if began %}|{% endif %} {{ item.program }} + {% if began %}, {% endif %} {{ item.program }} {% set began = True %} {% endif %} {% if item.subject %} - {% if began %}|{% endif %} {{ item.subject }} + {% if began %}, {% endif %} {{ item.subject }} {% set began = True %} {% endif %} {% if item.subject_custom %} - {% if began %}|{% endif %} {{ item.subject_custom }} + {% if began %}, {% endif %} {{ item.subject_custom }} {% set began = True %} {% endif %} {% if item.grade %} - {% if began %}|{% endif %} {{ item.grade }} + {% if began %}, {% endif %} {{ item.grade }} {% set began = True %} {% endif %} {% if item.text_type %} - {% if began %}|{% endif %} {{ item.text_type }} + {% if began %}, {% endif %} {{ item.text_type }} {% set began = True %} {% endif %} {% if item.text_type_custom %} - {% if began %}|{% endif %} {{ item.text_type_custom }} + {% if began %}, {% endif %} {{ item.text_type_custom }} {% set began = True %} {% endif %} {% if item.school_year %} - {% if began %}|{% endif %} {{ item.school_year }} + {% if began %}, {% endif %} {{ item.school_year }} {% set began = True %} {% endif %} {% if item.grammar_corrections %} - {% if began %}|{% endif %} {{ item.grammar_corrections }} + {% if began %}, {% endif %} {{ item.grammar_corrections }} {% set began = True %} {% endif %} + ]