mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-11-15 01:05:42 +00:00
Merge branch 'public-corpus' of gitlab.ub.uni-bielefeld.de:sfb1288inf/nopaque into public-corpus
This commit is contained in:
commit
018805a1b6
@ -1,7 +1,7 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
|
|
||||||
TEMPLATE_FOLDER = 'contributions/spacy_nlp_pipeline_models'
|
template_base_dir = 'contributions/spacy_nlp_pipeline_models'
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('spacy_nlp_pipeline_models', __name__)
|
bp = Blueprint('spacy_nlp_pipeline_models', __name__)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask import abort, current_app, jsonify, request
|
from flask import abort, current_app, request
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from app import db
|
from app import db
|
||||||
@ -29,9 +29,7 @@ def delete_spacy_model(spacy_nlp_pipeline_model_id):
|
|||||||
'message': \
|
'message': \
|
||||||
f'SpaCy NLP Pipeline Model "{snpm.title}" marked for deletion'
|
f'SpaCy NLP Pipeline Model "{snpm.title}" marked for deletion'
|
||||||
}
|
}
|
||||||
response = jsonify(resonse_data)
|
return resonse_data, 202
|
||||||
response.status_code = 202
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:spacy_nlp_pipeline_model_id>/is_public', methods=['PUT'])
|
@bp.route('/<hashid:spacy_nlp_pipeline_model_id>/is_public', methods=['PUT'])
|
||||||
@ -53,6 +51,4 @@ def update_spacy_nlp_pipeline_model_is_public(spacy_nlp_pipeline_model_id):
|
|||||||
f' is now {"public" if is_public else "private"}'
|
f' is now {"public" if is_public else "private"}'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
response = jsonify(response_data)
|
return response_data, 200
|
||||||
response.status_code = 200
|
|
||||||
return response
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
from flask import abort, flash, Markup, redirect, render_template, url_for
|
from flask import abort, flash, redirect, render_template, url_for
|
||||||
from flask_login import login_required, current_user
|
from flask_login import current_user, login_required
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import SpaCyNLPPipelineModel
|
from app.models import SpaCyNLPPipelineModel
|
||||||
from . import bp, TEMPLATE_FOLDER
|
from . import bp, template_base_dir
|
||||||
from .forms import (
|
from .forms import (
|
||||||
CreateSpaCyNLPPipelineModelForm,
|
CreateSpaCyNLPPipelineModelForm,
|
||||||
EditSpaCyNLPPipelineModelForm
|
EditSpaCyNLPPipelineModelForm
|
||||||
@ -13,7 +13,7 @@ from .forms import (
|
|||||||
@login_required
|
@login_required
|
||||||
def spacy_nlp_pipeline_models():
|
def spacy_nlp_pipeline_models():
|
||||||
return render_template(
|
return render_template(
|
||||||
f'{TEMPLATE_FOLDER}/spacy_nlp_pipeline_models.html.j2',
|
f'{template_base_dir}/spacy_nlp_pipeline_models.html.j2',
|
||||||
title='SpaCy NLP Pipeline Models'
|
title='SpaCy NLP Pipeline Models'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,13 +21,13 @@ def spacy_nlp_pipeline_models():
|
|||||||
@bp.route('/create', methods=['GET', 'POST'])
|
@bp.route('/create', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def create_spacy_nlp_pipeline_model():
|
def create_spacy_nlp_pipeline_model():
|
||||||
form = CreateSpaCyNLPPipelineModelForm(prefix='create-spacy-nlp-pipeline-model-form')
|
form_prefix = 'create-spacy-nlp-pipeline-model-form'
|
||||||
|
form = CreateSpaCyNLPPipelineModelForm(prefix=form_prefix)
|
||||||
if form.is_submitted():
|
if form.is_submitted():
|
||||||
if not form.validate():
|
if not form.validate():
|
||||||
response = {'errors': form.errors}
|
return {'errors': form.errors}, 400
|
||||||
return response, 400
|
|
||||||
try:
|
try:
|
||||||
spacy_nlp_pipeline_model = SpaCyNLPPipelineModel.create(
|
snpm = SpaCyNLPPipelineModel.create(
|
||||||
form.spacy_model_file.data,
|
form.spacy_model_file.data,
|
||||||
compatible_service_versions=form.compatible_service_versions.data,
|
compatible_service_versions=form.compatible_service_versions.data,
|
||||||
description=form.description.data,
|
description=form.description.data,
|
||||||
@ -44,18 +44,10 @@ def create_spacy_nlp_pipeline_model():
|
|||||||
except OSError:
|
except OSError:
|
||||||
abort(500)
|
abort(500)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
spacy_nlp_pipeline_model_url = url_for(
|
flash(f'SpaCy NLP Pipeline model "{snpm.title}" created')
|
||||||
'.spacy_nlp_pipeline_model',
|
return {}, 201, {'Location': url_for('.spacy_nlp_pipeline_models')}
|
||||||
spacy_nlp_pipeline_model_id=spacy_nlp_pipeline_model.id
|
|
||||||
)
|
|
||||||
message = Markup(
|
|
||||||
f'SpaCy NLP Pipeline model "{spacy_nlp_pipeline_model.title}" '
|
|
||||||
'created'
|
|
||||||
)
|
|
||||||
flash(message)
|
|
||||||
return '', 201, {'Location': spacy_nlp_pipeline_model_url}
|
|
||||||
return render_template(
|
return render_template(
|
||||||
f'{TEMPLATE_FOLDER}/create_spacy_nlp_pipeline_model.html.j2',
|
f'{template_base_dir}/create_spacy_nlp_pipeline_model.html.j2',
|
||||||
form=form,
|
form=form,
|
||||||
title='Create SpaCy NLP Pipeline Model'
|
title='Create SpaCy NLP Pipeline Model'
|
||||||
)
|
)
|
||||||
@ -64,24 +56,21 @@ def create_spacy_nlp_pipeline_model():
|
|||||||
@bp.route('/<hashid:spacy_nlp_pipeline_model_id>', methods=['GET', 'POST'])
|
@bp.route('/<hashid:spacy_nlp_pipeline_model_id>', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id):
|
def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id):
|
||||||
spacy_nlp_pipeline_model = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
|
snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
|
||||||
|
form_prefix = 'edit-spacy-nlp-pipeline-model-form'
|
||||||
form = EditSpaCyNLPPipelineModelForm(
|
form = EditSpaCyNLPPipelineModelForm(
|
||||||
data=spacy_nlp_pipeline_model.to_json_serializeable(),
|
data=snpm.to_json_serializeable(),
|
||||||
prefix='edit-spacy-nlp-pipeline-model-form'
|
prefix=form_prefix
|
||||||
)
|
)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
form.populate_obj(spacy_nlp_pipeline_model)
|
form.populate_obj(snpm)
|
||||||
if db.session.is_modified(spacy_nlp_pipeline_model):
|
if db.session.is_modified(snpm):
|
||||||
message = Markup(
|
flash(f'SpaCy NLP Pipeline model "{snpm.title}" updated')
|
||||||
f'SpaCy NLP Pipeline model "{spacy_nlp_pipeline_model.title}" '
|
|
||||||
'updated'
|
|
||||||
)
|
|
||||||
flash(message)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for('.spacy_nlp_pipeline_models'))
|
return redirect(url_for('.spacy_nlp_pipeline_models'))
|
||||||
return render_template(
|
return render_template(
|
||||||
f'{TEMPLATE_FOLDER}/spacy_nlp_pipeline_model.html.j2',
|
f'{template_base_dir}/spacy_nlp_pipeline_model.html.j2',
|
||||||
form=form,
|
form=form,
|
||||||
spacy_nlp_pipeline_model=spacy_nlp_pipeline_model,
|
spacy_nlp_pipeline_model=snpm,
|
||||||
title=f'{spacy_nlp_pipeline_model.title} {spacy_nlp_pipeline_model.version}'
|
title=f'{snpm.title} {snpm.version}'
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
|
|
||||||
TEMPLATE_FOLDER = 'contributions/tesseract_ocr_pipeline_models'
|
template_base_dir = 'contributions/tesseract_ocr_pipeline_models'
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('tesseract_ocr_pipeline_models', __name__)
|
bp = Blueprint('tesseract_ocr_pipeline_models', __name__)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask import abort, current_app, jsonify, request
|
from flask import abort, current_app, request
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from app import db
|
from app import db
|
||||||
@ -29,9 +29,7 @@ def delete_tesseract_model(tesseract_ocr_pipeline_model_id):
|
|||||||
'message': \
|
'message': \
|
||||||
f'Tesseract OCR Pipeline Model "{topm.title}" marked for deletion'
|
f'Tesseract OCR Pipeline Model "{topm.title}" marked for deletion'
|
||||||
}
|
}
|
||||||
response = jsonify(response_data)
|
return response_data, 202
|
||||||
response.status_code = 202
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>/is_public', methods=['PUT'])
|
@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>/is_public', methods=['PUT'])
|
||||||
@ -53,6 +51,4 @@ def update_tesseract_ocr_pipeline_model_is_public(tesseract_ocr_pipeline_model_i
|
|||||||
f' is now {"public" if is_public else "private"}'
|
f' is now {"public" if is_public else "private"}'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
response = jsonify(response_data)
|
return response_data, 200
|
||||||
response.status_code = 200
|
|
||||||
return response
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
from flask import abort, flash, Markup, redirect, render_template, url_for
|
from flask import abort, flash, redirect, render_template, url_for
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import TesseractOCRPipelineModel
|
from app.models import TesseractOCRPipelineModel
|
||||||
from . import bp, TEMPLATE_FOLDER
|
from . import bp, template_base_dir
|
||||||
from .forms import (
|
from .forms import (
|
||||||
CreateTesseractOCRPipelineModelForm,
|
CreateTesseractOCRPipelineModelForm,
|
||||||
EditTesseractOCRPipelineModelForm
|
EditTesseractOCRPipelineModelForm
|
||||||
@ -13,7 +13,7 @@ from .forms import (
|
|||||||
@login_required
|
@login_required
|
||||||
def tesseract_ocr_pipeline_models():
|
def tesseract_ocr_pipeline_models():
|
||||||
return render_template(
|
return render_template(
|
||||||
f'{TEMPLATE_FOLDER}/tesseract_ocr_pipeline_models.html.j2',
|
f'{template_base_dir}/tesseract_ocr_pipeline_models.html.j2',
|
||||||
title='Tesseract OCR Pipeline Models'
|
title='Tesseract OCR Pipeline Models'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,13 +21,13 @@ def tesseract_ocr_pipeline_models():
|
|||||||
@bp.route('/create', methods=['GET', 'POST'])
|
@bp.route('/create', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def create_tesseract_ocr_pipeline_model():
|
def create_tesseract_ocr_pipeline_model():
|
||||||
form = CreateTesseractOCRPipelineModelForm(prefix='create-tesseract-ocr-pipeline-model-form')
|
form_prefix = 'create-tesseract-ocr-pipeline-model-form'
|
||||||
|
form = CreateTesseractOCRPipelineModelForm(prefix=form_prefix)
|
||||||
if form.is_submitted():
|
if form.is_submitted():
|
||||||
if not form.validate():
|
if not form.validate():
|
||||||
response = {'errors': form.errors}
|
return {'errors': form.errors}, 400
|
||||||
return response, 400
|
|
||||||
try:
|
try:
|
||||||
tesseract_ocr_pipeline_model = TesseractOCRPipelineModel.create(
|
topm = TesseractOCRPipelineModel.create(
|
||||||
form.tesseract_model_file.data,
|
form.tesseract_model_file.data,
|
||||||
compatible_service_versions=form.compatible_service_versions.data,
|
compatible_service_versions=form.compatible_service_versions.data,
|
||||||
description=form.description.data,
|
description=form.description.data,
|
||||||
@ -43,15 +43,10 @@ def create_tesseract_ocr_pipeline_model():
|
|||||||
except OSError:
|
except OSError:
|
||||||
abort(500)
|
abort(500)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
tesseract_ocr_pipeline_model_url = url_for(
|
flash(f'Tesseract OCR Pipeline model "{topm.title}" created')
|
||||||
'.tesseract_ocr_pipeline_model',
|
return {}, 201, {'Location': url_for('.tesseract_ocr_pipeline_models')}
|
||||||
tesseract_ocr_pipeline_model_id=tesseract_ocr_pipeline_model.id
|
|
||||||
)
|
|
||||||
message = Markup(f'Tesseract OCR Pipeline model "<a href="{tesseract_ocr_pipeline_model_url}">{tesseract_ocr_pipeline_model.title}</a>" created')
|
|
||||||
flash(message)
|
|
||||||
return {}, 201, {'Location': tesseract_ocr_pipeline_model_url}
|
|
||||||
return render_template(
|
return render_template(
|
||||||
f'{TEMPLATE_FOLDER}/create_tesseract_ocr_pipeline_model.html.j2',
|
f'{template_base_dir}/create_tesseract_ocr_pipeline_model.html.j2',
|
||||||
form=form,
|
form=form,
|
||||||
title='Create Tesseract OCR Pipeline Model'
|
title='Create Tesseract OCR Pipeline Model'
|
||||||
)
|
)
|
||||||
@ -60,21 +55,21 @@ def create_tesseract_ocr_pipeline_model():
|
|||||||
@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>', methods=['GET', 'POST'])
|
@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id):
|
def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id):
|
||||||
tesseract_ocr_pipeline_model = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
|
topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
|
||||||
|
form_prefix = 'edit-tesseract-ocr-pipeline-model-form'
|
||||||
form = EditTesseractOCRPipelineModelForm(
|
form = EditTesseractOCRPipelineModelForm(
|
||||||
data=tesseract_ocr_pipeline_model.to_json_serializeable(),
|
data=topm.to_json_serializeable(),
|
||||||
prefix='edit-tesseract-ocr-pipeline-model-form'
|
prefix=form_prefix
|
||||||
)
|
)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
form.populate_obj(tesseract_ocr_pipeline_model)
|
form.populate_obj(topm)
|
||||||
if db.session.is_modified(tesseract_ocr_pipeline_model):
|
if db.session.is_modified(topm):
|
||||||
message = Markup(f'Tesseract OCR Pipeline model "<a href="{tesseract_ocr_pipeline_model.url}">{tesseract_ocr_pipeline_model.title}</a>" updated')
|
flash(f'Tesseract OCR Pipeline model "{topm.title}" updated')
|
||||||
flash(message)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for('.tesseract_ocr_pipeline_models'))
|
return redirect(url_for('.tesseract_ocr_pipeline_models'))
|
||||||
return render_template(
|
return render_template(
|
||||||
f'{TEMPLATE_FOLDER}/tesseract_ocr_pipeline_model.html.j2',
|
f'{template_base_dir}/tesseract_ocr_pipeline_model.html.j2',
|
||||||
form=form,
|
form=form,
|
||||||
tesseract_ocr_pipeline_model=tesseract_ocr_pipeline_model,
|
tesseract_ocr_pipeline_model=topm,
|
||||||
title=f'{tesseract_ocr_pipeline_model.title} {tesseract_ocr_pipeline_model.version}'
|
title=f'{topm.title} {topm.version}'
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
|
|
||||||
TEMPLATE_FOLDER = 'corpora/files'
|
template_base_dir = 'corpora/files'
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('files', __name__)
|
bp = Blueprint('files', __name__)
|
||||||
|
@ -11,7 +11,7 @@ import os
|
|||||||
from app import db
|
from app import db
|
||||||
from app.models import Corpus, CorpusFile, CorpusStatus
|
from app.models import Corpus, CorpusFile, CorpusStatus
|
||||||
from ..decorators import corpus_follower_permission_required
|
from ..decorators import corpus_follower_permission_required
|
||||||
from . import bp, TEMPLATE_FOLDER
|
from . import bp, template_base_dir
|
||||||
from .forms import CreateCorpusFileForm, UpdateCorpusFileForm
|
from .forms import CreateCorpusFileForm, UpdateCorpusFileForm
|
||||||
|
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ def create_corpus_file(corpus_id):
|
|||||||
flash(f'Corpus File "{corpus_file.filename}" added', category='corpus')
|
flash(f'Corpus File "{corpus_file.filename}" added', category='corpus')
|
||||||
return '', 201, {'Location': corpus.url}
|
return '', 201, {'Location': corpus.url}
|
||||||
return render_template(
|
return render_template(
|
||||||
f'{TEMPLATE_FOLDER}/create_corpus_file.html.j2',
|
f'{template_base_dir}/create_corpus_file.html.j2',
|
||||||
corpus=corpus,
|
corpus=corpus,
|
||||||
form=form,
|
form=form,
|
||||||
title='Add corpus file'
|
title='Add corpus file'
|
||||||
@ -79,7 +79,7 @@ def corpus_file(corpus_id, corpus_file_id):
|
|||||||
flash(f'Corpus file "{corpus_file.filename}" updated', category='corpus')
|
flash(f'Corpus file "{corpus_file.filename}" updated', category='corpus')
|
||||||
return redirect(corpus_file.corpus.url)
|
return redirect(corpus_file.corpus.url)
|
||||||
return render_template(
|
return render_template(
|
||||||
f'{TEMPLATE_FOLDER}/corpus_file.html.j2',
|
f'{template_base_dir}/corpus_file.html.j2',
|
||||||
corpus=corpus_file.corpus,
|
corpus=corpus_file.corpus,
|
||||||
corpus_file=corpus_file,
|
corpus_file=corpus_file,
|
||||||
form=form,
|
form=form,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
from .handlers import generic_error_handler
|
from .handlers import generic
|
||||||
|
|
||||||
|
|
||||||
def init_app(app):
|
def init_app(app):
|
||||||
app.register_error_handler(HTTPException, generic_error_handler)
|
app.register_error_handler(HTTPException, generic)
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
from flask import render_template
|
from flask import jsonify, render_template, request, Response
|
||||||
|
from werkzeug.exceptions import HTTPException
|
||||||
|
from typing import Tuple, Union
|
||||||
|
|
||||||
|
|
||||||
def generic_error_handler(e):
|
def generic(error: HTTPException) -> Tuple[Union[str, Response], int]:
|
||||||
print('test')
|
''' Generic error handler '''
|
||||||
return render_template('errors/error.html.j2', error=e), e.code
|
accent_json: bool = request.accept_mimetypes.accept_json
|
||||||
|
accept_html: bool = request.accept_mimetypes.accept_html
|
||||||
|
if accent_json and not accept_html:
|
||||||
|
response: Response = jsonify(str(error))
|
||||||
|
return response, error.code
|
||||||
|
return render_template('errors/error.html.j2', error=error), error.code
|
||||||
|
@ -92,7 +92,6 @@ class Form {
|
|||||||
}
|
}
|
||||||
if (request.status === 400) {
|
if (request.status === 400) {
|
||||||
let responseJson = JSON.parse(request.responseText);
|
let responseJson = JSON.parse(request.responseText);
|
||||||
console.log(responseJson);
|
|
||||||
for (let [inputName, inputErrors] of Object.entries(responseJson.errors)) {
|
for (let [inputName, inputErrors] of Object.entries(responseJson.errors)) {
|
||||||
let inputFieldElement = this.formElement
|
let inputFieldElement = this.formElement
|
||||||
.querySelector(`input[name$="${inputName}"], select[name$="${inputName}"]`)
|
.querySelector(`input[name$="${inputName}"], select[name$="${inputName}"]`)
|
||||||
@ -122,10 +121,11 @@ class Form {
|
|||||||
request.setRequestHeader('Accept', 'application/json');
|
request.setRequestHeader('Accept', 'application/json');
|
||||||
let formData = new FormData(this.formElement);
|
let formData = new FormData(this.formElement);
|
||||||
switch (this.formElement.enctype) {
|
switch (this.formElement.enctype) {
|
||||||
case 'application/x-www-form-urlencoded':
|
case 'application/x-www-form-urlencoded': {
|
||||||
let urlSearchParams = new URLSearchParams(formData);
|
let urlSearchParams = new URLSearchParams(formData);
|
||||||
request.send(urlSearchParams);
|
request.send(urlSearchParams);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 'multipart/form-data': {
|
case 'multipart/form-data': {
|
||||||
request.send(formData);
|
request.send(formData);
|
||||||
break;
|
break;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 id="title">{{ error.name }}</h1>
|
<h1 id="title">{{ error.code }} {{ error.name }}</h1>
|
||||||
<p>{{ error.description }}</p>
|
<p>{{ error.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endblock page_content %}
|
{% endblock page_content %}
|
||||||
|
Loading…
Reference in New Issue
Block a user