diff --git a/.vscode/settings.json b/.vscode/settings.json
index c2a8567eeb3aa10c172abd83c00915f76c89762a..5879e0139e4d5b112bf9570499d6382417986454 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -18,5 +18,5 @@
     },
     "[jinja-js]": {
         "editor.tabSize": 2
-    },
+    }
 }
diff --git a/app/admin/forms.py b/app/admin/forms.py
index f24ce8f36ae2776c1382cd38c464842a2f98b70b..ea68462400d1e41d370eb5052a9435f3f46c9641 100644
--- a/app/admin/forms.py
+++ b/app/admin/forms.py
@@ -1,14 +1,16 @@
-from wtforms import BooleanField, SelectField, SubmitField
-from app.forms import NopaqueForm
+from flask_wtf import FlaskForm
+from wtforms import SelectField, SubmitField
 from app.models import Role
 
 
-class UpdateUserForm(NopaqueForm):
+class UpdateUserForm(FlaskForm):
     role = SelectField('Role')
     submit = SubmitField()
 
     def __init__(self, user, *args, **kwargs):
         if 'data' not in kwargs:
             kwargs['data'] = {'role': user.role.hashid}
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'update-user-form'
         super().__init__(*args, **kwargs)
         self.role.choices = [(x.hashid, x.name) for x in Role.query.all()]
