Repository: rdmorganiser/rdmo (language: python)

Issue:
[API] Connect with 3rd party service, retrieve project data
### Rationale / Begründung


We received a request from another institution that plans to integrate data from rdmo into their (new) research data management service.
This 3rd party app/service (under development) wants to re-use the project data (questions and answers) from rdmo and retrieve this data via the JSON API.
The app is being developed by non-admin users of the rdmo instance in their institution and intends to have users pull their own project data from rdmo into this service.


The first requests were about the fields in the Projects and Values API.  
I think all the requested fields are, so far, already present in the API.  
Answers to questions in a project can be retrieved via the `/projects/projects/<project id>/values/` as shown below.
* Attribute ~~title~~ URI (not only the id)
  * already there
* Question `title_en`
  * Look up project values `/projects/projects/<project id>/values/` (-> list of answers & attributes)
  * select `<attribute id>` from list
  * look up `<attribute id>`:`/domain/attributes/<attribute id>/`  
  * select `<question id>` from list of questions that use this `<attribute id>`
  * look up `/questions/questions/<question id>/` and get `title_en`
* if there are options, list with title of options (not the id or URI)
  * select `<option id>` from list
  * look up with `<option id>`: `options/options/<option id>` and get title
* `text` of `value` 
  * already there
* `value_type`
  *  already there  

For practical purposes, a bundled API endpoint could be created for this service. 

