Šolar update.

This commit is contained in:
msinkec 2021-09-16 17:25:20 +02:00
parent 788f3bc3eb
commit d60ef26741
9 changed files with 595 additions and 243 deletions

179
app.py
View File

@ -163,6 +163,7 @@ def index_corpus(corpus_name):
if corpus_name not in ENABLED_CORPUSES: if corpus_name not in ENABLED_CORPUSES:
return 'Korpus "{}" ne obstaja.'.format(corpus_name), 404 return 'Korpus "{}" ne obstaja.'.format(corpus_name), 404
description = ""
if corpus_name == 'prevodi': if corpus_name == 'prevodi':
description = DESC_PREVODI description = DESC_PREVODI
elif corpus_name == 'gigafida': elif corpus_name == 'gigafida':
@ -253,19 +254,22 @@ def solar_register_post():
flash('Predolgo geslo.') flash('Predolgo geslo.')
return redirect('/solar/register') return redirect('/solar/register')
if not institution:
flash('Institucija ne obstaja.')
return redirect('/solar/register')
if institution_role not in ['coordinator', 'mentor', 'other']: if institution_role not in ['coordinator', 'mentor', 'other']:
flash('Neveljavna vloga v instituciji.') flash('Neveljavna vloga v instituciji.')
return redirect('/solar/register') return redirect('/solar/register')
if not institution:
institution_id = portal.base.add_institution(institution_name, "")
portal.base.grant_institution_corpus_access(institution_id, "solar")
else:
institution_id = institution.id
user_id = portal.base.register_new_user(name, email, password, active=False) user_id = portal.base.register_new_user(name, email, password, active=False)
portal.base.add_user_to_institution(user_id, institution.id, institution_role) portal.base.add_user_to_institution(user_id, institution_id, institution_role)
flash('Uspešna registracija.') portal.base.send_admins_new_user_notification_mail(user_id, upload_handler_solar.config)
flash('Podatki so bili poslani v potrditev. Ko bo registracija potrjena, boste o tem obveščeni po e-mailu, ki ste ga posredovali zgoraj.')
return redirect('/solar/login') return redirect('/solar/login')
@ -300,11 +304,13 @@ def solar(text):
institution_contract=institution_contract, institution_contract=institution_contract,
is_institution_coordinator=current_user_institution_coordinator) is_institution_coordinator=current_user_institution_coordinator)
elif text.startswith('zgodovina/') or text == 'zgodovina': elif text.startswith('zgodovina/') or text == 'zgodovina':
upload_items = portal.solar.get_upload_history(current_user.id) upload_items = []
if current_user_institution:
upload_items = portal.solar.get_institution_upload_history(current_user_institution.id, n=1000)
uploader_names = [] uploader_names = []
institution_names = [] institution_names = []
for item in upload_items: for item in upload_items:
uploader_names.append(portal.base.get_user_obj(current_user.id).name) uploader_names.append(portal.base.get_user_obj(item.upload_user).name)
institution = portal.base.get_institution_obj(item.institution) institution = portal.base.get_institution_obj(item.institution)
if not institution: if not institution:
institution_names.append(None) institution_names.append(None)
@ -358,6 +364,7 @@ def solar(text):
show_upload_form=show_upload_form, show_upload_form=show_upload_form,
collaborators=collaborators, collaborators=collaborators,
cooperation_history=cooperation_history, cooperation_history=cooperation_history,
user_id=current_user.id,
is_admin=is_admin, is_institution_coordinator=current_user_institution_coordinator) is_admin=is_admin, is_institution_coordinator=current_user_institution_coordinator)
elif text.startswith('admin/') or text == 'admin': elif text.startswith('admin/') or text == 'admin':
users = portal.base.get_all_users_join_institutions() users = portal.base.get_all_users_join_institutions()
@ -378,7 +385,9 @@ def solar(text):
@app.route('/solar/pogodbe', methods=['POST']) @app.route('/solar/pogodbe', methods=['POST'])
@login_required @login_required
def solar_upload_contract(): def solar_upload_contract():
return upload_handler_solar.handle_contract_upload(request, current_user.get_id()) msg = upload_handler_solar.handle_contract_upload(request, current_user.get_id())
flash(msg)
return redirect(redirect_url())
@app.route('/solar/adduser', methods=['POST']) @app.route('/solar/adduser', methods=['POST'])
@ -416,6 +425,12 @@ def solar_add_user():
flash('Predolgo geslo.') flash('Predolgo geslo.')
return redirect(redirect_url()) return redirect(redirect_url())
user = portal.base.get_user_obj_by_email(email)
if user:
#portal.base.undo_remove_user(user.id)
flash('Uporabnik s tem emailom je že vnešen v sistem.')
return redirect(redirect_url())
portal.base.register_new_user(name, email, password) portal.base.register_new_user(name, email, password)
flash('Uporabnik je bil uspešno dodan.') flash('Uporabnik je bil uspešno dodan.')
@ -437,6 +452,8 @@ def solar_activate_user():
if rowcount == 0: if rowcount == 0:
return '', 404 return '', 404
portal.base.send_user_activation_mail(user_id, upload_handler_solar.config)
flash('Uporabnik je bil aktiviran.') flash('Uporabnik je bil aktiviran.')
return redirect(redirect_url()) return redirect(redirect_url())
@ -494,23 +511,21 @@ def solar_del_user():
if not portal.base.is_admin(current_user.id): if not portal.base.is_admin(current_user.id):
return '', 404 return '', 404
user_id = request.form.get('user_id') user_id = request.form.get('user_id')
portal.base.del_user(user_id) portal.base.remove_user(user_id)
flash('Uporabnik je bil odstranjen.') flash('Uporabnik je bil odstranjen.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/<corpus_name>/addinstitution', methods=['POST']) @app.route('/solar/addinstitution', methods=['POST'])
@login_required @login_required
def add_institution(corpus_name): def add_institution():
if not portal.base.is_admin(current_user.id): if not portal.base.is_admin(current_user.id):
return '', 404 return '', 404
if not corpus_name in ENABLED_CORPUSES:
return '', 404
name = request.form.get('name') name = request.form.get('name')
region = request.form.get('region') region = request.form.get('region')
if not name: if not name:
flash('Prazno polje za ime.') flash('Prazno polje za naziv.')
return redirect(redirect_url()) return redirect(redirect_url())
if len(name) > 100: if len(name) > 100:
flash('Predolgo ime.') flash('Predolgo ime.')
@ -521,10 +536,142 @@ def add_institution(corpus_name):
return redirect(redirect_url()) return redirect(redirect_url())
institution_id = portal.base.add_institution(name, region) institution_id = portal.base.add_institution(name, region)
portal.base.grant_institution_corpus_access(institution_id, corpus_name) portal.base.grant_institution_corpus_access(institution_id, "solar") # TODO: throw out
flash('Institucija je bila dodana.') flash('Institucija je bila dodana.')
return redirect(redirect_url()) return redirect(redirect_url())
@app.route('/solar/mergeinstitutions', methods=['POST'])
@login_required
def merge_institutions():
if not portal.base.is_admin(current_user.id):
return '', 404
id_from = request.form.get('id-from')
id_to = request.form.get('id-to')
if not id_from or not id_to:
flash('Prazno polje.')
return redirect(redirect_url())
institution_from = portal.base.get_institution_obj(id_from)
institution_to = portal.base.get_institution_obj(id_to)
if not institution_from:
flash('Institucija z ID "{}" ne obstaja.'.format(id_from))
return redirect(redirect_url())
if not institution_to:
flash('Institucija z ID "{}" ne obstaja.'.format(id_to))
return redirect(redirect_url())
portal.base.transfer_users_institution(institution_from.id, institution_to.id)
portal.base.transfer_uploads_institution(institution_from.id, institution_to.id)
portal.base.transfer_contracts_institution(institution_from.id, institution_to.id)
portal.base.remove_institution(institution_from.id)
flash('Instituciji uspešno združeni')
return redirect(redirect_url())
@app.route('/solar/addcooperationhistoryitem', methods=['POST'])
@login_required
def add_cooperation_history_item():
if not portal.base.is_admin(current_user.id):
return '', 404
user_id = request.form.get('user')
institution_id = request.form.get('institution')
role = request.form.get('role')
school_year = request.form.get('school-year')
badge_text = request.form.get('badge-text')
user = portal.base.get_user_obj(user_id)
institution = portal.base.get_institution_obj(institution_id)
if not user:
flash('Uporabnik s tem ID-jem ne obstaja.')
return redirect(redirect_url())
if not institution:
flash('Institucija s tem ID-jem ne obstaja.')
return redirect(redirect_url())
if not role in ['coordinator', 'mentor', 'other']:
flash('Neveljavna vloga "{}".'.format(role))
return redirect(redirect_url())
if not school_year or not re.match('[0-9]{4}/[0-9]{2}', school_year):
flash('Šolsko leto mora biti formata "2021/22".')
return redirect(redirect_url())
portal.base.add_cooperation_history_item(user_id, institution_id, role, school_year, badge_text)
flash('Vnos dodan.')
return redirect(redirect_url())
@app.route('/solar/delcooperationhistoryitem', methods=['POST'])
@login_required
def del_cooperation_history_item():
if not portal.base.is_admin(current_user.id):
return '', 404
entry_id = request.form.get('entry-id')
portal.base.del_cooperation_history_item(entry_id)
flash('Vnos odstranjen.')
return redirect(redirect_url())
@app.route('/solar/changeuseremail', methods=['POST'])
@login_required
def change_user_email():
if not portal.base.is_admin(current_user.id):
return '', 404
user_id = request.form.get('user-id')
email = request.form.get('email')
if not re.search(portal.base.REGEX_EMAIL, email):
flash('Email napačnega formata.')
return redirect(redirect_url())
portal.base.update_user_email(user_id, email)
flash('Email spremenjen.')
return redirect(redirect_url())
@app.route('/solar/changeuserrole', methods=['POST'])
@login_required
def change_user_role():
if not portal.base.is_admin(current_user.id):
return '', 404
user_id = request.form.get('user-id')
role = request.form.get('role')
if not role in ['admin', 'user']:
flash('Neveljavna vloga.')
return redirect(redirect_url())
portal.base.update_user_role(user_id, role)
flash('Vloga spremenjena.')
return redirect(redirect_url())
@app.route('/solar/changeusername', methods=['POST'])
@login_required
def change_user_name():
if not portal.base.is_admin(current_user.id):
return '', 404
user_id = request.form.get('user-id')
name = request.form.get('name')
portal.base.update_user_name(user_id, name)
flash('Ime in priimek spremenjena.')
return redirect(redirect_url())
@app.route('/<corpus_name>/addusertoinstitution', methods=['POST']) @app.route('/<corpus_name>/addusertoinstitution', methods=['POST'])
@login_required @login_required
def add_user_institution_mapping(corpus_name): def add_user_institution_mapping(corpus_name):