diff --git a/app/auth/forms.py b/app/auth/forms.py
index 43db510ae5b29a5926e7891825fbe161e22e76c6..d8ca5770b491225dcc0a1fd093f3dd6c51f04677 100644
--- a/app/auth/forms.py
+++ b/app/auth/forms.py
@@ -1,3 +1,4 @@
+from flask_wtf import FlaskForm
 from wtforms import (
     BooleanField,
     PasswordField,
@@ -6,11 +7,10 @@ from wtforms import (
     ValidationError
 )
 from wtforms.validators import InputRequired, Email, EqualTo, Length, Regexp
-from app.forms import NopaqueForm
 from app.models import User
 
 
-class RegistrationForm(NopaqueForm):
+class RegistrationForm(FlaskForm):
     email = StringField(
         'Email',
         validators=[InputRequired(), Email(), Length(max=254)]
@@ -45,6 +45,11 @@ class RegistrationForm(NopaqueForm):
     )
     submit = SubmitField()
 
+    def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'registration-form'
+        super().__init__(*args, **kwargs)
+
     def validate_email(self, field):
         if User.query.filter_by(email=field.data.lower()).first():
             raise ValidationError('Email already registered')
@@ -54,19 +59,29 @@ class RegistrationForm(NopaqueForm):
             raise ValidationError('Username already in use')
 
 
-class LoginForm(NopaqueForm):
+class LoginForm(FlaskForm):
     user = StringField('Email or username', validators=[InputRequired()])
     password = PasswordField('Password', validators=[InputRequired()])
     remember_me = BooleanField('Keep me logged in')
     submit = SubmitField()
 
+    def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'login-form'
+        super().__init__(*args, **kwargs)
+
 
-class ResetPasswordRequestForm(NopaqueForm):
+class ResetPasswordRequestForm(FlaskForm):
     email = StringField('Email', validators=[InputRequired(), Email()])
     submit = SubmitField()
 
+    def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'reset-password-request-form'
+        super().__init__(*args, **kwargs)
 
-class ResetPasswordForm(NopaqueForm):
+
+class ResetPasswordForm(FlaskForm):
     password = PasswordField(
         'New password',
         validators=[
@@ -82,3 +97,8 @@ class ResetPasswordForm(NopaqueForm):
         ]
     )
     submit = SubmitField()
+
+    def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'reset-password-form'
+        super().__init__(*args, **kwargs)
diff --git a/app/contributions/forms.py b/app/contributions/forms.py
index 46777ff08b428671350edd65ab88793cac7e38f7..598fb7ccc64368ff677df494211ec4cb37c26ebf 100644
--- a/app/contributions/forms.py
+++ b/app/contributions/forms.py
@@ -1,3 +1,4 @@
+from flask_wtf import FlaskForm
 from wtforms import (
     StringField,
     SubmitField,
@@ -5,10 +6,9 @@ from wtforms import (
     IntegerField
 )
 from wtforms.validators import InputRequired, Length
-from app.forms import NopaqueForm
 
 
-class ContributionBaseForm(NopaqueForm):
+class ContributionBaseForm(FlaskForm):
     title = StringField(
         'Title',
         validators=[InputRequired(), Length(max=64)]
@@ -43,5 +43,5 @@ class ContributionBaseForm(NopaqueForm):
     submit = SubmitField()
 
 
-class EditContributionBaseForm(ContributionBaseForm):
+class UpdateContributionBaseForm(ContributionBaseForm):
     pass
diff --git a/app/contributions/spacy_nlp_pipeline_models/forms.py b/app/contributions/spacy_nlp_pipeline_models/forms.py
index 2670c1d1c0ba126c1eabf911b09475eda53c3799..dc3ca7812c7ca828e78ed465fafd54f18f052a7a 100644
--- a/app/contributions/spacy_nlp_pipeline_models/forms.py
+++ b/app/contributions/spacy_nlp_pipeline_models/forms.py
@@ -2,7 +2,7 @@ from flask_wtf.file import FileField, FileRequired
 from wtforms import StringField, ValidationError
 from wtforms.validators import InputRequired, Length
 from app.services import SERVICES
-from ..forms import ContributionBaseForm, EditContributionBaseForm
+from ..forms import ContributionBaseForm, UpdateContributionBaseForm
 
 
 class CreateSpaCyNLPPipelineModelForm(ContributionBaseForm):
@@ -20,6 +20,8 @@ class CreateSpaCyNLPPipelineModelForm(ContributionBaseForm):
             raise ValidationError('.tar.gz files only!')
 
     def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'create-spacy-nlp-pipeline-model-form'
         super().__init__(*args, **kwargs)
         service_manifest = SERVICES['spacy-nlp-pipeline']
         self.compatible_service_versions.choices = [('', 'Choose your option')]
@@ -29,12 +31,14 @@ class CreateSpaCyNLPPipelineModelForm(ContributionBaseForm):
         self.compatible_service_versions.default = ''
 
 
-class EditSpaCyNLPPipelineModelForm(EditContributionBaseForm):
+class UpdateSpaCyNLPPipelineModelForm(UpdateContributionBaseForm):
     pipeline_name = StringField(
         'Pipeline name',
         validators=[InputRequired(), Length(max=64)]
     )
     def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'edit-spacy-nlp-pipeline-model-form'
         super().__init__(*args, **kwargs)
         service_manifest = SERVICES['spacy-nlp-pipeline']
         self.compatible_service_versions.choices = [('', 'Choose your option')]
diff --git a/app/contributions/spacy_nlp_pipeline_models/routes.py b/app/contributions/spacy_nlp_pipeline_models/routes.py
index eab8ac49f5257f212f2d85d0c32b2b849d4d17e9..f53d55f1cf09428d79bc693a4a5d56e7d3d86841 100644
--- a/app/contributions/spacy_nlp_pipeline_models/routes.py
+++ b/app/contributions/spacy_nlp_pipeline_models/routes.py
@@ -6,7 +6,7 @@ from app.models import SpaCyNLPPipelineModel
 from . import bp
 from .forms import (
     CreateSpaCyNLPPipelineModelForm,
-    EditSpaCyNLPPipelineModelForm
+    UpdateSpaCyNLPPipelineModelForm
 )
 from .utils import (
     spacy_nlp_pipeline_model_dlc as spacy_nlp_pipeline_model_dlc
@@ -63,7 +63,7 @@ def create_spacy_nlp_pipeline_model():
 @login_required
 def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id):
     snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
-    form = EditSpaCyNLPPipelineModelForm(data=snpm.to_json_serializeable())
+    form = UpdateSpaCyNLPPipelineModelForm(data=snpm.to_json_serializeable())
     if form.validate_on_submit():
         form.populate_obj(snpm)
         if db.session.is_modified(snpm):
diff --git a/app/contributions/tesseract_ocr_pipeline_models/forms.py b/app/contributions/tesseract_ocr_pipeline_models/forms.py
index 51f0d76c2017029c46691719d3131ee779d5c39c..9a5979dd6896af65146bd221e807b83f30dc6fea 100644
--- a/app/contributions/tesseract_ocr_pipeline_models/forms.py
+++ b/app/contributions/tesseract_ocr_pipeline_models/forms.py
@@ -1,7 +1,7 @@
 from flask_wtf.file import FileField, FileRequired
 from wtforms import ValidationError
 from app.services import SERVICES
-from ..forms import ContributionBaseForm, EditContributionBaseForm
+from ..forms import ContributionBaseForm, UpdateContributionBaseForm
 
 
 class CreateTesseractOCRPipelineModelForm(ContributionBaseForm):
@@ -15,6 +15,8 @@ class CreateTesseractOCRPipelineModelForm(ContributionBaseForm):
             raise ValidationError('traineddata files only!')
 
     def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'create-tesseract-ocr-pipeline-model-form'
         service_manifest = SERVICES['tesseract-ocr-pipeline']
         super().__init__(*args, **kwargs)
         self.compatible_service_versions.choices = [('', 'Choose your option')]
@@ -24,8 +26,10 @@ class CreateTesseractOCRPipelineModelForm(ContributionBaseForm):
         self.compatible_service_versions.default = ''
 
 
-class EditTesseractOCRPipelineModelForm(EditContributionBaseForm):
+class UpdateTesseractOCRPipelineModelForm(UpdateContributionBaseForm):
     def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'edit-tesseract-ocr-pipeline-model-form'
         service_manifest = SERVICES['tesseract-ocr-pipeline']
         super().__init__(*args, **kwargs)
         self.compatible_service_versions.choices = [('', 'Choose your option')]
diff --git a/app/contributions/tesseract_ocr_pipeline_models/routes.py b/app/contributions/tesseract_ocr_pipeline_models/routes.py
index 51adf402e6e310d2e64852c2d50a97a103edb48f..e0261e803870b475bdfd390b6643977b54241877 100644
--- a/app/contributions/tesseract_ocr_pipeline_models/routes.py
+++ b/app/contributions/tesseract_ocr_pipeline_models/routes.py
@@ -6,7 +6,7 @@ from app.models import TesseractOCRPipelineModel
 from . import bp
 from .forms import (
     CreateTesseractOCRPipelineModelForm,
-    EditTesseractOCRPipelineModelForm
+    UpdateTesseractOCRPipelineModelForm
 )
 from .utils import (
     tesseract_ocr_pipeline_model_dlc as tesseract_ocr_pipeline_model_dlc
@@ -62,7 +62,7 @@ def create_tesseract_ocr_pipeline_model():
 @login_required
 def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id):
     topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
-    form = EditTesseractOCRPipelineModelForm(data=topm.to_json_serializeable())
+    form = UpdateTesseractOCRPipelineModelForm(data=topm.to_json_serializeable())
     if form.validate_on_submit():
         form.populate_obj(topm)
         if db.session.is_modified(topm):
diff --git a/app/corpora/files/forms.py b/app/corpora/files/forms.py
index c819fba2224cd701e86ff37112ac490934584985..e6918a831c7c9a6e52afd81741b6fa75778f05b6 100644
--- a/app/corpora/files/forms.py
+++ b/app/corpora/files/forms.py
@@ -1,3 +1,4 @@
+from flask_wtf import FlaskForm
 from flask_wtf.file import FileField, FileRequired
 from wtforms import (
     StringField,
@@ -6,10 +7,9 @@ from wtforms import (
     IntegerField
 )
 from wtforms.validators import InputRequired, Length
-from app.forms import NopaqueForm
 
 
-class CorpusFileBaseForm(NopaqueForm):
+class CorpusFileBaseForm(FlaskForm):
     author = StringField(
         'Author',
         validators=[InputRequired(), Length(max=255)]
@@ -41,6 +41,14 @@ class CreateCorpusFileForm(CorpusFileBaseForm):
         if not field.data.filename.lower().endswith('.vrt'):
             raise ValidationError('VRT files only!')
 
+    def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'create-corpus-file-form'
+        super().__init__(*args, **kwargs)
+
 
 class UpdateCorpusFileForm(CorpusFileBaseForm):
-    pass
+    def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'update-corpus-file-form'
+        super().__init__(*args, **kwargs)
diff --git a/app/corpora/forms.py b/app/corpora/forms.py
index e923ca1b9a38200f24c651c7c1e571af58d0bd0c..fa8ccd05ff7459fe40212884d3a7b0a5941a6fcd 100644
--- a/app/corpora/forms.py
+++ b/app/corpora/forms.py
@@ -1,9 +1,9 @@
+from flask_wtf import FlaskForm
 from wtforms import StringField, SubmitField, TextAreaField
 from wtforms.validators import InputRequired, Length
-from app.forms import NopaqueForm
 
 
-class CorpusBaseForm(NopaqueForm):
+class CorpusBaseForm(FlaskForm):
     description = TextAreaField(
         'Description',
         validators=[InputRequired(), Length(max=255)]
@@ -13,12 +13,21 @@ class CorpusBaseForm(NopaqueForm):
 
 
 class CreateCorpusForm(CorpusBaseForm):
-    pass
+    def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'create-corpus-form'
+        super().__init__(*args, **kwargs)
 
 
 class UpdateCorpusForm(CorpusBaseForm):
-    pass
+    def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'update-corpus-form'
+        super().__init__(*args, **kwargs)
 
 
-class ImportCorpusForm(NopaqueForm):
-    pass
+class ImportCorpusForm(FlaskForm):
+    def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'import-corpus-form'
+        super().__init__(*args, **kwargs)
diff --git a/app/forms.py b/app/forms.py
deleted file mode 100644
index 6ac58347d86dab0bd7980573224c8d8f53cefd96..0000000000000000000000000000000000000000
--- a/app/forms.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from flask_wtf import FlaskForm
-from wtforms.validators import ValidationError
-import re
-
-
-form_prefix_pattern = re.compile(r'(?<!^)(?=[A-Z])')
-
-
-def LimitFileSize(max_size_mb):
-    max_size_b = max_size_mb * 1024 * 1024
-    def file_length_check(form, field):
-        if len(field.data.read()) >= max_size_b:
-            raise ValidationError(
-                f'File size must be less or equal than {max_size_mb} MB'
-            )
-        field.data.seek(0)
-    return file_length_check
-
-
-class NopaqueForm(FlaskForm):
-    def __init__(self, *args, **kwargs):
-        if 'prefix' not in kwargs:
-            kwargs['prefix'] = \
-                form_prefix_pattern.sub('-', self.__class__.__name__).lower()
-        super().__init__(*args, **kwargs)
-
diff --git a/app/services/forms.py b/app/services/forms.py
index d53132f8c714c29113d08dc5651d7ea75af08f79..01b1cdd824722c0aecbfa700b527f7338a6f5342 100644
--- a/app/services/forms.py
+++ b/app/services/forms.py
@@ -1,3 +1,4 @@
+from flask_wtf import FlaskForm
 from flask_login import current_user
 from flask_wtf.file import FileField, FileRequired
 from wtforms import (
@@ -10,12 +11,11 @@ from wtforms import (
     ValidationError
 )
 from wtforms.validators import InputRequired, Length
-from app.forms import NopaqueForm
 from app.models import SpaCyNLPPipelineModel, TesseractOCRPipelineModel
 from . import SERVICES
 
 
-class CreateJobBaseForm(NopaqueForm):
+class CreateJobBaseForm(FlaskForm):
     description = StringField(
         'Description',
         validators=[InputRequired(), Length(max=255)]
@@ -38,6 +38,8 @@ class CreateFileSetupPipelineJobForm(CreateJobBaseForm):
                 raise ValidationError('JPEG, PNG and TIFF files only!')
 
     def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'create-file-setup-pipeline-job-form'
         service_manifest = SERVICES['file-setup-pipeline']
         version = kwargs.pop('version', service_manifest['latest_version'])
         super().__init__(*args, **kwargs)
@@ -65,6 +67,8 @@ class CreateTesseractOCRPipelineJobForm(CreateJobBaseForm):
             raise ValidationError('PDF files only!')
 
     def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'create-tesseract-ocr-pipeline-job-form'
         service_manifest = SERVICES['tesseract-ocr-pipeline']
         version = kwargs.pop('version', service_manifest['latest_version'])
         super().__init__(*args, **kwargs)
@@ -111,6 +115,8 @@ class CreateTranskribusHTRPipelineJobForm(CreateJobBaseForm):
             raise ValidationError('PDF files only!')
 
     def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'create-transkribus-htr-pipeline-job-form'
         transkribus_htr_pipeline_models = kwargs.pop('transkribus_htr_pipeline_models', [])
         service_manifest = SERVICES['transkribus-htr-pipeline']
         version = kwargs.pop('version', service_manifest['latest_version'])
@@ -149,6 +155,8 @@ class CreateSpacyNLPPipelineJobForm(CreateJobBaseForm):
             raise ValidationError('Plain text files only!')
 
     def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'create-spacy-nlp-pipeline-job-form'
         service_manifest = SERVICES['spacy-nlp-pipeline']
         version = kwargs.pop('version', service_manifest['latest_version'])
         super().__init__(*args, **kwargs)
diff --git a/app/services/routes.py b/app/services/routes.py
index 3a3cbd7b409a1928e6f27e908cff61b1eafc420c..7ab363846308b873dce159cfa4f3adaee3a55ba2 100644
--- a/app/services/routes.py
+++ b/app/services/routes.py
@@ -35,7 +35,7 @@ def file_setup_pipeline():
     version = request.args.get('version', service_manifest['latest_version'])
     if version not in service_manifest['versions']:
         abort(404)
-    form = CreateFileSetupPipelineJobForm(version=version)
+    form = CreateFileSetupPipelineJobForm(prefix='create-job-form', version=version)
     if form.is_submitted():
         if not form.validate():
             response = {'errors': form.errors}
@@ -77,7 +77,7 @@ def tesseract_ocr_pipeline():
     version = request.args.get('version', service_manifest['latest_version'])
     if version not in service_manifest['versions']:
         abort(404)
-    form = CreateTesseractOCRPipelineJobForm(version=version)
+    form = CreateTesseractOCRPipelineJobForm(prefix='create-job-form', version=version)
     if form.is_submitted():
         if not form.validate():
             response = {'errors': form.errors}
@@ -137,8 +137,8 @@ def transkribus_htr_pipeline():
         abort(500)
     transkribus_htr_pipeline_models = r.json()['trpModelMetadata']
     transkribus_htr_pipeline_models.append({'modelId': 48513, 'name': 'Caroline Minuscle', 'language': 'lat', 'isoLanguages': ['lat']})
-    print(transkribus_htr_pipeline_models[len(transkribus_htr_pipeline_models)-1])
     form = CreateTranskribusHTRPipelineJobForm(
+        prefix='create-job-form',
         transkribus_htr_pipeline_models=transkribus_htr_pipeline_models,
         version=version
     )
@@ -186,7 +186,7 @@ def spacy_nlp_pipeline():
     version = request.args.get('version', SERVICES[service]['latest_version'])
     if version not in service_manifest['versions']:
         abort(404)
-    form = CreateSpacyNLPPipelineJobForm(version=version)
+    form = CreateSpacyNLPPipelineJobForm(prefix='create-job-form', version=version)
     spacy_nlp_pipeline_models = SpaCyNLPPipelineModel.query.all()
     if form.is_submitted():
         if not form.validate():
diff --git a/app/settings/forms.py b/app/settings/forms.py
index e14ab91a905d9a4a14947a8353ffe80eb97a9a48..77c8687c18d80c52f631016d88e2a3ec79296bfd 100644
--- a/app/settings/forms.py
+++ b/app/settings/forms.py
@@ -1,4 +1,5 @@
 from flask_login import current_user
+from flask_wtf import FlaskForm
 from flask_wtf.file import FileField, FileRequired
 from wtforms import (
     PasswordField,
@@ -15,11 +16,11 @@ from wtforms.validators import (
     Length,
     Regexp
 )
-from app.forms import NopaqueForm, LimitFileSize
 from app.models import User, UserSettingJobStatusMailNotificationLevel
+from app.wtforms.validators import FileSize
 
 
-class UpdateAccountInformationForm(NopaqueForm):
+class UpdateAccountInformationForm(FlaskForm):
     email = StringField(
         'E-Mail',
         validators=[DataRequired(), Length(max=254), Email()]
@@ -43,6 +44,8 @@ class UpdateAccountInformationForm(NopaqueForm):
     def __init__(self, *args, user=current_user, **kwargs):
         if 'data' not in kwargs:
             kwargs['data'] = user.to_json_serializeable()
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'update-account-information-form'
         super().__init__(*args, **kwargs)
         self.user = user
 
@@ -57,7 +60,7 @@ class UpdateAccountInformationForm(NopaqueForm):
             raise ValidationError('Username already in use')
 
 
-class UpdateProfileInformationForm(NopaqueForm):
+class UpdateProfileInformationForm(FlaskForm):
     full_name = StringField(
         'Full name',
         validators=[Length(max=128)]
@@ -91,11 +94,13 @@ class UpdateProfileInformationForm(NopaqueForm):
     def __init__(self, *args, user=current_user, **kwargs):
         if 'data' not in kwargs:
             kwargs['data'] = user.to_json_serializeable()
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'update-profile-information-form'
         super().__init__(*args, **kwargs)
 
 
-class UpdateAvatarForm(NopaqueForm):
-    avatar = FileField('File', validators=[LimitFileSize(2)])
+class UpdateAvatarForm(FlaskForm):
+    avatar = FileField('File', validators=[FileRequired(), FileSize(2)])
     submit = SubmitField()
 
     def validate_avatar(self, field):
@@ -103,7 +108,13 @@ class UpdateAvatarForm(NopaqueForm):
         if field.data.mimetype not in valid_mimetypes:
             raise ValidationError('JPEG and PNG files only!')
 
-class UpdatePasswordForm(NopaqueForm):
+    def __init__(self, *args, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'update-avatar-form'
+        super().__init__(*args, **kwargs)
+
+
+class UpdatePasswordForm(FlaskForm):
     password = PasswordField('Old password', validators=[DataRequired()])
     new_password = PasswordField(
         'New password',
@@ -122,6 +133,8 @@ class UpdatePasswordForm(NopaqueForm):
     submit = SubmitField()
 
     def __init__(self, *args, user=current_user, **kwargs):
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'update-password-form'
         super().__init__(*args, **kwargs)
         self.user = user
 
@@ -130,7 +143,7 @@ class UpdatePasswordForm(NopaqueForm):
             raise ValidationError('Invalid password')
 
 
-class UpdateNotificationsForm(NopaqueForm):
+class UpdateNotificationsForm(FlaskForm):
     job_status_mail_notification_level = SelectField(
         'Job status mail notification level',
         choices=[
@@ -144,4 +157,6 @@ class UpdateNotificationsForm(NopaqueForm):
     def __init__(self, *args, user=current_user, **kwargs):
         if 'data' not in kwargs:
             kwargs['data'] = user.to_json_serializeable()
+        if 'prefix' not in kwargs:
+            kwargs['prefix'] = 'update-notifications-form'
         super().__init__(*args, **kwargs)
diff --git a/app/settings/json_routes.py b/app/settings/json_routes.py
index 01a553b0239c1824acc7d2612d42fd3a4ed57528..31002d260a1057187dbf752572bf26795c42b84e 100644
--- a/app/settings/json_routes.py
+++ b/app/settings/json_routes.py
@@ -8,32 +8,6 @@ from app.models import Avatar, User, ProfilePrivacySettings
 from . import bp
 
 
-@bp.route('/<hashid:user_id>', methods=['DELETE'])
-@login_required
-@content_negotiation(produces='application/json')
-def delete_user(user_id):
-    def _delete_user(app, user_id):
-        with app.app_context():
-            user = User.query.get(user_id)
-            user.delete()
-            db.session.commit()
-
-    user = User.query.get_or_404(user_id)
-    if not (user == current_user or current_user.is_administrator()):
-        abort(403)
-    thread = Thread(
-        target=_delete_user,
-        args=(current_app._get_current_object(), user_id)
-    )
-    if user == current_user:
-        logout_user()
-    thread.start()
-    response_data = {
-        'message': f'User "{user.username}" marked for deletion'
-    }
-    return response_data, 202
-
-
 @bp.route('/<hashid:user_id>/avatar', methods=['DELETE'])
 @content_negotiation(produces='application/json')
 def delete_profile_avatar(user_id):
diff --git a/app/static/js/App.js b/app/static/js/App.js
index 87d44d7194e47a4fae5d503eadd9874652ae52b2..6d54a7a5357a879fb6d2ac0437d12c20688c0499 100644
--- a/app/static/js/App.js
+++ b/app/static/js/App.js
@@ -91,7 +91,7 @@ class App {
       .filter((operation) => {return subRegExp.test(operation.path);});
     for (let operation of subFilteredPatch) {
       let [match, userId, jobId] = operation.path.match(subRegExp);
-      this.flash(`[<a href="/jobs/${jobId}">${this.data.users[userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-job-status="${operation.value}"></span>`, 'job');
+      this.flash(`[<a href="/jobs/${jobId}">${this.data.users[userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-status="${operation.value}"></span>`, 'job');
     }
 
     // Apply Patch
diff --git a/app/static/js/Requests/settings/settings.js b/app/static/js/Requests/settings/settings.js
index 8f1aee74198ddffcc54b9809e7e6916dfb35b429..d3137267f4b9e248b9bee83e9c9b08602cdecf23 100644
--- a/app/static/js/Requests/settings/settings.js
+++ b/app/static/js/Requests/settings/settings.js
@@ -1,6 +1,6 @@
 /*****************************************************************************
-* Users                                                              *
-* Fetch requests for /users routes                                   *
+* Settings                                                                   *
+* Fetch requests for /settings routes                                        *
 *****************************************************************************/
 Requests.settings = {};
 
diff --git a/app/static/js/Requests/users/users.js b/app/static/js/Requests/users/users.js
new file mode 100644
index 0000000000000000000000000000000000000000..00adbee91c62f0023ab5d87d49ecc6016cf8982a
--- /dev/null
+++ b/app/static/js/Requests/users/users.js
@@ -0,0 +1,15 @@
+/*****************************************************************************
+* Users                                                                      *
+* Fetch requests for /users routes                                           *
+*****************************************************************************/
+Requests.users = {};
+
+Requests.users.entity = {};
+
+Requests.settings.entity.delete = (userId) => {
+  let input = `/users/${userId}`;
+  let init = {
+    method: 'DELETE'
+  };
+  return Requests.JSONfetch(input, init);
+};
diff --git a/app/static/js/ResourceLists/JobInputList.js b/app/static/js/ResourceLists/JobInputList.js
index 7f1a51058d01cdb9674ef51d95c6491cb4f1a769..97a8dd14846b7bcd66b2455ca9a8f6726c550869 100644
--- a/app/static/js/ResourceLists/JobInputList.js
+++ b/app/static/js/ResourceLists/JobInputList.js
@@ -12,11 +12,7 @@ class JobInputList extends ResourceList {
     this.userId = listContainerElement.dataset.userId;
     this.jobId = listContainerElement.dataset.jobId;
     if (this.userId === undefined || this.jobId === undefined) {return;}
-    app.subscribeUser(this.userId).then((response) => {
-      app.socket.on('PATCH', (patch) => {
-        if (this.isInitialized) {this.onPatch(patch);}
-      });
-    });
+    app.subscribeUser(this.userId);
     app.getUser(this.userId).then((user) => {
       this.add(Object.values(user.jobs[this.jobId].inputs));
       this.isInitialized = true;
diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2
index 45e5457bf6ba668d964c658867a543fb2a386b6f..089a7cd2e9b6d9b936b1158dae6c7e9322d1554e 100644
--- a/app/templates/_scripts.html.j2
+++ b/app/templates/_scripts.html.j2
@@ -66,7 +66,8 @@
   'js/Requests/corpora/files.js',
   'js/Requests/corpora/followers.js',
   'js/Requests/jobs/jobs.js',
-  'js/Requests/settings/settings.js'
+  'js/Requests/settings/settings.js',
+  'js/Requests/users/users.js'
 %}
 <script src="{{ ASSET_URL }}"></script>
 {%- endassets %}
diff --git a/app/templates/services/corpus_analysis.html.j2 b/app/templates/services/corpus_analysis.html.j2
index 9ddc9ec3d1fa3e2c3bf2d5152fe8e70ad07a0f28..47fc6a47ae5d8e63c76aacaa0e687c56a722b84f 100644
--- a/app/templates/services/corpus_analysis.html.j2
+++ b/app/templates/services/corpus_analysis.html.j2
@@ -28,38 +28,11 @@
           <div class="corpus-list" data-user-id="{{ current_user.hashid }}"></div>
         </div>
         <div class="card-action right-align">
-          <a class="waves-effect waves-light btn" href="{{ url_for('corpora.import_corpus') }}">Import Corpus<i class="material-icons right">import_export</i></a>
+          <a class="btn disabled waves-effect waves-light" href="{{ url_for('corpora.import_corpus') }}">Import Corpus<i class="material-icons right">import_export</i></a>
           <a class="btn waves-effect waves-light" href="{{ url_for('corpora.create_corpus') }}">Create corpus<i class="material-icons right">add</i></a>
         </div>
       </div>
     </div>
-
-    <div class="col s12 query-result-list" data-user-id="{{ current_user.hashid }}" id="query-results">
-      <h2>My query results</h2>
-      <div class="card">
-        <div class="card-content">
-          <div class="input-field">
-            <i class="material-icons prefix">search</i>
-            <input id="search-query-results" class="search" type="search"></input>
-            <label for="search-query-results">Search query result</label>
-          </div>
-          <table class="highlight">
-            <thead>
-              <tr>
-                <th>Title and Description</th>
-                <th>Corpus and Query</th>
-                <th></th>
-              </tr>
-            </thead>
-            <tbody class="list"></tbody>
-          </table>
-          <ul class="pagination"></ul>
-        </div>
-        <div class="card-action right-align">
-          <a class="btn waves-effect waves-light disabled">Add query result<i class="material-icons right">file_upload</i></a>
-        </div>
-      </div>
-    </div>
   </div>
 </div>
 {% endblock page_content %}
diff --git a/app/templates/settings/settings.html.j2 b/app/templates/settings/settings.html.j2
index 004a553add1c526962247b273eba179ca4a26992..27c9590c204538933bcac55e1cce8000c141fa88 100644
--- a/app/templates/settings/settings.html.j2
+++ b/app/templates/settings/settings.html.j2
@@ -221,7 +221,7 @@ deleteAvatarButtonElement.addEventListener('click', () => {
 });
 
 document.querySelector('#delete-user').addEventListener('click', (event) => {
-  Requests.settings.entity.delete({{ user.hashid|tojson }})
+  Requests.users.entity.delete({{ user.hashid|tojson }})
     .then((response) => {window.location.href = '/';});
 });
 
diff --git a/app/users/json_routes.py b/app/users/json_routes.py
new file mode 100644
index 0000000000000000000000000000000000000000..d228f8f3c2207f4ac1fef9cc41cfc8a9afde5065
--- /dev/null
+++ b/app/users/json_routes.py
@@ -0,0 +1,33 @@
+from flask import abort, current_app
+from flask_login import current_user, login_required, logout_user
+from threading import Thread
+from app import db
+from app.decorators import content_negotiation
+from app.models import User
+from . import bp
+
+
+@bp.route('/<hashid:user_id>', methods=['DELETE'])
+@login_required
+@content_negotiation(produces='application/json')
+def delete_user(user_id):
+    def _delete_user(app, user_id):
+        with app.app_context():
+            user = User.query.get(user_id)
+            user.delete()
+            db.session.commit()
+
+    user = User.query.get_or_404(user_id)
+    if not (user == current_user or current_user.is_administrator()):
+        abort(403)
+    thread = Thread(
+        target=_delete_user,
+        args=(current_app._get_current_object(), user_id)
+    )
+    if user == current_user:
+        logout_user()
+    thread.start()
+    response_data = {
+        'message': f'User "{user.username}" marked for deletion'
+    }
+    return response_data, 202
diff --git a/app/wtforms/__init__.py b/app/wtforms/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/app/wtforms/validators.py b/app/wtforms/validators.py
new file mode 100644
index 0000000000000000000000000000000000000000..bef9568175e1f5a26548ca31e3fd2a720c31de7f
--- /dev/null
+++ b/app/wtforms/validators.py
@@ -0,0 +1,14 @@
+from wtforms.validators import ValidationError
+
+
+def FileSize(max_size_mb):
+    max_size_b = max_size_mb * 1024 * 1024
+
+    def file_length_check(form, field):
+        if len(field.data.read()) >= max_size_b:
+            raise ValidationError(
+                f'File size must be less or equal than {max_size_mb} MB'
+            )
+        field.data.seek(0)
+
+    return file_length_check