Another part of the integration would be the authentication and authorization via the API, similar as was discussed in https://github.com/rdmorganiser/rdmo/issues/539. 
Permissions for user accounts of the service could be transferred via the [DRF API Key library](https://florimondmanca.github.io/djangorestframework-api-key/).

### Affected 

 Managers, Admins

### Minimal functionality

* API endpoint for project data
  * have the requested project data for the 3rd party service available via the API
* API tokens by users
  * let users retrieve their own project data into the 3rd party service via an API call

### References / Verweise

* https://github.com/rdmorganiser/rdmo/issues/539
* https://github.com/rdmorganiser/rdmo/issues/506

add counters to project overview
This PR contains one of the features mentioned in #355.

> * Adding a counter which shows the total number of projects that exist on the site
> * Perhaps adding an additional counter which shows the number of projects which are currently displayed (after filters have been applied)

* #### Counters (numbers) were added to the templates 
  * `rdmo/projects/templates/projects/projects.html`
  * `rdmo/projects/templates/projects/projects_pagination.html`
  * `rdmo/projects/templates/projects/site_projects.html`

  * "Search" renamed to "Filter Projects
  * a counter is placed under filter input block in small text: 
    * https://github.com/rdmorganiser/rdmo/pull/606#discussion_r1165328881
    * the `number_of_projects` for the user was added to the `get_context_data` of the `FilterView`
    * [x] ~~a counter at the footer of the pagination, which shows the number of projects in the current page~~

- [x]  add new translations

* #### a bugfix for the pagination in combination with filter was added in the context data of `rdmo/projects/views/project.py`:
  * added function in  `rdmo/projects/utils.py`
 ```py
def set_context_querystring_with_filter_and_page(context: dict) -> dict:
    '''prepares the filter part of the querystring for the next and previous hyperlinks in the pagination'''
    if context["filter"].data:
        querystring = context["filter"].data.copy()
        if context["filter"].data.get('page'):
            del querystring['page']
        context['querystring'] = querystring.urlencode()
    return context
```
  * added tests in `rdmo/projects/tests/test_utils.py`

* #### a bugfix for https://github.com/rdmorganiser/rdmo/issues/578 was added in the `rdmo/projects/views/project.py`
 ```py
# the 'updated' from a Project should always return a valid DateTime value
# when Greatest returns null, then with Coalesce the for 'updated' is taken as fall-back
queryset = queryset.annotate(last_changed=Coalesce(Greatest(last_changed_subquery, 'updated'), 'updated'))
```
display a warning when an answer (value) in the interview could not be saved
### Rationale / Begründung

We had a case where answers, text with certain unicode characters, could not be saved to the database. This raised an error on the backend without any warning to the user. The user clicked "save" but the text area remained empty after returning to the question.
In general it would be helpful to display a warning to the user when (in the rare case) this happens.

(in this case, we needed to fix our mysql db tables to accept the unicode characters... )

### Affected 

Users

### Minimal functionality

* dispay a warning when saving a value (answer) raises an error

### References / Verweise

* https://github.com/rdmorganiser/rdmo/blob/c4946bb72ac0826b415daf817bce781931938298/rdmo/projects/templates/projects/project_questions_buttons.html#L24
Something like this:
```html
<div class="alert alert-warning" ng-show="service.errors.detail" role="alert">
  {$ service.errors.detail $}
</div>
```
* https://github.com/rdmorganiser/rdmo/blob/813df5e0b01a24183a6d989e53d31685f492d6dc/rdmo/projects/static/projects/js/project_questions/services.js#L777
*
* similar issue: https://github.com/rdmorganiser/rdmo/issues/604

Adjust UI if autosave is true
### Rationale / Begründung

If the autosave option is set to true, users might be confused about some buttons in the UI.

### Affected 

This mainly effects users.

### Minimal functionality

If the autosave option is set to true, the UI might be adjusted in order to reflect the new workflow. This issue aims at collecting potential changes. In particular the following might be changed:

1. Remove the "Save" button
2. Rename the "Save and proceed" button to "Proceed" or "Next"



Discussion / hints:
from the discussion, please tell me what you think about this feature request @jochenklar 
Basically, so that users can create their own API tokens.
I can start writing a PR for this afterwards.
Hi @MyPyDavid , please have a look here:

https://github.com/django-daiquiri/daiquiri/blob/master/daiquiri/auth/urls_accounts.py#L10
https://github.com/django-daiquiri/daiquiri/blob/master/daiquiri/auth/views.py#L52
https://github.com/django-daiquiri/daiquiri/blob/master/daiquiri/auth/templates/account/account_token.html

Maybe you can add it to RDMO in a similar way.





Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index deaaaf1df3..00e85f80e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
 # Changelog
 
+## RDMO 1.10.0 (Apr 27, 2023)
+
+* Allow users to create API access tokens (if ACCOUNT_ALLOW_USER_TOKEN is set)
+* Allow users to remove their account when using Shibboleth
+* Fix missing views when creating a project via API
+* Fix pagination when filtering projects
+* Add counter to projects filtering
+* Add an error message if save fails in the interview
+* Adjust interview buttons if PROJECT_QUESTIONS_AUTOSAVE is True
+* Adjust style for <summary> tag
+
 ## RDMO 1.9.2 (Feb 23, 2023)
 
 * Fix URL in invite emails in the multi site setup (#576)
diff --git a/rdmo/accounts/forms.py b/rdmo/accounts/forms.py
index 2a43e1ad9c..63fd10ceea 100644
--- a/rdmo/accounts/forms.py
+++ b/rdmo/accounts/forms.py
@@ -92,7 +92,9 @@ class RemoveForm(forms.Form):
     def __init__(self, *args, **kwargs):
         self.request = kwargs.pop('request')
         kwargs.setdefault('label_suffix', '')
-        super(RemoveForm, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
+        if not self.request.user.has_usable_password():
+            self.fields.pop('password')
 
     email = forms.CharField(widget=forms.TextInput(attrs={'required': 'false'}))
     email.label = _('E-mail')
diff --git a/rdmo/accounts/templates/account/account_token.html b/rdmo/accounts/templates/account/account_token.html
new file mode 100644
index 0000000000..9d8b1256cb
--- /dev/null
+++ b/rdmo/accounts/templates/account/account_token.html
@@ -0,0 +1,21 @@
+{% extends 'core/page.html' %}
+{% load i18n %}
+
+{% block page %}
+
+    <h1>{% trans "API token" %}</h1>
+
+    <p>{% trans 'Your API token is:' %} <code>{{ token }}</code></p>
+
+    <form method="post" action="{% url 'account_token' %}" novalidate>
+        {% csrf_token %}
+        <input type="submit" name="regenerate" value="{% trans 'Regenerate token' %}" class="btn btn-default" />
+    </form>
+
+    <p>
+        {% trans 'You can use this token in HTTP requests by using the <code>Authorization</code> Header:' %}
+    </p>
+
+    <pre>Authorization: Token {{ token }}</pre>
+
+{% endblock %}
diff --git a/rdmo/accounts/urls/__init__.py b/rdmo/accounts/urls/__init__.py
index b6853abd64..7574394317 100644
--- a/rdmo/accounts/urls/__init__.py
+++ b/rdmo/accounts/urls/__init__.py
@@ -2,7 +2,7 @@
 from django.contrib.auth import views as auth_views
 from django.urls import include, re_path
 
-from ..views import profile_update, remove_user, terms_of_use
+from ..views import profile_update, remove_user, terms_of_use, token
 
 urlpatterns = [
     # edit own profile
@@ -29,3 +29,8 @@
         re_path('^login/', auth_views.LoginView.as_view(template_name='account/login.html'), name='account_login'),
         re_path('^logout/', auth_views.LogoutView.as_view(next_page=settings.LOGIN_REDIRECT_URL), name='account_logout'),
     ]
+
+if settings.ACCOUNT_ALLOW_USER_TOKEN:
+    urlpatterns += [
+        re_path(r'^token/$', token, name='account_token')
+    ]
diff --git a/rdmo/accounts/utils.py b/rdmo/accounts/utils.py
index 1b628bc78b..10cd76acfb 100644
--- a/rdmo/accounts/utils.py
+++ b/rdmo/accounts/utils.py
@@ -4,6 +4,7 @@
 from django.contrib.auth import authenticate, get_user_model
 from django.contrib.auth.models import Group, Permission
 from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ObjectDoesNotExist
 
 from .models import Role
 from .settings import GROUPS
@@ -40,24 +41,52 @@ def set_group_permissions():
             group.permissions.add(permission)
 
 
-def delete_user(user, email, password):
+def delete_user(user=None, email=None, password=None):
+    if user is None or email is None:
+        log.debug('Deletion failed because either User or email is None')
+        return False
+
     username = user.username
 
-    try:
-        database_user = get_user_model().objects.get(email=email)
-    except get_user_model().DoesNotExist:
-        log.debug('User with email "%s" requested for deletion does not exist', email)
+    database_user = get_user_from_db_or_none(username, email)
+
+    if database_user is None:
+        log.info('Deletion of user "%s" failed, get_user_from_db_or_none returned None.', username)
         return False
 
-    if user == database_user and authenticate(username=username, password=password):
+    if user != database_user:
+        log.info('Deletion of user "%s" failed, the user from request (pk=%s) and database (pk=%s) differ.', username, user.pk, database_user.pk)
+        return False
+
+    if user.has_usable_password() and password is not None:
+        authenticated = authenticate(username=username, password=password)
+        if authenticated is None:
+            log.info('Deletion of user with usable password "%s" failed, authenticate returned None.', username)
+            return False
         try:
             user.delete()
-            log.debug('User "%s" deleted', username)
+            log.info('Deletion of user with usable password "%s" succeeded.', username)
             return True
-
         except Exception as e:
-            log.debug('An exception (%s) occured during user "%s" deletion: ', (str(e), username))
+            log.error('Deletion of user with usable password "%s" failed, an exception (%s) occured', (str(e), username))
+            return False
+    elif not user.has_usable_password() and password is None:
+        try:
+            user.delete()
+            log.info('Deletion of user without usable password "%s" succeeded.', username)
+            return True
+        except Exception as e:
+            log.error('Deletion of user without usable password "%s" failed, an exception (%s) occured', (str(e), username))
             return False
     else:
-        log.debug('Deletion of user "%s" failed because of an invalid password', username)
+        log.info('Deletion of user "%s" failed, probably wrong value for password given', username)
         return False
+
+
+def get_user_from_db_or_none(username: str, email: str):
+    try:
+        db_user = get_user_model().objects.get(username=username, email=email)
+        return db_user
+    except ObjectDoesNotExist:
+        log.error('Retrieval of user "%s" with email "%s" failed, user does not exist', username, email)
+        return None
diff --git a/rdmo/accounts/views.py b/rdmo/accounts/views.py
index 6a503d6433..c99dbcc30b 100644
--- a/rdmo/accounts/views.py
+++ b/rdmo/accounts/views.py
@@ -5,6 +5,7 @@
 from django.contrib.auth.decorators import login_required
 from django.http import HttpResponseRedirect
 from django.shortcuts import render
+from rest_framework.authtoken.models import Token
 
 from rdmo.core.utils import get_next, get_referer_path_info
 
@@ -40,36 +41,51 @@ def profile_update(request):
 
 @login_required()
 def remove_user(request):
-    if settings.PROFILE_DELETE:
-        log.debug('Remove user form for "%s"', request.user.username)
-
-        form = RemoveForm(request.POST or None, request=request)
-
-        if request.method == 'POST':
-            if 'cancel' in request.POST:
-                log.debug('User %s removal cancelled', str(request.user))
-
-                if settings.PROFILE_UPDATE:
-                    return HttpResponseRedirect('/account')
-                else:
-                    return HttpResponseRedirect('/')
-
-            if form.is_valid():
-                log.debug('Deleting user %s', request.user.username)
-
-                if delete_user(request.user, request.POST['email'], request.POST['password']):
-                    logout(request)
-                    return render(request, 'profile/profile_remove_success.html')
-                else:
-                    return render(request, 'profile/profile_remove_failed.html')
-
-        return render(request, 'profile/profile_remove_form.html', {
-            'form': form,
-            'next': get_referer_path_info(request, default='/')
-        })
-    else:
+    if not settings.PROFILE_DELETE:
+        log.info('Remove user form is disabled in settings PROFILE_DELETE')
         return render(request, 'profile/profile_remove_closed.html')
+    form = RemoveForm(request.POST or None, request=request)
+    log.debug('Remove user form initialized for "%s"' % request.user.username)
+    
+    if request.method == 'POST':
+        if 'cancel' in request.POST:
+            log.info('User %s removal cancelled', str(request.user))
+
+            if settings.PROFILE_UPDATE:
+                return HttpResponseRedirect('/account')
+            else:
+                return HttpResponseRedirect('/')
+
+        if form.is_valid():
+            user_is_deleted = delete_user(user=request.user,
+                                       email=request.POST['email'],
+                                       password=request.POST.get('password', None))
+
+            if user_is_deleted:
+                logout(request)
+                return render(request, 'profile/profile_remove_success.html')
+            else:
+                log.info('Remove user, deletion failed for %s', request.user.username)
+                return render(request, 'profile/profile_remove_failed.html')
+
+    return render(request, 'profile/profile_remove_form.html', {
+        'form': form,
+        'next': get_referer_path_info(request, default='/')
+    })
 
 
 def terms_of_use(request):
     return render(request, 'account/terms_of_use.html')
+
+@login_required()
+def token(request):
+    if request.method == 'POST':
+        try:
+            Token.objects.get(user=request.user).delete()
+        except Token.DoesNotExist:
+            pass
+
+    token, created = Token.objects.get_or_create(user=request.user)
+    return render(request, 'account/account_token.html', {
+        'token': token
+    })
diff --git a/rdmo/core/settings.py b/rdmo/core/settings.py
index 818ea9c117..45bfc22294 100644
--- a/rdmo/core/settings.py
+++ b/rdmo/core/settings.py
@@ -108,6 +108,7 @@
 ACCOUNT_PASSWORD_MIN_LENGTH = 4
 ACCOUNT_EMAIL_MAX_LENGTH = 190
 ACCOUNT_PREVENT_ENUMERATION = False
+ACCOUNT_ALLOW_USER_TOKEN = False
 
 ACCOUNT_ADAPTER = 'rdmo.accounts.adapter.AccountAdapter'
 
@@ -178,6 +179,7 @@
     'ACCOUNT',
     'ACCOUNT_SIGNUP',
     'ACCOUNT_TERMS_OF_USE',
+    'ACCOUNT_ALLOW_USER_TOKEN',
     'SOCIALACCOUNT',
     'PROFILE_UPDATE',
     'PROFILE_DELETE',
diff --git a/rdmo/core/static/core/css/base.scss b/rdmo/core/static/core/css/base.scss
index 39b5c29694..408e9487d2 100644
--- a/rdmo/core/static/core/css/base.scss
+++ b/rdmo/core/static/core/css/base.scss
@@ -115,6 +115,11 @@ table {
     }
 }
 
+summary {
+    color: $link-color;
+    cursor: pointer;
+}
+
 /* navbar */
 
 .navbar-default {
diff --git a/rdmo/core/templates/core/base_navigation.html b/rdmo/core/templates/core/base_navigation.html
index f709b9407e..816ead892d 100644
--- a/rdmo/core/templates/core/base_navigation.html
+++ b/rdmo/core/templates/core/base_navigation.html
@@ -117,6 +117,14 @@
                             {% include 'core/base_navigation_socialaccount.html' %}
                         {% endif %}
 
+                        {% if settings.ACCOUNT_ALLOW_USER_TOKEN %}
+                        <li>
+                            <a href="{% url 'account_token' %}">
+                                {% trans 'API token' %}
+                            </a>
+                        </li>
+                        {% endif %}
+
                         {% if settings.PROFILE_DELETE %}
                         <li>
                             <a href="{% url 'profile_remove' %}">
diff --git a/rdmo/locale/de/LC_MESSAGES/django.po b/rdmo/locale/de/LC_MESSAGES/django.po
index 1356e05b86..c88585d38e 100644
--- a/rdmo/locale/de/LC_MESSAGES/django.po
+++ b/rdmo/locale/de/LC_MESSAGES/django.po
@@ -2,7 +2,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: RDMO\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-11-28 11:07+0100\n"
+"POT-Creation-Date: 2023-04-21 00:59+0200\n"
 "PO-Revision-Date: 2023-01-11 11:31+0100\n"
 "Last-Translator: Jochen Klar <jklar@aip.de>\n"
 "Language-Team: RDMO <rdmo-team@listserv.dfn.de>\n"
@@ -202,7 +202,7 @@ msgstr "Die Seiten bei denen dieser Benutzer Manager ist."
 #: projects/models/membership.py:32
 #: projects/templates/projects/project_detail_invites.html:16
 #: projects/templates/projects/project_detail_memberships.html:21
-#: projects/templates/projects/projects.html:119
+#: projects/templates/projects/projects.html:126
 msgid "Role"
 msgstr "Rolle"
 
@@ -329,7 +329,7 @@ msgstr ""
 "\"%(email_url)s\">fordern sie eine neue Betätigungs-E-Mail an</a>."
 
 #: accounts/templates/account/login.html:6
-#: accounts/templates/account/login_form.html:20
+#: accounts/templates/account/login_form.html:22
 #: accounts/templates/account/password_reset_from_key_done.html:14
 #: core/templates/core/base_navigation.html:154
 #: core/templates/core/home.html:55
@@ -736,8 +736,8 @@ msgstr ""
 "%(site_name)s anzumelden. Es sind noch zusätzliche Angaben von Ihnen nötig:"
 
 #: accounts/templatetags/accounts_tags.py:19
-#: projects/templates/projects/projects.html:118
-#: projects/templates/projects/site_projects.html:39
+#: projects/templates/projects/projects.html:125
+#: projects/templates/projects/site_projects.html:49
 msgid "Name"
 msgstr "Name"
 
@@ -928,7 +928,7 @@ msgstr "Alle URI Prefixe"
 #: projects/templates/projects/project_detail_sidebar.html:11
 #: projects/templates/projects/project_view.html:42
 #: projects/templates/projects/projects.html:25
-#: projects/templates/projects/site_projects.html:23
+#: projects/templates/projects/site_projects.html:33
 #: questions/templates/questions/catalog_tree.html:104
 #: questions/templates/questions/catalogs_sidebar.html:78
 #: questions/templates/questions/catalogs_sidebar_showuri_checkboxes.html:26
@@ -1162,14 +1162,14 @@ msgid "Tasks"
 msgstr "Aufgaben"
 
 #: conditions/validators.py:14 domain/validators.py:15 options/validators.py:14
-#: options/validators.py:26 projects/forms.py:332 projects/forms.py:353
+#: options/validators.py:26 projects/forms.py:333 projects/forms.py:354
 #: questions/validators.py:16 questions/validators.py:28
 #: questions/validators.py:46 questions/validators.py:64 tasks/validators.py:14
 #: views/validators.py:14
 msgid "This field is required."
 msgstr "Dieses Feld wird benötigt."
 
-#: core/constants.py:14 core/settings.py:291
+#: core/constants.py:14 core/settings.py:294
 #: options/templates/options/options_export.html:39
 #: options/templates/options/options_modal_form_options.html:163
 #: projects/models/value.py:64 questions/constants.py:4
@@ -1231,83 +1231,83 @@ msgstr "Englisch"
 msgid "German"
 msgstr "Deutsch"
 
-#: core/settings.py:234
+#: core/settings.py:237
 msgid "PDF"
 msgstr "PDF"
 
-#: core/settings.py:235
+#: core/settings.py:238
 msgid "Rich Text Format"
 msgstr "Rich Text Format"
 
-#: core/settings.py:236
+#: core/settings.py:239
 msgid "Open Office"
 msgstr "Open Office"
 
-#: core/settings.py:237
+#: core/settings.py:240
 msgid "Microsoft Office"
 msgstr "Microsoft Office"
 
-#: core/settings.py:238
+#: core/settings.py:241
 msgid "HTML"
 msgstr "HTML"
 
-#: core/settings.py:239
+#: core/settings.py:242
 msgid "Markdown"
 msgstr "Markdown"
 
-#: core/settings.py:240
+#: core/settings.py:243
 msgid "mediawiki"
 msgstr "mediawiki"
 
-#: core/settings.py:241
+#: core/settings.py:244
 msgid "LaTeX"
 msgstr "LaTeX"
 
-#: core/settings.py:261 core/settings.py:267
+#: core/settings.py:264 core/settings.py:270
 msgid "RDMO XML"
 msgstr "RDMO XML"
 
-#: core/settings.py:262 domain/templates/domain/domain.html:74
+#: core/settings.py:265 domain/templates/domain/domain.html:74
 msgid "CSV comma separated"
 msgstr "CSV durch Komma getrennt"
 
-#: core/settings.py:263 domain/templates/domain/domain.html:79
+#: core/settings.py:266 domain/templates/domain/domain.html:79
 msgid "CSV semicolon separated"
 msgstr "CSV durch Semikolon getrennt"
 
-#: core/settings.py:292 questions/constants.py:5
+#: core/settings.py:295 questions/constants.py:5
 msgid "Textarea"
 msgstr "Textfeld"
 
-#: core/settings.py:293 questions/constants.py:6
+#: core/settings.py:296 questions/constants.py:6
 msgid "Yes/No"
 msgstr "Ja/Nein"
 
-#: core/settings.py:294 questions/constants.py:7
+#: core/settings.py:297 questions/constants.py:7
 msgid "Checkboxes"
 msgstr "Ankreuzfelder"
 
-#: core/settings.py:295 questions/constants.py:8
+#: core/settings.py:298 questions/constants.py:8
 msgid "Radio buttons"
 msgstr "Radio Buttons"
 
-#: core/settings.py:296 questions/constants.py:9
+#: core/settings.py:299 questions/constants.py:9
 msgid "Select drop-down"
 msgstr "Select-Dropdown-Menü"
 
-#: core/settings.py:297 questions/constants.py:10
+#: core/settings.py:300 questions/constants.py:10
 msgid "Autocomplete"
 msgstr "Autocomplete"
 
-#: core/settings.py:298 questions/constants.py:11
+#: core/settings.py:301 questions/constants.py:11
 msgid "Range slider"
 msgstr "Schieberegler"
 
-#: core/settings.py:299 questions/constants.py:12
+#: core/settings.py:302 questions/constants.py:12
 msgid "Date picker"
 msgstr "Datumspicker"
 
-#: core/settings.py:300 questions/constants.py:13
+#: core/settings.py:303 questions/constants.py:13
 msgid "File upload"
 msgstr "Dateien-Upload"
 
@@ -1367,7 +1367,7 @@ msgstr "Management"
 msgid "Domain"
 msgstr "Domäne"
 
-#: core/templates/core/base_navigation.html:61 projects/forms.py:308
+#: core/templates/core/base_navigation.html:61 projects/forms.py:309
 #: projects/models/project.py:64
 #: projects/templates/projects/project_detail_views.html:11 views/apps.py:7
 #: views/models.py:123 views/templates/views/views.html:91
@@ -2041,7 +2041,7 @@ msgstr "Url-Name"
 msgid "The url_name for this overlay."
 msgstr "Der url_name für diese Einblendung."
 
-#: overlays/models.py:26 projects/forms.py:320
+#: overlays/models.py:26 projects/forms.py:321
 #: projects/templates/projects/project_answers.html:13
 #: projects/templates/projects/project_view.html:28
 msgid "Current"
@@ -2114,11 +2114,11 @@ msgstr ""
 "Als Site-Manager oder Admin können Sie Benutzer direkt hinzufügen, ohne sie "
 "per E-Mail zu benachrichtigen, wenn Sie das folgende Kästchen aktivieren."
 
-#: projects/forms.py:202
+#: projects/forms.py:203
 msgid "The user is already a member of the project."
 msgstr "Der Benutzer ist bereits ein Mitglied dieses Projektes."
 
-#: projects/forms.py:214
+#: projects/forms.py:215
 msgid ""
 "A user with this username or e-mail was not found. Only registered users can "
 "be invited."
@@ -2126,51 +2126,51 @@ msgstr ""
 "Ein Benutzer mit diesem Benutzernamen oder dieser E-Mail wurde nicht "
 "gefunden. Nur registrierte Benutzer können eingeladen werden."
 
-#: projects/forms.py:218
+#: projects/forms.py:219
 msgid "Only existing users can be added silently."
 msgstr "Nur bestehende Benutzer können stillschweigend hinzugefügt werden."
 
-#: projects/forms.py:286 projects/forms.py:290
+#: projects/forms.py:287 projects/forms.py:291
 #, python-format
 msgid "Attach %s"
 msgstr "%s hinzufügen"
 
-#: projects/forms.py:296
+#: projects/forms.py:297
 msgid "Subject"
 msgstr "Betreff"
 
-#: projects/forms.py:297
+#: projects/forms.py:298
 msgid "Message"
 msgstr "Nachricht"
 
-#: projects/forms.py:304
+#: projects/forms.py:305
 msgid "Answers"
 msgstr "Antworten"
 
-#: projects/forms.py:305
+#: projects/forms.py:306
 msgid "Attach the output of \"View answers\"."
 msgstr "Die Ausgabe von \"Antworten anzeigen\" anhängen."
 
-#: projects/forms.py:312
+#: projects/forms.py:313
 msgid "Files"
 msgstr "Dateien"
 
-#: projects/forms.py:319 projects/models/snapshot.py:33
+#: projects/forms.py:320 projects/models/snapshot.py:33
 #: projects/models/value.py:38
 #: projects/templates/projects/project_detail_snapshots.html:20
 #: projects/templates/projects/project_import.html:93
 msgid "Snapshot"
 msgstr "Snapshot"
 
-#: projects/forms.py:323
+#: projects/forms.py:324
 msgid "Format"
 msgstr "Format"
 
-#: projects/forms.py:338 projects/forms.py:343
+#: projects/forms.py:339 projects/forms.py:344
 msgid "Recipients"
 msgstr "Empfänger"
 
-#: projects/forms.py:344
+#: projects/forms.py:345
 msgid "Enter recipients line by line"
 msgstr "Empfänger Zeile für Zeile eingeben"
 
@@ -3011,7 +3011,7 @@ msgstr "Projektinformationen bearbeiten"
 msgid "No description available."
 msgstr "Keine Beschreibung vorhanden."
 
-#: projects/templates/projects/project_detail_header_hierarchy.html:6
+#: projects/templates/projects/project_detail_header_hierarchy.html:7
 #: projects/templates/projects/project_detail_sidebar.html:52
 msgid "Update parent project"
 msgstr "Übergeordnetes Projekt bearbeiten"
@@ -3135,23 +3135,23 @@ msgid "Import values"
 msgstr "Werte importieren"
 
 #: projects/templates/projects/project_detail_sidebar.html:131
-#: projects/templates/projects/projects.html:62
+#: projects/templates/projects/projects.html:69
 msgid "Import from file"
 msgstr "Importieren aus Datei"
 
 #: projects/templates/projects/project_detail_sidebar.html:139
-#: projects/templates/projects/projects.html:71
-msgid "Import directly"
-msgstr "Projekt direkt importieren"
-
-#: projects/templates/projects/project_detail_sidebar.html:155
 msgid "Import from parent project"
 msgstr "Importieren aus übergeordnetem Projekt"
 
+#: projects/templates/projects/project_detail_sidebar.html:147
+#: projects/templates/projects/projects.html:78
+msgid "Import directly"
+msgstr "Projekt direkt importieren"
+
 #: projects/templates/projects/project_detail_snapshots.html:22
 #: projects/templates/projects/project_import.html:34
 #: projects/templates/projects/project_import.html:104
-#: projects/templates/projects/site_projects.html:40
+#: projects/templates/projects/site_projects.html:50
 msgid "Created"
 msgstr "Erstellt"
 
@@ -3387,7 +3387,7 @@ msgid "Please select"
 msgstr "Bitte auswählen"
 
 #: projects/templates/projects/project_questions_head.html:5
-#: projects/templates/projects/projects.html:113
+#: projects/templates/projects/projects.html:120
 msgid "My Projects"
 msgstr "Meine Projekte"
 
@@ -3437,7 +3437,7 @@ msgstr ""
 "\"></i> können aufgrund Ihrer Eingabe übersprungen werden."
 
 #: projects/templates/projects/project_questions_overview.html:14
-#: projects/templates/projects/site_projects.html:27
+#: projects/templates/projects/site_projects.html:37
 msgid "Back to my projects"
 msgstr "Zurück zu meinen Projekten"
 
@@ -3502,7 +3502,7 @@ msgid "Navigation"
 msgstr "Navigation"
 
 #: projects/templates/projects/project_view.html:90
-#: projects/views/project.py:175
+#: projects/views/project.py:198
 msgid "Error"
 msgstr "Fehler"
 
@@ -3519,8 +3519,8 @@ msgstr "Alle Projekte auf %(site)s anzeigen"
 
 #: projects/templates/projects/projects.html:47
 #: projects/templates/projects/site_projects.html:16
-msgid "Search projects"
-msgstr "Projekt suchen"
+msgid "Filter projects"
+msgstr "Projekte filtern"
 
 #: projects/templates/projects/projects.html:50
 #: projects/templates/projects/site_projects.html:19
@@ -3528,19 +3528,27 @@ msgid "Search project title"
 msgstr "Suche nach Projekttitel"
 
 #: projects/templates/projects/projects.html:57
+#: projects/templates/projects/site_projects.html:26
+#, python-format
+msgid ""
+"%(number_of_filtered_projects)s of %(number_of_projects)s projects shown"
+msgstr ""
+"%(number_of_filtered_projects)s von %(number_of_projects)s Projekte angezeigt"
+
+#: projects/templates/projects/projects.html:64
 msgid "Import existing project"
 msgstr "Vorhandenes Projekt importieren"
 
-#: projects/templates/projects/projects.html:88
+#: projects/templates/projects/projects.html:95
 msgid "Pending invitations"
 msgstr "Ausstehende Einladungen"
 
-#: projects/templates/projects/projects.html:104
+#: projects/templates/projects/projects.html:111
 msgid "Click on one of the links to join the projects."
 msgstr "Klicken Sie auf einen der Links, um an den Projekten teilzunehmen."
 
-#: projects/templates/projects/projects.html:120
-#: projects/templates/projects/site_projects.html:41
+#: projects/templates/projects/projects.html:127
+#: projects/templates/projects/site_projects.html:51
 msgid "Last changed"
 msgstr "Letzte Änderung"
 
@@ -3559,7 +3567,7 @@ msgstr "Seite %(number)s von %(num_pages)s."
 msgid "Next"
 msgstr "Nächste"
 
-#: projects/templates/projects/site_projects.html:34
+#: projects/templates/projects/site_projects.html:44
 #, python-format
 msgid "All projects on %(site)s"
 msgstr "Alle Projekte auf %(site)s"
@@ -3589,16 +3597,16 @@ msgstr "Set"
 msgid "You reached the file quota for this project."
 msgstr "Sie haben die Quota für dieses Projekt erreicht."
 
-#: projects/views/project.py:155
+#: projects/views/project.py:178
 msgid "Sorry, your invitation has been expired."
 msgstr "Entschuldigung, Ihre Einladung ist abgelaufen."
 
-#: projects/views/project.py:158
+#: projects/views/project.py:181
 #, python-format
 msgid "Sorry, but this invitation is for the user \"%s\"."
 msgstr "Entschuldigung, aber diese Einladung ist für den Benutzer \"%s\"."
 
-#: projects/views/project.py:172
+#: projects/views/project.py:195
 msgid "Sorry, the invitation link is not valid."
 msgstr "Der Einladungslink ist leider nicht gültig."
 
@@ -5064,6 +5072,9 @@ msgstr ""
 "Eine leere Liste bedeutet, dass die Ansicht mit jedem Katalog verwendet "
 "werden kann."
 
+#~ msgid "Search projects"
+#~ msgstr "Projekt suchen"
+
 #~ msgid "Questionnaire"
 #~ msgstr "Fragebogen"
 
diff --git a/rdmo/locale/es/LC_MESSAGES/django.po b/rdmo/locale/es/LC_MESSAGES/django.po
index aefdce02d9..c03499ab14 100644
--- a/rdmo/locale/es/LC_MESSAGES/django.po
+++ b/rdmo/locale/es/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-11-28 11:07+0100\n"
+"POT-Creation-Date: 2023-04-21 00:59+0200\n"
 "PO-Revision-Date: 2022-07-29 13:45+0200\n"
 "Last-Translator: DAVID MARTINEZ <dmartinezmu@cem.es>\n"
 "Language-Team: \n"
@@ -214,7 +214,7 @@ msgstr "Sitios para los cuales este usuario es gestor."
 #: projects/models/membership.py:32
 #: projects/templates/projects/project_detail_invites.html:16
 #: projects/templates/projects/project_detail_memberships.html:21
-#: projects/templates/projects/projects.html:119
+#: projects/templates/projects/projects.html:126
 msgid "Role"
 msgstr "Rol"
 
@@ -331,15 +331,15 @@ msgstr "Confirmar"
 #: accounts/templates/account/email_confirm.html:29
 #, python-format
 msgid ""
-"This e-mail confirmation link expired or is invalid. Please <a "
-"href=\"%(email_url)s\">issue a new e-mail confirmation request</a>."
+"This e-mail confirmation link expired or is invalid. Please <a href="
+"\"%(email_url)s\">issue a new e-mail confirmation request</a>."
 msgstr ""
 "Este enlace de confirmación del e-mail ha expirado o no es válido. Por favor "
 "<<a href=\"%(email_url)s\">emita un nueva petición de confirmación del e-"
 "mail</a>."
 
 #: accounts/templates/account/login.html:6
-#: accounts/templates/account/login_form.html:20
+#: accounts/templates/account/login_form.html:22
 #: accounts/templates/account/password_reset_from_key_done.html:14
 #: core/templates/core/base_navigation.html:154
 #: core/templates/core/home.html:55
@@ -353,20 +353,20 @@ msgstr "Login con Shibboleth"
 #: accounts/templates/account/login_form_account.html:7
 #, python-format
 msgid ""
-"If you have not created an account yet, then please <a "
-"href=\"%(signup_url)s\">sign up</a> first."
+"If you have not created an account yet, then please <a href=\"%(signup_url)s"
+"\">sign up</a> first."
 msgstr ""
-"Si todavía no ha creado una cuenta, por favor primero <a "
-"href=\"%(signup_url)s\">regístrese</a>."
+"Si todavía no ha creado una cuenta, por favor primero <a href="
+"\"%(signup_url)s\">regístrese</a>."
 
 #: accounts/templates/account/login_form_account.html:14
 #, python-format
 msgid ""
-"If you forgot your password and want to reset it, click <a "
-"href=\"%(reset_url)s\">here</a>."
+"If you forgot your password and want to reset it, click <a href="
+"\"%(reset_url)s\">here</a>."
 msgstr ""
-"Si ha olvidado su contraseña y quiere una nueva, pulse <a "
-"href=\"%(reset_url)s\">aquí</a>."
+"Si ha olvidado su contraseña y quiere una nueva, pulse <a href="
+"\"%(reset_url)s\">aquí</a>."
 
 #: accounts/templates/account/login_form_socialaccount.html:9
 msgid ""
@@ -494,8 +494,8 @@ msgstr "Crear nueva cuenta"
 msgid ""
 "Already have an account? Then please <a href=\"%(login_url)s\">sign in</a>."
 msgstr ""
-"¿Ya dispone de una cuenta? Entonces por favor <a "
-"href=\"%(login_url)s\">acceda</a>."
+"¿Ya dispone de una cuenta? Entonces por favor <a href=\"%(login_url)s"
+"\">acceda</a>."
 
 #: accounts/templates/account/signup.html:34
 #: accounts/templates/socialaccount/signup.html:33
@@ -733,12 +733,12 @@ msgstr "Acceso cancelado"
 #, python-format
 msgid ""
 "You decided to cancel logging in to our site using one of your existing "
-"accounts. If this was a mistake, please proceed to <a "
-"href=\"%(login_url)s\">sign in</a>."
+"accounts. If this was a mistake, please proceed to <a href=\"%(login_url)s"
+"\">sign in</a>."
 msgstr ""
 "Usted ha cancelado el acceso a nuestro site mediante una de sus cuentas "
-"registradas. Si esto ha sido un error, por favor proceda a <a "
-"href=\"%(login_url)s\">acceder</a>."
+"registradas. Si esto ha sido un error, por favor proceda a <a href="
+"\"%(login_url)s\">acceder</a>."
 
 #: accounts/templates/socialaccount/signup.html:9
 #, python-format
@@ -750,8 +750,8 @@ msgstr ""
 "%(site_name)s. Para finalizar, por favor complete el siguiente formulario:"
 
 #: accounts/templatetags/accounts_tags.py:19
-#: projects/templates/projects/projects.html:118
-#: projects/templates/projects/site_projects.html:39
+#: projects/templates/projects/projects.html:125
+#: projects/templates/projects/site_projects.html:49
 msgid "Name"
 msgstr "Nombre"
 
@@ -942,7 +942,7 @@ msgstr "Todos los prefijos del URI"
 #: projects/templates/projects/project_detail_sidebar.html:11
 #: projects/templates/projects/project_view.html:42
 #: projects/templates/projects/projects.html:25
-#: projects/templates/projects/site_projects.html:23
+#: projects/templates/projects/site_projects.html:33
 #: questions/templates/questions/catalog_tree.html:104
 #: questions/templates/questions/catalogs_sidebar.html:78
 #: questions/templates/questions/catalogs_sidebar_showuri_checkboxes.html:26
@@ -1177,14 +1177,14 @@ msgid "Tasks"
 msgstr "Tareas"
 
 #: conditions/validators.py:14 domain/validators.py:15 options/validators.py:14
-#: options/validators.py:26 projects/forms.py:332 projects/forms.py:353
+#: options/validators.py:26 projects/forms.py:333 projects/forms.py:354
 #: questions/validators.py:16 questions/validators.py:28
 #: questions/validators.py:46 questions/validators.py:64 tasks/validators.py:14
 #: views/validators.py:14
 msgid "This field is required."
 msgstr "Este campo es obligatorio."
 
-#: core/constants.py:14 core/settings.py:291
+#: core/constants.py:14 core/settings.py:294
 #: options/templates/options/options_export.html:39
 #: options/templates/options/options_modal_form_options.html:163
 #: projects/models/value.py:64 questions/constants.py:4
@@ -1246,83 +1246,83 @@ msgstr "Inglés"
 msgid "German"
 msgstr "Alemán"
 
-#: core/settings.py:234
+#: core/settings.py:237
 msgid "PDF"
 msgstr "PDF"
 
-#: core/settings.py:235
+#: core/settings.py:238
 msgid "Rich Text Format"
 msgstr "Rich Text Format"
 
-#: core/settings.py:236
+#: core/settings.py:239
 msgid "Open Office"
 msgstr "Open Office"
 
-#: core/settings.py:237
+#: core/settings.py:240
 msgid "Microsoft Office"
 msgstr "Microsoft Office"
 
-#: core/settings.py:238
+#: core/settings.py:241
 msgid "HTML"
 msgstr "HTML"
 
-#: core/settings.py:239
+#: core/settings.py:242
 msgid "Markdown"
 msgstr "Markdown"
 
-#: core/settings.py:240
+#: core/settings.py:243
 msgid "mediawiki"
 msgstr "mediawiki"
 
-#: core/settings.py:241
+#: core/settings.py:244
 msgid "LaTeX"
 msgstr "LaTeX"
 
-#: core/settings.py:261 core/settings.py:267
+#: core/settings.py:264 core/settings.py:270
 msgid "RDMO XML"
 msgstr "RDMO XML"
 
-#: core/settings.py:262 domain/templates/domain/domain.html:74
+#: core/settings.py:265 domain/templates/domain/domain.html:74
 msgid "CSV comma separated"
 msgstr "CSV separado por comas"
 
-#: core/settings.py:263 domain/templates/domain/domain.html:79
+#: core/settings.py:266 domain/templates/domain/domain.html:79
 msgid "CSV semicolon separated"
 msgstr "CSV separado por punto y coma"
 
-#: core/settings.py:292 questions/constants.py:5
+#: core/settings.py:295 questions/constants.py:5
 msgid "Textarea"
 msgstr "Área de texto"
 
-#: core/settings.py:293 questions/constants.py:6
+#: core/settings.py:296 questions/constants.py:6
 msgid "Yes/No"
 msgstr "Sí/No"
 
-#: core/settings.py:294 questions/constants.py:7
+#: core/settings.py:297 questions/constants.py:7
 msgid "Checkboxes"
 msgstr "Casillas de comprobación"
 
-#: core/settings.py:295 questions/constants.py:8
+#: core/settings.py:298 questions/constants.py:8
 msgid "Radio buttons"
 msgstr "Botones de radio"
 
-#: core/settings.py:296 questions/constants.py:9
+#: core/settings.py:299 questions/constants.py:9
 msgid "Select drop-down"
 msgstr "Menú desplegable de selección"
 
-#: core/settings.py:297 questions/constants.py:10
+#: core/settings.py:300 questions/constants.py:10
 msgid "Autocomplete"
 msgstr "Auto-completar"
 
-#: core/settings.py:298 questions/constants.py:11
+#: core/settings.py:301 questions/constants.py:11
 msgid "Range slider"
 msgstr "Deslizador de rango"
 
-#: core/settings.py:299 questions/constants.py:12
+#: core/settings.py:302 questions/constants.py:12
 msgid "Date picker"
 msgstr "Selección de fecha"
 
-#: core/settings.py:300 questions/constants.py:13
+#: core/settings.py:303 questions/constants.py:13
 msgid "File upload"
 msgstr "Subida de archivo"
 
@@ -1381,7 +1381,7 @@ msgstr "Gestión"
 msgid "Domain"
 msgstr "Dominio"
 
-#: core/templates/core/base_navigation.html:61 projects/forms.py:308
+#: core/templates/core/base_navigation.html:61 projects/forms.py:309
 #: projects/models/project.py:64
 #: projects/templates/projects/project_detail_views.html:11 views/apps.py:7
 #: views/models.py:123 views/templates/views/views.html:91
@@ -1540,11 +1540,11 @@ msgstr "Esto también borrará los siguientes atributos:"
 #: domain/templates/domain/domain_modal_show_attributes.html:15
 #, python-format
 msgid ""
-"This attribute is used for <b>%(values_count)s values</b> in "
-"<b>%(projects_count)s projects</b>."
+"This attribute is used for <b>%(values_count)s values</b> in <b>"
+"%(projects_count)s projects</b>."
 msgstr ""
-"Este atributo es utilizado para <b>%(values_count)s valores</b> en "
-"<b>%(projects_count)s proyectos</b>."
+"Este atributo es utilizado para <b>%(values_count)s valores</b> en <b>"
+"%(projects_count)s proyectos</b>."
 
 #: domain/templates/domain/domain_modal_delete_attributes.html:42
 #: domain/templates/domain/domain_modal_show_attributes.html:18
@@ -1908,11 +1908,11 @@ msgstr ""
 #: options/templates/options/options_modal_show_options.html:15
 #, python-format
 msgid ""
-"This option is used for <b>%(values_count)s values</b> in "
-"<b>%(projects_count)s projects</b>."
+"This option is used for <b>%(values_count)s values</b> in <b>"
+"%(projects_count)s projects</b>."
 msgstr ""
-"Esta opción es utilizada para <b>%(values_count)s valores</b> en "
-"<b>%(projects_count)s proyectos</b>."
+"Esta opción es utilizada para <b>%(values_count)s valores</b> en <b>"
+"%(projects_count)s proyectos</b>."
 
 #: options/templates/options/options_modal_delete_options.html:29
 #, python-format
@@ -1925,8 +1925,8 @@ msgstr ""
 msgid ""
 "You are about to permanently delete the option set <code>%(object)s</code>."
 msgstr ""
-"Está usted a punto de eliminar permanentemente el conjunto de opciones "
-"<code>%(object)s</code>."
+"Está usted a punto de eliminar permanentemente el conjunto de opciones <code>"
+"%(object)s</code>."
 
 #: options/templates/options/options_modal_delete_optionsets.html:23
 msgid ""
@@ -2055,7 +2055,7 @@ msgstr "Nombre del URL"
 msgid "The url_name for this overlay."
 msgstr "El url_name para esta superposición."
 
-#: overlays/models.py:26 projects/forms.py:320
+#: overlays/models.py:26 projects/forms.py:321
 #: projects/templates/projects/project_answers.html:13
 #: projects/templates/projects/project_view.html:28
 msgid "Current"
@@ -2127,11 +2127,11 @@ msgstr ""
 "Como gestor o administrador del sitio, usted puede añadir directament nuevos "
 "usuarios sin notificárselo via e-mail, marcando el siguiente checkbox."
 
-#: projects/forms.py:202
+#: projects/forms.py:203
 msgid "The user is already a member of the project."
 msgstr "El usuario ya es miembro del proyecto."
 
-#: projects/forms.py:214
+#: projects/forms.py:215
 msgid ""
 "A user with this username or e-mail was not found. Only registered users can "
 "be invited."
@@ -2139,51 +2139,51 @@ msgstr ""
 "No se ha encontrado un usuario con este nombre o e-mail. Solo los usuarios "
 "registrados pueden ser invitados."
 
-#: projects/forms.py:218
+#: projects/forms.py:219
 msgid "Only existing users can be added silently."
 msgstr "Solo se pueden añadir silenciosamente usuarios ya existentes."
 
-#: projects/forms.py:286 projects/forms.py:290
+#: projects/forms.py:287 projects/forms.py:291
 #, python-format
 msgid "Attach %s"
 msgstr "Adjuntar %s"
 
-#: projects/forms.py:296
+#: projects/forms.py:297
 msgid "Subject"
 msgstr "Asunto"
 
-#: projects/forms.py:297
+#: projects/forms.py:298
 msgid "Message"
 msgstr "Mensaje"
 
-#: projects/forms.py:304
+#: projects/forms.py:305
 msgid "Answers"
 msgstr "Respuestas"
 
-#: projects/forms.py:305
+#: projects/forms.py:306
 msgid "Attach the output of \"View answers\"."
 msgstr "Adjuntar la salida de \"Ver respuestas\"."
 
-#: projects/forms.py:312
+#: projects/forms.py:313
 msgid "Files"
 msgstr "Archivos"
 
-#: projects/forms.py:319 projects/models/snapshot.py:33
+#: projects/forms.py:320 projects/models/snapshot.py:33
 #: projects/models/value.py:38
 #: projects/templates/projects/project_detail_snapshots.html:20
 #: projects/templates/projects/project_import.html:93
 msgid "Snapshot"
 msgstr "Instantátanea"
 
-#: projects/forms.py:323
+#: projects/forms.py:324
 msgid "Format"
 msgstr "Formato"
 
-#: projects/forms.py:338 projects/forms.py:343
+#: projects/forms.py:339 projects/forms.py:344
 msgid "Recipients"
 msgstr "Destinatarios"
 
-#: projects/forms.py:344
+#: projects/forms.py:345
 msgid "Enter recipients line by line"
 msgstr "Introducir destinatarios línea por línea"
 
@@ -2847,11 +2847,11 @@ msgstr "Para quien pueda interesar,"
 #: projects/templates/projects/issue_send_message.txt:3
 #, python-format
 msgid ""
-"The following task was identified in the project \"%(project_title)s\" "
-"<%(project_url)s>:"
+"The following task was identified in the project \"%(project_title)s\" <"
+"%(project_url)s>:"
 msgstr ""
-"Se ha identificado la siguiente tarea en el proyecto \"%(project_title)s\" "
-"<%(project_url)s>:"
+"Se ha identificado la siguiente tarea en el proyecto \"%(project_title)s\" <"
+"%(project_url)s>:"
 
 #: projects/templates/projects/issue_send_message.txt:13
 #, python-format
@@ -3022,7 +3022,7 @@ msgstr "Actualizar la información del proyecto"
 msgid "No description available."
 msgstr "No hay disponible ninguna descripción."
 
-#: projects/templates/projects/project_detail_header_hierarchy.html:6
+#: projects/templates/projects/project_detail_header_hierarchy.html:7
 #: projects/templates/projects/project_detail_sidebar.html:52
 msgid "Update parent project"
 msgstr "Actualizar proyecto padre"
@@ -3146,23 +3146,23 @@ msgid "Import values"
 msgstr "Importar valores"
 
 #: projects/templates/projects/project_detail_sidebar.html:131
-#: projects/templates/projects/projects.html:62
+#: projects/templates/projects/projects.html:69
 msgid "Import from file"
 msgstr "Importar desde archivo"
 
 #: projects/templates/projects/project_detail_sidebar.html:139
-#: projects/templates/projects/projects.html:71
-msgid "Import directly"
-msgstr "Importar directamente"
-
-#: projects/templates/projects/project_detail_sidebar.html:155
 msgid "Import from parent project"
 msgstr "Importar desde el proyecto padre"
 
+#: projects/templates/projects/project_detail_sidebar.html:147
+#: projects/templates/projects/projects.html:78
+msgid "Import directly"
+msgstr "Importar directamente"
+
 #: projects/templates/projects/project_detail_snapshots.html:22
 #: projects/templates/projects/project_import.html:34
 #: projects/templates/projects/project_import.html:104
-#: projects/templates/projects/site_projects.html:40
+#: projects/templates/projects/site_projects.html:50
 msgid "Created"
 msgstr "Creado"
 
@@ -3399,7 +3399,7 @@ msgid "Please select"
 msgstr "Por favor seleccione"
 
 #: projects/templates/projects/project_questions_head.html:5
-#: projects/templates/projects/projects.html:113
+#: projects/templates/projects/projects.html:120
 msgid "My Projects"
 msgstr "Mis Proyectos"
 
@@ -3408,8 +3408,8 @@ msgstr "Mis Proyectos"
 msgid ""
 "You are about to permanently delete the %(name)s <strong>%(object)s</strong>."
 msgstr ""
-"Usted está a punto de eliminar permanentemente el %(name)s "
-"<strong>%(object)s</strong>."
+"Usted está a punto de eliminar permanentemente el %(name)s <strong>"
+"%(object)s</strong>."
 
 #: projects/templates/projects/project_questions_modal_delete_valuesets.html:23
 #, python-format
@@ -3442,14 +3442,14 @@ msgstr ""
 
 #: projects/templates/projects/project_questions_navigation_help.html:14
 msgid ""
-"Entries with <i class=\"fa fa-question-circle-o small\" aria-"
-"hidden=\"true\"></i> might be skipped based on your input."
+"Entries with <i class=\"fa fa-question-circle-o small\" aria-hidden=\"true"
+"\"></i> might be skipped based on your input."
 msgstr ""
-"Las entradas con <i class=\"fa fa-question-circle-o small\" aria-"
-"hidden=\"true\"></i> podrían ser ignoradas según lo que ha introducido."
+"Las entradas con <i class=\"fa fa-question-circle-o small\" aria-hidden="
+"\"true\"></i> podrían ser ignoradas según lo que ha introducido."
 
 #: projects/templates/projects/project_questions_overview.html:14
-#: projects/templates/projects/site_projects.html:27
+#: projects/templates/projects/site_projects.html:37
 msgid "Back to my projects"
 msgstr "Volver a mis proyectos"
 
@@ -3513,7 +3513,7 @@ msgid "Navigation"
 msgstr "Navegación"
 
 #: projects/templates/projects/project_view.html:90
-#: projects/views/project.py:175
+#: projects/views/project.py:198
 msgid "Error"
 msgstr "Error"
 
@@ -3528,8 +3528,8 @@ msgstr "Ver todos los proyectos en %(site)s"
 
 #: projects/templates/projects/projects.html:47
 #: projects/templates/projects/site_projects.html:16
-msgid "Search projects"
-msgstr "Buscar proyectos"
+msgid "Filter projects"
+msgstr "Filtrar proyectos"
 
 #: projects/templates/projects/projects.html:50
 #: projects/templates/projects/site_projects.html:19
@@ -3537,19 +3537,28 @@ msgid "Search project title"
 msgstr "Buscar título de proyecto"
 
 #: projects/templates/projects/projects.html:57
+#: projects/templates/projects/site_projects.html:26
+#, python-format
+msgid ""
+"%(number_of_filtered_projects)s of %(number_of_projects)s projects shown"
+msgstr ""
+"%(number_of_filtered_projects)s de los %(number_of_projects)s proyectos "
+"mostrados"
+
+#: projects/templates/projects/projects.html:64
 msgid "Import existing project"
 msgstr "Importar proyecto existente"
 
-#: projects/templates/projects/projects.html:88
+#: projects/templates/projects/projects.html:95
 msgid "Pending invitations"
 msgstr "Invitaciones pendientes"
 
-#: projects/templates/projects/projects.html:104
+#: projects/templates/projects/projects.html:111
 msgid "Click on one of the links to join the projects."
 msgstr "Pulse en uno de los enlaces para unirse a los proyectos."
 
-#: projects/templates/projects/projects.html:120
-#: projects/templates/projects/site_projects.html:41
+#: projects/templates/projects/projects.html:127
+#: projects/templates/projects/site_projects.html:51
 msgid "Last changed"
 msgstr "Modificado por última vez"
 
@@ -3568,7 +3577,7 @@ msgstr "Página %(number)s de %(num_pages)s."
 msgid "Next"
 msgstr "Siguiente"
 
-#: projects/templates/projects/site_projects.html:34
+#: projects/templates/projects/site_projects.html:44
 #, python-format
 msgid "All projects on %(site)s"
 msgstr "Todos los proyectos en %(site)s"
@@ -3598,16 +3607,16 @@ msgstr "Establecer"
 msgid "You reached the file quota for this project."
 msgstr "Usted ha alcanzado la quota de archivado para este proyecto."
 
-#: projects/views/project.py:155
+#: projects/views/project.py:178
 msgid "Sorry, your invitation has been expired."
 msgstr "Lo sentimos, su invitación ha expirado."
 
-#: projects/views/project.py:158
+#: projects/views/project.py:181
 #, python-format
 msgid "Sorry, but this invitation is for the user \"%s\"."
 msgstr "Lo sentimos, pero esta invitación es para el usuario \"%s\"."
 
-#: projects/views/project.py:172
+#: projects/views/project.py:195
 msgid "Sorry, the invitation link is not valid."
 msgstr "Lo sentimos, la invitación no es válida."
 
@@ -4340,8 +4349,8 @@ msgstr "Opcional"
 #: questions/templates/questions/catalogs_modal_delete_catalogs.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the catalog <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the catalog <code class=\"text-primary\">"
+"%(object)s</code>."
 msgstr ""
 "Estás a punto de eliminar permanentemente el catálogo <code class=\"text-"
 "primary\">%(object)s</code>."
@@ -4366,11 +4375,11 @@ msgstr ""
 #: questions/templates/questions/catalogs_modal_delete_questions.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the question <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the question <code class=\"text-primary"
+"\">%(object)s</code>."
 msgstr ""
-"Usted está a punto de eliminar permanentemente la pregunta <code "
-"class=\"text-primary\">%(object)s</code>."
+"Usted está a punto de eliminar permanentemente la pregunta <code class="
+"\"text-primary\">%(object)s</code>."
 
 #: questions/templates/questions/catalogs_modal_delete_questionsets.html:17
 #, python-format
@@ -4384,8 +4393,8 @@ msgstr ""
 #: questions/templates/questions/catalogs_modal_delete_sections.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the section <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the section <code class=\"text-primary\">"
+"%(object)s</code>."
 msgstr ""
 "Usted está a punto de eliminar permanentemente la sección <code class=\"text-"
 "primary\">%(object)s</code>."
@@ -5104,6 +5113,9 @@ msgstr ""
 "Una lista vacía implica que la vista puede ser utilizada por todos los "
 "catálogos."
 
+#~ msgid "Search projects"
+#~ msgstr "Buscar proyectos"
+
 #~ msgid "Questionnaire"
 #~ msgstr "Cuestionario"
 
diff --git a/rdmo/locale/fr/LC_MESSAGES/django.po b/rdmo/locale/fr/LC_MESSAGES/django.po
index 7e7eafcd2a..45615a4506 100644
--- a/rdmo/locale/fr/LC_MESSAGES/django.po
+++ b/rdmo/locale/fr/LC_MESSAGES/django.po
@@ -2,7 +2,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: RDMO\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-11-28 11:07+0100\n"
+"POT-Creation-Date: 2023-04-21 00:59+0200\n"
 "PO-Revision-Date: 2022-11-28 11:11+0100\n"
 "Last-Translator: Yonny Cardenas <cardenas@cc.in2p3.fr>\n"
 "Language-Team: RDMO <cardenas@cc.in2p3.fr>\n"
@@ -207,7 +207,7 @@ msgstr "Les sites dont cet utilisateur est le gestionnaire."
 #: projects/models/membership.py:32
 #: projects/templates/projects/project_detail_invites.html:16
 #: projects/templates/projects/project_detail_memberships.html:21
-#: projects/templates/projects/projects.html:119
+#: projects/templates/projects/projects.html:126
 msgid "Role"
 msgstr "Rôle"
 
@@ -325,15 +325,15 @@ msgstr "Confirmer"
 #: accounts/templates/account/email_confirm.html:29
 #, python-format
 msgid ""
-"This e-mail confirmation link expired or is invalid. Please <a "
-"href=\"%(email_url)s\">issue a new e-mail confirmation request</a>."
+"This e-mail confirmation link expired or is invalid. Please <a href="
+"\"%(email_url)s\">issue a new e-mail confirmation request</a>."
 msgstr ""
 "Ce lien de confirmation par e-mail a expiré ou n'est pas valide. Veuillez  "
 "<a href=\"%(email_url)s\"> faire une nouvelle demande de confirmation par e-"
 "mail </a>."
 
 #: accounts/templates/account/login.html:6
-#: accounts/templates/account/login_form.html:20
+#: accounts/templates/account/login_form.html:22
 #: accounts/templates/account/password_reset_from_key_done.html:14
 #: core/templates/core/base_navigation.html:154
 #: core/templates/core/home.html:55
@@ -347,8 +347,8 @@ msgstr "Connectez-vous avec Shibboleth"
 #: accounts/templates/account/login_form_account.html:7
 #, python-format
 msgid ""
-"If you have not created an account yet, then please <a "
-"href=\"%(signup_url)s\">sign up</a> first."
+"If you have not created an account yet, then please <a href=\"%(signup_url)s"
+"\">sign up</a> first."
 msgstr ""
 "Si vous n'avez pas encore créé de compte <a href=\"%(signup_url)s \"> "
 "inscrivez-vous d'abord </a>."
@@ -356,8 +356,8 @@ msgstr ""
 #: accounts/templates/account/login_form_account.html:14
 #, python-format
 msgid ""
-"If you forgot your password and want to reset it, click <a "
-"href=\"%(reset_url)s\">here</a>."
+"If you forgot your password and want to reset it, click <a href="
+"\"%(reset_url)s\">here</a>."
 msgstr ""
 "Si vous avez oublié votre mot de passe et que vous souhaitez le "
 "réinitialiser, cliquez sur <a href=\"%(reset_url)s\"> ici </a>."
@@ -440,9 +440,8 @@ msgid ""
 "a>."
 msgstr ""
 "Le lien de réinitialisation du mot de passe n'était pas valide, peut-être "
-"parce qu'il l'a déjà été utilisé. Veuillez demander une <a "
-"href=\"%(passwd_reset_url)s\"> nouvelle réinitialisation du mot de passe </"
-"a>."
+"parce qu'il l'a déjà été utilisé. Veuillez demander une <a href="
+"\"%(passwd_reset_url)s\"> nouvelle réinitialisation du mot de passe </a>."
 
 #: accounts/templates/account/password_reset_from_key.html:18
 msgid "Enter new password"
@@ -735,8 +734,8 @@ msgstr "Connexion annulée"
 #, python-format
 msgid ""
 "You decided to cancel logging in to our site using one of your existing "
-"accounts. If this was a mistake, please proceed to <a "
-"href=\"%(login_url)s\">sign in</a>."
+"accounts. If this was a mistake, please proceed to <a href=\"%(login_url)s"
+"\">sign in</a>."
 msgstr ""
 "Vous avez décidé d'annuler la connexion à notre site en utilisant l'un de "
 "vos comptes existants S'il s'agissait d'une erreur, veuillez passer à  <a "
@@ -748,13 +747,13 @@ msgid ""
 "You are about to use your <strong>%(provider_name)s</strong> account to "
 "login to %(site_name)s. As a final step, please complete the following form:"
 msgstr ""
-"Vous êtes sur le point d'utiliser votre compte pour "
-"<strong>%(provider_name)s</strong> se connecter à %(site_name)s. Pour "
-"terminer, veuillez remplir le formulaire suivant:"
+"Vous êtes sur le point d'utiliser votre compte pour <strong>"
+"%(provider_name)s</strong> se connecter à %(site_name)s. Pour terminer, "
+"veuillez remplir le formulaire suivant:"
 
 #: accounts/templatetags/accounts_tags.py:19
-#: projects/templates/projects/projects.html:118
-#: projects/templates/projects/site_projects.html:39
+#: projects/templates/projects/projects.html:125
+#: projects/templates/projects/site_projects.html:49
 msgid "Name"
 msgstr "Nom"
 
@@ -947,7 +946,7 @@ msgstr "Tous les préfixes URI"
 #: projects/templates/projects/project_detail_sidebar.html:11
 #: projects/templates/projects/project_view.html:42
 #: projects/templates/projects/projects.html:25
-#: projects/templates/projects/site_projects.html:23
+#: projects/templates/projects/site_projects.html:33
 #: questions/templates/questions/catalog_tree.html:104
 #: questions/templates/questions/catalogs_sidebar.html:78
 #: questions/templates/questions/catalogs_sidebar_showuri_checkboxes.html:26
@@ -1024,8 +1023,8 @@ msgstr "Cible"
 msgid ""
 "You are about to permanently delete the condition <code>%(object)s</code>."
 msgstr ""
-"Vous êtes sur le point de supprimer définitivement la condition "
-"<code>%(object)s</code>."
+"Vous êtes sur le point de supprimer définitivement la condition <code>"
+"%(object)s</code>."
 
 #: conditions/templates/conditions/conditions_modal_delete_conditions.html:23
 #: conditions/templates/conditions/conditions_modal_show_conditions.html:15
@@ -1182,14 +1181,14 @@ msgid "Tasks"
 msgstr "Tâches"
 
 #: conditions/validators.py:14 domain/validators.py:15 options/validators.py:14
-#: options/validators.py:26 projects/forms.py:332 projects/forms.py:353
+#: options/validators.py:26 projects/forms.py:333 projects/forms.py:354
 #: questions/validators.py:16 questions/validators.py:28
 #: questions/validators.py:46 questions/validators.py:64 tasks/validators.py:14
 #: views/validators.py:14
 msgid "This field is required."
 msgstr "Ce champ est obligatoire."
 
-#: core/constants.py:14 core/settings.py:291
+#: core/constants.py:14 core/settings.py:294
 #: options/templates/options/options_export.html:39
 #: options/templates/options/options_modal_form_options.html:163
 #: projects/models/value.py:64 questions/constants.py:4
@@ -1251,83 +1250,83 @@ msgstr "Anglais"
 msgid "German"
 msgstr "Allemand"
 
-#: core/settings.py:234
+#: core/settings.py:237
 msgid "PDF"
 msgstr "PDF"
 
-#: core/settings.py:235
+#: core/settings.py:238
 msgid "Rich Text Format"
 msgstr "Rich Text Format"
 
-#: core/settings.py:236
+#: core/settings.py:239
 msgid "Open Office"
 msgstr "Open Office"
 
-#: core/settings.py:237
+#: core/settings.py:240
 msgid "Microsoft Office"
 msgstr "Microsoft Office"
 
-#: core/settings.py:238
+#: core/settings.py:241
 msgid "HTML"
 msgstr "HTML"
 
-#: core/settings.py:239
+#: core/settings.py:242
 msgid "Markdown"
 msgstr "Markdown"
 
-#: core/settings.py:240
+#: core/settings.py:243
 msgid "mediawiki"
 msgstr "mediawiki"
 
-#: core/settings.py:241
+#: core/settings.py:244
 msgid "LaTeX"
 msgstr "LaTeX"
 
-#: core/settings.py:261 core/settings.py:267
+#: core/settings.py:264 core/settings.py:270
 msgid "RDMO XML"
 msgstr "RDMO XML"
 
-#: core/settings.py:262 domain/templates/domain/domain.html:74
+#: core/settings.py:265 domain/templates/domain/domain.html:74
 msgid "CSV comma separated"
 msgstr "CSV séparées par des virgules"
 
-#: core/settings.py:263 domain/templates/domain/domain.html:79
+#: core/settings.py:266 domain/templates/domain/domain.html:79
 msgid "CSV semicolon separated"
 msgstr "CSV point-virgule séparé"
 
-#: core/settings.py:292 questions/constants.py:5
+#: core/settings.py:295 questions/constants.py:5
 msgid "Textarea"
 msgstr "Zone de texte"
 
-#: core/settings.py:293 questions/constants.py:6
+#: core/settings.py:296 questions/constants.py:6
 msgid "Yes/No"
 msgstr "Oui/Non"
 
-#: core/settings.py:294 questions/constants.py:7
+#: core/settings.py:297 questions/constants.py:7
 msgid "Checkboxes"
 msgstr "Cases à cocher"
 
-#: core/settings.py:295 questions/constants.py:8
+#: core/settings.py:298 questions/constants.py:8
 msgid "Radio buttons"
 msgstr "Boutons radio"
 
-#: core/settings.py:296 questions/constants.py:9
+#: core/settings.py:299 questions/constants.py:9
 msgid "Select drop-down"
 msgstr "Sélectionnez le menu déroulant"
 
-#: core/settings.py:297 questions/constants.py:10
+#: core/settings.py:300 questions/constants.py:10
 msgid "Autocomplete"
 msgstr "Autocomplétion"
 
-#: core/settings.py:298 questions/constants.py:11
+#: core/settings.py:301 questions/constants.py:11
 msgid "Range slider"
 msgstr "Curseur de gamme"
 
-#: core/settings.py:299 questions/constants.py:12
+#: core/settings.py:302 questions/constants.py:12
 msgid "Date picker"
 msgstr "Sélecteur de date"
 
-#: core/settings.py:300 questions/constants.py:13
+#: core/settings.py:303 questions/constants.py:13
 msgid "File upload"
 msgstr "Téléchargement de fichiers"
 
@@ -1386,7 +1385,7 @@ msgstr "Gestion"
 msgid "Domain"
 msgstr "Domaine"
 
-#: core/templates/core/base_navigation.html:61 projects/forms.py:308
+#: core/templates/core/base_navigation.html:61 projects/forms.py:309
 #: projects/models/project.py:64
 #: projects/templates/projects/project_detail_views.html:11 views/apps.py:7
 #: views/models.py:123 views/templates/views/views.html:91
@@ -1535,8 +1534,8 @@ msgstr "Supprimer l'attribut"
 msgid ""
 "You are about to permanently delete the attribute <code>%(object)s</code>."
 msgstr ""
-"Vous êtes sur le point de supprimer définitivement l'attribut "
-"<code>%(object)s</code>."
+"Vous êtes sur le point de supprimer définitivement l'attribut <code>"
+"%(object)s</code>."
 
 #: domain/templates/domain/domain_modal_delete_attributes.html:23
 msgid "This will also delete the following attributes:"
@@ -1546,8 +1545,8 @@ msgstr "Cela supprimera également les attributs suivants:"
 #: domain/templates/domain/domain_modal_show_attributes.html:15
 #, python-format
 msgid ""
-"This attribute is used for <b>%(values_count)s values</b> in "
-"<b>%(projects_count)s projects</b>."
+"This attribute is used for <b>%(values_count)s values</b> in <b>"
+"%(projects_count)s projects</b>."
 msgstr ""
 "Cet attribut est utilisé pour les valeurs de <b>%(values_count)s </b> dans "
 "<b>%(projects_count)s projets</b>."
@@ -1914,11 +1913,11 @@ msgstr ""
 #: options/templates/options/options_modal_show_options.html:15
 #, python-format
 msgid ""
-"This option is used for <b>%(values_count)s values</b> in "
-"<b>%(projects_count)s projects</b>."
+"This option is used for <b>%(values_count)s values</b> in <b>"
+"%(projects_count)s projects</b>."
 msgstr ""
-"Cette option est utilisée pour <b>%(values_count)s valeurs</b> dans "
-"<b>%(projects_count)s projets</b>."
+"Cette option est utilisée pour <b>%(values_count)s valeurs</b> dans <b>"
+"%(projects_count)s projets</b>."
 
 #: options/templates/options/options_modal_delete_options.html:29
 #, python-format
@@ -2064,7 +2063,7 @@ msgstr "Nom de l'URL"
 msgid "The url_name for this overlay."
 msgstr "Le nom de l'url pour cette superposition."
 
-#: overlays/models.py:26 projects/forms.py:320
+#: overlays/models.py:26 projects/forms.py:321
 #: projects/templates/projects/project_answers.html:13
 #: projects/templates/projects/project_view.html:28
 msgid "Current"
@@ -2138,11 +2137,11 @@ msgstr ""
 "directement des utilisateurs sans les avertir par courrier électronique, "
 "lorsque vous cochez la case suivante."
 
-#: projects/forms.py:202
+#: projects/forms.py:203
 msgid "The user is already a member of the project."
 msgstr "L'utilisateur est déjà membre du projet."
 
-#: projects/forms.py:214
+#: projects/forms.py:215
 msgid ""
 "A user with this username or e-mail was not found. Only registered users can "
 "be invited."
@@ -2150,51 +2149,51 @@ msgstr ""
 "Un utilisateur avec ce nom d'utilisateur ou ce courriel n'a pas été trouvé. "
 "Seuls les utilisateurs enregistrés peuvent être invités."
 
-#: projects/forms.py:218
+#: projects/forms.py:219
 msgid "Only existing users can be added silently."
 msgstr "Seuls les utilisateurs existants peuvent être ajoutés en silence."
 
-#: projects/forms.py:286 projects/forms.py:290
+#: projects/forms.py:287 projects/forms.py:291
 #, python-format
 msgid "Attach %s"
 msgstr "Joindre le %s"
 
-#: projects/forms.py:296
+#: projects/forms.py:297
 msgid "Subject"
 msgstr "Sujet"
 
-#: projects/forms.py:297
+#: projects/forms.py:298
 msgid "Message"
 msgstr "Message"
 
-#: projects/forms.py:304
+#: projects/forms.py:305
 msgid "Answers"
 msgstr "Afficher les réponses"
 
-#: projects/forms.py:305
+#: projects/forms.py:306
 msgid "Attach the output of \"View answers\"."
 msgstr "Joignez la sortie de \"Voir les réponses\"."
 
-#: projects/forms.py:312
+#: projects/forms.py:313
 msgid "Files"
 msgstr "Fichiers"
 
-#: projects/forms.py:319 projects/models/snapshot.py:33
+#: projects/forms.py:320 projects/models/snapshot.py:33
 #: projects/models/value.py:38
 #: projects/templates/projects/project_detail_snapshots.html:20
 #: projects/templates/projects/project_import.html:93
 msgid "Snapshot"
 msgstr "Instantané"
 
-#: projects/forms.py:323
+#: projects/forms.py:324
 msgid "Format"
 msgstr "Format"
 
-#: projects/forms.py:338 projects/forms.py:343
+#: projects/forms.py:339 projects/forms.py:344
 msgid "Recipients"
 msgstr "Recipients"
 
-#: projects/forms.py:344
+#: projects/forms.py:345
 msgid "Enter recipients line by line"
 msgstr "Indiquer les destinataires ligne par ligne"
 
@@ -2862,11 +2861,11 @@ msgstr "A qui cela peut concerner,"
 #: projects/templates/projects/issue_send_message.txt:3
 #, python-format
 msgid ""
-"The following task was identified in the project \"%(project_title)s\" "
-"<%(project_url)s>:"
+"The following task was identified in the project \"%(project_title)s\" <"
+"%(project_url)s>:"
 msgstr ""
-"La tâche suivante a été identifiée dans le projet \"%(project_title)s\" "
-"<%(project_url)s>:"
+"La tâche suivante a été identifiée dans le projet \"%(project_title)s\" <"
+"%(project_url)s>:"
 
 #: projects/templates/projects/issue_send_message.txt:13
 #, python-format
@@ -3038,7 +3037,7 @@ msgstr "Mettre à jour les informations du projet"
 msgid "No description available."
 msgstr "Pas de description disponible."
 
-#: projects/templates/projects/project_detail_header_hierarchy.html:6
+#: projects/templates/projects/project_detail_header_hierarchy.html:7
 #: projects/templates/projects/project_detail_sidebar.html:52
 msgid "Update parent project"
 msgstr "Mise à jour du projet parent"
@@ -3162,23 +3161,23 @@ msgid "Import values"
 msgstr "Valeurs d'importation"
 
 #: projects/templates/projects/project_detail_sidebar.html:131
-#: projects/templates/projects/projects.html:62
+#: projects/templates/projects/projects.html:69
 msgid "Import from file"
 msgstr "Importation à partir d'un fichier"
 
 #: projects/templates/projects/project_detail_sidebar.html:139
-#: projects/templates/projects/projects.html:71
-msgid "Import directly"
-msgstr "Importation directe"
-
-#: projects/templates/projects/project_detail_sidebar.html:155
 msgid "Import from parent project"
 msgstr "Importation du projet parent"
 
+#: projects/templates/projects/project_detail_sidebar.html:147
+#: projects/templates/projects/projects.html:78
+msgid "Import directly"
+msgstr "Importation directe"
+
 #: projects/templates/projects/project_detail_snapshots.html:22
 #: projects/templates/projects/project_import.html:34
 #: projects/templates/projects/project_import.html:104
-#: projects/templates/projects/site_projects.html:40
+#: projects/templates/projects/site_projects.html:50
 msgid "Created"
 msgstr "Créé"
 
@@ -3414,7 +3413,7 @@ msgid "Please select"
 msgstr "Veuillez sélectionner"
 
 #: projects/templates/projects/project_questions_head.html:5
-#: projects/templates/projects/projects.html:113
+#: projects/templates/projects/projects.html:120
 msgid "My Projects"
 msgstr "Mes projets"
 
@@ -3423,8 +3422,8 @@ msgstr "Mes projets"
 msgid ""
 "You are about to permanently delete the %(name)s <strong>%(object)s</strong>."
 msgstr ""
-"Vous êtes sur le point de supprimer définitivement les %(name)s "
-"<strong>%(object)s</strong>."
+"Vous êtes sur le point de supprimer définitivement les %(name)s <strong>"
+"%(object)s</strong>."
 
 #: projects/templates/projects/project_questions_modal_delete_valuesets.html:23
 #, python-format
@@ -3458,14 +3457,14 @@ msgstr ""
 
 #: projects/templates/projects/project_questions_navigation_help.html:14
 msgid ""
-"Entries with <i class=\"fa fa-question-circle-o small\" aria-"
-"hidden=\"true\"></i> might be skipped based on your input."
+"Entries with <i class=\"fa fa-question-circle-o small\" aria-hidden=\"true"
+"\"></i> might be skipped based on your input."
 msgstr ""
-"Les entrées avec <i class=\"fa fa-question-circle-o small\" aria-"
-"hidden=\"true\"></i> peuvent être ignorées en fonction de votre saisie."
+"Les entrées avec <i class=\"fa fa-question-circle-o small\" aria-hidden="
+"\"true\"></i> peuvent être ignorées en fonction de votre saisie."
 
 #: projects/templates/projects/project_questions_overview.html:14
-#: projects/templates/projects/site_projects.html:27
+#: projects/templates/projects/site_projects.html:37
 msgid "Back to my projects"
 msgstr "Retour à mes projets"
 
@@ -3530,7 +3529,7 @@ msgid "Navigation"
 msgstr "Navigation"
 
 #: projects/templates/projects/project_view.html:90
-#: projects/views/project.py:175
+#: projects/views/project.py:198
 msgid "Error"
 msgstr "Erreur"
 
@@ -3547,8 +3546,8 @@ msgstr "Voir tous les projets sur %(site)s"
 
 #: projects/templates/projects/projects.html:47
 #: projects/templates/projects/site_projects.html:16
-msgid "Search projects"
-msgstr "Recherche de projets"
+msgid "Filter projects"
+msgstr "Filtrer les projets"
 
 #: projects/templates/projects/projects.html:50
 #: projects/templates/projects/site_projects.html:19
@@ -3556,19 +3555,27 @@ msgid "Search project title"
 msgstr "Recherche de projets"
 
 #: projects/templates/projects/projects.html:57
+#: projects/templates/projects/site_projects.html:26
+#, python-format
+msgid ""
+"%(number_of_filtered_projects)s of %(number_of_projects)s projects shown"
+msgstr ""
+"%(number_of_filtered_projects)s des %(number_of_projects)s projets présentés"
+
+#: projects/templates/projects/projects.html:64
 msgid "Import existing project"
 msgstr "Importer un projet existant"
 
-#: projects/templates/projects/projects.html:88
+#: projects/templates/projects/projects.html:95
 msgid "Pending invitations"
 msgstr "Invitations en cours"
 
-#: projects/templates/projects/projects.html:104
+#: projects/templates/projects/projects.html:111
 msgid "Click on one of the links to join the projects."
 msgstr "Cliquez sur l'un des liens pour rejoindre les projets."
 
-#: projects/templates/projects/projects.html:120
-#: projects/templates/projects/site_projects.html:41
+#: projects/templates/projects/projects.html:127
+#: projects/templates/projects/site_projects.html:51
 msgid "Last changed"
 msgstr "Dernière modification"
 
@@ -3580,14 +3587,14 @@ msgstr "Précédent"
 #: projects/templates/projects/projects_pagination.html:12
 #, python-format
 msgid "Page %(number)s of %(num_pages)s."
-msgstr "Page %(number)s de %(num_pages)s."
+msgstr "Page %(number)s sur %(num_pages)s."
 
 #: projects/templates/projects/projects_pagination.html:17
 #: projects/templates/projects/projects_pagination.html:19
 msgid "Next"
 msgstr "Suivant"
 
-#: projects/templates/projects/site_projects.html:34
+#: projects/templates/projects/site_projects.html:44
 #, python-format
 msgid "All projects on %(site)s"
 msgstr "Tous les projets sur %(site)s"
@@ -3618,16 +3625,16 @@ msgid "You reached the file quota for this project."
 msgstr ""
 "Le titre de ce projet.Vous avez atteint le quota de dossiers pour ce projet."
 
-#: projects/views/project.py:155
+#: projects/views/project.py:178
 msgid "Sorry, your invitation has been expired."
 msgstr "Désolé, votre invitation a expiré."
 
-#: projects/views/project.py:158
+#: projects/views/project.py:181
 #, python-format
 msgid "Sorry, but this invitation is for the user \"%s\"."
 msgstr "Désolé, mais cette invitation est pour l'utilisateur \"%s\"."
 
-#: projects/views/project.py:172
+#: projects/views/project.py:195
 msgid "Sorry, the invitation link is not valid."
 msgstr "Désolé, le lien d'invitation n'est pas valide."
 
@@ -4356,11 +4363,11 @@ msgstr "En option"
 #: questions/templates/questions/catalogs_modal_delete_catalogs.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the catalog <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the catalog <code class=\"text-primary\">"
+"%(object)s</code>."
 msgstr ""
-"Vous êtes sur le point de supprimer définitivement le catalogue <code "
-"class=\"text-primary\">%(object)s</code>."
+"Vous êtes sur le point de supprimer définitivement le catalogue <code class="
+"\"text-primary\">%(object)s</code>."
 
 #: questions/templates/questions/catalogs_modal_delete_catalogs.html:24
 #, python-format
@@ -4381,11 +4388,11 @@ msgstr ""
 #: questions/templates/questions/catalogs_modal_delete_questions.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the question <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the question <code class=\"text-primary"
+"\">%(object)s</code>."
 msgstr ""
-"Vous êtes sur le point de supprimer définitivement la question <code "
-"class=\"text-primary\">%(object)s</code>."
+"Vous êtes sur le point de supprimer définitivement la question <code class="
+"\"text-primary\">%(object)s</code>."
 
 #: questions/templates/questions/catalogs_modal_delete_questionsets.html:17
 #, python-format
@@ -4399,11 +4406,11 @@ msgstr ""
 #: questions/templates/questions/catalogs_modal_delete_sections.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the section <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the section <code class=\"text-primary\">"
+"%(object)s</code>."
 msgstr ""
-"Vous êtes sur le point de supprimer définitivement la section <code "
-"class=\"text-primary\">%(object)s</code>."
+"Vous êtes sur le point de supprimer définitivement la section <code class="
+"\"text-primary\">%(object)s</code>."
 
 #: questions/templates/questions/catalogs_modal_form_catalogs.html:12
 msgid "Create catalog"
@@ -4910,8 +4917,8 @@ msgstr "tous"
 #, python-format
 msgid "You are about to permanently delete the task <code>%(object)s</code>."
 msgstr ""
-"Vous êtes sur le point de supprimer définitivement la tâche "
-"<code>%(object)s</code>."
+"Vous êtes sur le point de supprimer définitivement la tâche <code>"
+"%(object)s</code>."
 
 #: tasks/templates/tasks/tasks_modal_form_tasks.html:12
 msgid "Create task"
@@ -5118,6 +5125,9 @@ msgid "An empty list implies that the view can be used with every catalog."
 msgstr ""
 "Une liste vide implique que la vue peut être utilisée avec chaque catalogue."
 
+#~ msgid "Search projects"
+#~ msgstr "Recherche de projets"
+
 #~ msgid "Questionnaire"
 #~ msgstr "Question"
 
@@ -5381,8 +5391,8 @@ msgstr ""
 #~ msgid ""
 #~ "You are about to permanently delete the entity <code>%(object)s</code>."
 #~ msgstr ""
-#~ "Vous êtes sur le point de supprimer définitivement l'entité "
-#~ "<code>%(object)s</code>."
+#~ "Vous êtes sur le point de supprimer définitivement l'entité <code>"
+#~ "%(object)s</code>."
 
 #~ msgid "The prefix for the URI of this entity."
 #~ msgstr "Préfixe de l'URI de cette entité."
diff --git a/rdmo/locale/it/LC_MESSAGES/django.po b/rdmo/locale/it/LC_MESSAGES/django.po
index 4a0eeb69a8..2ff52688d3 100644
--- a/rdmo/locale/it/LC_MESSAGES/django.po
+++ b/rdmo/locale/it/LC_MESSAGES/django.po
@@ -2,7 +2,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-11-28 11:07+0100\n"
+"POT-Creation-Date: 2023-04-21 00:59+0200\n"
 "PO-Revision-Date: \n"
 "Last-Translator: Dario Pilori <d.pilori@inrim.it>\n"
 "Language-Team: \n"
@@ -205,7 +205,7 @@ msgstr "I siti dei quali l'utente è gestore."
 #: projects/models/membership.py:32
 #: projects/templates/projects/project_detail_invites.html:16
 #: projects/templates/projects/project_detail_memberships.html:21
-#: projects/templates/projects/projects.html:119
+#: projects/templates/projects/projects.html:126
 msgid "Role"
 msgstr "Ruolo"
 
@@ -321,14 +321,14 @@ msgstr "Conferma"
 #: accounts/templates/account/email_confirm.html:29
 #, python-format
 msgid ""
-"This e-mail confirmation link expired or is invalid. Please <a "
-"href=\"%(email_url)s\">issue a new e-mail confirmation request</a>."
+"This e-mail confirmation link expired or is invalid. Please <a href="
+"\"%(email_url)s\">issue a new e-mail confirmation request</a>."
 msgstr ""
-"Il link di conferma è scaduto. È possibile <a "
-"href=\"%(email_url)s\">chiedere una nuova e-mail di conferma</a>."
+"Il link di conferma è scaduto. È possibile <a href=\"%(email_url)s"
+"\">chiedere una nuova e-mail di conferma</a>."
 
 #: accounts/templates/account/login.html:6
-#: accounts/templates/account/login_form.html:20
+#: accounts/templates/account/login_form.html:22
 #: accounts/templates/account/password_reset_from_key_done.html:14
 #: core/templates/core/base_navigation.html:154
 #: core/templates/core/home.html:55
@@ -342,20 +342,20 @@ msgstr "Accesso con Shibboleth"
 #: accounts/templates/account/login_form_account.html:7
 #, python-format
 msgid ""
-"If you have not created an account yet, then please <a "
-"href=\"%(signup_url)s\">sign up</a> first."
+"If you have not created an account yet, then please <a href=\"%(signup_url)s"
+"\">sign up</a> first."
 msgstr ""
-"Se non hai ancora creato un account, devi prima <a "
-"href=\"%(signup_url)s\">registrarti</a>."
+"Se non hai ancora creato un account, devi prima <a href=\"%(signup_url)s"
+"\">registrarti</a>."
 
 #: accounts/templates/account/login_form_account.html:14
 #, python-format
 msgid ""
-"If you forgot your password and want to reset it, click <a "
-"href=\"%(reset_url)s\">here</a>."
+"If you forgot your password and want to reset it, click <a href="
+"\"%(reset_url)s\">here</a>."
 msgstr ""
-"Password dimenticata? Per reimpostarla, cliccare <a "
-"href=\"%(reset_url)s\">qui</a>."
+"Password dimenticata? Per reimpostarla, cliccare <a href=\"%(reset_url)s"
+"\">qui</a>."
 
 #: accounts/templates/account/login_form_socialaccount.html:9
 msgid ""
@@ -431,8 +431,8 @@ msgid ""
 "a>."
 msgstr ""
 "Il link di reimpostazione password è errato, probabilmente perché già "
-"utilizzato. È possibile richiedere una <a "
-"href=\"%(passwd_reset_url)s\">nuova reimpostazione</a>."
+"utilizzato. È possibile richiedere una <a href=\"%(passwd_reset_url)s"
+"\">nuova reimpostazione</a>."
 
 #: accounts/templates/account/password_reset_from_key.html:18
 msgid "Enter new password"
@@ -585,8 +585,8 @@ msgid ""
 "<strong>Note:</strong> you can still <a href=\"%(email_url)s\">change your e-"
 "mail address</a>."
 msgstr ""
-"<strong>Attenzione:</strong> è ancora possibile <a "
-"href=\"%(email_url)s\">cambiare il proprio indirizzo e-mail</a>."
+"<strong>Attenzione:</strong> è ancora possibile <a href=\"%(email_url)s"
+"\">cambiare il proprio indirizzo e-mail</a>."
 
 #: accounts/templates/profile/profile_remove_closed.html:6
 #: accounts/templates/profile/profile_remove_failed.html:6
@@ -653,8 +653,8 @@ msgid ""
 msgstr ""
 "Inserire le informazioni aggiornate del proprio account. La password può "
 "essere cambiata con l'apposito <a href=\"%(password_url)s\">modulo</a>. Per "
-"modificare il proprio indirizzo e-mail, visitare <a "
-"href=\"%(email_url)s\">questa pagina</a>."
+"modificare il proprio indirizzo e-mail, visitare <a href=\"%(email_url)s"
+"\">questa pagina</a>."
 
 #: accounts/templates/profile/profile_update_form.html:33
 msgid ""
@@ -708,8 +708,8 @@ msgstr "Accesso annullato"
 #, python-format
 msgid ""
 "You decided to cancel logging in to our site using one of your existing "
-"accounts. If this was a mistake, please proceed to <a "
-"href=\"%(login_url)s\">sign in</a>."
+"accounts. If this was a mistake, please proceed to <a href=\"%(login_url)s"
+"\">sign in</a>."
 msgstr ""
 "L'accesso a questo sito mediante un altro account esistente è stato "
 "interrotto. In caso di errore, è possibile <a href=\"%(login_url)s\">entrare "
@@ -725,8 +725,8 @@ msgstr ""
 "%(site_name)s. Per completare la connessione, compilare il seguente modulo:"
 
 #: accounts/templatetags/accounts_tags.py:19
-#: projects/templates/projects/projects.html:118
-#: projects/templates/projects/site_projects.html:39
+#: projects/templates/projects/projects.html:125
+#: projects/templates/projects/site_projects.html:49
 msgid "Name"
 msgstr "Nome"
 
@@ -918,7 +918,7 @@ msgstr "Tutti i prefissi dell'URI"
 #: projects/templates/projects/project_detail_sidebar.html:11
 #: projects/templates/projects/project_view.html:42
 #: projects/templates/projects/projects.html:25
-#: projects/templates/projects/site_projects.html:23
+#: projects/templates/projects/site_projects.html:33
 #: questions/templates/questions/catalog_tree.html:104
 #: questions/templates/questions/catalogs_sidebar.html:78
 #: questions/templates/questions/catalogs_sidebar_showuri_checkboxes.html:26
@@ -1152,14 +1152,14 @@ msgid "Tasks"
 msgstr "Attività"
 
 #: conditions/validators.py:14 domain/validators.py:15 options/validators.py:14
-#: options/validators.py:26 projects/forms.py:332 projects/forms.py:353
+#: options/validators.py:26 projects/forms.py:333 projects/forms.py:354
 #: questions/validators.py:16 questions/validators.py:28
 #: questions/validators.py:46 questions/validators.py:64 tasks/validators.py:14
 #: views/validators.py:14
 msgid "This field is required."
 msgstr "Questo campo è richiesto."
 
-#: core/constants.py:14 core/settings.py:291
+#: core/constants.py:14 core/settings.py:294
 #: options/templates/options/options_export.html:39
 #: options/templates/options/options_modal_form_options.html:163
 #: projects/models/value.py:64 questions/constants.py:4
@@ -1221,83 +1221,83 @@ msgstr "Inglese"
 msgid "German"
 msgstr "Tedesco"
 
-#: core/settings.py:234
+#: core/settings.py:237
 msgid "PDF"
 msgstr "PDF"
 
-#: core/settings.py:235
+#: core/settings.py:238
 msgid "Rich Text Format"
 msgstr "Rich Text Format"
 
-#: core/settings.py:236
+#: core/settings.py:239
 msgid "Open Office"
 msgstr "Open Office"
 
-#: core/settings.py:237
+#: core/settings.py:240
 msgid "Microsoft Office"
 msgstr "Microsoft Office"
 
-#: core/settings.py:238
+#: core/settings.py:241
 msgid "HTML"
 msgstr "HTML"
 
-#: core/settings.py:239
+#: core/settings.py:242
 msgid "Markdown"
 msgstr "Markdown"
 
-#: core/settings.py:240
+#: core/settings.py:243
 msgid "mediawiki"
 msgstr "mediawiki"
 
-#: core/settings.py:241
+#: core/settings.py:244
 msgid "LaTeX"
 msgstr "LaTeX"
 
-#: core/settings.py:261 core/settings.py:267
+#: core/settings.py:264 core/settings.py:270
 msgid "RDMO XML"
 msgstr "RDMO XML"
 
-#: core/settings.py:262 domain/templates/domain/domain.html:74
+#: core/settings.py:265 domain/templates/domain/domain.html:74
 msgid "CSV comma separated"
 msgstr "CSV separato da virgole"
 
-#: core/settings.py:263 domain/templates/domain/domain.html:79
+#: core/settings.py:266 domain/templates/domain/domain.html:79
 msgid "CSV semicolon separated"
 msgstr "CSV separato da punto e virgola"
 
-#: core/settings.py:292 questions/constants.py:5
+#: core/settings.py:295 questions/constants.py:5
 msgid "Textarea"
 msgstr "Textarea"
 
-#: core/settings.py:293 questions/constants.py:6
+#: core/settings.py:296 questions/constants.py:6
 msgid "Yes/No"
 msgstr "Sì/No"
 
-#: core/settings.py:294 questions/constants.py:7
+#: core/settings.py:297 questions/constants.py:7
 msgid "Checkboxes"
 msgstr "Caselle di controllo"
 
-#: core/settings.py:295 questions/constants.py:8
+#: core/settings.py:298 questions/constants.py:8
 msgid "Radio buttons"
 msgstr "Pulsanti radio"
 
-#: core/settings.py:296 questions/constants.py:9
+#: core/settings.py:299 questions/constants.py:9
 msgid "Select drop-down"
 msgstr "Seleziona il menu a tendina"
 
-#: core/settings.py:297 questions/constants.py:10
+#: core/settings.py:300 questions/constants.py:10
 msgid "Autocomplete"
 msgstr "Autocompletamento"
 
-#: core/settings.py:298 questions/constants.py:11
+#: core/settings.py:301 questions/constants.py:11
 msgid "Range slider"
 msgstr "Cursore di gamma"
 
-#: core/settings.py:299 questions/constants.py:12
+#: core/settings.py:302 questions/constants.py:12
 msgid "Date picker"
 msgstr "Raccoglitore di date"
 
-#: core/settings.py:300 questions/constants.py:13
+#: core/settings.py:303 questions/constants.py:13
 msgid "File upload"
 msgstr "Caricamento dei file"
 
@@ -1356,7 +1356,7 @@ msgstr "Gestione"
 msgid "Domain"
 msgstr "Dominio"
 
-#: core/templates/core/base_navigation.html:61 projects/forms.py:308
+#: core/templates/core/base_navigation.html:61 projects/forms.py:309
 #: projects/models/project.py:64
 #: projects/templates/projects/project_detail_views.html:11 views/apps.py:7
 #: views/models.py:123 views/templates/views/views.html:91
@@ -1514,11 +1514,11 @@ msgstr "Verranno eliminati anche i seguenti attributi:"
 #: domain/templates/domain/domain_modal_show_attributes.html:15
 #, python-format
 msgid ""
-"This attribute is used for <b>%(values_count)s values</b> in "
-"<b>%(projects_count)s projects</b>."
+"This attribute is used for <b>%(values_count)s values</b> in <b>"
+"%(projects_count)s projects</b>."
 msgstr ""
-"Questo attributo è usato per <b>%(values_count)s valori</b> in "
-"<b>%(projects_count)s progetti</b>."
+"Questo attributo è usato per <b>%(values_count)s valori</b> in <b>"
+"%(projects_count)s progetti</b>."
 
 #: domain/templates/domain/domain_modal_delete_attributes.html:42
 #: domain/templates/domain/domain_modal_show_attributes.html:18
@@ -1879,11 +1879,11 @@ msgstr ""
 #: options/templates/options/options_modal_show_options.html:15
 #, python-format
 msgid ""
-"This option is used for <b>%(values_count)s values</b> in "
-"<b>%(projects_count)s projects</b>."
+"This option is used for <b>%(values_count)s values</b> in <b>"
+"%(projects_count)s projects</b>."
 msgstr ""
-"Questa opzione è usata per <b>%(values_count)s valori</b> in "
-"<b>%(projects_count)s progetti</b>."
+"Questa opzione è usata per <b>%(values_count)s valori</b> in <b>"
+"%(projects_count)s progetti</b>."
 
 #: options/templates/options/options_modal_delete_options.html:29
 #, python-format
@@ -2024,7 +2024,7 @@ msgstr "Nome Url"
 msgid "The url_name for this overlay."
 msgstr "Il nome dell'url per questo sovrapposto."
 
-#: overlays/models.py:26 projects/forms.py:320
+#: overlays/models.py:26 projects/forms.py:321
 #: projects/templates/projects/project_answers.html:13
 #: projects/templates/projects/project_view.html:28
 msgid "Current"
@@ -2097,11 +2097,11 @@ msgstr ""
 "utenti senza notificarli via e-mail, quando selezioni la seguente casella di "
 "controllo."
 
-#: projects/forms.py:202
+#: projects/forms.py:203
 msgid "The user is already a member of the project."
 msgstr "L'utente è già membro del progetto."
 
-#: projects/forms.py:214
+#: projects/forms.py:215
 msgid ""
 "A user with this username or e-mail was not found. Only registered users can "
 "be invited."
@@ -2109,51 +2109,51 @@ msgstr ""
 "Un utente con questo nome utente o e-mail non è stato trovato. Solo gli "
 "utenti registrati possono essere invitati."
 
-#: projects/forms.py:218
+#: projects/forms.py:219
 msgid "Only existing users can be added silently."
 msgstr "Solo gli utenti esistenti possono essere aggiunti silenziosamente."
 
-#: projects/forms.py:286 projects/forms.py:290
+#: projects/forms.py:287 projects/forms.py:291
 #, python-format
 msgid "Attach %s"
 msgstr "Allega %s"
 
-#: projects/forms.py:296
+#: projects/forms.py:297
 msgid "Subject"
 msgstr "Oggetto"
 
-#: projects/forms.py:297
+#: projects/forms.py:298
 msgid "Message"
 msgstr "Messaggio"
 
-#: projects/forms.py:304
+#: projects/forms.py:305
 msgid "Answers"
 msgstr "Risposte"
 
-#: projects/forms.py:305
+#: projects/forms.py:306
 msgid "Attach the output of \"View answers\"."
 msgstr "Allega il risultato di \"Visualizza risposte\"."
 
-#: projects/forms.py:312
+#: projects/forms.py:313
 msgid "Files"
 msgstr "Files"
 
-#: projects/forms.py:319 projects/models/snapshot.py:33
+#: projects/forms.py:320 projects/models/snapshot.py:33
 #: projects/models/value.py:38
 #: projects/templates/projects/project_detail_snapshots.html:20
 #: projects/templates/projects/project_import.html:93
 msgid "Snapshot"
 msgstr "Istantanea"
 
-#: projects/forms.py:323
+#: projects/forms.py:324
 msgid "Format"
 msgstr "Formato"
 
-#: projects/forms.py:338 projects/forms.py:343
+#: projects/forms.py:339 projects/forms.py:344
 msgid "Recipients"
 msgstr "Destinatari"
 
-#: projects/forms.py:344
+#: projects/forms.py:345
 msgid "Enter recipients line by line"
 msgstr "Inserire i destinatari linea per linea"
 
@@ -2813,8 +2813,8 @@ msgstr "A chi possa interessare,"
 #: projects/templates/projects/issue_send_message.txt:3
 #, python-format
 msgid ""
-"The following task was identified in the project \"%(project_title)s\" "
-"<%(project_url)s>:"
+"The following task was identified in the project \"%(project_title)s\" <"
+"%(project_url)s>:"
 msgstr ""
 "La seguente attività è stata identificata nel progetto \"%(project_title)s\" "
 "<%(project_url)s>:"
@@ -2985,7 +2985,7 @@ msgstr "Aggiorna informazioni sul progetto"
 msgid "No description available."
 msgstr "Nessuna descrizione disponibile."
 
-#: projects/templates/projects/project_detail_header_hierarchy.html:6
+#: projects/templates/projects/project_detail_header_hierarchy.html:7
 #: projects/templates/projects/project_detail_sidebar.html:52
 msgid "Update parent project"
 msgstr "Aggiornare il progetto padre"
@@ -3108,23 +3108,23 @@ msgid "Import values"
 msgstr "Valori di importazione"
 
 #: projects/templates/projects/project_detail_sidebar.html:131
-#: projects/templates/projects/projects.html:62
+#: projects/templates/projects/projects.html:69
 msgid "Import from file"
 msgstr "Importazione da file"
 
 #: projects/templates/projects/project_detail_sidebar.html:139
-#: projects/templates/projects/projects.html:71
-msgid "Import directly"
-msgstr "Importare direttamente"
-
-#: projects/templates/projects/project_detail_sidebar.html:155
 msgid "Import from parent project"
 msgstr "Importazione dal progetto padre"
 
+#: projects/templates/projects/project_detail_sidebar.html:147
+#: projects/templates/projects/projects.html:78
+msgid "Import directly"
+msgstr "Importare direttamente"
+
 #: projects/templates/projects/project_detail_snapshots.html:22
 #: projects/templates/projects/project_import.html:34
 #: projects/templates/projects/project_import.html:104
-#: projects/templates/projects/site_projects.html:40
+#: projects/templates/projects/site_projects.html:50
 msgid "Created"
 msgstr "Creato"
 
@@ -3358,7 +3358,7 @@ msgid "Please select"
 msgstr "Selezionare"
 
 #: projects/templates/projects/project_questions_head.html:5
-#: projects/templates/projects/projects.html:113
+#: projects/templates/projects/projects.html:120
 msgid "My Projects"
 msgstr "I miei progetti"
 
@@ -3401,14 +3401,14 @@ msgstr ""
 
 #: projects/templates/projects/project_questions_navigation_help.html:14
 msgid ""
-"Entries with <i class=\"fa fa-question-circle-o small\" aria-"
-"hidden=\"true\"></i> might be skipped based on your input."
+"Entries with <i class=\"fa fa-question-circle-o small\" aria-hidden=\"true"
+"\"></i> might be skipped based on your input."
 msgstr ""
 "Le voci con <i class=\"fa-question-circle-o small\" aria-hidden=\"true\"></"
 "i> potrebbero essere saltate in base al tuo input."
 
 #: projects/templates/projects/project_questions_overview.html:14
-#: projects/templates/projects/site_projects.html:27
+#: projects/templates/projects/site_projects.html:37
 msgid "Back to my projects"
 msgstr "Torna ai miei progetti"
 
@@ -3471,7 +3471,7 @@ msgid "Navigation"
 msgstr "Navigazione"
 
 #: projects/templates/projects/project_view.html:90
-#: projects/views/project.py:175
+#: projects/views/project.py:198
 msgid "Error"
 msgstr "Errore"
 
@@ -3487,8 +3487,8 @@ msgstr "Visualizza tutti i progetti in %(site)s"
 
 #: projects/templates/projects/projects.html:47
 #: projects/templates/projects/site_projects.html:16
-msgid "Search projects"
-msgstr "Ricerca progetti"
+msgid "Filter projects"
+msgstr "Filtra i progetti"
 
 #: projects/templates/projects/projects.html:50
 #: projects/templates/projects/site_projects.html:19
@@ -3496,19 +3496,27 @@ msgid "Search project title"
 msgstr "Ricerca titolo del progetto"
 
 #: projects/templates/projects/projects.html:57
+#: projects/templates/projects/site_projects.html:26
+#, python-format
+msgid ""
+"%(number_of_filtered_projects)s of %(number_of_projects)s projects shown"
+msgstr ""
+"%(number_of_filtered_projects)s di %(number_of_projects)s progetti mostrati"
+
+#: projects/templates/projects/projects.html:64
 msgid "Import existing project"
 msgstr "Importazione di un progetto esistente"
 
-#: projects/templates/projects/projects.html:88
+#: projects/templates/projects/projects.html:95
 msgid "Pending invitations"
 msgstr "Inviti in sospeso"
 
-#: projects/templates/projects/projects.html:104
+#: projects/templates/projects/projects.html:111
 msgid "Click on one of the links to join the projects."
 msgstr "Clicca su uno dei link per unirti ai progetti."
 
-#: projects/templates/projects/projects.html:120
-#: projects/templates/projects/site_projects.html:41
+#: projects/templates/projects/projects.html:127
+#: projects/templates/projects/site_projects.html:51
 msgid "Last changed"
 msgstr "Ultima modifica"
 
@@ -3527,7 +3535,7 @@ msgstr "Pagina %(number)s di %(num_pages)s."
 msgid "Next"
 msgstr "Successivo"
 
-#: projects/templates/projects/site_projects.html:34
+#: projects/templates/projects/site_projects.html:44
 #, python-format
 msgid "All projects on %(site)s"
 msgstr "Tutti i progetti su %(site)s"
@@ -3557,16 +3565,16 @@ msgstr "Raccolta"
 msgid "You reached the file quota for this project."
 msgstr "Hai raggiunto la quota di file per questo progetto."
 
-#: projects/views/project.py:155
+#: projects/views/project.py:178
 msgid "Sorry, your invitation has been expired."
 msgstr "Spiacente, il tuo invito è scaduto."
 
-#: projects/views/project.py:158
+#: projects/views/project.py:181
 #, python-format
 msgid "Sorry, but this invitation is for the user \"%s\"."
 msgstr "Spiacente, ma questo invito è per l'utente \"%s\"."
 
-#: projects/views/project.py:172
+#: projects/views/project.py:195
 msgid "Sorry, the invitation link is not valid."
 msgstr "Spiacente, il link di invito non è valido."
 
@@ -4288,8 +4296,8 @@ msgstr "Opzionale"
 #: questions/templates/questions/catalogs_modal_delete_catalogs.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the catalog <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the catalog <code class=\"text-primary\">"
+"%(object)s</code>."
 msgstr ""
 "Il catalogo <code class=\"text-primary\">%(object)s</code> sta per essere "
 "eliminato permanentemente."
@@ -4313,8 +4321,8 @@ msgstr ""
 #: questions/templates/questions/catalogs_modal_delete_questions.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the question <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the question <code class=\"text-primary"
+"\">%(object)s</code>."
 msgstr ""
 "La domanda <code class=\"text-primary\">%(object)s</code> sta per essere "
 "eliminata permanentemente."
@@ -4331,8 +4339,8 @@ msgstr ""
 #: questions/templates/questions/catalogs_modal_delete_sections.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the section <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the section <code class=\"text-primary\">"
+"%(object)s</code>."
 msgstr ""
 "La sezione <code class=\"text-primary\">%(object)s</code> sta per essere "
 "eliminata permanentemente."
@@ -5045,6 +5053,9 @@ msgstr ""
 "Una lista vuota significa che questa vista può essere usata con tutti i "
 "cataloghi."
 
+#~ msgid "Search projects"
+#~ msgstr "Ricerca progetti"
+
 #~ msgid "Questionnaire"
 #~ msgstr "Questionario"
 
diff --git a/rdmo/locale/nl/LC_MESSAGES/django.po b/rdmo/locale/nl/LC_MESSAGES/django.po
index d66e9f541e..bc925dcba3 100644
--- a/rdmo/locale/nl/LC_MESSAGES/django.po
+++ b/rdmo/locale/nl/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-11-28 11:07+0100\n"
+"POT-Creation-Date: 2023-04-21 00:59+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -202,7 +202,7 @@ msgstr ""
 #: projects/models/membership.py:32
 #: projects/templates/projects/project_detail_invites.html:16
 #: projects/templates/projects/project_detail_memberships.html:21
-#: projects/templates/projects/projects.html:119
+#: projects/templates/projects/projects.html:126
 msgid "Role"
 msgstr ""
 
@@ -311,12 +311,12 @@ msgstr ""
 #: accounts/templates/account/email_confirm.html:29
 #, python-format
 msgid ""
-"This e-mail confirmation link expired or is invalid. Please <a "
-"href=\"%(email_url)s\">issue a new e-mail confirmation request</a>."
+"This e-mail confirmation link expired or is invalid. Please <a href="
+"\"%(email_url)s\">issue a new e-mail confirmation request</a>."
 msgstr ""
 
 #: accounts/templates/account/login.html:6
-#: accounts/templates/account/login_form.html:20
+#: accounts/templates/account/login_form.html:22
 #: accounts/templates/account/password_reset_from_key_done.html:14
 #: core/templates/core/base_navigation.html:154
 #: core/templates/core/home.html:55
@@ -330,15 +330,15 @@ msgstr ""
 #: accounts/templates/account/login_form_account.html:7
 #, python-format
 msgid ""
-"If you have not created an account yet, then please <a "
-"href=\"%(signup_url)s\">sign up</a> first."
+"If you have not created an account yet, then please <a href=\"%(signup_url)s"
+"\">sign up</a> first."
 msgstr ""
 
 #: accounts/templates/account/login_form_account.html:14
 #, python-format
 msgid ""
-"If you forgot your password and want to reset it, click <a "
-"href=\"%(reset_url)s\">here</a>."
+"If you forgot your password and want to reset it, click <a href="
+"\"%(reset_url)s\">here</a>."
 msgstr ""
 
 #: accounts/templates/account/login_form_socialaccount.html:9
@@ -664,8 +664,8 @@ msgstr ""
 #, python-format
 msgid ""
 "You decided to cancel logging in to our site using one of your existing "
-"accounts. If this was a mistake, please proceed to <a "
-"href=\"%(login_url)s\">sign in</a>."
+"accounts. If this was a mistake, please proceed to <a href=\"%(login_url)s"
+"\">sign in</a>."
 msgstr ""
 
 #: accounts/templates/socialaccount/signup.html:9
@@ -676,8 +676,8 @@ msgid ""
 msgstr ""
 
 #: accounts/templatetags/accounts_tags.py:19
-#: projects/templates/projects/projects.html:118
-#: projects/templates/projects/site_projects.html:39
+#: projects/templates/projects/projects.html:125
+#: projects/templates/projects/site_projects.html:49
 msgid "Name"
 msgstr ""
 
@@ -863,7 +863,7 @@ msgstr ""
 #: projects/templates/projects/project_detail_sidebar.html:11
 #: projects/templates/projects/project_view.html:42
 #: projects/templates/projects/projects.html:25
-#: projects/templates/projects/site_projects.html:23
+#: projects/templates/projects/site_projects.html:33
 #: questions/templates/questions/catalog_tree.html:104
 #: questions/templates/questions/catalogs_sidebar.html:78
 #: questions/templates/questions/catalogs_sidebar_showuri_checkboxes.html:26
@@ -1092,14 +1092,14 @@ msgid "Tasks"
 msgstr ""
 
 #: conditions/validators.py:14 domain/validators.py:15 options/validators.py:14
-#: options/validators.py:26 projects/forms.py:332 projects/forms.py:353
+#: options/validators.py:26 projects/forms.py:333 projects/forms.py:354
 #: questions/validators.py:16 questions/validators.py:28
 #: questions/validators.py:46 questions/validators.py:64 tasks/validators.py:14
 #: views/validators.py:14
 msgid "This field is required."
 msgstr ""
 
-#: core/constants.py:14 core/settings.py:291
+#: core/constants.py:14 core/settings.py:294
 #: options/templates/options/options_export.html:39
 #: options/templates/options/options_modal_form_options.html:163
 #: projects/models/value.py:64 questions/constants.py:4
@@ -1161,83 +1161,83 @@ msgstr ""
 msgid "German"
 msgstr ""
 
-#: core/settings.py:234
+#: core/settings.py:237
 msgid "PDF"
 msgstr ""
 
-#: core/settings.py:235
+#: core/settings.py:238
 msgid "Rich Text Format"
 msgstr ""
 
-#: core/settings.py:236
+#: core/settings.py:239
 msgid "Open Office"
 msgstr ""
 
-#: core/settings.py:237
+#: core/settings.py:240
 msgid "Microsoft Office"
 msgstr ""
 
-#: core/settings.py:238
+#: core/settings.py:241
 msgid "HTML"
 msgstr ""
 
-#: core/settings.py:239
+#: core/settings.py:242
 msgid "Markdown"
 msgstr ""
 
-#: core/settings.py:240
+#: core/settings.py:243
 msgid "mediawiki"
 msgstr ""
 
-#: core/settings.py:241
+#: core/settings.py:244
 msgid "LaTeX"
 msgstr ""
 
-#: core/settings.py:261 core/settings.py:267
+#: core/settings.py:264 core/settings.py:270
 msgid "RDMO XML"
 msgstr ""
 
-#: core/settings.py:262 domain/templates/domain/domain.html:74
+#: core/settings.py:265 domain/templates/domain/domain.html:74
 msgid "CSV comma separated"
 msgstr ""
 
-#: core/settings.py:263 domain/templates/domain/domain.html:79
+#: core/settings.py:266 domain/templates/domain/domain.html:79
 msgid "CSV semicolon separated"
 msgstr ""
 
-#: core/settings.py:292 questions/constants.py:5
+#: core/settings.py:295 questions/constants.py:5
 msgid "Textarea"
 msgstr ""
 
-#: core/settings.py:293 questions/constants.py:6
+#: core/settings.py:296 questions/constants.py:6
 msgid "Yes/No"
 msgstr ""
 
-#: core/settings.py:294 questions/constants.py:7
+#: core/settings.py:297 questions/constants.py:7
 msgid "Checkboxes"
 msgstr ""
 
-#: core/settings.py:295 questions/constants.py:8
+#: core/settings.py:298 questions/constants.py:8
 msgid "Radio buttons"
 msgstr ""
 
-#: core/settings.py:296 questions/constants.py:9
+#: core/settings.py:299 questions/constants.py:9
 msgid "Select drop-down"
 msgstr ""
 
-#: core/settings.py:297 questions/constants.py:10
+#: core/settings.py:300 questions/constants.py:10
 msgid "Autocomplete"
 msgstr ""
 
-#: core/settings.py:298 questions/constants.py:11
+#: core/settings.py:301 questions/constants.py:11
 msgid "Range slider"
 msgstr ""
 
-#: core/settings.py:299 questions/constants.py:12
+#: core/settings.py:302 questions/constants.py:12
 msgid "Date picker"
 msgstr ""
 
-#: core/settings.py:300 questions/constants.py:13
+#: core/settings.py:303 questions/constants.py:13
 msgid "File upload"
 msgstr ""
 
@@ -1296,7 +1296,7 @@ msgstr ""
 msgid "Domain"
 msgstr ""
 
-#: core/templates/core/base_navigation.html:61 projects/forms.py:308
+#: core/templates/core/base_navigation.html:61 projects/forms.py:309
 #: projects/models/project.py:64
 #: projects/templates/projects/project_detail_views.html:11 views/apps.py:7
 #: views/models.py:123 views/templates/views/views.html:91
@@ -1450,8 +1450,8 @@ msgstr ""
 #: domain/templates/domain/domain_modal_show_attributes.html:15
 #, python-format
 msgid ""
-"This attribute is used for <b>%(values_count)s values</b> in "
-"<b>%(projects_count)s projects</b>."
+"This attribute is used for <b>%(values_count)s values</b> in <b>"
+"%(projects_count)s projects</b>."
 msgstr ""
 
 #: domain/templates/domain/domain_modal_delete_attributes.html:42
@@ -1794,8 +1794,8 @@ msgstr ""
 #: options/templates/options/options_modal_show_options.html:15
 #, python-format
 msgid ""
-"This option is used for <b>%(values_count)s values</b> in "
-"<b>%(projects_count)s projects</b>."
+"This option is used for <b>%(values_count)s values</b> in <b>"
+"%(projects_count)s projects</b>."
 msgstr ""
 
 #: options/templates/options/options_modal_delete_options.html:29
@@ -1922,7 +1922,7 @@ msgstr ""
 msgid "The url_name for this overlay."
 msgstr ""
 
-#: overlays/models.py:26 projects/forms.py:320
+#: overlays/models.py:26 projects/forms.py:321
 #: projects/templates/projects/project_answers.html:13
 #: projects/templates/projects/project_view.html:28
 msgid "Current"
@@ -1989,61 +1989,61 @@ msgid ""
 "via e-mail, when you check the following checkbox."
 msgstr ""
 
-#: projects/forms.py:202
+#: projects/forms.py:203
 msgid "The user is already a member of the project."
 msgstr ""
 
-#: projects/forms.py:214
+#: projects/forms.py:215
 msgid ""
 "A user with this username or e-mail was not found. Only registered users can "
 "be invited."
 msgstr ""
 
-#: projects/forms.py:218
+#: projects/forms.py:219
 msgid "Only existing users can be added silently."
 msgstr ""
 
-#: projects/forms.py:286 projects/forms.py:290
+#: projects/forms.py:287 projects/forms.py:291
 #, python-format
 msgid "Attach %s"
 msgstr ""
 
-#: projects/forms.py:296
+#: projects/forms.py:297
 msgid "Subject"
 msgstr ""
 
-#: projects/forms.py:297
+#: projects/forms.py:298
 msgid "Message"
 msgstr ""
 
-#: projects/forms.py:304
+#: projects/forms.py:305
 msgid "Answers"
 msgstr ""
 
-#: projects/forms.py:305
+#: projects/forms.py:306
 msgid "Attach the output of \"View answers\"."
 msgstr ""
 
-#: projects/forms.py:312
+#: projects/forms.py:313
 msgid "Files"
 msgstr ""
 
-#: projects/forms.py:319 projects/models/snapshot.py:33
+#: projects/forms.py:320 projects/models/snapshot.py:33
 #: projects/models/value.py:38
 #: projects/templates/projects/project_detail_snapshots.html:20
 #: projects/templates/projects/project_import.html:93
 msgid "Snapshot"
 msgstr ""
 
-#: projects/forms.py:323
+#: projects/forms.py:324
 msgid "Format"
 msgstr ""
 
-#: projects/forms.py:338 projects/forms.py:343
+#: projects/forms.py:339 projects/forms.py:344
 msgid "Recipients"
 msgstr ""
 
-#: projects/forms.py:344
+#: projects/forms.py:345
 msgid "Enter recipients line by line"
 msgstr ""
 
@@ -2682,8 +2682,8 @@ msgstr ""
 #: projects/templates/projects/issue_send_message.txt:3
 #, python-format
 msgid ""
-"The following task was identified in the project \"%(project_title)s\" "
-"<%(project_url)s>:"
+"The following task was identified in the project \"%(project_title)s\" <"
+"%(project_url)s>:"
 msgstr ""
 
 #: projects/templates/projects/issue_send_message.txt:13
@@ -2834,7 +2834,7 @@ msgstr ""
 msgid "No description available."
 msgstr ""
 
-#: projects/templates/projects/project_detail_header_hierarchy.html:6
+#: projects/templates/projects/project_detail_header_hierarchy.html:7
 #: projects/templates/projects/project_detail_sidebar.html:52
 msgid "Update parent project"
 msgstr ""
@@ -2948,23 +2948,23 @@ msgid "Import values"
 msgstr ""
 
 #: projects/templates/projects/project_detail_sidebar.html:131
-#: projects/templates/projects/projects.html:62
+#: projects/templates/projects/projects.html:69
 msgid "Import from file"
 msgstr ""
 
 #: projects/templates/projects/project_detail_sidebar.html:139
-#: projects/templates/projects/projects.html:71
-msgid "Import directly"
+msgid "Import from parent project"
 msgstr ""
 
-#: projects/templates/projects/project_detail_sidebar.html:155
-msgid "Import from parent project"
+#: projects/templates/projects/project_detail_sidebar.html:147
+#: projects/templates/projects/projects.html:78
+msgid "Import directly"
 msgstr ""
 
 #: projects/templates/projects/project_detail_snapshots.html:22
 #: projects/templates/projects/project_import.html:34
 #: projects/templates/projects/project_import.html:104
-#: projects/templates/projects/site_projects.html:40
+#: projects/templates/projects/site_projects.html:50
 msgid "Created"
 msgstr ""
 
@@ -3180,7 +3180,7 @@ msgid "Please select"
 msgstr ""
 
 #: projects/templates/projects/project_questions_head.html:5
-#: projects/templates/projects/projects.html:113
+#: projects/templates/projects/projects.html:120
 msgid "My Projects"
 msgstr ""
 
@@ -3217,12 +3217,12 @@ msgstr ""
 
 #: projects/templates/projects/project_questions_navigation_help.html:14
 msgid ""
-"Entries with <i class=\"fa fa-question-circle-o small\" aria-"
-"hidden=\"true\"></i> might be skipped based on your input."
+"Entries with <i class=\"fa fa-question-circle-o small\" aria-hidden=\"true"
+"\"></i> might be skipped based on your input."
 msgstr ""
 
 #: projects/templates/projects/project_questions_overview.html:14
-#: projects/templates/projects/site_projects.html:27
+#: projects/templates/projects/site_projects.html:37
 msgid "Back to my projects"
 msgstr ""
 
@@ -3277,7 +3277,7 @@ msgid "Navigation"
 msgstr ""
 
 #: projects/templates/projects/project_view.html:90
-#: projects/views/project.py:175
+#: projects/views/project.py:198
 msgid "Error"
 msgstr ""
 
@@ -3292,7 +3292,7 @@ msgstr ""
 
 #: projects/templates/projects/projects.html:47
 #: projects/templates/projects/site_projects.html:16
-msgid "Search projects"
+msgid "Filter projects"
 msgstr ""
 
 #: projects/templates/projects/projects.html:50
@@ -3301,19 +3301,26 @@ msgid "Search project title"
 msgstr ""
 
 #: projects/templates/projects/projects.html:57
+#: projects/templates/projects/site_projects.html:26
+#, python-format
+msgid ""
+"%(number_of_filtered_projects)s of %(number_of_projects)s projects shown"
+msgstr ""
+
+#: projects/templates/projects/projects.html:64
 msgid "Import existing project"
 msgstr ""
 
-#: projects/templates/projects/projects.html:88
+#: projects/templates/projects/projects.html:95
 msgid "Pending invitations"
 msgstr ""
 
-#: projects/templates/projects/projects.html:104
+#: projects/templates/projects/projects.html:111
 msgid "Click on one of the links to join the projects."
 msgstr ""
 
-#: projects/templates/projects/projects.html:120
-#: projects/templates/projects/site_projects.html:41
+#: projects/templates/projects/projects.html:127
+#: projects/templates/projects/site_projects.html:51
 msgid "Last changed"
 msgstr ""
 
@@ -3332,7 +3339,7 @@ msgstr ""
 msgid "Next"
 msgstr ""
 
-#: projects/templates/projects/site_projects.html:34
+#: projects/templates/projects/site_projects.html:44
 #, python-format
 msgid "All projects on %(site)s"
 msgstr ""
@@ -3360,16 +3367,16 @@ msgstr ""
 msgid "You reached the file quota for this project."
 msgstr ""
 
-#: projects/views/project.py:155
+#: projects/views/project.py:178
 msgid "Sorry, your invitation has been expired."
 msgstr ""
 
-#: projects/views/project.py:158
+#: projects/views/project.py:181
 #, python-format
 msgid "Sorry, but this invitation is for the user \"%s\"."
 msgstr ""
 
-#: projects/views/project.py:172
+#: projects/views/project.py:195
 msgid "Sorry, the invitation link is not valid."
 msgstr ""
 
@@ -4055,8 +4062,8 @@ msgstr ""
 #: questions/templates/questions/catalogs_modal_delete_catalogs.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the catalog <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the catalog <code class=\"text-primary\">"
+"%(object)s</code>."
 msgstr ""
 
 #: questions/templates/questions/catalogs_modal_delete_catalogs.html:24
@@ -4075,8 +4082,8 @@ msgstr ""
 #: questions/templates/questions/catalogs_modal_delete_questions.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the question <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the question <code class=\"text-primary"
+"\">%(object)s</code>."
 msgstr ""
 
 #: questions/templates/questions/catalogs_modal_delete_questionsets.html:17
@@ -4089,8 +4096,8 @@ msgstr ""
 #: questions/templates/questions/catalogs_modal_delete_sections.html:17
 #, python-format
 msgid ""
-"You are about to permanently delete the section <code class=\"text-"
-"primary\">%(object)s</code>."
+"You are about to permanently delete the section <code class=\"text-primary\">"
+"%(object)s</code>."
 msgstr ""
 
 #: questions/templates/questions/catalogs_modal_form_catalogs.html:12
diff --git a/rdmo/projects/serializers/v1/__init__.py b/rdmo/projects/serializers/v1/__init__.py
index 66dd279b89..01445a6118 100644
--- a/rdmo/projects/serializers/v1/__init__.py
+++ b/rdmo/projects/serializers/v1/__init__.py
@@ -55,7 +55,8 @@ class Meta:
             'guests',
             'created',
             'updated',
-            'site'
+            'site',
+            'views'
         )
         read_only_fields = (
             'snapshots',
diff --git a/rdmo/projects/static/projects/js/project_questions/services.js b/rdmo/projects/static/projects/js/project_questions/services.js
index 021c8bdc94..a22a5a6ba3 100644
--- a/rdmo/projects/static/projects/js/project_questions/services.js
+++ b/rdmo/projects/static/projects/js/project_questions/services.js
@@ -65,7 +65,8 @@ angular.module('project_questions')
     var service = {
         set_prefix: '',
         set_index: null,
-        values: null
+        values: null,
+        error: null
     };
 
     service.init = function(project_id) {
@@ -774,10 +775,12 @@ angular.module('project_questions')
                     value.changed = false;
                 }
             }, function (response) {
-                value.errors = response.data.value;
+                if (response.status == 500) {
+                    service.error = response;
+                }
             })
         }