View File

@ -0,0 +1,30 @@
"""Added is_removed column to users and institutions.
Revision ID: 84168f439c55
Revises: c60b7bfaaf85
Create Date: 2021-09-06 10:55:40.303790
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '84168f439c55'
down_revision = 'c60b7bfaaf85'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('institution', sa.Column('is_removed', sa.Boolean(), server_default='true', nullable=False))
op.add_column('registered_user', sa.Column('is_removed', sa.Boolean(), server_default='true', nullable=False))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('registered_user', 'is_removed')
op.drop_column('institution', 'is_removed')
# ### end Alembic commands ###

View File

@ -68,7 +68,7 @@ class ContractCreator:
class UploadHandler: class UploadHandler:
ENABLED_FILETYPES = ['txt', 'csv', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'xml', 'mxliff', 'tmx'] ENABLED_FILETYPES = ['txt', 'csv', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'xml', 'mxliff', 'tmx', 'jpg', 'jpeg', 'png']
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.config = kwargs self.config = kwargs
@ -210,7 +210,7 @@ class UploadHandler:
for key, f in files.items(): for key, f in files.items():
if key.startswith('file'): if key.startswith('file'):
suffix = f.filename.split('.')[-1] suffix = f.filename.split('.')[-1]
if self.ENABLED_FILETYPES and suffix not in self.ENABLED_FILETYPES: if self.ENABLED_FILETYPES and suffix.lower() not in self.ENABLED_FILETYPES:
return 'Datoteka "{}" ni pravilnega formata.'.format(f.filename) return 'Datoteka "{}" ni pravilnega formata.'.format(f.filename)
return None return None
@ -263,10 +263,10 @@ def get_institution_cooperation_history(institution_id):
if row.user not in res: if row.user not in res:
res[row.user] = { res[row.user] = {
'coordinator': [], 'coordinator': [],
'moderator': [], 'mentor': [],
'other': [] 'other': []
} }
res[row.user][row.role].append(row.school_year) res[row.user][row.role].append((row.school_year, row.badge_text))
return res return res
@ -275,6 +275,22 @@ def get_cooperation_history():
return UserCooperationHistory.query.all() return UserCooperationHistory.query.all()
def add_cooperation_history_item(user_id, institution_id, role, school_year, badge_text):
model_obj = UserCooperationHistory(
user=user_id,
institution=institution_id,
role=role,
school_year=school_year,
badge_text=badge_text
)
db.session.add(model_obj)
db.session.commit()
return model_obj.id
def del_cooperation_history_item(entry_id):
db.session.query(UserCooperationHistory).filter_by(id=entry_id).delete()
db.session.commit()
def has_user_corpus_access(user_id, corpus_name): def has_user_corpus_access(user_id, corpus_name):
user = RegisteredUser.query.filter_by(id=user_id).first() user = RegisteredUser.query.filter_by(id=user_id).first()
@ -303,6 +319,8 @@ def is_admin(user_id):
def get_user_obj(user_id): def get_user_obj(user_id):
return RegisteredUser.query.filter_by(id=user_id).first() return RegisteredUser.query.filter_by(id=user_id).first()
def get_user_obj_by_email(email):
return RegisteredUser.query.filter_by(email=email).first()
def get_institution_obj(institution_id): def get_institution_obj(institution_id):
return Institution.query.filter_by(id=institution_id).first() return Institution.query.filter_by(id=institution_id).first()
@ -370,10 +388,38 @@ def update_user_password(user_id, new_password):
return rowcount return rowcount
def del_user(user_id): def update_user_role(user_id, role):
db.session.query(UserCooperationHistory).filter(UserCooperationHistory.user == user_id).delete() rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'role': role})
db.session.query(UserInstitutionMapping).filter(UserInstitutionMapping.user == user_id).delete() db.session.commit()
db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).delete() return rowcount
def update_user_email(user_id, new_email):
rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'email': new_email})
db.session.commit()
return rowcount
def update_user_name(user_id, new_name):
rowcount = db.session.query(RegisteredUser).filter_by(id=user_id).update({'name': new_name})
db.session.commit()
return rowcount
def remove_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()
db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).update({'is_removed': True})
db.session.commit()
def undo_remove_user(user_id):
db.session.query(RegisteredUser).filter(RegisteredUser.id == user_id).update({'is_removed': False})
db.session.commit()
def remove_institution(institution_id):
db.session.query(Institution).filter(Institution.id == institution_id).update({'is_removed': True})
db.session.commit() db.session.commit()
@ -383,22 +429,22 @@ def del_user_from_institution(user_id, institution_id):
def get_all_active_users(): def get_all_active_users():
return RegisteredUser.query.filter_by(active=True).order_by(RegisteredUser.id).all() return RegisteredUser.query.filter_by(is_removed=False).filter_by(active=True).order_by(RegisteredUser.id).all()
def get_all_inactive_users(): def get_all_inactive_users():
return RegisteredUser.query.filter_by(active=False).order_by(RegisteredUser.id).all() return RegisteredUser.query.filter_by(is_removed=False).filter_by(active=False).order_by(RegisteredUser.id).all()
def get_all_users_join_institutions(active=True): def get_all_users_join_institutions(active=True):
#return RegisteredUser.query.filter_by(active=True).order_by(RegisteredUser.id).all() #return RegisteredUser.query.filter_by(active=True).order_by(RegisteredUser.id).all()
return db.session.query(RegisteredUser, UserInstitutionMapping).outerjoin(UserInstitutionMapping, return db.session.query(RegisteredUser, UserInstitutionMapping).filter(RegisteredUser.is_removed==False).outerjoin(UserInstitutionMapping,
RegisteredUser.id == UserInstitutionMapping.user).filter(RegisteredUser.active == active).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): def get_all_active_institution_users(institution_id):
return RegisteredUser.query.filter_by(active=True).join(UserInstitutionMapping, return RegisteredUser.query.filter_by(is_removed=False).filter_by(active=True).join(UserInstitutionMapping,
RegisteredUser.id == UserInstitutionMapping.user).filter(UserInstitutionMapping.institution == institution_id).all() RegisteredUser.id == UserInstitutionMapping.user).filter(UserInstitutionMapping.institution == institution_id).all()
@ -437,6 +483,28 @@ def get_password_reset_token(email, key, expires=600):
'exp': int(time.time()) + expires}, 'exp': int(time.time()) + expires},
key=key, algorithm='HS256') key=key, algorithm='HS256')
def transfer_users_institution(institution_id_from, institution_id_to):
rowcount = db.session.query(UserInstitutionMapping).filter_by(institution=institution_id_from).update(
{'institution': institution_id_to})
db.session.commit()
return rowcount
def transfer_uploads_institution(institution_id_from, institution_id_to):
rowcount = db.session.query(UploadSolar).filter_by(institution=institution_id_from).update(
{'institution': institution_id_to})
db.session.commit()
return rowcount
def transfer_contracts_institution(institution_id_from, institution_id_to):
rowcount = db.session.query(ContractsSolar).filter_by(institution=institution_id_from).update(
{'institution': institution_id_to})
db.session.commit()
return rowcount
def verify_reset_token(token, key): def verify_reset_token(token, key):
try: try:
message = jwt.decode(token, message = jwt.decode(token,
@ -455,11 +523,18 @@ def verify_reset_token(token, key):
def send_resetpass_mail(email, config): def send_resetpass_mail(email, config):
jwt_token = get_password_reset_token(email, config['APP_SECRET_KEY']) jwt_token = get_password_reset_token(email, config['APP_SECRET_KEY'])
text = ''' body = '''
Zahtevali ste ponastavitev gesla vašega uporabniškega računa. Zahtevali ste ponastavitev gesla vašega uporabniškega računa.
Geslo lahko ponastavite na naslednji povezavi: https://zbiranje.slovenscina.eu/solar/resetpass/{}'''.format(jwt_token) Geslo lahko ponastavite na naslednji povezavi: https://zbiranje.slovenscina.eu/solar/resetpass/{}'''.format(jwt_token)
message = MIMEMultipart()
message['From'] = config['MAIL_LOGIN']
message['To'] = email
message['Subject'] = 'Ponastavitev gesla'
message.attach(MIMEText(body, "plain"))
text = message.as_string()
# Create a secure SSL context # Create a secure SSL context
context = ssl.create_default_context() context = ssl.create_default_context()
@ -469,3 +544,53 @@ def send_resetpass_mail(email, config):
server.sendmail(config['MAIL_LOGIN'], email, text) server.sendmail(config['MAIL_LOGIN'], email, text)
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
def send_admins_new_user_notification_mail(user_id, config):
user = RegisteredUser.query.filter_by(id=user_id).first()
body = '''
Nov uporabnik "{}" je ustvaril uporabniški račun na portalu za oddajanje besedil Šolar in čaka na odobritev.
'''.format(user.name)
message = MIMEMultipart()
message['From'] = config['MAIL_LOGIN']
message['To'] = email
message['Subject'] = 'Ponastavitev gesla'
message.attach(MIMEText(body, "plain"))
text = message.as_string()
admins = RegisteredUser.query.filter_by(role="admin").all()
# Create a secure SSL context
context = ssl.create_default_context()
for admin in admins:
try:
with SMTP_SSL(config['MAIL_HOST'], config['SMTP_PORT'], context=context) as server:
server.login(config['MAIL_LOGIN'], config['MAIL_PASS'])
server.sendmail(config['MAIL_LOGIN'], admin.email, text)
except Exception:
traceback.print_exc()
def send_user_activation_mail(user_id, config):
user = RegisteredUser.query.filter_by(id=user_id).first()
body = '''Vaš uporabniški račun "{}" na portalu Šolar je bil odobren.'''.format(user.name)
message = MIMEMultipart()
message['From'] = config['MAIL_LOGIN']
message['To'] = email
message['Subject'] = 'Ponastavitev gesla'
message.attach(MIMEText(body, "plain"))
text = message.as_string()
# Create a secure SSL context
context = ssl.create_default_context()
try:
with SMTP_SSL(config['MAIL_HOST'], config['SMTP_PORT'], context=context) as server:
server.login(config['MAIL_LOGIN'], config['MAIL_PASS'])
server.sendmail(config['MAIL_LOGIN'], user.email, text)
except Exception:
traceback.print_exc()

View File

@ -67,6 +67,7 @@ class RegisteredUser(UserMixin, db.Model):
active = db.Column(db.Boolean, nullable=True) active = db.Column(db.Boolean, nullable=True)
last_login = db.Column(db.DateTime, nullable=True) last_login = db.Column(db.DateTime, nullable=True)
registered = db.Column(db.DateTime, nullable=True) registered = db.Column(db.DateTime, nullable=True)
is_removed = db.Column(db.Boolean, nullable=False, default=False, server_default="false")
class UserInstitutionMapping(db.Model): class UserInstitutionMapping(db.Model):
@ -104,6 +105,7 @@ class Institution(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False, unique=True) name = db.Column(db.String, nullable=False, unique=True)
region = db.Column(db.String, nullable=False) region = db.Column(db.String, nullable=False)
is_removed = db.Column(db.Boolean, nullable=False, default=False, server_default="false")
class CorpusAccess(db.Model): class CorpusAccess(db.Model):

View File

@ -79,62 +79,58 @@ class UploadHandlerSolar(UploadHandler):
if contract_type not in ['sola', 'ucenci-starsi']: if contract_type not in ['sola', 'ucenci-starsi']:
return 'Neveljaven tip pogodbe.' return 'Neveljaven tip pogodbe.'
f_obj = None #for key, f in request.files.items():
for key, f in request.files.items(): for f in request.files.getlist("file[]"):
if key.startswith('file'):
mimetype = f.content_type
if mimetype != 'application/pdf':
return 'Datoteka "{}" ni formata PDF.'.format(f.filename)
else:
f_obj = f
break
if not f_obj:
return 'Niste naložili nobene datoteke.'
base = self.get_uploads_subdir('contracts') mimetype = f.content_type
f_hash = hashlib.md5(f_obj.read()).hexdigest() if mimetype != 'application/pdf':
f_obj.seek(0, 0) return 'Datoteka "{}" ni formata PDF.'.format(f.filename)
if not f:
return 'Niste naložili nobene datoteke.'
# First byte used for indexing, similarly like git does for example. base = self.get_uploads_subdir('contracts')
sub_dir = base / f_hash[:2] f_hash = hashlib.md5(f.read()).hexdigest()
if not sub_dir.exists(): f.seek(0, 0)
sub_dir.mkdir()
path = sub_dir / (f_hash[2:] + '.pdf') # First byte used for indexing, similarly like git does for example.
f_obj.save(path) sub_dir = base / f_hash[:2]
if not sub_dir.exists():
sub_dir.mkdir()
timestamp = datetime.now() path = sub_dir / (f_hash[2:] + '.pdf')
user_obj = RegisteredUser.query.filter_by(id=user_id).one() f.save(path)
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_coordinator = True if user_institution_mapping.role == 'coordinator' else False
if contract_type == 'sola': timestamp = datetime.now()
if not is_institution_coordinator: user_institution_mapping = UserInstitutionMapping.query.filter_by(user=user_id).first()
return 'Vaš uporabnik nima pravic za nalaganje pogodbe s šolo.' if user_institution_mapping is None:
# TODO: insert institution contract return 'Vaš uporabnik ni dodeljen nobeni inštituciji.'
model_obj = InstitutionContract( institution_id = user_institution_mapping.institution
institution=institution_id, is_institution_coordinator = True if user_institution_mapping.role == 'coordinator' else False
corpus='solar',
timestamp=timestamp,
file_contract=f_hash,
original_filename=f_obj.filename
)
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,
original_filename=f_obj.filename
)
self.store_model(model_obj) if contract_type == 'sola':
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,
original_filename=f.filename
)
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,
original_filename=f.filename
)
self.store_model(model_obj)
return 'Nalaganje pogodbe je bilo uspešno.' return 'Nalaganje pogodbe je bilo uspešno.'
@staticmethod @staticmethod
@ -168,10 +164,14 @@ 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() return UploadSolar.query.filter_by(upload_user=user_id).order_by(desc(UploadSolar.timestamp)).limit(n).all()
def get_institution_upload_history(institution_id, n=20):
return UploadSolar.query.filter_by(institution=institution_id).order_by(desc(UploadSolar.timestamp)).limit(n).all()
def get_all_institutions(): def get_all_institutions():
# TODO: do filtering purely within an SQL query # TODO: do filtering purely within an SQL query
res = [] res = []
for institution in Institution.query.all(): for institution in Institution.query.filter_by(is_removed=False).all():
row = CorpusAccess.query.filter_by(institution=institution.id, corpus='solar').first() row = CorpusAccess.query.filter_by(institution=institution.id, corpus='solar').first()
if row: if row:
res.append(institution) res.append(institution)

View File

@ -197,10 +197,12 @@ label {
} }
.collaborators-item { .collaborators-item {
height: 30px; height: fit-content;
width: 97%;
margin: 5px; margin: 5px;
border: 0px; border: 0px;
border-bottom: 2px solid #c4c4c4; border-bottom: 2px solid #c4c4c4;
display: inline-block;
} }
.collaborators-item-name { .collaborators-item-name {
@ -209,6 +211,17 @@ label {
font-weight: normal; font-weight: normal;
font-size: 16px; font-size: 16px;
color: #46535b; color: #46535b;
float: left;
}
.collaborators-item-years {
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 16px;
float: right;
width: 180px;
max-width: 180px;
} }
#awards-container { #awards-container {

View File

@ -38,22 +38,6 @@
{% endif %} {% endif %}
{% endwith %} {% endwith %}
<h2>Uporabniki</h2> <h2>Uporabniki</h2>
<h3>Dodaj uporabnika</h3>
<form action="/solar/adduser" method="post">
<label for="name">Ime in priimek:</label><br>
<input type="text" id="name" name="name"><br>
<label for="email">Email:</label><br>
<input type="text" id="email" name="email"><br>
<label for="password">Geslo:</label><br>
<input type="password" id="password" name="password"><br>
<input type="submit" value="Dodaj">
</form>
<h3>Odstrani uporabnika</h3>
<form action="/solar/deluser" method="post">
<label for="user_id">ID uporabnika:</label><br>
<input type="text" id="user_id" name="user_id"><br>
<input type="submit" value="Odstrani">
</form>
<h3>Aktivni uporabniki</h3> <h3>Aktivni uporabniki</h3>
<div class="tableFixHead"> <div class="tableFixHead">
<table> <table>
@ -62,6 +46,7 @@
<th>ID</th> <th>ID</th>
<th>Ime in priimek</th> <th>Ime in priimek</th>
<th>Email</th> <th>Email</th>
<th>Vloga</th>
<th>ID institucije</th> <th>ID institucije</th>
<th>Vloga v instituciji</th> <th>Vloga v instituciji</th>
</tr> </tr>
@ -72,11 +57,44 @@
<td>{{item[0].id}}</td> <td>{{item[0].id}}</td>
<td>{{item[0].name}}</td> <td>{{item[0].name}}</td>
<td>{{item[0].email}}</td> <td>{{item[0].email}}</td>
<td>{{item[0].role}}</td>
<td>{{item[1].institution}}</td> <td>{{item[1].institution}}</td>
<td>{{item[1].role}}</td> <td>{{item[1].role}}</td>
{% endfor %} {% endfor %}
</table> </table>
</div> </div>
<h3>Dodaj uporabnika</h3>
<form action="/solar/adduser" method="post">
<label for="name">Ime in priimek:</label><br>
<input type="text" id="name" name="name"><br>
<label for="email">Email:</label><br>
<input type="text" id="email" name="email"><br>
<label for="password">Geslo:</label><br>
<input type="password" id="password" name="password"><br>
<input type="submit" value="Dodaj">
</form>
<h3>Spremeni email uporabnika</h3>
<form action="/solar/changeuseremail" method="post">
<label for="user-id">ID uporabnika:</label><br>
<input type="text" id="user-id" name="user-id"><br>
<label for="email">Nov email:</label><br>
<input type="text" id="email" name="email"><br>
<input type="submit" value="Spremeni">
</form>
<h3>Spremeni ime in priimek uporabnika</h3>
<form action="/solar/changeusername" method="post">
<label for="user-id">ID uporabnika:</label><br>
<input type="text" id="user-id" name="user-id"><br>
<label for="name">Ime in priimek:</label><br>
<input type="text" id="name" name="name"><br>
<input type="submit" value="Spremeni">
</form>
<h3>Odstrani uporabnika</h3>
<form action="/solar/deluser" method="post">
<label for="user_id">ID uporabnika:</label><br>
<input type="text" id="user_id" name="user_id"><br>
<input type="submit" value="Odstrani">
</form>
<h3>Dodeli uporabnika instituciji</h3> <h3>Dodeli uporabnika instituciji</h3>
<form action="/solar/addusertoinstitution" method="post"> <form action="/solar/addusertoinstitution" method="post">
<label for="user_id">ID uporabnika:</label> <label for="user_id">ID uporabnika:</label>
@ -97,6 +115,47 @@
<input type="text" id="user_id" name="user_id"><br> <input type="text" id="user_id" name="user_id"><br>
<input type="submit" value="Odstrani"> <input type="submit" value="Odstrani">
</form> </form>
<h3>Spremeni vlogo uporabniškega računa</h3>
<form action="/solar/changeuserrole" method="post">
<label for="user-id">ID uporabnika:</label>
<input type="text" id="user-id" name="user-id"><br>
<label for="role">Vloga:</label>
<select name="role" id="role">
<option value="admin">Administrator/-ka</option>
<option value="user">Navaden uporabnik</option>
</select>
<input type="submit" value="Spremeni">
</form>
<h3>Čakajoči uporabniki</h3>
<div class="tableFixHead">
<table>
<thead>
<tr>
<th>ID</th>
<th>Ime in priimek</th>
<th>Email</th>
<th>Institucija</th>
<th>Vloga v instituciji</th>
</tr>
</thead>
<tbody>
{% for item in inactive_users %}
<tr>
<td>{{item[0].id}}</td>
<td>{{item[0].name}}</td>
<td>{{item[0].email}}</td>
<td>{{item[1].institution}}</td>
<td>{{item[1].role}}</td>
</tr>
{% endfor %}
</table>
</div>
<h3>Aktiviraj uporabnika</h3>
<form action="/solar/activateuser" method="post">
<label for="id">ID uporabnika:</label>
<input type="text" id="id" name="id"><br>
<input type="submit" value="Aktiviraj">
</form>
<div> </div> <div> </div>
<h2>Institucije</h2> <h2>Institucije</h2>
<h3>Dodaj institucijo</h3> <h3>Dodaj institucijo</h3>
@ -139,35 +198,13 @@
{% endfor %} {% endfor %}
</table> </table>
</div> </div>
<h3>Čakajoči uporabniki</h3> <h3>Združi instituciji</h3>
<div class="tableFixHead"> <form action="/solar/mergeinstitutions" method="post">
<table> <label for="id-from">Institucijo z ID</label>
<thead> <input type="text" id="id-from" name="id-from">
<tr> <label for="id-to">združi v institucijo z ID</label>
<th>ID</th> <input type="text" id="id-to" name="id-to"><br>
<th>Ime in priimek</th> <input type="submit" value="Združi">
<th>Email</th>
<th>Institucija</th>
<th>Vloga v instituciji</th>
</tr>
</thead>
<tbody>
{% for item in inactive_users %}
<tr>
<td>{{item[0].id}}</td>
<td>{{item[0].name}}</td>
<td>{{item[0].email}}</td>
<td>{{item[1].institution}}</td>
<td>{{item[1].role}}</td>
</tr>
{% endfor %}
</table>
</div>
<h3>Aktiviraj uporabnika</h3>
<form action="/solar/activateuser" method="post">
<label for="id">ID uporabnika:</label>
<input type="text" id="id" name="id"><br>
<input type="submit" value="Aktiviraj">
</form> </form>
<h2>Zgodovina sodelovanja</h2> <h2>Zgodovina sodelovanja</h2>
<div class="tableFixHead"> <div class="tableFixHead">
@ -195,4 +232,28 @@
{% endfor %} {% endfor %}
</table> </table>
</div> </div>
<h3>Dodaj vnos</h3>
<form action="/solar/addcooperationhistoryitem" method="post">
<label for="user">ID uporabnika</label>
<input type="text" id="user" name="user"><br>
<label for="institution">ID institucije</label>
<input type="text" id="institution" name="institution"><br>
<label for="role">Vloga</label>
<select name="role" id="role">
<option value="coordinator">Koordinator/-ka</option>
<option value="mentor">Mentor/-ica</option>
<option value="other">Druga vloga</option>
</select><br>
<label for="school-year">Šolsko leto (npr. "2021/22")</label>
<input type="text" id="school-year" name="school-year"><br>
<label for="badge-text">Besedilo značke</label>
<input type="text" id="badge-text" name="badge-text"><br>
<input type="submit" value="Dodaj">
</form>
<h3>Odstrani vnos</h3>
<form action="/solar/delcooperationhistoryitem" method="post">
<label for="entry-id">ID vnosa</label>
<input type="text" id="entry-id" name="entry-id"><br>
<input type="submit" value="Odstrani">
</form>
</body> </body>

View File

@ -126,105 +126,6 @@
<div id="popup-terms" style="display: none"> <div id="popup-terms" style="display: none">
<div id="popup-terms-text"> <div id="popup-terms-text">
<h2>POGODBA O PRENOSU AVTORSKIH PRAVIC</h2>
<h3>UVODNE DOLOČBE</h3>
<h4><b>1. člen</b></h4>
<p>1.1. Stranki uvodoma ugotavljata, da naročnik izvaja projekt Razvoj slovenščine v digitalnem
okolju RSDO (v nadaljevanju projekt RSDO), ki je bil na javnem razpisu Razvoj slovenščine v
digitalnem okolju jezikovni viri in tehnologije (JR-ESRR-Razvoj slovenščine v digitalnem
okolju), objavljenem v Uradnem listu RS št. 70/19 dne 29. 11. 2019, sprejet v sofinanciranje
in katerega vsebina je razvidna s spletnih strani https://slovenscina.eu.</p>
<p>1.2. Stranki uvodoma ugotavljata, da bo naročnik v okviru projekta RSDO:
- izdelal osrednjo digitalno slovarsko bazo, ki združuje različne tipe jezikovnih podatkov o
slovenščini v odprtem dostopu,
- izdelal terminološki portal z integriranim iskalnikom po slovenskih terminoloških virih, zlasti
terminoloških slovarjih,
- izdelal korpus prevodov po različnih domenah za učenje strojnega prevajalnika za jezikovni
par angleščina-slovenščina in slovenščina-angleščina.</p>
<p>1.3. Stranki uvodoma ugotavljata, da bo naročnik pri projektu RSDO za vse zgoraj opisane
namene zbiral in uporabil besedilne vire, ki so navedeni v prilogi k tej pogodbi in ki so lahko
avtorska dela ali drugi predmeti varstva v skladu z Zakonom o avtorski in sorodnih pravicah
(Uradni list RS, št. 16/07 uradno prečiščeno besedilo, 68/08, 110/13, 56/15, 63/16 ZKUASP
in 59/19; ZASP) in na katerih ima imetnik pravic avtorske, avtorski sorodne ali druge pravice v
skladu z ZASP (v nadaljevanju avtorska dela).</p>
<p>1.4. Stranki ugotavljata, da bodo avtorska dela in vse njihove morebitne spremembe in
predelave, ter zbirke podatkov, ki bodo med izvajanjem projekta RSDO nastale, javno
dostopni pod pogoji prostih licenc (npr. CC BY-SA) in bodo na voljo za nekomercialen in
komercialen razvoj tehnologij, za raziskave in za druge raziskovalne namene
posameznikom, raziskovalnim in izobraževalnim institucijam, neprofitnim organizacijam,
državnim organom, organizacijam z javnimi pooblastili in gospodarskim družbam v Sloveniji
in tujini.</p>
<h3>PREDMET POGODBE</h3>
<h4><b>2. člen</b></h4>
<p>2.1. Predmet pogodbe so vsa avtorska dela imetnika pravic, ki so navedena v prilogi k tej
pogodbi.</p>
<p>2.2. S podpisom te pogodbe imetnik avtorskih pravic na naročnika prenaša avtorske pravice
na avtorskih delih na način in v obsegu, kakor je navedeno v 3. členu te pogodbe.</p>
<h3>PRENOS AVTORSKIH PRAVIC</h3>
<h4><b>3. člen</b></h4>
<p>3.1. S podpisom te pogodbe imetnik pravic na avtorskih delih, ki so predmet te pogodbe, na
naročnika neizključno, brez časovnih in teritorialnih omejitev prenaša vse materialne avtorske
pravice, avtorski sorodne pravice in druge pravice avtorja v skladu z ZASP, zlasti pravico
reproduciranja (23. člen ZASP), distribuiranja (24. člena ZASP), dajanja v najem (25. člen ZASP),
priobčitve javnosti (26. do 32.a člen ZASP), vključno s pravico dajanja na voljo javnosti (32.a
člen ZASP) in pravico predelave (33. člen ZASP).</p>
<p>3.2. S podpisom te pogodbe imetnik pravic izrecno soglaša, da naročnik pravice iz točke 3.1.
prenaša naprej na tretje osebe brez omejitev.</p>
<h3>JAMČEVANJE IMETNIKA PRAVIC</h3>
<h4><b>4. člen</b></h4>
<p>4.1. S podpisom te pogodbe imetnik pravic jamči, da je na avtorskih delih, ki so predmet te
pogodbe, imetnik vseh avtorskih pravic, avtorski sorodnih pravic in drugih pravic avtorja v
skladu z ZASP, ki so potrebne za prenos pravic po tej pogodbi, in da na avtorskih delih ne
obstajajo pravice tretjih oseb, ki bi naročniku preprečevale njihovo uporabo.</p>
<p>4.2. Določbe te pogodbe ne vplivajo na prenos moralnih avtorskih pravic, ki so v skladu z
določbami ZASP neprenosljive.</p>
<h3>OSEBNI PODATKI</h3>
<h4><b>5. člen</b></h4>
<p>6.1. Stranki se zavezujeta, da bosta vse morebitne osebne podatke, ki jih bosta obdelovali za
namene izvajanja te pogodbe, obdelovali na način, da bosta upoštevali vse veljavne predpise
o varstvu osebnih podatkov in da bosta posameznikom, na katere se osebni podatki nanašajo,
zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatkov.<p>
<h3>KONTAKTNE OSEBE</h3>
<h4><b>6. člen</b></h4>
<p>7.1 Kontaktna oseba za izvedbo te pogodbe na strani naročnika je [xxx].</p>
<p>7.2. Kontaktna oseba za izvedbo te pogodbe na strani imetnika pravic je [xxx].</p>
<h3>KONČNE DOLOČBE</h3>
<h4><b>7. člen</b></h4>
<p>8.1. Če je katerakoli določba te pogodbe nična, ostanejo druga določila te pogodbe v veljavi.</p>
<h4><b>8. člen</b></h4>
<p>9.1. Za razmerja v zvezi s to pogodbo se uporabljajo pravni predpisi Republike Slovenije.</p>
<p>9.2. Spore iz te pogodbe bosta stranki reševali po mirni poti. V primeru, da mirna rešitev ne
bo mogoča, je za vse spore v zvezi s to pogodbo pristojno sodišče v Ljubljani.</p>
<h4><b>9. člen</b></h4>
<p>10.1. Ta pogodba nadomešča vsa predhodna pogajanja, ponudbe in druge dogovore med
strankama.</p>
<p>10.2. Ta pogodba je sestavljena v [dveh] istovetnih izvodih, od katerih prejme vsaka stranka
po enega.</p>
<p>10.3. Pogodbeni stranki s podpisom potrjujeta veljavnost te pogodbe.</p>
</div> </div>
<button id="button-submit-cancel" class="button-terms" style="background: #ff2d2d;">Prekliči</button> <button id="button-submit-cancel" class="button-terms" style="background: #ff2d2d;">Prekliči</button>
<button id="button-submit-final" class="button-terms">Oddaj</button> <button id="button-submit-final" class="button-terms">Oddaj</button>
@ -249,6 +150,10 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
var scrollboxTriggered = false; var scrollboxTriggered = false;
var form = document.forms["my-dropzone"]; var form = document.forms["my-dropzone"];
{% if not institution %}
btnSubmit.disabled = true;
{% endif %}
function isEmptyOrSpaces(str){ function isEmptyOrSpaces(str){
return str == null || str.match(/^ *$/) !== null; return str == null || str.match(/^ *$/) !== null;
} }
@ -263,7 +168,7 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
paramName: "file", // The name that will be used to transfer the file paramName: "file", // The name that will be used to transfer the file
maxFilesize: 1000, // MB maxFilesize: 1000, // MB
timeout: 5000000, // milliseconds timeout: 5000000, // milliseconds
acceptedFiles: ".txt, .csv, .pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx", acceptedFiles: ".txt, .csv, .pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .jpg, .jpeg, .png",
maxFiles: 20, maxFiles: 20,
dictDefaultMessage: `Kliknite ali odložite datoteke sem.`, dictDefaultMessage: `Kliknite ali odložite datoteke sem.`,
dictFallbackMessage: "Vaš brskalnik ne podpira izbiranje datotek z odlaganjem (\"drag & drop\").", dictFallbackMessage: "Vaš brskalnik ne podpira izbiranje datotek z odlaganjem (\"drag & drop\").",
@ -322,10 +227,18 @@ zagotovili vse potrebne informacije v skladu s predpisi o varstvu osebnih podatk
btnSubmit.textContent = "Potrdi"; btnSubmit.textContent = "Potrdi";
} else { } else {
// Then make terms popup visible // Then make terms popup visible
btnSubmit.disabled = true; //btnSubmit.disabled = true;
btnSubmitFinal.disabled = true; //btnSubmitFinal.disabled = true;
elemTermsPopup.style.display = "inline"; //elemTermsPopup.style.display = "inline";
scrollboxtriggered = false; //scrollboxtriggered = false;
// Hand off data to dropzone
dz.processQueue();
// Clear fields and hide popup agian
dataConfirmNotification.style.display = "none";
btnSubmit.textContent = "Oddaj";
form.reset();
} }
}); });

View File

@ -28,7 +28,17 @@
<button onclick="window.location.replace('/solar/pogodbe');" class="selection-tab-button selected">POGODBE</button> <button onclick="window.location.replace('/solar/pogodbe');" class="selection-tab-button selected">POGODBE</button>
</div> </div>
</div> </div>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="message-notification">
{{ messages[0] }}
</div>
<div id="contract-container" style="height: 250px;">
{% else %}
<div id="contract-container"> <div id="contract-container">
{% endif %}
{% endwith %}
{% if contract_school %} {% if contract_school %}
<div class="contract-item"> <div class="contract-item">
<img src="/static/image/contract.png" alt="contract" class="contract-item-icon"/> <img src="/static/image/contract.png" alt="contract" class="contract-item-icon"/>
@ -71,7 +81,7 @@
<label style="width: 80%; text-align: right;" for="ucenci-starsi">Pogodba z učenci / starši</label> <label style="width: 80%; text-align: right;" for="ucenci-starsi">Pogodba z učenci / starši</label>
<input style="width: 20%;" type="radio" id="ucenci-starsi" name="tip-pogodbe" value="ucenci-starsi" checked> <input style="width: 20%;" type="radio" id="ucenci-starsi" name="tip-pogodbe" value="ucenci-starsi" checked>
</div> </div>
<input style="font-size: 10px;" type="file" id="file-contract" name="filename"> <input style="font-size: 10px;" type="file" id="file-contract" name="file[]" multiple="">
<button style="float: right;" type="submit">Oddaj pogodbo</button> <button style="float: right;" type="submit">Oddaj pogodbo</button>
</form> </form>
</div> </div>
@ -84,11 +94,26 @@
{% for collaborator in collaborators %} {% for collaborator in collaborators %}
<div class="collaborators-item"> <div class="collaborators-item">
<div class="collaborators-item-name">{{collaborator.name}}</div> <div class="collaborators-item-name">{{collaborator.name}}</div>
{% for history_item in cooperation_history %} {% if collaborator.id in cooperation_history %}
{% if collaborator.id == history_item.user %} {% if cooperation_history[collaborator.id]["coordinator"]|length > 0 %}
<div>{{history_item.badge_text}}</div> <div class="collaborators-item-years"><b>Vodenje:</b> {% for item in cooperation_history[collaborator.id]["coordinator"] %}
{% endif %} {% if loop.index != 1 %}, {% endif %}
{% endfor %} {{item[0]}}
{% endfor %}</div>
{% endif %}
{% if cooperation_history[collaborator.id]["mentor"]|length > 0 %}
<div class="collaborators-item-years"><b>Mentorstvo:</b> {% for item in cooperation_history[collaborator.id]["mentor"] %}
{% if loop.index != 1 %}, {% endif %}
{{item[0]}}
{% endfor %}</div>
{% endif %}
{% if cooperation_history[collaborator.id]["other"]|length > 0 %}
<div class="collaborators-item-years"><b>Drugo:</b> {% for item in cooperation_history[collaborator.id]["other"] %}
{% if loop.index != 1 %}, {% endif %}
{{item[0]}}
{% endfor %}</div>
{% endif %}
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@ -96,13 +121,49 @@
<div id="awards-container"> <div id="awards-container">
<div class="container-title">Sodelovanje v letih</div> <div class="container-title">Sodelovanje v letih</div>
<div style="overflow-y: auto; margin: auto; width: 100%; height: 200px;"> <div style="overflow-y: auto; margin: auto; width: 100%; height: 200px;">
{% for item in cooperation_history %} {% if cooperation_history.keys()|length > 0 %}
<div style="border-bottom: 2px solid #c4c4c4; min-height: 50px;"> {% if user_id in cooperation_history %}
<img src="/static/image/star.png" alt="star" style="float: left; width: 40px;"/> {% for item in cooperation_history[user_id]['coordinator'] %}
<div class="collaborators-item-name" <div style="border-bottom: 2px solid #c4c4c4; min-height: 50px; margin-bottom: 10px;">
style="float: right; width: 250px; margin-bottom: 20px; margin-top: 10px; text-align: left; margin-left: 20px;">{{item.badge_text}}</div> <img src="/static/image/star.png" alt="star" style="float: left; width: 40px;"/>
</div> <div class="collaborators-item-name"
{% endfor %} style="float: right; width: 250px; text-align: left; margin-left: 20px;text-overflow: ellipsis; overflow: hidden; white-space: nowrap;">
{{item[1]}}
</div>
<div class="collaborators-item-name"
style="float: right; width: 250px; text-align: left; margin-left: 20px;">
{{item[0]}}
</div>
</div>
{% endfor %}
{% for item in cooperation_history[user_id]['mentor'] %}
<div style="border-bottom: 2px solid #c4c4c4; min-height: 50px; margin-bottom: 10px;">
<img src="/static/image/star.png" alt="star" style="float: left; width: 40px;"/>
<div class="collaborators-item-name"
style="float: right; width: 250px; text-align: left; margin-left: 20px;text-overflow: ellipsis; overflow: hidden; white-space: nowrap;">
{{item[1]}}
</div>
<div class="collaborators-item-name"
style="float: right; width: 250px; text-align: left; margin-left: 20px;">
{{item[0]}}
</div>
</div>
{% endfor %}
{% for item in cooperation_history[user_id]['other'] %}
<div style="border-bottom: 2px solid #c4c4c4; min-height: 50px; margin-bottom: 10px;">
<img src="/static/image/star.png" alt="star" style="float: left; width: 40px;"/>
<div class="collaborators-item-name"
style="float: right; width: 250px; text-align: left; margin-left: 20px;text-overflow: ellipsis; overflow: hidden; white-space: nowrap;">
{{item[1]}}
</div>
<div class="collaborators-item-name"
style="float: right; width: 250px; text-align: left; margin-left: 20px;">
{{item[0]}}
</div>
</div>
{% endfor %}
{% endif %}
{% endif %}
</div> </div>
</div> </div>
</div> </div>