mirror of
https://github.com/sartography/protocol-builder-mock.git
synced 2025-01-26 07:30:10 +00:00
Add / Edit studies, show in a sensible form, allow modifying list of requirements.
This commit is contained in:
parent
a0965bc0fe
commit
51c9b8be86
1
Pipfile
1
Pipfile
@ -13,6 +13,7 @@ flask-wtf = "*"
|
||||
sqlalchemy = "*"
|
||||
flask-sqlalchemy = "*"
|
||||
flask-table = "*"
|
||||
flask-migrate = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
|
37
Pipfile.lock
generated
37
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "72fff5c0cf2514d4307a4fc4eb60d57cc5bd0bf19603dc77587835bf01e43bd7"
|
||||
"sha256": "a5291bb0360424bf22621f3a5aed3906df624e7237a9c8f4c80a34249014a6fa"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@ -16,6 +16,12 @@
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"alembic": {
|
||||
"hashes": [
|
||||
"sha256:2df2519a5b002f881517693b95626905a39c5faf4b5a1f94de4f1441095d1d26"
|
||||
],
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||
@ -84,6 +90,14 @@
|
||||
],
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"flask-migrate": {
|
||||
"hashes": [
|
||||
"sha256:6fb038be63d4c60727d5dfa5f581a6189af5b4e2925bc378697b4f0a40cfb4e1",
|
||||
"sha256:a96ff1875a49a40bd3e8ac04fce73fdb0870b9211e6168608cbafa4eb839d502"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.5.2"
|
||||
},
|
||||
"flask-sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327",
|
||||
@ -206,6 +220,12 @@
|
||||
],
|
||||
"version": "==3.2.0"
|
||||
},
|
||||
"mako": {
|
||||
"hashes": [
|
||||
"sha256:2984a6733e1d472796ceef37ad48c26f4a984bb18119bb2dbc37a44d8f6e75a4"
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||
@ -258,6 +278,21 @@
|
||||
],
|
||||
"version": "==0.15.7"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||
],
|
||||
"version": "==2.8.1"
|
||||
},
|
||||
"python-editor": {
|
||||
"hashes": [
|
||||
"sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d",
|
||||
"sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b",
|
||||
"sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8"
|
||||
],
|
||||
"version": "==1.0.4"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||
|
84
app.py
84
app.py
@ -1,19 +1,22 @@
|
||||
import datetime
|
||||
from datetime import date
|
||||
|
||||
import connexion
|
||||
from flask import url_for, json, redirect, render_template, request, flash
|
||||
from flask_migrate import Migrate
|
||||
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
PROTOCOLS = {}
|
||||
|
||||
|
||||
def get_user_studies(user_id):
|
||||
return {"protocols": [p for p in PROTOCOLS.values() if p['user_id'] == user_id][:limit]}
|
||||
|
||||
|
||||
def required_docs(id):
|
||||
return {
|
||||
id: 21,
|
||||
requirements: []
|
||||
'id': 21,
|
||||
'requirements': []
|
||||
}
|
||||
|
||||
|
||||
@ -32,8 +35,9 @@ conn.add_api('api.yml')
|
||||
|
||||
app = conn.app
|
||||
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
|
||||
db = SQLAlchemy(app)
|
||||
migrate = Migrate(app, db)
|
||||
|
||||
def has_no_empty_params(rule):
|
||||
defaults = rule.defaults if rule.defaults is not None else ()
|
||||
@ -55,52 +59,60 @@ def site_map():
|
||||
|
||||
app.config['SECRET_KEY'] = 'a really really really really long secret key'
|
||||
|
||||
from forms import Study, StudyForm, StudySearchForm, StudyTable, RequiredDocument
|
||||
from forms import StudyForm, StudySearchForm, StudyTable
|
||||
from models import Study, RequiredDocument
|
||||
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def index():
|
||||
search = StudySearchForm(request.form)
|
||||
if request.method == 'POST':
|
||||
return search_results(search)
|
||||
return render_template('index.html', form=search)
|
||||
|
||||
|
||||
@app.route('/results')
|
||||
def search_results(search):
|
||||
results = []
|
||||
search_string = search.data['search']
|
||||
if search.data['search'] == '':
|
||||
qry = db.session.query(Study)
|
||||
results = qry.all()
|
||||
if not results:
|
||||
flash('No results found!')
|
||||
return redirect('/')
|
||||
else:
|
||||
# display results
|
||||
studies = db.session.query("Study").all()
|
||||
table = StudyTable(studies)
|
||||
return render_template('results.html', table=table)
|
||||
# display results
|
||||
studies = db.session.query(Study).order_by(Study.last_updated.desc()).all()
|
||||
table = StudyTable(studies)
|
||||
return render_template('index.html', table=table)
|
||||
|
||||
|
||||
@app.route('/new_study', methods=['GET', 'POST'])
|
||||
def new_study():
|
||||
form = StudyForm(request.form)
|
||||
action = "/new_study"
|
||||
if request.method == 'POST':
|
||||
# save the study
|
||||
study = Study()
|
||||
study.id = form.id
|
||||
study.title = form.title
|
||||
# for r in form.requirements:
|
||||
# requirement = RequiredDocument(id = r.id,
|
||||
# study.requirements = form.requirements
|
||||
db.session.add(study)
|
||||
db.session.commit()
|
||||
flash('Album created successfully!')
|
||||
_update_study(study, form)
|
||||
flash('Study created successfully!')
|
||||
return redirect('/')
|
||||
|
||||
form = StudyForm(request.form)
|
||||
return render_template('study_form.html', form=form)
|
||||
|
||||
@app.route('/study/<study_id>}', methods=['GET', 'POST'])
|
||||
def edit_study(study_id):
|
||||
study = db.session.query(Study).filter(Study.study_id == study_id).first()
|
||||
form = StudyForm(request.form, obj=study)
|
||||
if request.method == 'GET':
|
||||
action = "/study/" + study_id
|
||||
if study.requirements:
|
||||
form.requirements.data = list(map(lambda r: r.code, list(study.requirements)))
|
||||
if request.method == 'POST':
|
||||
_update_study(study, form)
|
||||
flash('Study updated successfully!')
|
||||
return redirect('/')
|
||||
return render_template('study_form.html', form=form)
|
||||
|
||||
|
||||
def _update_study(study, form):
|
||||
if study.study_id:
|
||||
db.session.query(RequiredDocument).filter(RequiredDocument.study_id == study.study_id).delete()
|
||||
for r in form.requirements:
|
||||
if r.checked:
|
||||
requirement = RequiredDocument(code=r.data, name=r.label.text, study=study)
|
||||
db.session.add(requirement)
|
||||
study.title = form.title.data
|
||||
study.netbadge_id = form.netbadge_id.data
|
||||
study.last_updated = datetime.datetime.now()
|
||||
study.q_complete = form.q_complete.data
|
||||
db.session.add(study)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# run our standalone gevent server
|
||||
|
81
forms.py
81
forms.py
@ -1,58 +1,8 @@
|
||||
from flask_table import Table, Col
|
||||
from flask_table import Table, Col, DateCol, LinkCol, BoolCol, DatetimeCol, NestedTableCol
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import SelectMultipleField, SubmitField, StringField, IntegerField
|
||||
from wtforms import SelectMultipleField, SubmitField, StringField, IntegerField, BooleanField, DateField, widgets
|
||||
|
||||
from app import db
|
||||
|
||||
|
||||
class Study(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(80), nullable=False)
|
||||
requirements = db.relationship("RequiredDocument", backref="study", lazy='dynamic')
|
||||
|
||||
|
||||
class RequiredDocument(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(80), nullable=False)
|
||||
active = db.Column(db.Boolean, default=False)
|
||||
study_id = db.Column(db.Integer, db.ForeignKey('study.id'))
|
||||
|
||||
@staticmethod
|
||||
def all():
|
||||
docs = [RequiredDocument(id=1, name="Investigators Brochure"),
|
||||
RequiredDocument(id=6, name="Cancer Center's PRC Approval Form"),
|
||||
RequiredDocument(id=8, name="SOM CTO IND/IDE Review Letter"),
|
||||
RequiredDocument(id=9, name="HIRE Approval"),
|
||||
RequiredDocument(id=10, name="Cancer Center's PRC Approval Waiver"),
|
||||
RequiredDocument(id=12, name="Certificate of Confidentiality Application"),
|
||||
RequiredDocument(id=14, name="Institutional Biosafety Committee Approval"),
|
||||
RequiredDocument(id=18, name="SOM CTO Approval Letter - UVA PI Multisite Trial"),
|
||||
RequiredDocument(id=20,
|
||||
name="IRB Approval or Letter of Approval from Administration: Study Conducted at non- UVA Facilities "),
|
||||
RequiredDocument(id=21, name="New Medical Device Form"),
|
||||
RequiredDocument(id=22, name="SOM CTO Review regarding need for IDE"),
|
||||
RequiredDocument(id=23, name="SOM CTO Review regarding need for IND"),
|
||||
RequiredDocument(id=24, name="InfoSec Approval"),
|
||||
RequiredDocument(id=25, name="Scientific Pre-review Documentation"),
|
||||
RequiredDocument(id=26, name="IBC Number"),
|
||||
RequiredDocument(id=32, name="IDS - Investigational Drug Service Approval"),
|
||||
RequiredDocument(id=36, name="RDRC Approval "),
|
||||
RequiredDocument(id=40, name="SBS/IRB Approval-FERPA"),
|
||||
RequiredDocument(id=41, name="HIRE Standard Radiation Language"),
|
||||
RequiredDocument(id=42, name="COI Management Plan "),
|
||||
RequiredDocument(id=43, name="SOM CTO Approval Letter-Non UVA, Non Industry PI MultiSite Study"),
|
||||
RequiredDocument(id=44, name="GRIME Approval"),
|
||||
RequiredDocument(id=45, name="GMEC Approval"),
|
||||
RequiredDocument(id=46, name="IRB Reliance Agreement Request Form- IRB-HSR is IRB of Record"),
|
||||
RequiredDocument(id=47, name="Non UVA IRB Approval - Initial and Last Continuation"),
|
||||
RequiredDocument(id=48, name="MR Physicist Approval- Use of Gadolinium"),
|
||||
RequiredDocument(id=49, name="SOM CTO Approval- Non- UVA Academia PI of IDE"),
|
||||
RequiredDocument(id=51, name="IDS Waiver"),
|
||||
RequiredDocument(id=52, name="Package Inserts"),
|
||||
RequiredDocument(id=53, name="IRB Reliance Agreement Request Form- IRB-HSR Not IRB of Record"),
|
||||
RequiredDocument(id=54, name="ESCRO Approval"),
|
||||
RequiredDocument(id=57, name="Laser Safety Officer Approval")]
|
||||
return docs
|
||||
from models import RequiredDocument
|
||||
|
||||
|
||||
class StudySearchForm(FlaskForm):
|
||||
@ -60,11 +10,26 @@ class StudySearchForm(FlaskForm):
|
||||
|
||||
|
||||
class StudyForm(FlaskForm):
|
||||
id = IntegerField('Study Id')
|
||||
title = StringField('Title')
|
||||
requirements = SelectMultipleField("Requirements", choices=[(rd.id, rd.name) for rd in RequiredDocument.all()])
|
||||
netbadge_id = StringField('UVA Id for Primary Investigator')
|
||||
requirements = SelectMultipleField("Requirements", choices=[(rd.code, rd.name) for rd in RequiredDocument.all()])
|
||||
hsr_number = StringField('HSR Number')
|
||||
q_complete = BooleanField('Complete in Protocol Builder?')
|
||||
# last_updated = DateField('Last Updated')
|
||||
|
||||
|
||||
class RequirementsTable(Table):
|
||||
code = Col('Code')
|
||||
name = Col('Name')
|
||||
|
||||
class StudyTable(Table):
|
||||
id = Col('Id')
|
||||
title = Col('Artist')
|
||||
requirements = Col('Title')
|
||||
def sort_url(self, col_id, reverse=False):
|
||||
pass
|
||||
edit = LinkCol('Edit', 'edit_study', url_kwargs=dict(study_id='study_id'))
|
||||
study_id = Col('Study Id')
|
||||
title = Col('Title')
|
||||
netbadge_id = Col('Primary Investigator')
|
||||
last_updated = DatetimeCol('Last Update', "medium")
|
||||
q_complete = BoolCol('Complete?')
|
||||
requirements = NestedTableCol('Requirements', RequirementsTable)
|
||||
|
||||
|
80
models.py
Normal file
80
models.py
Normal file
@ -0,0 +1,80 @@
|
||||
from sqlalchemy import func
|
||||
from app import db
|
||||
|
||||
|
||||
class Study(db.Model):
|
||||
study_id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(80), nullable=False)
|
||||
netbadge_id = db.Column(db.String(), nullable=False)
|
||||
requirements = db.relationship("RequiredDocument", backref="study", lazy='dynamic')
|
||||
investigators = db.relationship("Investigator", backref="study", lazy='dynamic')
|
||||
last_updated = db.Column(db.DateTime(timezone=True), default=func.now())
|
||||
hsr_number = db.Column(db.String())
|
||||
q_complete = db.Column(db.Integer, nullable=True)
|
||||
|
||||
|
||||
class Investigator(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
study_id = db.Column(db.Integer, db.ForeignKey('study.study_id'))
|
||||
netbadge_id = db.Column(db.String(), nullable=False)
|
||||
type = db.Column(db.String(), nullable=False)
|
||||
description = db.Column(db.String(), nullable=False)
|
||||
|
||||
@staticmethod
|
||||
def all_types(self):
|
||||
types = [
|
||||
Investigator(type="PI", description="Primary Investigator"),
|
||||
Investigator(type="SI", description="Sub Investigator"),
|
||||
Investigator(type="DC", description="Department Contact"),
|
||||
Investigator(type="SC_I", description="Study Coordinator 1"),
|
||||
Investigator(type="SC_II", description="Study Coordinator 2"),
|
||||
Investigator(type="AS_C", description="Additional Study Coordinators"),
|
||||
Investigator(type="DEPT_CH", description="Department Chair"),
|
||||
Investigator(type="IRBC", description="IRB Coordinator"),
|
||||
Investigator(type="SCI", description="Scientific Contact"),
|
||||
]
|
||||
return types
|
||||
|
||||
|
||||
class RequiredDocument(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
code = db.Column(db.String(), nullable=False, default="")
|
||||
name = db.Column(db.String(), nullable=False, default="")
|
||||
study_id = db.Column(db.Integer, db.ForeignKey('study.study_id'))
|
||||
|
||||
@staticmethod
|
||||
def all():
|
||||
docs = [RequiredDocument(code=1, name="Investigators Brochure"),
|
||||
RequiredDocument(code=6, name="Cancer Center's PRC Approval Form"),
|
||||
RequiredDocument(code=8, name="SOM CTO IND/IDE Review Letter"),
|
||||
RequiredDocument(code=9, name="HIRE Approval"),
|
||||
RequiredDocument(code=10, name="Cancer Center's PRC Approval Waiver"),
|
||||
RequiredDocument(code=12, name="Certificate of Confidentiality Application"),
|
||||
RequiredDocument(code=14, name="Institutional Biosafety Committee Approval"),
|
||||
RequiredDocument(code=18, name="SOM CTO Approval Letter - UVA PI Multisite Trial"),
|
||||
RequiredDocument(code=20,
|
||||
name="IRB Approval or Letter of Approval from Administration: Study Conducted at non- UVA Facilities "),
|
||||
RequiredDocument(code=21, name="New Medical Device Form"),
|
||||
RequiredDocument(code=22, name="SOM CTO Review regarding need for IDE"),
|
||||
RequiredDocument(code=23, name="SOM CTO Review regarding need for IND"),
|
||||
RequiredDocument(code=24, name="InfoSec Approval"),
|
||||
RequiredDocument(code=25, name="Scientific Pre-review Documentation"),
|
||||
RequiredDocument(code=26, name="IBC Number"),
|
||||
RequiredDocument(code=32, name="IDS - Investigational Drug Service Approval"),
|
||||
RequiredDocument(code=36, name="RDRC Approval "),
|
||||
RequiredDocument(code=40, name="SBS/IRB Approval-FERPA"),
|
||||
RequiredDocument(code=41, name="HIRE Standard Radiation Language"),
|
||||
RequiredDocument(code=42, name="COI Management Plan "),
|
||||
RequiredDocument(code=43, name="SOM CTO Approval Letter-Non UVA, Non Industry PI MultiSite Study"),
|
||||
RequiredDocument(code=44, name="GRIME Approval"),
|
||||
RequiredDocument(code=45, name="GMEC Approval"),
|
||||
RequiredDocument(code=46, name="IRB Reliance Agreement Request Form- IRB-HSR is IRB of Record"),
|
||||
RequiredDocument(code=47, name="Non UVA IRB Approval - Initial and Last Continuation"),
|
||||
RequiredDocument(code=48, name="MR Physicist Approval- Use of Gadolinium"),
|
||||
RequiredDocument(code=49, name="SOM CTO Approval- Non- UVA Academia PI of IDE"),
|
||||
RequiredDocument(code=51, name="IDS Waiver"),
|
||||
RequiredDocument(code=52, name="Package Inserts"),
|
||||
RequiredDocument(code=53, name="IRB Reliance Agreement Request Form- IRB-HSR Not IRB of Record"),
|
||||
RequiredDocument(code=54, name="ESCRO Approval"),
|
||||
RequiredDocument(code=57, name="Laser Safety Officer Approval")]
|
||||
return docs
|
@ -1,24 +1,93 @@
|
||||
|
||||
<doctype html>
|
||||
<head>
|
||||
<head>
|
||||
<title>Protocol Builder Mock</title>
|
||||
</head>
|
||||
</head>
|
||||
<style>
|
||||
table {
|
||||
border: 1px solid #1C6EA4;
|
||||
background-color: #EEEEEE;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
<h2>Protocol Builder Mock</h2>
|
||||
table td, table.blueTable th {
|
||||
border: 1px solid #AAAAAA;
|
||||
padding: 3px 2px;
|
||||
}
|
||||
|
||||
<p><p>
|
||||
<a href="{{ url_for('.new_study') }}"> New Study </a>
|
||||
table tbody td {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
table tr:nth-child(even) {
|
||||
background: #D0E4F5;
|
||||
}
|
||||
|
||||
table thead {
|
||||
background: #1C6EA4;
|
||||
background: -moz-linear-gradient(top, #5592bb 0%, #327cad 66%, #1C6EA4 100%);
|
||||
background: -webkit-linear-gradient(top, #5592bb 0%, #327cad 66%, #1C6EA4 100%);
|
||||
background: linear-gradient(to bottom, #5592bb 0%, #327cad 66%, #1C6EA4 100%);
|
||||
border-bottom: 2px solid #444444;
|
||||
}
|
||||
|
||||
table thead th {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
border-left: 2px solid #D0E4F5;
|
||||
}
|
||||
|
||||
table thead th:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
table tfoot {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
background: #D0E4F5;
|
||||
background: -moz-linear-gradient(top, #dcebf7 0%, #d4e6f6 66%, #D0E4F5 100%);
|
||||
background: -webkit-linear-gradient(top, #dcebf7 0%, #d4e6f6 66%, #D0E4F5 100%);
|
||||
background: linear-gradient(to bottom, #dcebf7 0%, #d4e6f6 66%, #D0E4F5 100%);
|
||||
border-top: 2px solid #444444;
|
||||
}
|
||||
|
||||
table tfoot td {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
table tfoot .links {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table tfoot .links a {
|
||||
display: inline-block;
|
||||
background: #1C6EA4;
|
||||
color: #FFFFFF;
|
||||
padding: 2px 8px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h2>Protocol Builder Mock</h2>
|
||||
|
||||
<p>
|
||||
<p>
|
||||
<a href="{{ url_for('.new_study') }}"> New Study </a>
|
||||
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<ul class=flashes>
|
||||
{% for message in messages %}
|
||||
<li>{{ message }}</li>
|
||||
{% endfor %}
|
||||
{% for message in messages %}
|
||||
<li>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{{ table }}
|
||||
<h3>Current Studies</h3>
|
||||
{{ table }}
|
||||
|
||||
</doctype>
|
@ -8,11 +8,14 @@
|
||||
select {
|
||||
height: 600px;
|
||||
}
|
||||
input {
|
||||
width: 500px;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
|
||||
<h2>New Study</h2>
|
||||
<form action="/new_study" method="post">
|
||||
<form action="{{action}}" method="post">
|
||||
|
||||
{{ form.csrf_token() }}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user