-    };
+};
 
     service.storeValues = function() {
         var promises = [];
@@ -798,6 +801,7 @@ angular.module('project_questions')
     };
 
     service.prev = function() {
+        service.error = null; // reset error when moving to previous questionset
         if (service.questionset.prev !== null) {
             back = true;
             service.initView(service.questionset.prev);
@@ -805,15 +809,19 @@ angular.module('project_questions')
     };
 
     service.next = function() {
+        service.error = null; // reset error when moving to next questionset
         if (service.questionset.id !== null) {
             service.initView(service.questionset.next);
         }
     };
 
     service.jump = function(section, questionset) {
+        service.error = null; // reset error before saving
         if (service.settings.project_questions_autosave) {
             service.save(false).then(function() {
-                if (angular.isDefined(questionset)) {
+                if (service.error !== null) {
+                    // pass, dont jump
+                } else if (angular.isDefined(questionset)) {
                     service.initView(questionset.id);
                 } else if (angular.isDefined(section)) {
                     if (angular.isDefined(section.questionset)) {
@@ -851,8 +859,11 @@ angular.module('project_questions')
     };
 
     service.save = function(proceed) {
+        service.error = null; // reset error
         return service.storeValues().then(function() {
-            if (angular.isDefined(proceed) && proceed) {
+            if (service.error !== null) {
+                // pass
+            } else if (angular.isDefined(proceed) && proceed) {
                 if (service.settings.project_questions_cycle_sets && service.questionset.is_collection) {
                     if (service.set_index === null) {
                         service.next();
diff --git a/rdmo/projects/templates/projects/project_questions.html b/rdmo/projects/templates/projects/project_questions.html
index a36fe34c5d..892877e5a0 100644
--- a/rdmo/projects/templates/projects/project_questions.html
+++ b/rdmo/projects/templates/projects/project_questions.html
@@ -47,7 +47,7 @@
 {% endblock %}
 
 {% block page %}
-    
+
     <div class="project-questions-form" ng-show="service.questionset.id" ng-cloak>
         {% include 'projects/project_questions_head.html' %}
         <h2>
@@ -70,6 +70,7 @@ <h2>
         </div>
 
         {% include 'projects/project_questions_buttons.html' %}
+        {% include 'projects/project_questions_save_error.html' %}
     </div>
 
     <div class="project-questions-form" ng-show="service.questionset && !service.questionset.id">
diff --git a/rdmo/projects/templates/projects/project_questions_buttons.html b/rdmo/projects/templates/projects/project_questions_buttons.html
index 485791c22e..0616c07a4a 100644
--- a/rdmo/projects/templates/projects/project_questions_buttons.html
+++ b/rdmo/projects/templates/projects/project_questions_buttons.html
@@ -22,16 +22,22 @@
     <div class="form-group questions-buttons">
         <div class="pull-right">
             <div ng-hide="service.project.read_only || service.questionset.elements.length < 1">
+                {% if not settings.PROJECT_QUESTIONS_AUTOSAVE %}
                 <button type="button" ng-click="service.save(false)" class="btn btn-default">
                     {% trans 'Save' %}
                 </button>
+                {% endif %}
                 <button type="button" ng-click="service.save(true)" class="btn btn-primary">
-                    {% trans 'Save and proceed' %}</span>
+                {% if settings.PROJECT_QUESTIONS_AUTOSAVE %}
+                {% trans 'Proceed' %}
+                {% else %}
+                {% trans 'Save and proceed' %}
+                {% endif %}
                 </button>
             </div>
             <div ng-show="service.project.read_only || service.questionset.elements.length < 1">
                 <button type="button" ng-click="service.next()" class="btn btn-primary">
-                    {% trans 'Proceed' %}</span>
+                    {% trans 'Proceed' %}
                 </button>
             </div>
         </div>
diff --git a/rdmo/projects/templates/projects/project_questions_save_error.html b/rdmo/projects/templates/projects/project_questions_save_error.html
new file mode 100644
index 0000000000..a3d6d90cba
--- /dev/null
+++ b/rdmo/projects/templates/projects/project_questions_save_error.html
@@ -0,0 +1,12 @@
+{% load i18n %}
+{% load core_tags %}
+
+    <div class="text-danger" ng-show="service.error">
+        <p>
+            {% trans 'An error occurred while saving the answer. Please contact support if this problem persists.' %}
+        </p>
+
+        <p>
+            {$ service.error.statusText $} ({$ service.error.status $}).
+        </p>
+    </div>
diff --git a/rdmo/projects/templates/projects/projects.html b/rdmo/projects/templates/projects/projects.html
index 8e6a88f26f..7f26af6c54 100644
--- a/rdmo/projects/templates/projects/projects.html
+++ b/rdmo/projects/templates/projects/projects.html
@@ -44,7 +44,7 @@ <h2>{% trans 'Options' %}</h2>
     </ul>
     {% endif %}
 
-    <h2>{% trans 'Search projects' %}</h2>
+    <h2>{% trans 'Filter projects' %}</h2>
 
     <form method="GET" class="projects-search">
         <input type="text" class="form-control" id="search" name="title" placeholder="{% trans 'Search project title' %}"
@@ -52,6 +52,13 @@ <h2>{% trans 'Search projects' %}</h2>
         <a href="{% url 'projects' %}" class="projects-search-reset">
             <i class="fa fa-times" aria-hidden="true"></i>
         </a>
+        <p class="text-muted">
+            <small>
+                {% blocktrans trimmed %}
+                {{ number_of_filtered_projects }} of {{ number_of_projects }} projects shown
+                {% endblocktrans %}
+            </small>
+        </p>
     </form>
 
     <h2>{% trans 'Import existing project' %}</h2>
diff --git a/rdmo/projects/templates/projects/projects_pagination.html b/rdmo/projects/templates/projects/projects_pagination.html
index ee24f4c389..d0c6e28250 100644
--- a/rdmo/projects/templates/projects/projects_pagination.html
+++ b/rdmo/projects/templates/projects/projects_pagination.html
@@ -4,7 +4,7 @@
     <div class="text-center">
         <ul class="pager">
             {% if page_obj.has_previous %}
-            <li class="previous"><a href="?page={{ page_obj.previous_page_number }}">{% trans 'Previous' %}</a></li>
+            <li class="previous"><a href="?page={{ page_obj.previous_page_number }}{% if querystring %}&amp;{{ querystring }}{% endif %}">{% trans 'Previous' %}</a></li>
             {% else %}
             <li class="previous disabled"><a href="">{% trans 'Previous' %}</a></li>
             {% endif %}
@@ -14,7 +14,7 @@
                 {% endblocktrans %}
             </li>
             {% if page_obj.has_next %}
-            <li class="next"><a href="?page={{ page_obj.next_page_number }}">{% trans 'Next' %}</a></li>
+            <li class="next"><a href="?page={{ page_obj.next_page_number }}{% if querystring %}&amp;{{ querystring }}{% endif %}">{% trans 'Next' %}</a></li>
             {% else %}
             <li class="next disabled"><a href="">{% trans 'Next' %}</a></li>
             {% endif %}
diff --git a/rdmo/projects/templates/projects/site_projects.html b/rdmo/projects/templates/projects/site_projects.html
index 99d750782d..a8d65e83af 100644
--- a/rdmo/projects/templates/projects/site_projects.html
+++ b/rdmo/projects/templates/projects/site_projects.html
@@ -13,11 +13,21 @@
 {% endblock %}
 
 {% block sidebar %}
-    <h2>{% trans 'Search projects' %}</h2>
+    <h2>{% trans 'Filter projects' %}</h2>
 
-    <form method="GET">
+    <form method="GET" class="projects-search">
         <input type="text" class="form-control" id="search" name="title" placeholder="{% trans 'Search project title' %}"
                value="{{request.GET.title }}" />
+        <a href="{% url 'site_projects' %}" class="projects-search-reset">
+            <i class="fa fa-times" aria-hidden="true"></i>
+        </a>
+        <p class="text-muted">
+            <small>
+                {% blocktrans trimmed %}
+                {{ number_of_filtered_projects }} of {{ number_of_projects }} projects shown
+                {% endblocktrans %}
+            </small>
+        </p>
     </form>
 
     <h2>{% trans 'Options' %}</h2>
diff --git a/rdmo/projects/utils.py b/rdmo/projects/utils.py
index c39044403b..9461e21d34 100644
--- a/rdmo/projects/utils.py
+++ b/rdmo/projects/utils.py
@@ -145,3 +145,13 @@ def get_invite_email_project_path(invite) -> str:
             invited_user_member_domain = invite.user.role.member.first().domain
             project_invite_path = 'http://' + invited_user_member_domain + project_invite_path
     return project_invite_path
+
+
+def set_context_querystring_with_filter_and_page(context: dict) -> dict:
+    '''prepares the filter part of the querystring for the next and previous hyperlinks in the pagination'''
+    if context["filter"].data:
+        querystring = context["filter"].data.copy()
+        if context["filter"].data.get('page'):
+            del querystring['page']
+        context['querystring'] = querystring.urlencode()
+    return context
diff --git a/rdmo/projects/views/project.py b/rdmo/projects/views/project.py
index 94c7c3858e..2b9f059850 100644
--- a/rdmo/projects/views/project.py
+++ b/rdmo/projects/views/project.py
@@ -4,11 +4,13 @@
 from django.core.exceptions import PermissionDenied
 from django.db import models
 from django.db.models import F, OuterRef, Subquery
+from django.db.models.functions import Coalesce, Greatest
 from django.forms import Form
 from django.http import Http404
 from django.shortcuts import get_object_or_404, redirect
 from django.urls import reverse_lazy
 from django.utils.decorators import method_decorator
+from django.utils.http import urlencode
 from django.utils.translation import gettext_lazy as _
 from django.views.decorators.csrf import ensure_csrf_cookie
 from django.views.generic import DeleteView, DetailView, TemplateView
@@ -25,6 +27,7 @@
 
 from ..filters import ProjectFilter
 from ..models import Integration, Invite, Membership, Project, Value
+from ..utils import set_context_querystring_with_filter_and_page
 
 logger = logging.getLogger(__name__)
 
@@ -52,7 +55,10 @@ def get_queryset(self):
         last_changed_subquery = models.Subquery(
             Value.objects.filter(project=models.OuterRef('pk')).order_by('-updated').values('updated')[:1]
         )
-        queryset = queryset.annotate(last_changed=models.functions.Greatest('updated', last_changed_subquery))
+        # the 'updated' field from a Project always returns a valid DateTime value
+        # when Greatest returns null, then Coalesce will return the value for 'updated' as a fall-back
+        # when Greatest returns a value, then Coalesce will return this value
+        queryset = queryset.annotate(last_changed=Coalesce(Greatest(last_changed_subquery, 'updated'), 'updated'))
 
         # order by last changed
         queryset = queryset.order_by('-last_changed')
@@ -61,8 +67,11 @@ def get_queryset(self):
 
     def get_context_data(self, **kwargs):
         context = super(ProjectsView, self).get_context_data(**kwargs)
+        context['number_of_projects'] = self.get_queryset().count()
         context['invites'] = Invite.objects.filter(user=self.request.user)
         context['is_site_manager'] = is_site_manager(self.request.user)
+        context['number_of_filtered_projects'] = context["filter"].qs.count()
+        context = set_context_querystring_with_filter_and_page(context)
         return context
 
 
@@ -82,12 +91,22 @@ def get_queryset(self):
             last_changed_subquery = models.Subquery(
                 Value.objects.filter(project=models.OuterRef('pk')).order_by('-updated').values('updated')[:1]
             )
-            queryset = queryset.annotate(last_changed=models.functions.Greatest('updated', last_changed_subquery))
+            # the 'updated' field from a Project always returns a valid DateTime value
+            # when Greatest returns null, then Coalesce will return the value for 'updated' as a fall-back
+            # when Greatest returns a value, then Coalesce will return this value
+            queryset = queryset.annotate(last_changed=Coalesce(Greatest(last_changed_subquery, 'updated'), 'updated'))
 
             return queryset
         else:
             raise PermissionDenied()
 
+    def get_context_data(self, **kwargs):
+        context = super(SiteProjectsView, self).get_context_data(**kwargs)
+        context['number_of_projects'] = self.get_queryset().count()
+        context['number_of_filtered_projects'] = context["filter"].qs.count()
+        context = set_context_querystring_with_filter_and_page(context)
+        return context
+
 
 class ProjectDetailView(ObjectPermissionMixin, DetailView):
     model = Project
diff --git a/rdmo/projects/views/project_create.py b/rdmo/projects/views/project_create.py
index 511bbd491f..aadc44cdb7 100644
--- a/rdmo/projects/views/project_create.py
+++ b/rdmo/projects/views/project_create.py
@@ -2,11 +2,9 @@
 
 from django.contrib.auth.mixins import LoginRequiredMixin
 from django.contrib.sites.shortcuts import get_current_site
-from django.http import HttpResponseRedirect
-from django.shortcuts import render
 from django.urls import reverse_lazy
-from django.utils.translation import gettext_lazy as _
 from django.views.generic import CreateView, TemplateView
+
 from rdmo.core.views import RedirectViewMixin
 from rdmo.questions.models import Catalog
 from rdmo.tasks.models import Task
@@ -45,6 +43,7 @@ def form_valid(self, form):
 
         # add all tasks to project
         tasks = Task.objects.filter_current_site() \
+                            .filter_catalog(self.object.catalog) \
                             .filter_group(self.request.user) \
                             .filter_availability(self.request.user)
         for task in tasks:
diff --git a/rdmo/projects/viewsets.py b/rdmo/projects/viewsets.py
index fb9cd3b291..f0b3abc80f 100644
--- a/rdmo/projects/viewsets.py
+++ b/rdmo/projects/viewsets.py
@@ -22,6 +22,8 @@
 from rdmo.core.utils import human2bytes, return_file_response
 from rdmo.options.models import OptionSet
 from rdmo.questions.models import Catalog, Question, QuestionSet
+from rdmo.tasks.models import Task
+from rdmo.views.models import View
 
 from .filters import SnapshotFilterBackend, ValueFilterBackend
 from .models import (Continuation, Integration, Issue, Membership, Project,
@@ -150,6 +152,23 @@ def progress(self, request, pk=None):
     def perform_create(self, serializer):
         project = serializer.save(site=get_current_site(self.request))
 
+        # add all tasks to project
+        tasks = Task.objects.filter_current_site() \
+                            .filter_catalog(project.catalog) \
+                            .filter_group(self.request.user) \
+                            .filter_availability(self.request.user)
+        for task in tasks:
+            project.tasks.add(task)
+
+        if self.request.data.get('views') is None:
+            # add all views to project
+            views = View.objects.filter_current_site() \
+                                .filter_catalog(project.catalog) \
+                                .filter_group(self.request.user) \
+                                .filter_availability(self.request.user)
+            for view in views:
+                project.views.add(view)
+
         # add current user as owner
         membership = Membership(project=project, user=self.request.user, role='owner')
         membership.save()


Test patch:
diff --git a/rdmo/accounts/tests/test_utils.py b/rdmo/accounts/tests/test_utils.py
new file mode 100644
index 0000000000..706f23166b
--- /dev/null
+++ b/rdmo/accounts/tests/test_utils.py
@@ -0,0 +1,110 @@
+import pytest
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import AnonymousUser
+
+from rdmo.accounts.models import Role
+from rdmo.accounts.utils import get_full_name, is_site_manager, delete_user, get_user_from_db_or_none
+
+
+normal_users = (
+    ('user', 'user', 'user@example.com'),
+)
+
+site_managers = (
+    ('site', 'site', 'site@example.com'),
+)
+
+users = (*normal_users, *site_managers)
+
+
+@pytest.mark.parametrize('username,password,email', users)
+def test_get_full_name(db, username, password, email):
+    user = get_user_model().objects.get(username=username, email=email)
+    assert get_full_name(user) == user.first_name + ' ' + user.last_name
+
+
+@pytest.mark.parametrize('username,password,email', users)
+def test_get_full_name_returns_username(db, username, password, email):
+    user = get_user_model().objects.get(username=username, email=email)
+    user.first_name = ''
+    user.save()
+    assert get_full_name(user) == username
+
+
+def test_is_site_manager_returns_true_for_superuser(admin_user):
+    assert is_site_manager(admin_user) is True
+
+
+def test_is_site_manager_returns_false_for_not_authenticated_user():
+    assert is_site_manager(AnonymousUser()) is False
+
+
+@pytest.mark.parametrize('username,password,email', site_managers)
+def test_is_site_manager_returns_true_for_site_managers(db, client, username, password, email):
+    client.login(username=username, password=password)
+    user = get_user_model().objects.get(username=username, email=email)
+    assert is_site_manager(user) is True
+
+
+@pytest.mark.parametrize('username,password,email', site_managers)
+def test_is_site_manager_returns_false_when_role_doesnotexist_(db, client, username, password, email):
+    client.login(username=username, password=password)
+    Role.objects.all().delete()
+    user = get_user_model().objects.get(username=username, email=email)
+    assert is_site_manager(user) is False
+
+
+@pytest.mark.parametrize('username,password,email', users)
+def test_delete_user_returns_true(db, username, password, email):
+    user = get_user_model().objects.get(username=username, email=email)
+    assert delete_user(user=user, email=email, password=password) is True
+
+
+@pytest.mark.parametrize('username,password,email', users)
+def test_delete_user_returns_false_when_user_or_email_is_none(db, username, password, email):
+    user = get_user_model().objects.get(username=username, email=email)
+    for test_user, test_email in ((user, None), (None, email), (None, None)):
+        assert delete_user(user=test_user, email=test_email) is False
+
+
+@pytest.mark.parametrize('username,password,email', users)
+def test_delete_user_returns_false_when_user_is_not_equal_to_db_user(db, username, password, email):
+    user = get_user_model().objects.get(username=username, email=email)
+    user.pk += 1
+    assert delete_user(user=user, email=email, password=None) is False
+
+
+
+@pytest.mark.parametrize('username,password,email', users)
+def test_delete_user_returns_false_when_user_with_usable_password_gives_none_for_password(db, username, password, email):
+    user = get_user_model().objects.get(username=username, email=email)
+    assert delete_user(user=user, email=email, password=None) is False
+
+
+@pytest.mark.parametrize('username,password,email', users)
+def test_delete_user_returns_false_when_delete_user_raises_an_exception(db, username, password, email):
+    user = get_user_model().objects.get(username=username, email=email)
+    def _delete(): raise ValueError
+    user.delete = _delete
+    assert delete_user(user=user, email=email, password=password) is False
+
+
+@pytest.mark.parametrize('username,password,email', users)
+def test_delete_user_returns_false_when_delete_is_called_for_user_without_usable_password_and_raises_an_exception(db, username, password, email):
+    user = get_user_model().objects.get(username=username, email=email)
+    user.set_unusable_password()
+    def _delete(): raise ValueError
+    user.delete = _delete
+    assert delete_user(user=user, email=email) is False
+
+
+@pytest.mark.parametrize('username,password,email', users)
+def test_get_user_from_db_or_none_returns_user(db, username, password, email):
+    user = get_user_model().objects.get(username=username, email=email)
+    assert get_user_from_db_or_none(username, email) == user
+
+
+@pytest.mark.parametrize('username,password,email', users)
+def test_get_user_from_db_or_none_returns_none_when_wrong_input_was_given(db, username, password, email):
+    for test_username, test_email in ((username, 'none@example.com'), ('none', email), (None, None)):
+        assert get_user_from_db_or_none(test_username, test_email) is None
diff --git a/rdmo/accounts/tests/test_views.py b/rdmo/accounts/tests/test_views.py
index eef1efb72e..ef06426c68 100644
--- a/rdmo/accounts/tests/test_views.py
+++ b/rdmo/accounts/tests/test_views.py
@@ -1,10 +1,16 @@
 import re
 
-from django.conf import settings
+import pytest
+
+from django.contrib.auth import get_user_model
 from django.core import mail
+from django.db.models import ObjectDoesNotExist
 from django.urls import reverse
+from django.urls.exceptions import NoReverseMatch
 
-from django.contrib.auth import get_user_model
+from pytest_django.asserts import assertTemplateUsed
+
+from rdmo.accounts.tests.utils import reload_app_urlconf_in_testcase
 
 users = (
     ('editor', 'editor'),
@@ -14,34 +20,47 @@
     ('anonymous', None),
 )
 
+boolean_toggle = (True, False)
+
 
-def test_get_profile_update(db, client):
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_get_profile_update(db, client, settings, test_setting):
     """
     An authorized GET request to the profile update form returns the form.
     """
+    settings.PROFILE_UPDATE = test_setting
     client.login(username='user', password='user')
 
     url = reverse('profile_update')
     response = client.get(url)
     assert response.status_code == 200
+    if settings.PROFILE_UPDATE:
+        assertTemplateUsed(response, 'profile/profile_update_form.html')
+    else:
+        assertTemplateUsed(response, 'profile/profile_update_closed.html')
 
 
-def test_get_profile_update_redirect(db, client):
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_get_profile_update_redirect(db, client, settings, test_setting):
     """
     An unauthorized GET request to the profile update form gets
     redirected to login.
     """
+    settings.PROFILE_UPDATE = test_setting
+    client.logout()
     url = reverse('profile_update')
     response = client.get(url)
     assert response.status_code == 302
     assert response.url == reverse('account_login') + '?next=' + url
 
 
-def test_post_profile_update(db, client):
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_post_profile_update(db, client, settings, test_setting):
     """
     An authorized POST request to the profile update form updates the
     user and redirects to home.
     """
+    settings.PROFILE_UPDATE = test_setting
     client.login(username='user', password='user')
 
     url = reverse('profile_update')
@@ -60,12 +79,13 @@ def test_post_profile_update(db, client):
     else:
         assert response.status_code == 200
 
-
-def test_post_profile_update_cancel(db, client):
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_post_profile_update_cancel(db, client, settings, test_setting):
     """
     An authorized POST request to the profile update form updates with
     cancel redirects to home.
     """
+    settings.PROFILE_UPDATE = test_setting
     client.login(username='user', password='user')
 
     url = reverse('profile_update')
@@ -83,12 +103,13 @@ def test_post_profile_update_cancel(db, client):
     else:
         assert response.status_code == 200
 
-
-def test_post_profile_update_cancel2(db, client):
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_post_profile_update_cancel2(db, client, settings, test_setting):
     """
     An authorized POST request to the profile update form updates with
     cancel and the next field redirects to the given url.
     """
+    settings.PROFILE_UPDATE = test_setting
     client.login(username='user', password='user')
 
     url = reverse('profile_update')
@@ -107,12 +128,13 @@ def test_post_profile_update_cancel2(db, client):
     else:
         assert response.status_code == 200
 
-
-def test_post_profile_update_next(db, client):
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_post_profile_update_next(db, client, settings, test_setting):
     """
     An authorized POST request to the profile update form with next field
     updates the user and redirects to the given url.
     """
+    settings.PROFILE_UPDATE = test_setting
     client.login(username='user', password='user')
 
     url = reverse('profile_update')
@@ -132,12 +154,13 @@ def test_post_profile_update_next(db, client):
     else:
         assert response.status_code == 200
 
-
-def test_post_profile_update_next2(db, client):
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_post_profile_update_next2(db, client, settings, test_setting):
     """
     An authorized POST request to the profile update form with next
     field set to profile_update updates the user and redirects to home.
     """
+    settings.PROFILE_UPDATE = test_setting
     client.login(username='user', password='user')
 
     url = reverse('profile_update')
@@ -157,65 +180,74 @@ def test_post_profile_update_next2(db, client):
     else:
         assert response.status_code == 200
 
-
-def test_password_change_get(db, client):
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_password_change_get(db, client, settings, test_setting):
     """
     An authorized GET request to the password change form returns the form.
     """
-    if settings.ACCOUNT:
-        client.login(username='user', password='user')
-
-        url = reverse('account_change_password')
-        response = client.get(url)
-        assert response.status_code == 200
+    settings.ACCOUNT = test_setting
+    # independent of the setting, the reverse url exists
+    client.login(username='user', password='user')
+    url = reverse('account_change_password')
+    response = client.get(url)
+    assert response.status_code == 200
 
 
-def test_password_change_post(db, client):
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_password_change_post(db, client, settings, test_setting):
     """
     An authorized POST request to the password change form updates the
     password and redirects to home.
     """
-    if settings.ACCOUNT:
-        client.login(username='user', password='user')
-
-        url = reverse('account_change_password')
-        data = {
-            'old_password': 'user',
-            'new_password1': 'resu',
-            'new_password2': 'resu',
-        }
-        response = client.post(url, data)
-        assert response.status_code == 200
+    settings.ACCOUNT = test_setting
+    client.login(username='user', password='user')
+    url = reverse('account_change_password')
+    data = {
+        'old_password': 'user',
+        'new_password1': 'resu',
+        'new_password2': 'resu',
+    }
+    response = client.post(url, data)
+    assert response.status_code == 200
 
 
-def test_password_reset_get(db, client):
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_password_reset_get(db, client, settings, test_setting):
     """
     A GET request to the password reset form returns the form.
     """
-    if settings.ACCOUNT:
-        url = reverse('account_reset_password')
-        response = client.get(url)
-        assert response.status_code == 200
+    settings.ACCOUNT = test_setting
+
+    url = reverse('account_reset_password')
+    response = client.get(url)
+    assert response.status_code == 200
 
 
-def test_password_reset_post_invalid(db, client):
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_password_reset_post_invalid(db, client, settings, test_setting):
     """
     A POST request to the password reset form with an invalid mail address
     sends no mail.
     """
-    if settings.ACCOUNT:
-        url = reverse('account_reset_password')
-        data = {'email': 'wrong@example.com'}
-        response = client.post(url, data)
-        assert response.status_code == 200
-        assert len(mail.outbox) == 0
+    settings.ACCOUNT = test_setting
 
+    url = reverse('account_reset_password')
+    data = {'email': 'wrong@example.com'}
+    response = client.post(url, data)
+    assert response.status_code == 200
+    assert len(mail.outbox) == 0
 
-def test_password_reset_post_valid(db, client):
+
+@pytest.mark.urls('rdmo.accounts.urls')
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_password_reset_post_valid(db, client, settings, test_setting):
     """
     A POST request to the password reset form with an invalid mail address
     sends a mail with a correct link.
     """
+    settings.ACCOUNT = test_setting
+    reload_app_urlconf_in_testcase('accounts')
+
     if settings.ACCOUNT:
         url = reverse('account_reset_password')
         data = {'email': 'user@example.com'}
@@ -225,83 +257,235 @@ def test_password_reset_post_valid(db, client):
         assert len(mail.outbox) == 1
 
         # get the link from the mail
-        urls = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', mail.outbox[0].body)
+        urls = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', mail.outbox[0].body)  # complicated regex
+        # urls = [i.strip() for i in mail.outbox[0].body.splitlines() if i.strip().startswith('http')]  # simpler alternative
         assert len(urls) == 1
 
         # get the password_reset page
         response = client.get(urls[0])
         assert response.status_code == 302
-        assert response.url == reverse('account_reset_password_from_key', args=['4','set-password'])
+        assert response.url == reverse('account_reset_password_from_key', args=['4', 'set-password'])
+    else:
+        pytest.raises(NoReverseMatch, reverse, 'account_reset_password')
+
+
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_remove_user_get(db, client, settings, test_setting):
+    settings.PROFILE_DELETE = test_setting
 
+    client.login(username='user', password='user')
+    url = reverse('profile_remove')
+    response = client.get(url)
 
-def test_remove_user_get(db, client):
+    assert response.status_code == 200
     if settings.PROFILE_DELETE:
-        client.login(username='user', password='user')
+        assertTemplateUsed(response, 'profile/profile_remove_form.html')
+    else:
+        assertTemplateUsed(response, 'profile/profile_remove_closed.html')
 
-        url = reverse('profile_remove')
-        response = client.get(url)
-        assert response.status_code == 200
 
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_remove_user_post(db, client, settings, test_setting):
+    settings.PROFILE_DELETE = test_setting
 
-def test_remove_user_post(db, client):
+    client.login(username='user', password='user')
+    url = reverse('profile_remove')
+    data = {
+        'email': 'user@example.com',
+        'password': 'user',
+        'consent': True
+    }
+    response = client.post(url, data)
+
+    assert response.status_code == 200
     if settings.PROFILE_DELETE:
-        client.login(username='user', password='user')
-
-        url = reverse('profile_remove')
-        data = {
-            'email': 'user@example.com',
-            'password': 'user',
-            'consent': True
-        }
-        response = client.post(url, data)
-        assert response.status_code == 200
-        assert not get_user_model().objects.filter(username='user').exists()
+        assertTemplateUsed(response, 'profile/profile_remove_success.html')
+        pytest.raises(ObjectDoesNotExist, get_user_model().objects.get, username='user')
+    else:
+        assertTemplateUsed(response, 'profile/profile_remove_closed.html')
+        assert get_user_model().objects.get(username='user')
+
+
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_remove_user_post_cancelled(db, client, settings, test_setting):
+    settings.PROFILE_UPDATE = test_setting
+    settings.PROFILE_DELETE = True
+
+    client.login(username='user', password='user')
+    url = reverse('profile_remove')
+    response = client.post(url, {'cancel': 'cancel'})
+
+    assert response.status_code == 302
+    assert get_user_model().objects.filter(username='user').exists()
+    if settings.PROFILE_UPDATE:
+        assert response.url == '/account'
+    else:
+        assert response.url == '/'
+
 
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_remove_user_post_invalid_email(db, client, settings, test_setting):
+    settings.PROFILE_DELETE = test_setting
+
+    client.login(username='user', password='user')
+    url = reverse('profile_remove')
+    data = {
+        'email': 'invalid',
+        'password': 'user',
+        'consent': True
+    }
+    response = client.post(url, data)
 
-def test_remove_user_post_invalid_email(db, client):
+    assert response.status_code == 200
+    assert get_user_model().objects.get(username='user')
     if settings.PROFILE_DELETE:
-        client.login(username='user', password='user')
-
-        url = reverse('profile_remove')
-        data = {
-            'email': 'invalid',
-            'password': 'user',
-            'consent': True
-        }
-        response = client.post(url, data)
-        assert response.status_code == 200
-        assert get_user_model().objects.filter(username='user').exists()
+        assertTemplateUsed(response, 'profile/profile_remove_failed.html')
+    else:
+        assertTemplateUsed(response, 'profile/profile_remove_closed.html')
+
 
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_remove_user_post_invalid_password(db, client, settings, test_setting):
+    settings.PROFILE_DELETE = test_setting
+
+    client.login(username='user', password='user')
+    url = reverse('profile_remove')
+    data = {
+        'email': 'user@example.com',
+        'password': 'invalid',
+        'consent': True
+    }
+    response = client.post(url, data)
 
-def test_remove_user_post_invalid_password(db, client):
+    assert response.status_code == 200
+    assert get_user_model().objects.get(username='user')
     if settings.PROFILE_DELETE:
-        client.login(username='user', password='user')
-
-        url = reverse('profile_remove')
-        data = {
-            'email': 'user@example.com',
-            'password': 'invalid',
-            'consent': True
-        }
-        response = client.post(url, data)
-        assert response.status_code == 200
-        assert get_user_model().objects.filter(username='user').exists()
+        assertTemplateUsed(response, 'profile/profile_remove_failed.html')
+    else:
+        assertTemplateUsed(response, 'profile/profile_remove_closed.html')
+
 
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_remove_user_post_invalid_consent(db, client, settings, test_setting):
+    settings.PROFILE_DELETE = test_setting
 
-def test_remove_user_post_invalid_consent(db, client):
+    client.login(username='user', password='user')
+    url = reverse('profile_remove')
+    data = {
+        'email': 'user@example.com',
+        'password': 'user',
+        'consent': False
+    }
+
+    response = client.post(url, data)
+    assert response.status_code == 200
     if settings.PROFILE_DELETE:
-        client.login(username='user', password='user')
+        assertTemplateUsed(response, 'profile/profile_remove_form.html')
+        assert response.context['form'].errors['consent'] == ['This field is required.']  # check if consent is required
+    else:
+        assertTemplateUsed(response, 'profile/profile_remove_closed.html')
 
-        url = reverse('profile_remove')
-        data = {
-            'email': 'user@example.com',
-            'password': 'user',
-            'consent': False
-        }
 
-        response = client.post(url, data)
-        assert response.status_code == 200
-        assert get_user_model().objects.filter(username='user').exists()
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_remove_a_user_without_usable_password_post(db, client, settings, test_setting):
+    settings.PROFILE_DELETE = test_setting
+
+    user = get_user_model().objects.get(username='user', email='user@example.com')
+    user.set_unusable_password()
+    user.save()
+
+    client.force_login(user=user)  # login without password
+    url = reverse('profile_remove')
+    data = {
+        'email': user.email,
+        'consent': True
+    }
+    response = client.post(url, data)
+
+    assert response.status_code == 200
+    if settings.PROFILE_DELETE:
+        assertTemplateUsed(response, 'profile/profile_remove_success.html')
+        pytest.raises(ObjectDoesNotExist, get_user_model().objects.get, pk=user.pk)
+    else:
+        assertTemplateUsed(response, 'profile/profile_remove_closed.html')
+    client.logout()
+
+
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_remove_a_user_without_usable_password_post_invalid_email(db, client, settings, test_setting):
+    settings.PROFILE_DELETE = test_setting
+
+    user = get_user_model().objects.get(username='user', email='user@example.com')
+    user.set_unusable_password()
+    user.save()
+
+    client.force_login(user=user)  # login without password
+    url = reverse('profile_remove')
+    data = {
+        'email': 'invalid',
+        'consent': True
+    }
+    response = client.post(url, data)
+
+    assert response.status_code == 200
+    get_user_model().objects.get(pk=user.pk)
+    if settings.PROFILE_DELETE:
+        assertTemplateUsed(response, 'profile/profile_remove_failed.html')
+    else:
+        assertTemplateUsed(response, 'profile/profile_remove_closed.html')
+    client.logout()
+
+
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_remove_a_user_without_usable_password_post_empty_email(db, client, settings, test_setting):
+    settings.PROFILE_DELETE = test_setting
+
+    user = get_user_model().objects.get(username='user', email='user@example.com')
+    user.set_unusable_password()
+    user.save()
+
+    client.force_login(user=user)  # login without password
+    url = reverse('profile_remove')
+    data = {
+        'email': '',
+        'consent': True
+    }
+    response = client.post(url, data)
+
+    assert response.status_code == 200
+    assert get_user_model().objects.get(pk=user.pk)
+    if settings.PROFILE_DELETE:
+        assertTemplateUsed(response, 'profile/profile_remove_form.html')
+        assert response.context['form'].errors['email'] == ['This field is required.']  # check if email is required
+    else:
+        assertTemplateUsed(response, 'profile/profile_remove_closed.html')
+    client.logout()
+
+
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_remove_a_user_without_usable_password_post_invalid_consent(db, client, settings, test_setting):
+    settings.PROFILE_DELETE = test_setting
+
+    user = get_user_model().objects.get(username='user', email='user@example.com')
+    user.set_unusable_password()
+    user.save()
+
+    client.force_login(user=user)  # login without password
+    url = reverse('profile_remove')
+    data = {
+        'email': user.email,
+        'consent': False
+    }
+    response = client.post(url, data)
+
+    assert response.status_code == 200
+    assert get_user_model().objects.get(pk=user.pk)
+    if settings.PROFILE_DELETE:
+        assertTemplateUsed(response, 'profile/profile_remove_form.html')
+        assert response.context['form'].errors['consent'] == ['This field is required.']  # check if consent is required
+    else:
+        assertTemplateUsed(response, 'profile/profile_remove_closed.html')
+    client.logout()
 
 
 def test_signup(db, client):
@@ -332,3 +516,82 @@ def test_signup_next(db, client):
 
     assert response.status_code == 302
     assert response.url == '/about/'
+
+
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+@pytest.mark.parametrize('username,password', users)
+def test_terms_of_use(db, client, settings, username, password, test_setting):
+    settings.ACCOUNT_TERMS_OF_USE = test_setting
+    reload_app_urlconf_in_testcase('accounts')
+    client.login(username=username, password=password)
+    if settings.ACCOUNT_TERMS_OF_USE:
+        url = reverse('terms_of_use')
+        response = client.get(url)
+        assert response.status_code == 200
+        assertTemplateUsed('accounts/terms_of_use.html')
+    else:
+        with pytest.raises(NoReverseMatch):
+            reverse('terms_of_use')
+
+
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_token_get_for_user(db, client, settings, test_setting):
+    settings.ACCOUNT_ALLOW_USER_TOKEN = test_setting
+    reload_app_urlconf_in_testcase('accounts')
+
+    client.login(username='user', password='user')
+
+    if settings.ACCOUNT_ALLOW_USER_TOKEN:
+        url = reverse('account_token')
+        response = client.get(url)
+        assert response.status_code == 200
+    else:
+        with pytest.raises(NoReverseMatch):
+            reverse('account_token')
+
+
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_token_get_for_anonymous(db, client, settings, test_setting):
+    settings.ACCOUNT_ALLOW_USER_TOKEN = test_setting
+    reload_app_urlconf_in_testcase('accounts')
+    client.login(username='anonymous', password=None)
+
+    if settings.ACCOUNT_ALLOW_USER_TOKEN:
+        url = reverse('account_token')
+        response = client.get(url)
+        assert response.status_code == 302
+        assert response.url == reverse('account_login') + '?next=' + url
+    else:
+        with pytest.raises(NoReverseMatch):
+            reverse('account_token')
+
+
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_token_post_for_user(db, client, settings, test_setting):
+    settings.ACCOUNT_ALLOW_USER_TOKEN = test_setting
+    reload_app_urlconf_in_testcase('accounts')
+    client.login(username='user', password='user')
+
+    if settings.ACCOUNT_ALLOW_USER_TOKEN:
+        url = reverse('account_token')
+        response = client.post(url)
+        assert response.status_code == 200
+    else:
+        with pytest.raises(NoReverseMatch):
+            reverse('account_token')
+
+
+@pytest.mark.parametrize('test_setting', boolean_toggle)
+def test_token_post_for_anonymous(db, client, settings, test_setting):
+    settings.ACCOUNT_ALLOW_USER_TOKEN = test_setting
+    reload_app_urlconf_in_testcase('accounts')
+    client.login(username='anonymous', password=None)
+    # breakpoint()
+    if settings.ACCOUNT_ALLOW_USER_TOKEN:
+        url = reverse('account_token')
+        response = client.post(url)
+        assert response.status_code == 302
+        assert response.url == reverse('account_login') + '?next=' + url
+    else:
+        with pytest.raises(NoReverseMatch):
+            reverse('account_token')
diff --git a/rdmo/accounts/tests/utils.py b/rdmo/accounts/tests/utils.py
new file mode 100644
index 0000000000..c5d9040b36
--- /dev/null
+++ b/rdmo/accounts/tests/utils.py
@@ -0,0 +1,28 @@
+import sys
+
+from importlib import reload, import_module
+
+from django.urls import clear_url_caches
+
+
+def reload_urlconf(urlconf=None, settings=None):
+    clear_url_caches()
+    if urlconf is None and settings is None:
+        from django.conf import settings
+        urlconf = settings.ROOT_URLCONF
+    elif urlconf is None and settings is not None:
+        # take the settings during pytest run
+        urlconf = settings.ROOT_URLCONF
+    if urlconf in sys.modules:
+        reload(sys.modules[urlconf])
+    else:
+        import_module(urlconf)
+
+
+def reload_app_urlconf_in_testcase(app_name: str=None, settings=None) -> None:
+    # reload the urlconf of the app
+    reload_urlconf(urlconf=f'rdmo.{app_name}.urls', settings=settings)
+    # reload the core urlconf
+    reload_urlconf(urlconf='rdmo.core.urls', settings=settings)
+    # reload the testcase settings urlconf
+    reload_urlconf(settings=settings)
diff --git a/rdmo/projects/tests/test_utils.py b/rdmo/projects/tests/test_utils.py
new file mode 100644
index 0000000000..86f3d99dce
--- /dev/null
+++ b/rdmo/projects/tests/test_utils.py
@@ -0,0 +1,34 @@
+import pytest
+
+from django.http import QueryDict
+
+from ..utils import set_context_querystring_with_filter_and_page
+from ..filters import ProjectFilter
+
+GET_queries = [
+    'page=2&title=project',
+    'page=2',
+    'title=project',
+    ''
+]
+
+@pytest.mark.parametrize('GET_query', GET_queries)
+def test_set_context_querystring_with_filter_and_page(GET_query):
+    querydict = QueryDict(GET_query)
+    filter = ProjectFilter(querydict)
+    context = {'filter': filter}
+    context = set_context_querystring_with_filter_and_page(context)
+
+    if 'page' in GET_query and 'title' in GET_query:
+        assert 'querystring' in context
+        assert context['querystring'] == 'title=project'
+        querydict_copy = querydict.copy()
+        del querydict_copy['page']
+        assert context['querystring'] == querydict_copy.urlencode()
+    elif 'page' not in GET_query and 'title' in GET_query:
+        assert 'querystring' in context
+        assert context['querystring'] == 'title=project'
+    elif 'page' in GET_query and 'title' not in GET_query:
+        assert context.get('querystring', 'not-in-context') == ''
+    else:
+        assert context.get('querystring', 'not-in-context') == 'not-in-context'
diff --git a/rdmo/projects/tests/test_view_project.py b/rdmo/projects/tests/test_view_project.py
index 4ae7cde300..1c48b5d985 100644
--- a/rdmo/projects/tests/test_view_project.py
+++ b/rdmo/projects/tests/test_view_project.py
@@ -2,6 +2,7 @@
 
 import pytest
 from django.urls import reverse
+from pytest_django.asserts import assertTemplateUsed
 
 from rdmo.views.models import View
 
@@ -66,12 +67,16 @@ def test_list(db, client, username, password):
 
     if password:
         assert response.status_code == 200
+        assertTemplateUsed(response, 'projects/projects.html')
 
         if username == 'site':
             assert projects == []
+            assert response.context['number_of_projects'] == len([])
         else:
-            assert sorted(list(set([int(project_id) for project_id in projects]))) \
-                == view_project_permission_map.get(username, [])
+            # breakpoint()
+            user_projects_map = view_project_permission_map.get(username, [])
+            assert sorted(list(set(map(int, projects)))) == user_projects_map
+            assert response.context['number_of_projects'] == len(user_projects_map)
     else:
         assert response.status_code == 302
 
@@ -85,11 +90,16 @@ def test_site(db, client, username, password):
 
     projects = re.findall(r'/projects/(\d+)/update/', response.content.decode())
 
-    if username == 'site':
-        assert sorted([int(project_id) for project_id in projects]) \
-            == view_project_permission_map.get(username, [])
-    elif password:
-        assert response.status_code == 403
+    if password:
+
+        if username == 'site':
+            assert response.status_code == 200
+            assertTemplateUsed(response, 'projects/site_projects.html')
+            user_projects_map = view_project_permission_map.get(username, [])
+            assert sorted(list(set(map(int, projects)))) == user_projects_map
+            assert response.context['number_of_projects'] == len(user_projects_map)
+        else:
+            assert response.status_code == 403
     else:
         assert response.status_code == 302
 
diff --git a/testing/fixtures/users.json b/testing/fixtures/users.json
index 05f1991419..3632bd2ef3 100644
--- a/testing/fixtures/users.json
+++ b/testing/fixtures/users.json
@@ -159,7 +159,7 @@
       "username": "api",
       "first_name": "Alex",
       "last_name": "Api",
-      "email": "api@exmple.com",
+      "email": "api@example.com",
       "is_staff": false,
       "is_active": true,
       "date_joined": "2017-04-25T12:27:45Z",
@@ -179,7 +179,7 @@
       "username": "site",
       "first_name": "Sandra",
       "last_name": "Site",
-      "email": "site@exmple.com",
+      "email": "site@example.com",
       "is_staff": false,
       "is_active": true,
       "date_joined": "2017-04-25T12:27:45Z",